1
0
forked from baron/baron-sso

테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거

This commit is contained in:
2026-05-13 18:05:51 +09:00
parent a4d707d4d8
commit 5e7b7b878c
85 changed files with 4808 additions and 734 deletions

View File

@@ -22,6 +22,11 @@ func Run(db *gorm.DB) error {
return fmt.Errorf("tenant seeding failed: %w", err)
}
// 3. Normalize staging seed/read-model data
if err := SanitizeLegacyUserMetadata(db); err != nil {
return fmt.Errorf("legacy user metadata sanitize failed: %w", err)
}
slog.Info("[Bootstrap] User seed skipped (Kratos is SoT)")
slog.Info("[Bootstrap] Bootstrap completed successfully.")
return nil

View File

@@ -0,0 +1,34 @@
package bootstrap
import (
"fmt"
"log/slog"
"gorm.io/gorm"
)
const sanitizeLegacyUserMetadataSQL = `
update users
set metadata = metadata - 'hanmacFamily' - 'userType',
updated_at = now()
where metadata ? 'hanmacFamily'
or metadata ? 'userType'
`
// SanitizeLegacyUserMetadata removes legacy UI classification flags from Baron user metadata.
func SanitizeLegacyUserMetadata(db *gorm.DB) error {
if db == nil {
return fmt.Errorf("database is not configured")
}
if !db.Migrator().HasTable("users") {
slog.Info("[Bootstrap] Legacy user metadata sanitize skipped because users table does not exist")
return nil
}
result := db.Exec(sanitizeLegacyUserMetadataSQL)
if result.Error != nil {
return fmt.Errorf("sanitize legacy user metadata: %w", result.Error)
}
slog.Info("[Bootstrap] Legacy user metadata sanitized", "rowsAffected", result.RowsAffected)
return nil
}

View File

@@ -0,0 +1,157 @@
package bootstrap
import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/testsupport"
"context"
"log"
"os"
"path/filepath"
"testing"
"time"
"github.com/testcontainers/testcontainers-go"
postgres_module "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
gorm_postgres "gorm.io/driver/postgres"
"gorm.io/gorm"
)
func TestSanitizeLegacyUserMetadataRemovesClassificationFlags(t *testing.T) {
db := openBootstrapPostgresTestDB(t)
if err := db.AutoMigrate(&domain.User{}); err != nil {
t.Fatalf("failed to migrate users table: %v", err)
}
user := domain.User{
ID: "10000000-0000-0000-0000-000000000001",
Email: "legacy@example.com",
Name: "Legacy User",
Role: domain.RoleUser,
Status: domain.UserStatusActive,
Metadata: domain.JSONMap{
"hanmacFamily": true,
"userType": "hanmac",
"employeeId": "E001",
"nested": map[string]any{
"userType": "must stay nested",
},
},
}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
if err := SanitizeLegacyUserMetadata(db); err != nil {
t.Fatalf("SanitizeLegacyUserMetadata returned error: %v", err)
}
if err := SanitizeLegacyUserMetadata(db); err != nil {
t.Fatalf("SanitizeLegacyUserMetadata must be idempotent: %v", err)
}
var got domain.User
if err := db.First(&got, "id = ?", user.ID).Error; err != nil {
t.Fatalf("failed to load sanitized user: %v", err)
}
if _, ok := got.Metadata["hanmacFamily"]; ok {
t.Fatalf("hanmacFamily must be removed from metadata: %#v", got.Metadata)
}
if _, ok := got.Metadata["userType"]; ok {
t.Fatalf("userType must be removed from metadata: %#v", got.Metadata)
}
if got.Metadata["employeeId"] != "E001" {
t.Fatalf("employeeId = %#v, want E001", got.Metadata["employeeId"])
}
nested, ok := got.Metadata["nested"].(map[string]any)
if !ok || nested["userType"] != "must stay nested" {
t.Fatalf("nested metadata must be preserved: %#v", got.Metadata["nested"])
}
}
func TestRunSanitizesLegacyUserMetadata(t *testing.T) {
db := openBootstrapPostgresTestDB(t)
if err := db.AutoMigrate(&domain.User{}); err != nil {
t.Fatalf("failed to migrate users table: %v", err)
}
user := domain.User{
ID: "20000000-0000-0000-0000-000000000001",
Email: "run-legacy@example.com",
Name: "Run Legacy User",
Role: domain.RoleUser,
Status: domain.UserStatusActive,
Metadata: domain.JSONMap{
"hanmacFamily": true,
"userType": "external",
"employeeId": "E002",
},
}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
dir := t.TempDir()
path := filepath.Join(dir, "seed-tenant.csv")
csv := "id,name,type,parent_tenant_slug,slug,memo,email_domain\n" +
"30000000-0000-0000-0000-000000000001,Seed Root,COMPANY_GROUP,,seed-root,seed root,\n"
if err := os.WriteFile(path, []byte(csv), 0o600); err != nil {
t.Fatalf("failed to write seed csv: %v", err)
}
t.Setenv(seedTenantCSVPathEnv, path)
if err := Run(db); err != nil {
t.Fatalf("Run returned error: %v", err)
}
var got domain.User
if err := db.First(&got, "id = ?", user.ID).Error; err != nil {
t.Fatalf("failed to load sanitized user: %v", err)
}
if _, ok := got.Metadata["hanmacFamily"]; ok {
t.Fatalf("Run must remove hanmacFamily from metadata: %#v", got.Metadata)
}
if _, ok := got.Metadata["userType"]; ok {
t.Fatalf("Run must remove userType from metadata: %#v", got.Metadata)
}
if got.Metadata["employeeId"] != "E002" {
t.Fatalf("employeeId = %#v, want E002", got.Metadata["employeeId"])
}
}
func openBootstrapPostgresTestDB(t *testing.T) *gorm.DB {
t.Helper()
if !testsupport.DockerAvailable() {
t.Skip("Docker provider is unavailable in this environment")
}
ctx := context.Background()
postgresContainer, err := postgres_module.Run(ctx,
"postgres:16-alpine",
postgres_module.WithDatabase("testdb"),
postgres_module.WithUsername("user"),
postgres_module.WithPassword("password"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(30*time.Second),
),
)
if err != nil {
t.Fatalf("failed to start postgres container: %v", err)
}
t.Cleanup(func() {
if err := postgresContainer.Terminate(ctx); err != nil {
log.Printf("failed to terminate postgres container: %v", err)
}
})
connStr, err := postgresContainer.ConnectionString(ctx, "sslmode=disable")
if err != nil {
t.Fatalf("failed to get postgres connection string: %v", err)
}
db, err := gorm.Open(gorm_postgres.Open(connStr), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open postgres connection: %v", err)
}
return db
}