ultisuite-backend/internal/integrationtest/migration/roster_test.go
R3D347HR4Y 1ffd0817d8
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(migration): enhance migration API with roster and audit export features
- Added endpoints for listing and importing migration rosters.
- Introduced audit export functionality for migration jobs in CSV and NDJSON formats.
- Implemented tenant mismatch validation for Microsoft migration claims.
- Enhanced error handling for email claiming and migration processes.
- Added integration tests for roster import and claim workflows.
2026-06-13 13:11:30 +02:00

134 lines
4.3 KiB
Go

//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 TestMigrationRosterImportAndClaim(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)
}
createResp, err := adminClient.Post("/api/v1/admin/migration/projects", map[string]any{
"name": "Roster migration",
"source_provider": "google",
})
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)
csv := "email,display_name,alternate_emails\n" +
"migratee-" + uuid.NewString() + "@example.com,Test User,alt-" + uuid.NewString() + "@example.com\n"
importResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/roster", map[string]string{
"csv": csv,
})
integrationtest.FailIf(err, t, "import roster")
integrationtest.FailUnlessStatus(t, importResp, 200)
var importResult struct {
Created int `json:"created"`
SkippedDuplicates int `json:"skipped_duplicates"`
}
integrationtest.DecodeJSON(t, importResp, &importResult)
if importResult.Created != 1 {
t.Fatalf("expected 1 created, got %#v", importResult)
}
listResp, err := adminClient.Get("/api/v1/admin/migration/projects/" + created.ID + "/roster")
integrationtest.FailIf(err, t, "list roster")
integrationtest.FailUnlessStatus(t, listResp, 200)
var rosterList struct {
Roster []struct {
Email string `json:"email"`
DisplayName string `json:"display_name"`
Status string `json:"status"`
} `json:"roster"`
}
integrationtest.DecodeJSON(t, listResp, &rosterList)
if len(rosterList.Roster) != 1 {
t.Fatalf("expected 1 roster entry, got %d", len(rosterList.Roster))
}
if rosterList.Roster[0].Status != "invited" {
t.Fatalf("expected invited status, got %q", rosterList.Roster[0].Status)
}
migrateeEmail := rosterList.Roster[0].Email
dupResp, err := adminClient.Post("/api/v1/admin/migration/projects/"+created.ID+"/roster", map[string]string{
"csv": csv,
})
integrationtest.FailIf(err, t, "duplicate import")
integrationtest.FailUnlessStatus(t, dupResp, 200)
var dupResult struct {
SkippedDuplicates int `json:"skipped_duplicates"`
}
integrationtest.DecodeJSON(t, dupResp, &dupResult)
if dupResult.SkippedDuplicates != 1 {
t.Fatalf("expected 1 skipped duplicate, got %d", dupResult.SkippedDuplicates)
}
var inviteToken string
err = h.Pool.QueryRow(ctx, `
SELECT token FROM migration_invites WHERE project_id = $1::uuid AND email = $2
`, created.ID, migrateeEmail).Scan(&inviteToken)
if err != nil {
t.Fatalf("lookup invite token: %v", err)
}
if inviteToken == "" {
t.Fatal("missing invite token for roster entry")
}
migrateeClaims := integrationtest.RegularUser(integrationtest.NewExternalID("roster-migratee"))
migrateeClaims.Email = migrateeEmail
migrateeClient, err := h.Client(migrateeClaims)
integrationtest.FailIf(err, t, "migratee client")
if _, err := users.EnsureUser(ctx, h.Pool, migrateeClaims); err != nil {
t.Fatalf("ensure migratee: %v", err)
}
claimResp, err := migrateeClient.Post("/api/v1/migration/claim", map[string]string{
"token": inviteToken,
"password": "test-password-123",
"display_name": "Test User",
})
integrationtest.FailIf(err, t, "claim invite")
integrationtest.FailUnlessStatus(t, claimResp, 200)
var rosterStatus string
err = h.Pool.QueryRow(ctx, `
SELECT status FROM migration_roster WHERE project_id = $1::uuid AND email = $2
`, created.ID, migrateeEmail).Scan(&rosterStatus)
if err != nil {
t.Fatalf("roster status: %v", err)
}
if rosterStatus != "claimed" {
t.Fatalf("expected claimed roster, got %q", rosterStatus)
}
}