- 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.
116 lines
3.0 KiB
Go
116 lines
3.0 KiB
Go
package migration
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestJobAuditExportCSVFormat(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
cw := csv.NewWriter(&buf)
|
|
if err := cw.Write(jobAuditCSVHeaders); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
row := JobAuditExportRow{
|
|
ItemID: "msg-fail",
|
|
RelPath: "Inbox/foo.eml",
|
|
Status: ItemStatusFailed,
|
|
Error: "upload timeout",
|
|
Service: "mail",
|
|
Timestamp: "2026-06-13T12:00:00Z",
|
|
}
|
|
if err := writeJobAuditCSVRow(cw, row); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cw.Flush()
|
|
if err := cw.Error(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reader := csv.NewReader(strings.NewReader(buf.String()))
|
|
records, err := reader.ReadAll()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(records) != 2 {
|
|
t.Fatalf("records = %d, want 2", len(records))
|
|
}
|
|
if got := strings.Join(records[0], ","); got != "item_id,rel_path,status,error,service,timestamp" {
|
|
t.Fatalf("headers = %q", got)
|
|
}
|
|
if records[1][0] != "msg-fail" || records[1][2] != ItemStatusFailed || records[1][3] != "upload timeout" {
|
|
t.Fatalf("row = %#v", records[1])
|
|
}
|
|
}
|
|
|
|
func TestProjectAuditExportCSVFormat(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
cw := csv.NewWriter(&buf)
|
|
if err := cw.Write(projectAuditCSVHeaders); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := writeProjectAuditCSVRow(cw, JobAuditExportRow{
|
|
JobID: "job-1",
|
|
ItemID: "file-1",
|
|
Status: ItemStatusImported,
|
|
Service: "drive",
|
|
Timestamp: "2026-06-13T12:00:00Z",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cw.Flush()
|
|
|
|
reader := csv.NewReader(strings.NewReader(buf.String()))
|
|
records, err := reader.ReadAll()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if records[0][0] != "job_id" || records[1][0] != "job-1" {
|
|
t.Fatalf("records = %#v", records)
|
|
}
|
|
}
|
|
|
|
func TestJobAuditExportNDJSONFormat(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
enc := json.NewEncoder(&buf)
|
|
if err := enc.Encode(JobAuditExportRow{
|
|
ItemID: "msg-skip",
|
|
Status: ItemStatusSkipped,
|
|
Error: "file too large",
|
|
Service: "mail",
|
|
Timestamp: "2026-06-13T12:00:00Z",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
line := strings.TrimSpace(buf.String())
|
|
var decoded JobAuditExportRow
|
|
if err := json.Unmarshal([]byte(line), &decoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded.ItemID != "msg-skip" || decoded.Status != ItemStatusSkipped || decoded.Error != "file too large" {
|
|
t.Fatalf("decoded = %#v", decoded)
|
|
}
|
|
}
|
|
|
|
func TestJobAuditExportMeta(t *testing.T) {
|
|
csvMeta := jobAuditExportMeta("csv", "01234567-abcd-efgh", false)
|
|
if csvMeta.ContentType != "text/csv; charset=utf-8" {
|
|
t.Fatalf("csv content type = %q", csvMeta.ContentType)
|
|
}
|
|
if !strings.HasSuffix(csvMeta.FileName, ".csv") {
|
|
t.Fatalf("csv filename = %q", csvMeta.FileName)
|
|
}
|
|
|
|
ndMeta := jobAuditExportMeta("ndjson", "01234567-abcd-efgh", true)
|
|
if ndMeta.ContentType != "application/x-ndjson; charset=utf-8" {
|
|
t.Fatalf("ndjson content type = %q", ndMeta.ContentType)
|
|
}
|
|
if !strings.HasPrefix(ndMeta.FileName, "migration-project-audit-") {
|
|
t.Fatalf("project filename = %q", ndMeta.FileName)
|
|
}
|
|
}
|