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:
parent
57a9eb6835
commit
6f03ff2465
23
.github/workflows/ci.yml
vendored
23
.github/workflows/ci.yml
vendored
@ -17,6 +17,14 @@ jobs:
|
||||
go-version: '1.25'
|
||||
cache: true
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
if [ -n "$(gofmt -l .)" ]; then
|
||||
echo "Files not formatted:"
|
||||
gofmt -l .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
@ -41,6 +49,21 @@ jobs:
|
||||
curl -fsS http://localhost:8080/healthz | grep -q 'ok'
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -12,6 +12,11 @@ go.work
|
||||
/bin
|
||||
/dist
|
||||
|
||||
# Runtime data
|
||||
data/
|
||||
*.db
|
||||
coverage.out
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
57
.golangci.yml
Normal file
57
.golangci.yml
Normal 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
35
Makefile
Normal 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/## / /'
|
||||
@ -8,10 +8,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/elfoundation/hatch/internal/store"
|
||||
"github.com/elfoundation/hatch/internal/testutil"
|
||||
)
|
||||
|
||||
func TestV1CaptureRequest(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
r := testRouter(repo)
|
||||
|
||||
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"])
|
||||
}
|
||||
// Verify request stored in repo.
|
||||
if len(repo.requests) != 1 {
|
||||
t.Fatalf("expected 1 request stored, got %d", len(repo.requests))
|
||||
if len(repo.Requests) != 1 {
|
||||
t.Fatalf("expected 1 request stored, got %d", len(repo.Requests))
|
||||
}
|
||||
if repo.requests[0].Headers != `{"X-Custom":"value"}` {
|
||||
t.Errorf("expected headers JSON, got %q", repo.requests[0].Headers)
|
||||
if repo.Requests[0].Headers != `{"X-Custom":"value"}` {
|
||||
t.Errorf("expected headers JSON, got %q", repo.Requests[0].Headers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestV1ListRequests(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "list-ep")
|
||||
// Add a few requests.
|
||||
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) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
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: "POST", Path: "/orders", Body: []byte(`{"item":"apple"}`)})
|
||||
@ -111,7 +112,7 @@ func TestV1SearchRequests(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestV1MockSet(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "mock-ep")
|
||||
r := testRouter(repo)
|
||||
|
||||
@ -138,7 +139,7 @@ func TestV1MockSet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestV1OpenAPI(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
r := testRouter(repo)
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/endpoints/test/openapi.json", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
@ -11,89 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/elfoundation/hatch/internal/store"
|
||||
"github.com/elfoundation/hatch/internal/testutil"
|
||||
"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.
|
||||
func testRouter(repo store.Repository) chi.Router {
|
||||
@ -104,7 +25,7 @@ func testRouter(repo store.Repository) chi.Router {
|
||||
}
|
||||
|
||||
func TestHealthz(t *testing.T) {
|
||||
r := testRouter(newFakeRepo())
|
||||
r := testRouter(testutil.NewFakeRepository())
|
||||
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
@ -121,7 +42,7 @@ func TestHealthz(t *testing.T) {
|
||||
func TestCaptureRecordsAllVerbs(t *testing.T) {
|
||||
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
||||
for _, m := range methods {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
r := testRouter(repo)
|
||||
|
||||
body := ""
|
||||
@ -138,11 +59,11 @@ func TestCaptureRecordsAllVerbs(t *testing.T) {
|
||||
if w.Code != 200 {
|
||||
t.Errorf("%s: expected 200, got %d", m, w.Code)
|
||||
}
|
||||
if len(repo.requests) != 1 {
|
||||
t.Errorf("%s: expected 1 request, got %d", m, len(repo.requests))
|
||||
if len(repo.Requests) != 1 {
|
||||
t.Errorf("%s: expected 1 request, got %d", m, len(repo.Requests))
|
||||
continue
|
||||
}
|
||||
reqCaptured := repo.requests[0]
|
||||
reqCaptured := repo.Requests[0]
|
||||
if reqCaptured.Method != m {
|
||||
t.Errorf("%s: wrong method %s", m, reqCaptured.Method)
|
||||
}
|
||||
@ -150,7 +71,7 @@ func TestCaptureRecordsAllVerbs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSSEStreamReceivesEventOnCapture(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
srv := httptest.NewServer(testRouter(repo))
|
||||
defer srv.Close()
|
||||
|
||||
@ -224,7 +145,7 @@ func TestSSEStreamReceivesEventOnCapture(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInspectReturnsHTML(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "ep")
|
||||
repo.AppendRequest(nil, "ep", &store.Request{
|
||||
Method: "POST",
|
||||
@ -290,7 +211,7 @@ func TestInspectReturnsHTML(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInspectEmptyState(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "new-ep")
|
||||
|
||||
r := testRouter(repo)
|
||||
@ -320,7 +241,7 @@ func TestInspectEmptyState(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInspectAutoCreatesEndpoint(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
|
||||
// The endpoint doesn't exist yet.
|
||||
r := testRouter(repo)
|
||||
@ -350,7 +271,7 @@ func TestInspectAutoCreatesEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCaptureReturnsJSON200(t *testing.T) {
|
||||
r := testRouter(newFakeRepo())
|
||||
r := testRouter(testutil.NewFakeRepository())
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest("GET", "/t", nil))
|
||||
|
||||
@ -369,7 +290,7 @@ func TestCaptureReturnsJSON200(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockSetsAndConfiguresResponse(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "mock-ep")
|
||||
|
||||
r := testRouter(repo)
|
||||
@ -399,7 +320,7 @@ func TestMockSetsAndConfiguresResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockReturnsConfiguredResponseOnCapture(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "mock-cap")
|
||||
// Pre-set a mock.
|
||||
repo.SetMock(nil, &store.MockConfig{
|
||||
@ -427,16 +348,16 @@ func TestMockReturnsConfiguredResponseOnCapture(t *testing.T) {
|
||||
}
|
||||
|
||||
// Request should still be captured even when mock responds.
|
||||
if len(repo.requests) != 1 {
|
||||
t.Fatalf("expected 1 captured request, got %d", len(repo.requests))
|
||||
if len(repo.Requests) != 1 {
|
||||
t.Fatalf("expected 1 captured request, got %d", len(repo.Requests))
|
||||
}
|
||||
if repo.requests[0].Method != "POST" {
|
||||
t.Errorf("captured method: %s", repo.requests[0].Method)
|
||||
if repo.Requests[0].Method != "POST" {
|
||||
t.Errorf("captured method: %s", repo.Requests[0].Method)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockAutoCreatesEndpointOnSet(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
r := testRouter(repo)
|
||||
|
||||
// Set a mock on an endpoint that doesn't exist yet.
|
||||
|
||||
@ -8,13 +8,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/elfoundation/hatch/internal/store"
|
||||
"github.com/elfoundation/hatch/internal/testutil"
|
||||
)
|
||||
|
||||
func TestReplayMissingTargetURL(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "ep")
|
||||
repo.AppendRequest(nil, "ep", &store.Request{Method: "POST", Path: "/webhook", Headers: "{}"})
|
||||
reqID := repo.requests[0].ID
|
||||
reqID := repo.Requests[0].ID
|
||||
|
||||
r := testRouter(repo)
|
||||
body := `{}`
|
||||
@ -34,10 +35,10 @@ func TestReplayMissingTargetURL(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReplayInvalidScheme(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "ep")
|
||||
repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"})
|
||||
reqID := repo.requests[0].ID
|
||||
reqID := repo.Requests[0].ID
|
||||
|
||||
r := testRouter(repo)
|
||||
body := `{"target_url": "ftp://bad.scheme/"}`
|
||||
@ -52,10 +53,10 @@ func TestReplayInvalidScheme(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReplaySSRFBlocksLocalhost(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "ep")
|
||||
repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"})
|
||||
reqID := repo.requests[0].ID
|
||||
reqID := repo.Requests[0].ID
|
||||
|
||||
r := testRouter(repo)
|
||||
body := `{"target_url": "http://localhost:8080/"}`
|
||||
@ -70,10 +71,10 @@ func TestReplaySSRFBlocksLocalhost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReplaySSRFBlocksPrivate(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "ep")
|
||||
repo.AppendRequest(nil, "ep", &store.Request{Method: "GET", Path: "/", Headers: "{}"})
|
||||
reqID := repo.requests[0].ID
|
||||
reqID := repo.Requests[0].ID
|
||||
|
||||
r := testRouter(repo)
|
||||
for _, addr := range []string{
|
||||
@ -96,7 +97,7 @@ func TestReplaySSRFBlocksPrivate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReplayNotFound(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
r := testRouter(repo)
|
||||
body := `{"target_url": "https://example.com/"}`
|
||||
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()
|
||||
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
repo.CreateEndpoint(nil, "ep")
|
||||
repo.AppendRequest(nil, "ep", &store.Request{
|
||||
Method: "POST",
|
||||
@ -127,7 +128,7 @@ func TestReplaySuccess(t *testing.T) {
|
||||
Query: "foo=bar",
|
||||
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.
|
||||
t.Setenv("HATCH_ALLOW_PRIVATE_REPLAY", "true")
|
||||
@ -175,7 +176,7 @@ func TestReplayE2ECaptureThenReplay(t *testing.T) {
|
||||
defer sink.Close()
|
||||
|
||||
// Step 2: Capture a request.
|
||||
repo := newFakeRepo()
|
||||
repo := testutil.NewFakeRepository()
|
||||
rt := testRouter(repo)
|
||||
|
||||
captureReq := httptest.NewRequest("POST", "/e2e-test", strings.NewReader(`{"order":"1"}`))
|
||||
@ -183,10 +184,10 @@ func TestReplayE2ECaptureThenReplay(t *testing.T) {
|
||||
captureW := httptest.NewRecorder()
|
||||
rt.ServeHTTP(captureW, captureReq)
|
||||
|
||||
if len(repo.requests) != 1 {
|
||||
t.Fatalf("expected 1 captured request, got %d", len(repo.requests))
|
||||
if len(repo.Requests) != 1 {
|
||||
t.Fatalf("expected 1 captured request, got %d", len(repo.Requests))
|
||||
}
|
||||
captured := repo.requests[0]
|
||||
captured := repo.Requests[0]
|
||||
if captured.Method != "POST" {
|
||||
t.Errorf("captured method: %s", captured.Method)
|
||||
}
|
||||
|
||||
98
internal/testutil/fake_repo.go
Normal file
98
internal/testutil/fake_repo.go
Normal 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 = ¬FoundError{}
|
||||
Loading…
Reference in New Issue
Block a user