package recruiting import ( "errors" "strings" "testing" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgconn" ) // TestParseTokenRequiresAValue. An update without a concurrency token must be refused rather than // silently treated as last write wins, which is the failure mode the token exists to prevent. func TestParseTokenRequiresAValue(t *testing.T) { for _, v := range []string{"", " ", "\t"} { if _, err := parseToken(v); !errors.Is(err, ErrInvalid) { t.Errorf("parseToken(%q) error = %v, want a validation error", v, err) } } } func TestParseTokenRejectsNonRFC3339(t *testing.T) { for _, v := range []string{"yesterday", "2026-07-20", "1721520000"} { if _, err := parseToken(v); err == nil { t.Errorf("parseToken(%q) accepted a value that is not RFC3339", v) } } } // TestTokenRoundTripPreservesMicroseconds is the property the whole optimistic concurrency scheme rests // on. Postgres stores timestamptz at microsecond precision; if the wire format truncated it, every // update would compare unequal and every write would 409 forever. func TestTokenRoundTripPreservesMicroseconds(t *testing.T) { original := time.Date(2026, 7, 21, 1, 49, 23, 90678000, time.UTC) parsed, err := parseToken(ts(original)) if err != nil { t.Fatalf("round trip failed: %v", err) } if !parsed.Equal(original) { t.Errorf("round trip lost precision: %s became %s", original.Format(time.RFC3339Nano), parsed.Format(time.RFC3339Nano)) } } // TestCleanTrimsAndBounds. func TestCleanTrimsAndBounds(t *testing.T) { got, err := clean("name", " Acme Defense ", 200) if err != nil || got != "Acme Defense" { t.Errorf("clean trimmed to %q err=%v, want \"Acme Defense\"", got, err) } if _, err := clean("name", strings.Repeat("x", 201), 200); err == nil { t.Error("an over length value was accepted") } if got, err := clean("name", strings.Repeat("x", 200), 200); err != nil || len(got) != 200 { t.Errorf("a value exactly at the bound was rejected: %v", err) } } // TestTranslatePGMapsConstraintsToTypedErrors is the reason a duplicate client name is a 409 that names // the field instead of a 500. The constraint names are declared in migration 0047 and this test is what // stops a rename there silently turning a clean conflict back into an internal error. func TestTranslatePGMapsConstraintsToTypedErrors(t *testing.T) { cases := []struct { name string code string constraint string column string wantField string wantDup bool wantVal bool }{ {"duplicate client name", "23505", "uq_clients_org_name", "", "name", true, false}, {"duplicate client uei", "23505", "uq_clients_org_uei", "", "uei", true, false}, {"two primary contacts", "23505", "uq_client_contacts_primary", "", "is_primary", true, false}, {"unknown unique", "23505", "some_other_uq", "", "some_other_uq", true, false}, {"status check", "23514", "clients_status_check", "", "status", false, true}, {"not null", "23502", "", "name", "name", false, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { err := translatePG(&pgconn.PgError{Code: c.code, ConstraintName: c.constraint, ColumnName: c.column}) var dup *DuplicateError var val *ValidationError switch { case c.wantDup: if !errors.As(err, &dup) { t.Fatalf("want a DuplicateError, got %T: %v", err, err) } if dup.Field != c.wantField { t.Errorf("field = %q, want %q", dup.Field, c.wantField) } case c.wantVal: if !errors.As(err, &val) { t.Fatalf("want a ValidationError, got %T: %v", err, err) } if val.Field != c.wantField { t.Errorf("field = %q, want %q", val.Field, c.wantField) } if !errors.Is(err, ErrInvalid) { t.Error("a ValidationError must unwrap to ErrInvalid so the handler can classify it") } } }) } } // TestTranslatePGMapsForeignKeyToNotFound. A write naming a client that does not exist is a 404 for the // caller, not a 500. func TestTranslatePGMapsForeignKeyToNotFound(t *testing.T) { if err := translatePG(&pgconn.PgError{Code: "23503"}); !errors.Is(err, ErrNotFound) { t.Errorf("foreign key violation mapped to %v, want ErrNotFound", err) } } // TestTranslatePGPassesThroughNonPGErrors. A context cancellation must not be reported as a constraint // problem. func TestTranslatePGPassesThroughNonPGErrors(t *testing.T) { sentinel := errors.New("context canceled") if got := translatePG(sentinel); !errors.Is(got, sentinel) { t.Errorf("a non Postgres error was rewritten: %v", got) } if translatePG(nil) != nil { t.Error("translatePG(nil) must stay nil") } } // TestStatusVocabulariesMatchTheMigrations pins the Go allowlists against the CHECK constraints in 0047. // If they drift, the database rejects a value the service accepted and the caller gets a 500. func TestStatusVocabulariesMatchTheMigrations(t *testing.T) { for _, s := range []string{"prospect", "active", "inactive"} { if !clientStatuses[s] { t.Errorf("client status %q is in migration 0047 but missing from the service allowlist", s) } } if len(clientStatuses) != 3 { t.Errorf("client status allowlist has %d entries, migration 0047 permits 3", len(clientStatuses)) } for _, s := range []string{"active", "inactive"} { if !contactStatuses[s] { t.Errorf("contact status %q is in migration 0047 but missing from the service allowlist", s) } } if len(contactStatuses) != 2 { t.Errorf("contact status allowlist has %d entries, migration 0047 permits 2", len(contactStatuses)) } } func TestItoa(t *testing.T) { for _, c := range []struct { in int want string }{{0, "0"}, {7, "7"}, {200, "200"}, {4000, "4000"}} { if got := itoa(c.in); got != c.want { t.Errorf("itoa(%d) = %q, want %q", c.in, got, c.want) } } } // TestOwnershipDecision is the full access matrix. Ownership is the lock, so this is the rule that // decides whether two recruiters can ever be in the same client at once. func TestOwnershipDecision(t *testing.T) { alice := uuid.MustParse("11111111-1111-1111-1111-111111111111") bob := uuid.MustParse("22222222-2222-2222-2222-222222222222") cases := []struct { name string holder *uuid.UUID actor uuid.UUID isAdmin bool wantClaim bool wantErr bool }{ {"unowned account is claimed by the editor", nil, alice, false, true, false}, {"unowned account is claimed by an admin too", nil, alice, true, true, false}, {"the holder may edit", &alice, alice, false, false, false}, {"another recruiter is refused", &alice, bob, false, false, true}, {"an admin may edit an account they do not hold", &alice, bob, true, false, false}, {"the holder who is also admin may edit", &alice, alice, true, false, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { o := owner{userID: c.holder, name: "Alice"} actor := c.actor claim, err := ownershipDecision(o, &actor, c.isAdmin) if c.wantErr { var notOwner *ErrNotOwner if !errors.As(err, ¬Owner) { t.Fatalf("want ErrNotOwner, got %v", err) } if notOwner.OwnerName != "Alice" { t.Errorf("the refusal must name the holder, got %q", notOwner.OwnerName) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if claim != c.wantClaim { t.Errorf("claim = %v, want %v", claim, c.wantClaim) } }) } } // TestOwnershipRefusalNamesTheHolder. The answer to a collision is a conversation, so the error must // carry who to talk to rather than a bare denial. func TestOwnershipRefusalNamesTheHolder(t *testing.T) { alice := uuid.MustParse("11111111-1111-1111-1111-111111111111") bob := uuid.MustParse("22222222-2222-2222-2222-222222222222") _, err := ownershipDecision(owner{userID: &alice, name: "Alice Chen"}, &bob, false) if err == nil || !strings.Contains(err.Error(), "Alice Chen") { t.Fatalf("error %v does not name the holder", err) } }