package integration_test import ( "context" "encoding/json" "net/http" "testing" "time" "galaxy/integration/testenv" ) // TestMailFlow_LoginCodeAndAdminListing triggers a login code email // (which uses backend's mail outbox), waits for mailpit to capture // the SMTP delivery, and verifies the admin endpoints expose the // same delivery via the typed list response. // // Resend on a `sent` row returns 409 (per OpenAPI/decision record); // this test asserts that contract by attempting a resend on the // captured (and now sent) delivery. func TestMailFlow_LoginCodeAndAdminListing(t *testing.T) { plat := testenv.Bootstrap(t, testenv.BootstrapOptions{}) ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() // Trigger a login code, which writes one mail_deliveries row and // drains via the worker into mailpit. sess := testenv.RegisterSession(t, plat, "pilot+mail@example.com") if sess.DeviceSessionID == "" { t.Fatalf("session not established") } admin := testenv.NewBackendAdminClient(plat.Backend.HTTPURL, plat.Backend.AdminUser, plat.Backend.AdminPassword) raw, resp, err := admin.Do(ctx, http.MethodGet, "/api/v1/admin/mail/deliveries?page=1&page_size=10", nil) if err != nil { t.Fatalf("list deliveries: %v", err) } if resp.StatusCode != http.StatusOK { t.Fatalf("list deliveries: status %d body=%s", resp.StatusCode, string(raw)) } var list struct { Items []struct { DeliveryID string `json:"delivery_id"` Status string `json:"status"` TemplateID string `json:"template_id"` } `json:"items"` } if err := json.Unmarshal(raw, &list); err != nil { t.Fatalf("decode list: %v", err) } if len(list.Items) == 0 { t.Fatalf("admin list returned no deliveries; expected at least the login code row") } var sent string deadline := time.Now().Add(15 * time.Second) for time.Now().Before(deadline) && sent == "" { raw, resp, err = admin.Do(ctx, http.MethodGet, "/api/v1/admin/mail/deliveries?page=1&page_size=10", nil) if err != nil || resp.StatusCode != http.StatusOK { t.Fatalf("list deliveries during wait: %v status=%v", err, resp) } _ = json.Unmarshal(raw, &list) for _, it := range list.Items { if it.Status == "sent" { sent = it.DeliveryID break } } if sent == "" { time.Sleep(300 * time.Millisecond) } } if sent == "" { t.Fatalf("no delivery reached `sent` within 15s; admin list = %+v", list.Items) } // Resend on a sent row must return 409. raw, resp, err = admin.Do(ctx, http.MethodPost, "/api/v1/admin/mail/deliveries/"+sent+"/resend", nil) if err != nil { t.Fatalf("resend sent delivery: %v", err) } if resp.StatusCode != http.StatusConflict { t.Fatalf("resend on sent delivery: status %d body=%s, want 409", resp.StatusCode, string(raw)) } }