Merge PR #2: Phase 1 Foundation

Reviewed and merged Phase 1 Foundation PR. All tests pass, CI changes look good, linting config is comprehensive, and shared test utilities improve code quality. The merge preserves the existing CLI on main.
This commit is contained in:
Naranmandakh Enkhbat 2026-06-24 11:39:33 +08:00 committed by GitHub
commit 65b2435275
8 changed files with 261 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

34
Makefile Normal file
View File

@ -0,0 +1,34 @@
.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
fmt:
gofmt -s -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{}