forked from baron/baron-sso
287 lines
9.4 KiB
Go
287 lines
9.4 KiB
Go
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 TestCanonicalizeLegacyUserStatuses(t *testing.T) {
|
|
db := openBootstrapPostgresTestDB(t)
|
|
if err := db.AutoMigrate(&domain.User{}); err != nil {
|
|
t.Fatalf("failed to migrate users table: %v", err)
|
|
}
|
|
|
|
users := []domain.User{
|
|
{ID: "11000000-0000-0000-0000-000000000001", Email: "inactive@example.com", Name: "Inactive", Role: domain.RoleUser, Status: "inactive"},
|
|
{ID: "11000000-0000-0000-0000-000000000002", Email: "leave@example.com", Name: "Leave", Role: domain.RoleUser, Status: "leave_of_absence"},
|
|
{ID: "11000000-0000-0000-0000-000000000003", Email: "baron-only@example.com", Name: "Baron Only", Role: domain.RoleUser, Status: "baron_only"},
|
|
{ID: "11000000-0000-0000-0000-000000000004", Email: "active@example.com", Name: "Active", Role: domain.RoleUser, Status: domain.UserStatusActive},
|
|
}
|
|
if err := db.Create(&users).Error; err != nil {
|
|
t.Fatalf("failed to create users: %v", err)
|
|
}
|
|
|
|
if err := CanonicalizeLegacyUserStatuses(db); err != nil {
|
|
t.Fatalf("CanonicalizeLegacyUserStatuses returned error: %v", err)
|
|
}
|
|
if err := CanonicalizeLegacyUserStatuses(db); err != nil {
|
|
t.Fatalf("CanonicalizeLegacyUserStatuses must be idempotent: %v", err)
|
|
}
|
|
|
|
got := map[string]string{}
|
|
var loaded []domain.User
|
|
if err := db.Find(&loaded).Error; err != nil {
|
|
t.Fatalf("failed to load users: %v", err)
|
|
}
|
|
for _, user := range loaded {
|
|
got[user.Email] = user.Status
|
|
}
|
|
|
|
if got["inactive@example.com"] != domain.UserStatusPreboarding {
|
|
t.Fatalf("inactive status = %q, want %q", got["inactive@example.com"], domain.UserStatusPreboarding)
|
|
}
|
|
if got["leave@example.com"] != domain.UserStatusTemporaryLeave {
|
|
t.Fatalf("leave status = %q, want %q", got["leave@example.com"], domain.UserStatusTemporaryLeave)
|
|
}
|
|
if got["baron-only@example.com"] != domain.UserStatusBaronGuest {
|
|
t.Fatalf("baron_only status = %q, want %q", got["baron-only@example.com"], domain.UserStatusBaronGuest)
|
|
}
|
|
if got["active@example.com"] != domain.UserStatusActive {
|
|
t.Fatalf("active status = %q, want %q", got["active@example.com"], domain.UserStatusActive)
|
|
}
|
|
}
|
|
|
|
func TestCanonicalizeUserAppointmentTenantsUsesTenantUUID(t *testing.T) {
|
|
db := openBootstrapPostgresTestDB(t)
|
|
if err := db.AutoMigrate(&domain.Tenant{}, &domain.User{}); err != nil {
|
|
t.Fatalf("failed to migrate users and tenants tables: %v", err)
|
|
}
|
|
|
|
tenant := domain.Tenant{
|
|
ID: "30000000-0000-0000-0000-000000000101",
|
|
Type: domain.TenantTypeOrganization,
|
|
Name: "통합시스템",
|
|
Slug: "intigrated-system",
|
|
Status: domain.TenantStatusActive,
|
|
}
|
|
if err := db.Create(&tenant).Error; err != nil {
|
|
t.Fatalf("failed to create tenant: %v", err)
|
|
}
|
|
|
|
user := domain.User{
|
|
ID: "30000000-0000-0000-0000-000000000201",
|
|
Email: "appointment@example.com",
|
|
Name: "Appointment User",
|
|
Role: domain.RoleUser,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": tenant.ID,
|
|
"tenantSlug": "tech-planning",
|
|
"tenantName": "기술기획",
|
|
"grade": "연구원",
|
|
},
|
|
map[string]any{
|
|
"tenantId": "30000000-0000-0000-0000-000000000999",
|
|
"tenantSlug": "unknown-old",
|
|
"tenantName": "Unknown Old",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if err := db.Create(&user).Error; err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
if err := CanonicalizeUserAppointmentTenants(db); err != nil {
|
|
t.Fatalf("CanonicalizeUserAppointmentTenants returned error: %v", err)
|
|
}
|
|
if err := CanonicalizeUserAppointmentTenants(db); err != nil {
|
|
t.Fatalf("CanonicalizeUserAppointmentTenants 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 canonicalized user: %v", err)
|
|
}
|
|
appointments, ok := got.Metadata["additionalAppointments"].([]any)
|
|
if !ok || len(appointments) != 2 {
|
|
t.Fatalf("additionalAppointments = %#v, want two appointments", got.Metadata["additionalAppointments"])
|
|
}
|
|
first, ok := appointments[0].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("first appointment = %#v, want object", appointments[0])
|
|
}
|
|
if first["tenantId"] != tenant.ID {
|
|
t.Fatalf("tenantId = %#v, want %s", first["tenantId"], tenant.ID)
|
|
}
|
|
if first["tenantSlug"] != tenant.Slug {
|
|
t.Fatalf("tenantSlug = %#v, want %s", first["tenantSlug"], tenant.Slug)
|
|
}
|
|
if first["tenantName"] != tenant.Name {
|
|
t.Fatalf("tenantName = %#v, want %s", first["tenantName"], tenant.Name)
|
|
}
|
|
if first["grade"] != "연구원" {
|
|
t.Fatalf("grade = %#v, want preserved value", first["grade"])
|
|
}
|
|
second, ok := appointments[1].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("second appointment = %#v, want object", appointments[1])
|
|
}
|
|
if second["tenantSlug"] != "unknown-old" || second["tenantName"] != "Unknown Old" {
|
|
t.Fatalf("unknown tenant appointment must be preserved: %#v", second)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|