//go:build integration package migration_test import ( "context" "testing" "github.com/google/uuid" "github.com/ultisuite/ulti-backend/internal/integrationtest" "github.com/ultisuite/ulti-backend/internal/users" ) func TestClaimInviteRequiresActiveProjectDomain(t *testing.T) { h := integrationtest.RequireHarness(t) ctx := context.Background() adminClient, adminClaims := integrationtest.RequireAdminClient(t, h) if _, err := users.EnsureUser(ctx, h.Pool, adminClaims); err != nil { t.Fatalf("ensure admin: %v", err) } if err := users.GrantPlatformAdmin(ctx, h.Pool, adminClaims.Sub); err != nil { t.Fatalf("grant admin: %v", err) } domainName := "migration-" + uuid.NewString()[:8] + ".test" var domainID string err := h.Pool.QueryRow(ctx, ` INSERT INTO mail_domains (name, status, is_platform_domain) VALUES ($1, 'pending_verification', false) RETURNING id::text `, domainName).Scan(&domainID) integrationtest.FailIf(err, t, "insert domain") createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{ "name": "Domain-bound migration", "source_provider": "google", "domain_id": domainID, }) integrationtest.FailIf(err, t, "create project") integrationtest.FailUnlessStatus(t, createResp, 201) var created struct { ID string `json:"id"` } integrationtest.DecodeJSON(t, createResp, &created) actResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/activate", nil) integrationtest.FailIf(err, t, "activate project") integrationtest.FailUnlessStatus(t, actResp, 200) migrateeEmail := "user@" + domainName inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]string{ "email": migrateeEmail, }) integrationtest.FailIf(err, t, "create invite") integrationtest.FailUnlessStatus(t, inviteResp, 201) var invite struct { Token string `json:"token"` } integrationtest.DecodeJSON(t, inviteResp, &invite) migrateeClaims := integrationtest.RegularUser(integrationtest.NewExternalID("domain-claim")) migrateeClaims.Email = migrateeEmail migrateeClient, err := h.Client(migrateeClaims) integrationtest.FailIf(err, t, "migratee client") _, err = users.EnsureUser(ctx, h.Pool, migrateeClaims) integrationtest.FailIf(err, t, "ensure migratee") claimResp, err := migrateeClient.Post("/api/v1/migration/claim", map[string]string{ "token": invite.Token, "password": "test-password-123", }) integrationtest.FailIf(err, t, "claim invite") if claimResp.Status != 400 { t.Fatalf("status = %d, want 400 for inactive domain; body=%s", claimResp.Status, string(claimResp.Body)) } } func TestClaimInviteWithActiveDomainSucceeds(t *testing.T) { h := integrationtest.RequireHarness(t) ctx := context.Background() adminClient, adminClaims := integrationtest.RequireAdminClient(t, h) if _, err := users.EnsureUser(ctx, h.Pool, adminClaims); err != nil { t.Fatalf("ensure admin: %v", err) } if err := users.GrantPlatformAdmin(ctx, h.Pool, adminClaims.Sub); err != nil { t.Fatalf("grant admin: %v", err) } domainName := "active-" + uuid.NewString()[:8] + ".test" var domainID string err := h.Pool.QueryRow(ctx, ` INSERT INTO mail_domains (name, status, is_platform_domain) VALUES ($1, 'active', false) RETURNING id::text `, domainName).Scan(&domainID) integrationtest.FailIf(err, t, "insert domain") createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{ "name": "Active domain migration", "source_provider": "google", "domain_id": domainID, }) integrationtest.FailIf(err, t, "create project") integrationtest.FailUnlessStatus(t, createResp, 201) var created struct { ID string `json:"id"` } integrationtest.DecodeJSON(t, createResp, &created) actResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/activate", nil) integrationtest.FailIf(err, t, "activate project") integrationtest.FailUnlessStatus(t, actResp, 200) migrateeEmail := "user@" + domainName inviteResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/invites", map[string]string{ "email": migrateeEmail, }) integrationtest.FailIf(err, t, "create invite") integrationtest.FailUnlessStatus(t, inviteResp, 201) var invite struct { Token string `json:"token"` } integrationtest.DecodeJSON(t, inviteResp, &invite) migrateeClaims := integrationtest.RegularUser(integrationtest.NewExternalID("domain-claim-ok")) migrateeClaims.Email = migrateeEmail migrateeClient, err := h.Client(migrateeClaims) integrationtest.FailIf(err, t, "migratee client") userID, err := users.EnsureUser(ctx, h.Pool, migrateeClaims) integrationtest.FailIf(err, t, "ensure migratee") claimResp, err := migrateeClient.Post("/api/v1/migration/claim", map[string]string{ "token": invite.Token, "password": "test-password-123", }) integrationtest.FailIf(err, t, "claim invite") integrationtest.FailUnlessStatus(t, claimResp, 200) var mailboxCount int if err := h.Pool.QueryRow(ctx, ` SELECT COUNT(*) FROM mailboxes mb JOIN mail_domains md ON md.id = mb.domain_id WHERE mb.user_id = $1::uuid AND md.id = $2::uuid `, userID, domainID).Scan(&mailboxCount); err != nil { t.Fatalf("count mailboxes: %v", err) } if mailboxCount != 1 { t.Fatalf("mailbox count = %d, want 1", mailboxCount) } }