test: regression coverage for error mapping and client error extraction

- handlers: pgx.ErrNoRows→404, InputError→400 with message preserved,
  pgconn 23505→409 without constraint/SQLSTATE leak, unknown→500
- services: export NewInputError so error construction is testable
- frontend: vitest for errorMessage helper — server reason preferred,
  status text and fallbacks verified

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-24 22:58:55 +02:00
1 parent 89f3d84234
commit a71baa49cd
3 files changed
+78

No files matched your search

@@ -4,9 +4,14 @@ import (
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/tdvorak/primora/apps/backend/internal/services"
)
func newTestContext(rawQuery string) (*gin.Context, *httptest.ResponseRecorder) {
@@ -104,4 +109,46 @@ func TestHandleErrorStatusMapping(t *testing.T) {
t.Fatalf("unexpected status: %d", recorder.Code)
}
})
t.Run("maps missing rows to not found", func(t *testing.T) {
t.Parallel()
ctx, recorder := newTestContext("")
handler.handleError(ctx, pgx.ErrNoRows)
if recorder.Code != http.StatusNotFound {
t.Fatalf("unexpected status: %d", recorder.Code)
}
})
t.Run("maps InputError to bad request with message", func(t *testing.T) {
t.Parallel()
ctx, recorder := newTestContext("")
handler.handleError(ctx, services.NewInputError("webhook url must be https"))
if recorder.Code != http.StatusBadRequest {
t.Fatalf("unexpected status: %d", recorder.Code)
}
if !strings.Contains(recorder.Body.String(), "webhook url must be https") {
t.Fatalf("expected reason in body, got %s", recorder.Body.String())
}
})
t.Run("maps unique violation to conflict without leaking", func(t *testing.T) {
t.Parallel()
ctx, recorder := newTestContext("")
handler.handleError(ctx, &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint \"projects_slug_key\""})
if recorder.Code != http.StatusConflict {
t.Fatalf("unexpected status: %d", recorder.Code)
}
if strings.Contains(recorder.Body.String(), "projects_slug_key") || strings.Contains(recorder.Body.String(), "23505") {
t.Fatalf("raw constraint leaked: %s", recorder.Body.String())
}
})
t.Run("maps unknown pg error to internal error", func(t *testing.T) {
t.Parallel()
ctx, recorder := newTestContext("")
handler.handleError(ctx, &pgconn.PgError{Code: "XX000", Message: "internal"})
if recorder.Code != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", recorder.Code)
}
})
}
@@ -1534,6 +1534,8 @@ type InputError struct{ msg string }
func (e *InputError) Error() string { return e.msg }
func NewInputError(msg string) error { return &InputError{msg: msg} }
func inputErrorf(format string, args ...any) error {
return &InputError{msg: fmt.Sprintf(format, args...)}
}