package service import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/repository" "context" "fmt" "os" "strings" "testing" "time" "github.com/stretchr/testify/require" "gorm.io/driver/postgres" "gorm.io/gorm" ) func TestWorksmobileLiveSamanUsersDirectoryProvisioning(t *testing.T) { if os.Getenv("WORKSMOBILE_LIVE_SAMAN_PROVISIONING") != "1" { t.Skip("live Worksmobile Saman provisioning is disabled") } ctx := context.Background() db, err := gorm.Open(postgres.Open(worksmobileLiveDSN()), &gorm.Config{}) require.NoError(t, err) tenantRepo := repository.NewTenantRepository(db) userRepo := repository.NewUserRepository(db) userGroupRepo := repository.NewUserGroupRepository(db) outboxRepo := repository.NewWorksmobileOutboxRepository(db) tenantService := NewTenantService(tenantRepo, userRepo, userGroupRepo, nil) client := NewWorksmobileHTTPClientWithAuth("", os.Getenv("SAMAN_SCIM_LONGLIVE_TOKEN"), WorksmobileOAuthConfig{ ClientID: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_ID"), ClientSecret: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SECRET"), ServiceAccount: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SERVICE_ACCOUNT"), PrivateKey: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY"), Scope: getenvDefault("WORKS_ADMIN_OAUTH_SCOPE", "directory"), }) syncService := NewWorksmobileSyncService(tenantService, userRepo, outboxRepo, client) worker := NewWorksmobileRelayWorker(outboxRepo, client) root, err := tenantService.GetTenantBySlug(ctx, HanmacFamilyTenantSlug) require.NoError(t, err) samanTenant, err := tenantService.GetTenantBySlug(ctx, "saman") require.NoError(t, err) createWorksmobileLiveOrgUnitIfMissing(t, ctx, client, *samanTenant) targetEmails := []string{"tester@samaneng.com", "orgadmin@samaneng.com"} for _, email := range targetEmails { user, err := userRepo.FindByEmail(ctx, email) require.NoError(t, err) dedupeKey := "user:" + strings.ToLower(WorksmobileUserStatusAction(user.Status)) + ":" + user.ID job := findWorksmobileLiveOutboxByDedupe(t, db, dedupeKey) if job.Status != domain.WorksmobileOutboxStatusProcessed { remote, err := client.FindUser(ctx, user.Email) require.NoError(t, err) if remote != nil { require.NoError(t, outboxRepo.MarkProcessed(ctx, job.ID)) continue } item, err := syncService.EnqueueUserSync(ctx, root.ID, user.ID) require.NoError(t, err) require.NotEmpty(t, item) require.NoError(t, outboxRepo.MarkRetry(ctx, job.ID)) job = findWorksmobileLiveOutboxByDedupe(t, db, dedupeKey) err = worker.processJob(ctx, job) require.NoError(t, err) } processed, err := outboxRepo.FindByID(ctx, job.ID) require.NoError(t, err) require.Equal(t, domain.WorksmobileOutboxStatusProcessed, processed.Status) } credentials, err := syncService.ListInitialPasswordCredentials(ctx, root.ID) require.NoError(t, err) seen := map[string]bool{} for _, credential := range credentials { if credential.Email == "tester@samaneng.com" || credential.Email == "orgadmin@samaneng.com" { require.Equal(t, domain.WorksmobileOutboxStatusProcessed, credential.Status) require.Len(t, credential.InitialPassword, 16) seen[credential.Email] = true } } require.True(t, seen["tester@samaneng.com"]) require.True(t, seen["orgadmin@samaneng.com"]) remoteUsers, err := client.ListUsers(ctx) require.NoError(t, err) remoteByEmail := map[string]WorksmobileRemoteUser{} for _, user := range remoteUsers { remoteByEmail[user.Email] = user } require.NotEmpty(t, remoteByEmail["tester@samaneng.com"].ID) require.NotEmpty(t, remoteByEmail["orgadmin@samaneng.com"].ID) remoteGroups, err := client.ListGroups(ctx) require.NoError(t, err) foundSamanOrgUnit := false for _, group := range remoteGroups { if group.ExternalID == samanTenant.ID { foundSamanOrgUnit = true require.Equal(t, "삼안", group.DisplayName) } } require.True(t, foundSamanOrgUnit) } func createWorksmobileLiveOrgUnitIfMissing(t *testing.T, ctx context.Context, client *WorksmobileHTTPClient, tenant domain.Tenant) { t.Helper() payload, err := BuildWorksmobileOrgUnitPayload(tenant, nil, 1) require.NoError(t, err) if tenant.ParentID != nil { payload.ParentOrgUnitID = "" } err = client.CreateOrgUnit(ctx, payload) if apiErr, ok := err.(WorksmobileHTTPError); ok && apiErr.StatusCode == 409 { return } require.NoError(t, err) } func worksmobileLiveDSN() string { host := getenvDefault("DB_HOST", "localhost") port := getenvDefault("DB_PORT", "5432") user := getenvDefault("DB_USER", "baron") password := os.Getenv("DB_PASSWORD") name := getenvDefault("DB_NAME", "baron_sso") return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Seoul", host, user, password, name, port) } func findWorksmobileLiveOutboxByDedupe(t *testing.T, db *gorm.DB, dedupeKey string) domain.WorksmobileOutbox { t.Helper() var job domain.WorksmobileOutbox deadline := time.Now().Add(3 * time.Second) for { err := db.Where("dedupe_key = ?", dedupeKey).First(&job).Error if err == nil { return job } if time.Now().After(deadline) { require.NoError(t, err) } time.Sleep(100 * time.Millisecond) } }