Add linting, formatting, and test infrastructure for Phase 1

- Add .golangci.yml with sensible linting rules
- Add Makefile with test, lint, fmt, vet, build targets
- Enhance CI pipeline with Go formatting check and golangci-lint job
- Create shared testutil.FakeRepository for handler tests
- Refactor handler tests to use shared FakeRepository
- Add data/ and coverage.out to .gitignore

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Riley Zhang 2026-06-24 04:59:52 +02:00
parent 57a9eb6835
commit 6f03ff2465
8 changed files with 262 additions and 121 deletions

View File

@ -17,6 +17,14 @@ jobs:
go-version: '1.25' go-version: '1.25'
cache: true cache: true
- name: Check formatting
run: |
if [ -n "$(gofmt -l .)" ]; then
echo "Files not formatted:"
gofmt -l .
exit 1
fi
- name: Vet - name: Vet
run: go vet ./... run: go vet ./...
@ -41,6 +49,21 @@ jobs:
curl -fsS http://localhost:8080/healthz | grep -q 'ok' curl -fsS http://localhost:8080/healthz | grep -q 'ok'
docker stop hatch-ci docker stop hatch-ci
lint-go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.25'
cache: true
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
lint-docs: lint-docs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

5
.gitignore vendored
View File

@ -12,6 +12,11 @@ go.work
/bin /bin
/dist /dist
# Runtime data
data/
*.db
coverage.out
# IDE # IDE
.idea/ .idea/
.vscode/ .vscode/

57
.golangci.yml Normal file
View File

@ -0,0 +1,57 @@
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- misspell
- unconvert
- unparam
- prealloc
- gocritic
- revive
linters-settings:
errcheck:
check-type-assertions: true
check-blank: true
govet:
check-shadowing: true
revive:
rules:
- name: blank-imports
- name: context-as-argument
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: range
- name: receiver-naming
- name: indent-error-flow
- name: superfluous-else
- name: unreachable-code
- name: redefines-builtin-id
issues:
exclude-rules:
# Exclude some linters from running on test files
- path: _test\.go
linters:
- errcheck
- gocritic
- unparam
# Exclude known issues in test files
- path: _test\.go
text: "func name"
run:
timeout: 5m
modules-download-mode: readonly

35
Makefile Normal file
View File

@ -0,0 +1,35 @@
.PHONY: all build test lint fmt vet clean help
all: lint test build
## build: Build the hatch binary
build:
CGO_ENABLED=0 go build -o hatch ./cmd/hatch
## test: Run all tests with race detection and coverage
test:
go test ./... -v -race -coverprofile=coverage.out
## lint: Run golangci-lint
lint:
golangci-lint run ./...
## fmt: Format code with gofmt and goimports
fmt:
gofmt -s -w .
goimports -w .
## vet: Run go vet
vet:
go vet ./...
## clean: Remove build artifacts
clean:
rm -f hatch coverage.out
## help: Show this help message
help:
@echo "Usage: make [target]"
@echo ""
@echo "Targets:"
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /'

View File

@ -8,10 +8,11 @@ import (
"testing" "testing"
"github.com/elfoundation/hatch/internal/store" "github.com/elfoundation/hatch/internal/store"
"github.com/elfoundation/hatch/internal/testutil"
) )
func TestV1CaptureRequest(t *testing.T) { func TestV1CaptureRequest(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
r := testRouter(repo) r := testRouter(repo)
body := `{"method":"POST","path":"/test","headers":{"X-Custom":"value"},"query":"foo=bar","body":"hello"}` body := `{"method":"POST","path":"/test","headers":{"X-Custom":"value"},"query":"foo=bar","body":"hello"}`
@ -35,16 +36,16 @@ func TestV1CaptureRequest(t *testing.T) {
t.Errorf("expected path /test, got %v", resp["path"]) t.Errorf("expected path /test, got %v", resp["path"])
} }
// Verify request stored in repo. // Verify request stored in repo.
if len(repo.requests) != 1 { if len(repo.Requests) != 1 {
t.Fatalf("expected 1 request stored, got %d", len(repo.requests)) t.Fatalf("expected 1 request stored, got %d", len(repo.Requests))
} }
if repo.requests[0].Headers != `{"X-Custom":"value"}` { if repo.Requests[0].Headers != `{"X-Custom":"value"}` {
t.Errorf("expected headers JSON, got %q", repo.requests[0].Headers) t.Errorf("expected headers JSON, got %q", repo.Requests[0].Headers)
} }
} }
func TestV1ListRequests(t *testing.T) { func TestV1ListRequests(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "list-ep") repo.CreateEndpoint(nil, "list-ep")
// Add a few requests. // Add a few requests.
repo.AppendRequest(nil, "list-ep", &store.Request{Method: "GET", Path: "/a"}) repo.AppendRequest(nil, "list-ep", &store.Request{Method: "GET", Path: "/a"})
@ -77,7 +78,7 @@ func TestV1ListRequests(t *testing.T) {
} }
func TestV1SearchRequests(t *testing.T) { func TestV1SearchRequests(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "search-ep") repo.CreateEndpoint(nil, "search-ep")
repo.AppendRequest(nil, "search-ep", &store.Request{Method: "GET", Path: "/users", Headers: `{"Accept":"json"}`}) repo.AppendRequest(nil, "search-ep", &store.Request{Method: "GET", Path: "/users", Headers: `{"Accept":"json"}`})
repo.AppendRequest(nil, "search-ep", &store.Request{Method: "POST", Path: "/orders", Body: []byte(`{"item":"apple"}`)}) repo.AppendRequest(nil, "search-ep", &store.Request{Method: "POST", Path: "/orders", Body: []byte(`{"item":"apple"}`)})
@ -111,7 +112,7 @@ func TestV1SearchRequests(t *testing.T) {
} }
func TestV1MockSet(t *testing.T) { func TestV1MockSet(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "mock-ep") repo.CreateEndpoint(nil, "mock-ep")
r := testRouter(repo) r := testRouter(repo)
@ -138,7 +139,7 @@ func TestV1MockSet(t *testing.T) {
} }
func TestV1OpenAPI(t *testing.T) { func TestV1OpenAPI(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
r := testRouter(repo) r := testRouter(repo)
req := httptest.NewRequest(http.MethodGet, "/v1/endpoints/test/openapi.json", nil) req := httptest.NewRequest(http.MethodGet, "/v1/endpoints/test/openapi.json", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

@ -11,89 +11,10 @@ import (
"time" "time"
"github.com/elfoundation/hatch/internal/store" "github.com/elfoundation/hatch/internal/store"
"github.com/elfoundation/hatch/internal/testutil"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
// fakeRepo implements store.Repository for tests.
type fakeRepo struct {
endpoints map[string]*store.Endpoint
requests []*store.Request
mocks map[string]*store.MockConfig
}
func newFakeRepo() *fakeRepo {
return &fakeRepo{
endpoints: map[string]*store.Endpoint{},
mocks: map[string]*store.MockConfig{},
}
}
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 = "req-" + string(rune(len(f.requests)+'0'))
r.EndpointID = eid
f.requests = append(f.requests, r)
return nil
}
func (f *fakeRepo) GetRequest(_ context.Context, id string) (*store.Request, error) {
for _, r := range f.requests {
if r.ID == id {
return r, nil
}
}
return nil, errNotFound
}
func (f *fakeRepo) ListRequests(_ context.Context, _ string, _ int) ([]*store.Request, error) {
return f.requests, nil
}
func (f *fakeRepo) SearchRequests(_ context.Context, _ string, query string, limit int) ([]*store.Request, error) {
if query == "" {
return f.requests, nil
}
var matched []*store.Request
for _, r := range f.requests {
if strings.Contains(r.Method, query) ||
strings.Contains(r.Path, query) ||
strings.Contains(r.Headers, query) ||
strings.Contains(r.Query, query) ||
strings.Contains(string(r.Body), query) {
matched = append(matched, r)
if limit > 0 && len(matched) >= limit {
break
}
}
}
return matched, nil
}
func (f *fakeRepo) GetMock(_ context.Context, endpointID string) (*store.MockConfig, error) {
m, ok := f.mocks[endpointID]
if !ok {
return nil, errNotFound
}
return m, nil
}
func (f *fakeRepo) SetMock(_ context.Context, mock *store.MockConfig) error {
f.mocks[mock.EndpointID] = mock
return 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 }
// testRouter creates a chi router with all routes registered using a fake repo. // testRouter creates a chi router with all routes registered using a fake repo.
func testRouter(repo store.Repository) chi.Router { func testRouter(repo store.Repository) chi.Router {
@ -104,7 +25,7 @@ func testRouter(repo store.Repository) chi.Router {
} }
func TestHealthz(t *testing.T) { func TestHealthz(t *testing.T) {
r := testRouter(newFakeRepo()) r := testRouter(testutil.NewFakeRepository())
req := httptest.NewRequest(http.MethodGet, "/healthz", nil) req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
r.ServeHTTP(w, req) r.ServeHTTP(w, req)
@ -121,7 +42,7 @@ func TestHealthz(t *testing.T) {
func TestCaptureRecordsAllVerbs(t *testing.T) { func TestCaptureRecordsAllVerbs(t *testing.T) {
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"} methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
for _, m := range methods { for _, m := range methods {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
r := testRouter(repo) r := testRouter(repo)
body := "" body := ""
@ -138,11 +59,11 @@ func TestCaptureRecordsAllVerbs(t *testing.T) {
if w.Code != 200 { if w.Code != 200 {
t.Errorf("%s: expected 200, got %d", m, w.Code) t.Errorf("%s: expected 200, got %d", m, w.Code)
} }
if len(repo.requests) != 1 { if len(repo.Requests) != 1 {
t.Errorf("%s: expected 1 request, got %d", m, len(repo.requests)) t.Errorf("%s: expected 1 request, got %d", m, len(repo.Requests))
continue continue
} }
reqCaptured := repo.requests[0] reqCaptured := repo.Requests[0]
if reqCaptured.Method != m { if reqCaptured.Method != m {
t.Errorf("%s: wrong method %s", m, reqCaptured.Method) t.Errorf("%s: wrong method %s", m, reqCaptured.Method)
} }
@ -150,7 +71,7 @@ func TestCaptureRecordsAllVerbs(t *testing.T) {
} }
func TestSSEStreamReceivesEventOnCapture(t *testing.T) { func TestSSEStreamReceivesEventOnCapture(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
srv := httptest.NewServer(testRouter(repo)) srv := httptest.NewServer(testRouter(repo))
defer srv.Close() defer srv.Close()
@ -224,7 +145,7 @@ func TestSSEStreamReceivesEventOnCapture(t *testing.T) {
} }
func TestInspectReturnsHTML(t *testing.T) { func TestInspectReturnsHTML(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "ep") repo.CreateEndpoint(nil, "ep")
repo.AppendRequest(nil, "ep", &store.Request{ repo.AppendRequest(nil, "ep", &store.Request{
Method: "POST", Method: "POST",
@ -290,7 +211,7 @@ func TestInspectReturnsHTML(t *testing.T) {
} }
func TestInspectEmptyState(t *testing.T) { func TestInspectEmptyState(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "new-ep") repo.CreateEndpoint(nil, "new-ep")
r := testRouter(repo) r := testRouter(repo)
@ -320,7 +241,7 @@ func TestInspectEmptyState(t *testing.T) {
} }
func TestInspectAutoCreatesEndpoint(t *testing.T) { func TestInspectAutoCreatesEndpoint(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
// The endpoint doesn't exist yet. // The endpoint doesn't exist yet.
r := testRouter(repo) r := testRouter(repo)
@ -350,7 +271,7 @@ func TestInspectAutoCreatesEndpoint(t *testing.T) {
} }
func TestCaptureReturnsJSON200(t *testing.T) { func TestCaptureReturnsJSON200(t *testing.T) {
r := testRouter(newFakeRepo()) r := testRouter(testutil.NewFakeRepository())
w := httptest.NewRecorder() w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest("GET", "/t", nil)) r.ServeHTTP(w, httptest.NewRequest("GET", "/t", nil))
@ -369,7 +290,7 @@ func TestCaptureReturnsJSON200(t *testing.T) {
} }
func TestMockSetsAndConfiguresResponse(t *testing.T) { func TestMockSetsAndConfiguresResponse(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "mock-ep") repo.CreateEndpoint(nil, "mock-ep")
r := testRouter(repo) r := testRouter(repo)
@ -399,7 +320,7 @@ func TestMockSetsAndConfiguresResponse(t *testing.T) {
} }
func TestMockReturnsConfiguredResponseOnCapture(t *testing.T) { func TestMockReturnsConfiguredResponseOnCapture(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "mock-cap") repo.CreateEndpoint(nil, "mock-cap")
// Pre-set a mock. // Pre-set a mock.
repo.SetMock(nil, &store.MockConfig{ repo.SetMock(nil, &store.MockConfig{
@ -427,16 +348,16 @@ func TestMockReturnsConfiguredResponseOnCapture(t *testing.T) {
} }
// Request should still be captured even when mock responds. // Request should still be captured even when mock responds.
if len(repo.requests) != 1 { if len(repo.Requests) != 1 {
t.Fatalf("expected 1 captured request, got %d", len(repo.requests)) t.Fatalf("expected 1 captured request, got %d", len(repo.Requests))
} }
if repo.requests[0].Method != "POST" { if repo.Requests[0].Method != "POST" {
t.Errorf("captured method: %s", repo.requests[0].Method) t.Errorf("captured method: %s", repo.Requests[0].Method)
} }
} }
func TestMockAutoCreatesEndpointOnSet(t *testing.T) { func TestMockAutoCreatesEndpointOnSet(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
r := testRouter(repo) r := testRouter(repo)
// Set a mock on an endpoint that doesn't exist yet. // Set a mock on an endpoint that doesn't exist yet.

View File

@ -8,13 +8,14 @@ import (
"testing" "testing"
"github.com/elfoundation/hatch/internal/store" "github.com/elfoundation/hatch/internal/store"
"github.com/elfoundation/hatch/internal/testutil"
) )
func TestReplayMissingTargetURL(t *testing.T) { func TestReplayMissingTargetURL(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "ep") repo.CreateEndpoint(nil, "ep")
repo.AppendRequest(nil, "ep", &store.Request{Method: "POST", Path: "/webhook", Headers: "{}"}) repo.AppendRequest(nil, "ep", &store.Request{Method: "POST", Path: "/webhook", Headers: "{}"})
reqID := repo.requests[0].ID reqID := repo.Requests[0].ID
r := testRouter(repo) r := testRouter(repo)
body := `{}` body := `{}`
@ -34,10 +35,10 @@ func TestReplayMissingTargetURL(t *testing.T) {
} }
func TestReplayInvalidScheme(t *testing.T) { func TestReplayInvalidScheme(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "ep") repo.CreateEndpoint(nil, "ep")
repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"}) repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"})
reqID := repo.requests[0].ID reqID := repo.Requests[0].ID
r := testRouter(repo) r := testRouter(repo)
body := `{"target_url": "ftp://bad.scheme/"}` body := `{"target_url": "ftp://bad.scheme/"}`
@ -52,10 +53,10 @@ func TestReplayInvalidScheme(t *testing.T) {
} }
func TestReplaySSRFBlocksLocalhost(t *testing.T) { func TestReplaySSRFBlocksLocalhost(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "ep") repo.CreateEndpoint(nil, "ep")
repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"}) repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"})
reqID := repo.requests[0].ID reqID := repo.Requests[0].ID
r := testRouter(repo) r := testRouter(repo)
body := `{"target_url": "http://localhost:8080/"}` body := `{"target_url": "http://localhost:8080/"}`
@ -70,10 +71,10 @@ func TestReplaySSRFBlocksLocalhost(t *testing.T) {
} }
func TestReplaySSRFBlocksPrivate(t *testing.T) { func TestReplaySSRFBlocksPrivate(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "ep") repo.CreateEndpoint(nil, "ep")
repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"}) repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"})
reqID := repo.requests[0].ID reqID := repo.Requests[0].ID
r := testRouter(repo) r := testRouter(repo)
for _, addr := range []string{ for _, addr := range []string{
@ -96,7 +97,7 @@ func TestReplaySSRFBlocksPrivate(t *testing.T) {
} }
func TestReplayNotFound(t *testing.T) { func TestReplayNotFound(t *testing.T) {
repo := newFakeRepo() repo := testutil.NewFakeRepository()
r := testRouter(repo) r := testRouter(repo)
body := `{"target_url": "https://example.com/"}` body := `{"target_url": "https://example.com/"}`
req := httptest.NewRequest(http.MethodPost, "/e/ep/requests/nonexistent/replay", strings.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/e/ep/requests/nonexistent/replay", strings.NewReader(body))
@ -118,7 +119,7 @@ func TestReplaySuccess(t *testing.T) {
})) }))
defer sink.Close() defer sink.Close()
repo := newFakeRepo() repo := testutil.NewFakeRepository()
repo.CreateEndpoint(nil, "ep") repo.CreateEndpoint(nil, "ep")
repo.AppendRequest(nil, "ep", &store.Request{ repo.AppendRequest(nil, "ep", &store.Request{
Method: "POST", Method: "POST",
@ -127,7 +128,7 @@ func TestReplaySuccess(t *testing.T) {
Query: "foo=bar", Query: "foo=bar",
Body: []byte(`{"msg":"hello"}`), Body: []byte(`{"msg":"hello"}`),
}) })
reqID := repo.requests[0].ID reqID := repo.Requests[0].ID
// Set env to allow private replay since httptest server is on loopback. // Set env to allow private replay since httptest server is on loopback.
t.Setenv("HATCH_ALLOW_PRIVATE_REPLAY", "true") t.Setenv("HATCH_ALLOW_PRIVATE_REPLAY", "true")
@ -175,7 +176,7 @@ func TestReplayE2ECaptureThenReplay(t *testing.T) {
defer sink.Close() defer sink.Close()
// Step 2: Capture a request. // Step 2: Capture a request.
repo := newFakeRepo() repo := testutil.NewFakeRepository()
rt := testRouter(repo) rt := testRouter(repo)
captureReq := httptest.NewRequest("POST", "/e2e-test", strings.NewReader(`{"order":"1"}`)) captureReq := httptest.NewRequest("POST", "/e2e-test", strings.NewReader(`{"order":"1"}`))
@ -183,10 +184,10 @@ func TestReplayE2ECaptureThenReplay(t *testing.T) {
captureW := httptest.NewRecorder() captureW := httptest.NewRecorder()
rt.ServeHTTP(captureW, captureReq) rt.ServeHTTP(captureW, captureReq)
if len(repo.requests) != 1 { if len(repo.Requests) != 1 {
t.Fatalf("expected 1 captured request, got %d", len(repo.requests)) t.Fatalf("expected 1 captured request, got %d", len(repo.Requests))
} }
captured := repo.requests[0] captured := repo.Requests[0]
if captured.Method != "POST" { if captured.Method != "POST" {
t.Errorf("captured method: %s", captured.Method) t.Errorf("captured method: %s", captured.Method)
} }

View File

@ -0,0 +1,98 @@
package testutil
import (
"context"
"strings"
"github.com/elfoundation/hatch/internal/store"
)
// FakeRepository implements store.Repository for tests.
type FakeRepository struct {
Endpoints map[string]*store.Endpoint
Requests []*store.Request
Mocks map[string]*store.MockConfig
}
// NewFakeRepository creates a new FakeRepository.
func NewFakeRepository() *FakeRepository {
return &FakeRepository{
Endpoints: map[string]*store.Endpoint{},
Mocks: map[string]*store.MockConfig{},
}
}
func (f *FakeRepository) CreateEndpoint(_ context.Context, url string) (*store.Endpoint, error) {
e := &store.Endpoint{ID: url, URL: url, CreatedAt: "t", UpdatedAt: "t"}
f.Endpoints[url] = e
return e, nil
}
func (f *FakeRepository) GetEndpoint(_ context.Context, id string) (*store.Endpoint, error) {
e, ok := f.Endpoints[id]
if !ok {
return nil, errNotFound
}
return e, nil
}
func (f *FakeRepository) AppendRequest(_ context.Context, eid string, r *store.Request) error {
r.ID = "req-" + string(rune(len(f.Requests)+'0'))
r.EndpointID = eid
f.Requests = append(f.Requests, r)
return nil
}
func (f *FakeRepository) GetRequest(_ context.Context, id string) (*store.Request, error) {
for _, r := range f.Requests {
if r.ID == id {
return r, nil
}
}
return nil, errNotFound
}
func (f *FakeRepository) ListRequests(_ context.Context, _ string, _ int) ([]*store.Request, error) {
return f.Requests, nil
}
func (f *FakeRepository) SearchRequests(_ context.Context, _ string, query string, limit int) ([]*store.Request, error) {
if query == "" {
return f.Requests, nil
}
var matched []*store.Request
for _, r := range f.Requests {
if strings.Contains(r.Method, query) ||
strings.Contains(r.Path, query) ||
strings.Contains(r.Headers, query) ||
strings.Contains(r.Query, query) ||
strings.Contains(string(r.Body), query) {
matched = append(matched, r)
if limit > 0 && len(matched) >= limit {
break
}
}
}
return matched, nil
}
func (f *FakeRepository) GetMock(_ context.Context, endpointID string) (*store.MockConfig, error) {
m, ok := f.Mocks[endpointID]
if !ok {
return nil, errNotFound
}
return m, nil
}
func (f *FakeRepository) SetMock(_ context.Context, mock *store.MockConfig) error {
f.Mocks[mock.EndpointID] = mock
return nil
}
func (f *FakeRepository) Close() error { return nil }
type notFoundError struct{}
func (e *notFoundError) Error() string { return "not found" }
var errNotFound = &notFoundError{}