package web import ( "errors" "fmt" "testing" "github.com/jackc/pgx/v5/pgconn" ) func TestFieldErrorsFromDB_ConstraintNameHit(t *testing.T) { msgs := ConstraintMessages{ "workspaces_org_id_slug_key": {Field: "slug", Message: "A workspace with this slug already exists in this organization."}, } // Wrapped, as sqlc/database/sql returns it in practice. err := fmt.Errorf("exec insert: %w", &pgconn.PgError{ Code: "23505", ConstraintName: "workspaces_org_id_slug_key", Message: `duplicate key value violates unique constraint "workspaces_org_id_slug_key"`, }) fe, ok := FieldErrorsFromDB(err, msgs) if !ok { t.Fatalf("FieldErrorsFromDB ok = false, want true") } if got := fe.Get("slug"); got != "A workspace with this slug already exists in this organization." { t.Errorf("fe.Get(slug) = %q, want the mapped message", got) } if len(fe) != 1 { t.Errorf("len(fe) = %d, want exactly one entry", len(fe)) } // The named match must win over the per-class fallback. if fe.Has("") { t.Errorf("form-level fallback populated alongside a named constraint hit: %q", fe.Get("")) } } func TestFieldErrorsFromDB_ClassFallback(t *testing.T) { cases := []struct { code string want string }{ {"23505", "A record with these values already exists."}, {"23503", "A referenced record no longer exists or is still in use."}, {"23514", "This combination of values is not allowed."}, {"22001", "One of the values is too long."}, } for _, tc := range cases { t.Run(tc.code, func(t *testing.T) { err := &pgconn.PgError{Code: tc.code, ConstraintName: "not_in_the_map"} fe, ok := FieldErrorsFromDB(err, ConstraintMessages{ "some_other_constraint": {Field: "name", Message: "unrelated"}, }) if !ok { t.Fatalf("ok = false, want true for SQLSTATE %s", tc.code) } if got := fe.Get(""); got != tc.want { t.Errorf("fe.Get(\"\") = %q, want %q", got, tc.want) } if fe.Has("name") { t.Errorf("unrelated named mapping leaked into the result") } }) } } func TestFieldErrorsFromDB_NilMapStillFallsBack(t *testing.T) { fe, ok := FieldErrorsFromDB(&pgconn.PgError{Code: "23503"}, nil) if !ok { t.Fatalf("ok = false, want true — nil ConstraintMessages must still allow class fallback") } if fe.Get("") == "" { t.Errorf("expected a form-level fallback message, got none") } } func TestFieldErrorsFromDB_NotOK(t *testing.T) { cases := []struct { name string err error }{ {"non-pg error", errors.New("dial tcp 127.0.0.1:5432: connection refused")}, {"wrapped non-pg error", fmt.Errorf("query: %w", errors.New("context canceled"))}, {"pg error with unrecognized class", &pgconn.PgError{Code: "42703", Message: `column "nope" does not exist`}}, {"nil-ish sql error text mentioning SQLSTATE", errors.New("ERROR: something (SQLSTATE 23505)")}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { fe, ok := FieldErrorsFromDB(tc.err, ConstraintMessages{ "chk_grants_recipient": {Field: "", Message: "A grant must name exactly one recipient."}, }) if ok { t.Fatalf("ok = true, want false (fe=%v) — caller must slog + render a generic message", fe) } if fe != nil { t.Errorf("fe = %v, want nil on ok=false", fe) } }) } } func TestFieldErrorsFromDB_ExclusionConstraintNeedsNamedEntry(t *testing.T) { // SQLSTATE 23P01 (exclusion_violation) has no per-class fallback: without // a named entry it must return ok=false, and with one the name must win. exclErr := &pgconn.PgError{Code: "23P01", ConstraintName: "excl_pool_provision_ladders_active_overlap"} if _, ok := FieldErrorsFromDB(exclErr, nil); ok { t.Fatalf("ok = true for unmapped 23P01, want false") } fe, ok := FieldErrorsFromDB(exclErr, ConstraintMessages{ "excl_pool_provision_ladders_active_overlap": {Field: "product_id", Message: "This pool already has an active plan on that ladder."}, }) if !ok { t.Fatalf("ok = false for mapped 23P01, want true") } if got := fe.Get("product_id"); got != "This pool already has an active plan on that ladder." { t.Errorf("fe.Get(product_id) = %q, want the mapped message", got) } }