sqlstate_test.go 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. package httpserver
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "github.com/danfragoso/pizzasql-next/pkg/storage"
  11. )
  12. type httpErrorBody struct {
  13. Error struct {
  14. Code string `json:"code"`
  15. Message string `json:"message"`
  16. Details map[string]interface{} `json:"details"`
  17. } `json:"error"`
  18. }
  19. func TestHTTPUniqueViolationReturns23505(t *testing.T) {
  20. server := setupKVTestServer(t)
  21. queryHTTP(t, server, "CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
  22. queryHTTP(t, server, "INSERT INTO users (id, email) VALUES (1, 'a@x')")
  23. body, _ := json.Marshal(QueryRequest{SQL: "INSERT INTO users (id, email) VALUES (2, 'a@x')"})
  24. r := httptest.NewRequest(http.MethodPost, "/query", strings.NewReader(string(body)))
  25. w := httptest.NewRecorder()
  26. server.handleQuery(w, r)
  27. if w.Code != http.StatusConflict {
  28. t.Fatalf("status = %d, want 409; body %s", w.Code, w.Body.String())
  29. }
  30. var resp httpErrorBody
  31. if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
  32. t.Fatalf("decode: %v", err)
  33. }
  34. if resp.Error.Code != "23505" {
  35. t.Fatalf("code = %q, want 23505", resp.Error.Code)
  36. }
  37. constraint, _ := resp.Error.Details["constraint"].(string)
  38. if constraint == "" || !strings.Contains(resp.Error.Message, constraint) {
  39. t.Fatalf("details.constraint = %q, message = %q", constraint, resp.Error.Message)
  40. }
  41. }
  42. func TestSQLStateHTTPErrorMapping(t *testing.T) {
  43. tests := []struct {
  44. name string
  45. err error
  46. status int
  47. code string
  48. }{
  49. {"unique", fmt.Errorf("UNIQUE constraint failed: uniq_users_email"), http.StatusConflict, "23505"},
  50. {"not null", fmt.Errorf("NOT NULL constraint failed: t.v"), http.StatusBadRequest, "23502"},
  51. {"serialization", fmt.Errorf("commit: %w", storage.ErrSerialization), http.StatusConflict, "40001"},
  52. {"unsupported", fmt.Errorf("feature not supported: DEFAULT is not constant"), http.StatusBadRequest, "0A000"},
  53. {"other", errors.New("boom"), http.StatusInternalServerError, "EXECUTION_ERROR"},
  54. }
  55. for _, tt := range tests {
  56. t.Run(tt.name, func(t *testing.T) {
  57. got := sqlStateHTTPError(tt.err)
  58. if got.Status != tt.status || got.Code != tt.code {
  59. t.Fatalf("got status=%d code=%s, want status=%d code=%s", got.Status, got.Code, tt.status, tt.code)
  60. }
  61. })
  62. }
  63. }