package main import ( "strings" "testing" "github.com/descrybe/descrybe-v2/apps/api/internal/billing" ) func TestLegacyIDFromSyntheticEmail(t *testing.T) { t.Parallel() if got := legacyIDFromSyntheticEmail("user_abc@legacy.local"); got != "user_abc" { t.Fatalf("got %q", got) } if got := legacyIDFromSyntheticEmail(" User_ABC@Legacy.Local "); got != "user_abc" { t.Fatalf("got %q", got) } if got := legacyIDFromSyntheticEmail("real@example.com"); got != "" { t.Fatalf("want empty, got %q", got) } } func TestParseEmailPatchJSONMap(t *testing.T) { t.Parallel() m, err := parseEmailPatchJSON([]byte(`{"user_1":"A@Example.COM","user_2":""}`)) if err != nil { t.Fatal(err) } if m["user_1"] != "a@example.com" { t.Fatalf("got %#v", m) } if _, ok := m["user_2"]; ok { t.Fatal("empty emails must be dropped") } } func TestParseEmailPatchJSONInventoryEmails(t *testing.T) { t.Parallel() raw := []byte(`{ "version": 1, "emails": {"user_x":"x@example.com"}, "users": [{"legacy_user_id":"user_x","email":"user_x@legacy.local"}] }`) m, err := parseEmailPatchJSON(raw) if err != nil { t.Fatal(err) } if m["user_x"] != "x@example.com" { t.Fatalf("got %#v", m) } } func TestParseEmailPatchJSONArrayClerkish(t *testing.T) { t.Parallel() raw := []byte(`[ {"id":"user_a","primary_email_address":"a@ex.com"}, {"id":"user_b","email_addresses":[{"email_address":"b@ex.com","primary":true}]} ]`) m, err := parseEmailPatchJSON(raw) if err != nil { t.Fatal(err) } if m["user_a"] != "a@ex.com" || m["user_b"] != "b@ex.com" { t.Fatalf("got %#v", m) } } func TestParseEmailPatchCSV(t *testing.T) { t.Parallel() m, err := parseEmailPatchCSV([]byte("id,primary_email_address\nuser_c,C@Ex.COM\n")) if err != nil { t.Fatal(err) } if m["user_c"] != "c@ex.com" { t.Fatalf("got %#v", m) } } func TestPlanEmailPatchesSafety(t *testing.T) { t.Parallel() rows := []legacyEmailRow{ {ID: "u1", Email: "user_1@legacy.local", LegacyUserID: "user_1", A1Member: true}, {ID: "u2", Email: "a1-primary@descrybe.local", LegacyUserID: "user_live", A1Member: true}, {ID: "u3", Email: "user_3@legacy.local", LegacyUserID: "user_3"}, {ID: "u4", Email: "user_4@legacy.local", LegacyUserID: "user_4"}, {ID: "u5", Email: "user_5@legacy.local", LegacyUserID: "user_5"}, } byLegacy := map[string]string{ "user_1": "real1@example.com", "user_live": "should-not-apply@example.com", "user_3": "user_3@legacy.local", "user_4": "taken@example.com", "user_5": "real5@example.com", "user_missing": "ghost@example.com", } occupied := map[string]string{ "user_1@legacy.local": "u1", "a1-primary@descrybe.local": "u2", "user_3@legacy.local": "u3", "user_4@legacy.local": "u4", "user_5@legacy.local": "u5", "taken@example.com": "other", } plan := planEmailPatches(rows, byLegacy, occupied) if len(plan.Apply) != 1 || plan.Apply[0].UserID != "u5" || plan.Apply[0].ToEmail != "real5@example.com" { t.Fatalf("apply=%#v", plan.Apply) } if plan.Apply[0].A1Member { t.Fatal("apply list must never include a1_member=true") } reasons := map[string]bool{} for _, s := range plan.Skips { reasons[s.Reason] = true if s.UserID == "u1" && s.Reason != "a1_member" { t.Fatalf("A1 synthetic email must skip as a1_member, skip=%#v", s) } if s.UserID == "u2" && s.Reason != "a1_member" { t.Fatalf("live A1 email must skip as a1_member, skip=%#v", s) } } for _, want := range []string{ "a1_member", "target_still_synthetic", "target_email_owned_by_other", "no_synthetic_user_for_legacy_id", } { if !reasons[want] { t.Fatalf("missing skip reason %q in %#v", want, plan.Skips) } } } func TestPlanEmailPatchesEmptyMap(t *testing.T) { t.Parallel() plan := planEmailPatches(nil, nil, nil) if len(plan.Skips) != 1 || !strings.Contains(plan.Skips[0].Reason, "empty") { t.Fatalf("got %#v", plan.Skips) } } func TestRowIsA1Member(t *testing.T) { t.Parallel() if !rowIsA1Member(legacyEmailRow{LegacyCompanyIDs: []string{billing.A1LegacyCompanyID}}) { t.Fatal("expected A1 by legacy company id") } if !rowIsA1Member(legacyEmailRow{Companies: []string{"A1 Slovenija"}}) { t.Fatal("expected A1 by name") } if rowIsA1Member(legacyEmailRow{Companies: []string{"Acme"}}) { t.Fatal("Acme is not A1") } }