- Added configuration options for Stalwart hosted mail in .env.example. - Updated Docker Compose to include Stalwart service with health checks. - Introduced new API endpoints for managing mail domains and migration projects. - Enhanced Authentik blueprints for user enrollment and post-migration security. - Updated OAuth handling for Google and Microsoft migration processes. - Improved error handling and response structures in the mail API. - Added integration tests for email claiming and migration workflows.
160 lines
5.3 KiB
Go
160 lines
5.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 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)
|
|
}
|
|
}
|