package api import ( "encoding/json" "errors" "fmt" "net/http/httptest" "testing" "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" ) func decodeEnvelope(t *testing.T, body []byte) errorBody { t.Helper() var env errorBody if err := json.Unmarshal(body, &env); err != nil { t.Fatalf("decode envelope: %v (body=%s)", err, string(body)) } return env } func TestWriteErr_APIError(t *testing.T) { rec := httptest.NewRecorder() writeErr(rec, apierror.NotFound("track")) if rec.Code != 404 { t.Fatalf("status: want 404, got %d", rec.Code) } env := decodeEnvelope(t, rec.Body.Bytes()) if env.Error.Code != "track_not_found" { t.Errorf("code: want track_not_found, got %q", env.Error.Code) } if env.Error.Message != "track not found" { t.Errorf("message: want %q, got %q", "track not found", env.Error.Message) } } func TestWriteErr_Sentinel(t *testing.T) { rec := httptest.NewRecorder() writeErr(rec, apierror.ErrUnauthorized) if rec.Code != 401 { t.Fatalf("status: want 401, got %d", rec.Code) } env := decodeEnvelope(t, rec.Body.Bytes()) if env.Error.Code != "unauthorized" { t.Errorf("code: want unauthorized, got %q", env.Error.Code) } if env.Error.Message != "authentication required" { t.Errorf("message: want %q, got %q", "authentication required", env.Error.Message) } } func TestWriteErr_PlainError(t *testing.T) { rec := httptest.NewRecorder() writeErr(rec, errors.New("boom")) if rec.Code != 500 { t.Fatalf("status: want 500, got %d", rec.Code) } env := decodeEnvelope(t, rec.Body.Bytes()) if env.Error.Code != "server_error" { t.Errorf("code: want server_error, got %q", env.Error.Code) } if env.Error.Message != "internal server error" { t.Errorf("message: want %q, got %q", "internal server error", env.Error.Message) } } func TestWriteErr_WrappedAPIError(t *testing.T) { rec := httptest.NewRecorder() writeErr(rec, fmt.Errorf("ctx: %w", apierror.BadRequest("bad", "no good"))) if rec.Code != 400 { t.Fatalf("status: want 400, got %d", rec.Code) } env := decodeEnvelope(t, rec.Body.Bytes()) if env.Error.Code != "bad" { t.Errorf("code: want bad, got %q", env.Error.Code) } if env.Error.Message != "no good" { t.Errorf("message: want %q, got %q", "no good", env.Error.Message) } } func TestWriteErr_EnvelopeShape(t *testing.T) { rec := httptest.NewRecorder() writeErr(rec, apierror.BadRequest("x", "y")) var raw map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &raw); err != nil { t.Fatalf("decode raw: %v", err) } if len(raw) != 1 { t.Fatalf("top-level keys: want 1, got %d (%v)", len(raw), raw) } inner, ok := raw["error"].(map[string]any) if !ok { t.Fatalf("error: want object, got %T", raw["error"]) } if len(inner) != 2 { t.Fatalf("inner keys: want 2, got %d (%v)", len(inner), inner) } if _, ok := inner["code"]; !ok { t.Errorf("inner.code missing") } if _, ok := inner["message"]; !ok { t.Errorf("inner.message missing") } } func TestWriteErr_ContentType(t *testing.T) { rec := httptest.NewRecorder() writeErr(rec, apierror.BadRequest("x", "y")) if got := rec.Header().Get("Content-Type"); got != "application/json" { t.Errorf("Content-Type: want application/json, got %q", got) } }