- Add mock_status, mock_headers, mock_body columns to endpoints schema
- Add MockConfig struct and UpsertMock to Repository interface
- Implement UpsertMock in sqliteRepo (INSERT ON CONFLICT UPDATE)
- Update GetEndpoint to return mock configuration when present
- Add PUT /e/{endpoint_id}/mock handler with JSON body {status, headers, body}
- Add catch-all /{endpoint_id} handler returning mock or default 200
- Header sanitization: only allow Content-Type, Cache-Control, X-*
- Wire handler into cmd/hatch main with SQLite store
- Fix Dockerfile: COPY go.sum for reproducible builds
- Tests: store mock CRUD, handler mock config + header sanitization
Co-Authored-By: Paperclip <noreply@paperclip.ing>
75 lines
2.5 KiB
Go
75 lines
2.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"github.com/elfoundation/hatch/internal/store"
|
|
)
|
|
|
|
type fakeRepo struct {
|
|
endpoints map[string]*store.Endpoint
|
|
requests []*store.Request
|
|
}
|
|
func newFakeRepo() *fakeRepo { return &fakeRepo{endpoints: map[string]*store.Endpoint{}} }
|
|
func (f *fakeRepo) CreateEndpoint(_ context.Context, u string) (*store.Endpoint, error) {
|
|
e := &store.Endpoint{ID: u, URL: u, CreatedAt: "t", UpdatedAt: "t"}
|
|
f.endpoints[u] = e; return e, nil
|
|
}
|
|
func (f *fakeRepo) GetEndpoint(_ context.Context, id string) (*store.Endpoint, error) {
|
|
e, ok := f.endpoints[id]; if !ok { return nil, errNotFound }; return e, nil
|
|
}
|
|
func (f *fakeRepo) AppendRequest(_ context.Context, eid string, r *store.Request) error {
|
|
r.ID = "x"; r.EndpointID = eid; f.requests = append(f.requests, r); return nil
|
|
}
|
|
func (f *fakeRepo) ListRequests(_ context.Context, _ string, _ int) ([]*store.Request, error) { return f.requests, nil }
|
|
func (f *fakeRepo) Close() error { return nil }
|
|
var errNotFound = &se{"nf"}
|
|
type se struct{ m string }
|
|
func (e *se) Error() string { return e.m }
|
|
|
|
func TestRecordsAllVerbs(t *testing.T) {
|
|
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
|
for _, m := range methods {
|
|
repo := newFakeRepo()
|
|
h := New(repo)
|
|
body := ""
|
|
if m == "POST" || m == "PUT" || m == "PATCH" { body = `{"k":"v"}` }
|
|
req := httptest.NewRequest(m, "/ep", strings.NewReader(body))
|
|
if m == "GET" { req.Header.Set("Accept", "text/html") }
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, req)
|
|
if w.Result().StatusCode != 200 { t.Errorf("%s: expected 200", m) }
|
|
if len(repo.requests) != 1 { t.Errorf("%s: expected 1 request", m); continue }
|
|
r := repo.requests[0]
|
|
if r.Method != m { t.Errorf("%s: wrong method %s", m, r.Method) }
|
|
if r.Path != "/ep" { t.Errorf("%s: wrong path", m) }
|
|
}
|
|
}
|
|
|
|
func TestHandlerReturnsJSON200(t *testing.T) {
|
|
h := New(newFakeRepo())
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, httptest.NewRequest("GET", "/t", nil))
|
|
resp := w.Result()
|
|
if resp.StatusCode != 200 { t.Fatalf("expected 200") }
|
|
if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") { t.Error("not json") }
|
|
data, _ := io.ReadAll(resp.Body); resp.Body.Close()
|
|
var v map[string]interface{}
|
|
if json.Unmarshal(data, &v) != nil { t.Fatal("invalid json") }
|
|
}
|
|
|
|
func TestExtractID(t *testing.T) {
|
|
for _, tc := range []struct{ p, w string }{
|
|
{"/a","a"},{"/a/b","a"},{"/",""},{"/x?y=z","x"},
|
|
} {
|
|
if g := extractID(tc.p); g != tc.w {
|
|
t.Errorf("extractID(%q)=%q want %q", tc.p, g, tc.w)
|
|
}
|
|
}
|
|
}
|