{"id":46034803,"url":"https://github.com/yesdevnull/trenchcoat","last_synced_at":"2026-03-01T04:43:31.240Z","repository":{"id":340879661,"uuid":"1167987497","full_name":"yesdevnull/trenchcoat","owner":"yesdevnull","description":"Extensible mock and proxy-to-mock HTTP server for API testing","archived":false,"fork":false,"pushed_at":"2026-02-27T03:22:33.000Z","size":39,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-27T05:45:24.301Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yesdevnull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-26T22:35:10.000Z","updated_at":"2026-02-27T03:22:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yesdevnull/trenchcoat","commit_stats":null,"previous_names":["yesdevnull/trenchcoat"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/yesdevnull/trenchcoat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesdevnull%2Ftrenchcoat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesdevnull%2Ftrenchcoat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesdevnull%2Ftrenchcoat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesdevnull%2Ftrenchcoat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yesdevnull","download_url":"https://codeload.github.com/yesdevnull/trenchcoat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesdevnull%2Ftrenchcoat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29960253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T01:47:18.291Z","status":"online","status_checked_at":"2026-03-01T02:00:07.437Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-01T04:43:30.560Z","updated_at":"2026-03-01T04:43:31.222Z","avatar_url":"https://github.com/yesdevnull.png","language":"Go","readme":"# Trenchcoat\n\nExtensible mock, and proxy-to-mock, HTTP server written in Go.\n\nTrenchcoat serves mock HTTP responses based on configurable request/response definitions called \"coats\". It has two primary modes:\n\n- **Serve** — acts as a mock HTTP server, matching incoming requests against loaded coats and returning defined responses.\n- **Proxy** — acts as an HTTP proxy, forwarding requests to their destination, capturing request/response pairs, and writing them as coat files for future use as mocks.\n\nIt ships as a single static binary with no runtime dependencies and also provides a Go package for embedding mock servers directly in test suites.\n\n## Installation\n\n### Latest release\n\n```sh\ngo install github.com/yesdevnull/trenchcoat/cmd/trenchcoat@latest\n```\n\n### Latest dev version\n\nInstall the latest commit on the `main` branch:\n\n```sh\ngo install github.com/yesdevnull/trenchcoat/cmd/trenchcoat@main\n```\n\n## Quick start\n\nCreate a coat file `mocks/hello.yaml`:\n\n```yaml\ncoats:\n  - name: hello\n    request:\n      uri: \"/hello\"\n    response:\n      code: 200\n      headers:\n        Content-Type: application/json\n      body: '{\"message\": \"Hello, world!\"}'\n```\n\nStart the mock server:\n\n```sh\ntrenchcoat serve --coats mocks/\n```\n\nIn another terminal:\n\n```sh\ncurl http://localhost:8080/hello\n# {\"message\": \"Hello, world!\"}\n```\n\n## CLI usage\n\n### `trenchcoat serve`\n\nStart the mock HTTP server.\n\n```\ntrenchcoat serve [flags]\n```\n\n| Flag | Default | Description |\n|---|---|---|\n| `--coats` | `[]` | Paths to coat files or directories to load (non-recursive; `*.yaml`, `*.yml`, `*.json`). |\n| `--port` | `8080` | Port to listen on. |\n| `--tls-cert` | | Path to TLS certificate file (PEM). Enables HTTPS. |\n| `--tls-key` | | Path to TLS private key file (PEM). Required with `--tls-cert`. |\n| `--tls-ca` | | Path to CA certificate chain (PEM). Appended to the system trust store. |\n| `--watch` | `false` | Watch coat files for changes and hot-reload without restarting. |\n| `--verbose` | `false` | Log each incoming request, match result, and matched coat name. |\n| `--log-format` | `text` | Log output format: `text` or `json`. |\n| `--config` | | Path to configuration file (see [Configuration](#configuration)). |\n\n### `trenchcoat proxy`\n\nStart in proxy capture mode. Forwards requests to an upstream and captures request/response pairs as coat files.\n\n```\ntrenchcoat proxy \u003cupstream-url\u003e [flags]\n```\n\n| Flag | Default | Description |\n|---|---|---|\n| `--port` | `8080` | Port to listen on. |\n| `--write-dir` | `.` | Directory to write captured coat files to. Created if it doesn't exist. |\n| `--filter` | | Only capture requests whose URI matches this glob (e.g. `/api/*`). Empty captures all. |\n| `--strip-headers` | `Authorization,Cookie,Set-Cookie` | Headers to redact from captured coats. Set to empty string to disable. |\n| `--dedupe` | `overwrite` | Deduplication strategy: `overwrite`, `skip`, or `append`. |\n| `--tls-cert` | | Path to TLS certificate file (PEM). |\n| `--tls-key` | | Path to TLS private key file (PEM). |\n| `--tls-ca` | | Path to CA certificate chain (PEM). |\n| `--verbose` | `false` | Log each proxied request and capture event. |\n| `--log-format` | `text` | Log output format: `text` or `json`. |\n\nCaptured files are named `{METHOD}_{sanitised_path}_{status_code}_{unix_timestamp}.yaml`.\n\n### `trenchcoat validate`\n\nValidate coat files for schema correctness without starting a server.\n\n```\ntrenchcoat validate \u003cpath\u003e...\n```\n\nExits 0 if all files are valid, non-zero with diagnostics if any errors are found.\n\n## Configuration\n\nTrenchcoat supports an optional YAML configuration file to avoid repetitive flag usage. CLI flags always take precedence over config file values.\n\nConfig file discovery order:\n\n1. Path specified by `--config`.\n2. `.trenchcoat.yaml` or `.trenchcoat.yml` in the current working directory.\n3. `~/.config/trenchcoat/config.yaml`.\n\n```yaml\n# .trenchcoat.yaml\nport: 8080\nlog_format: text\ncoats:\n  - ./mocks/api.yaml\n  - ./mocks/auth.yaml\nwatch: true\n\ntls:\n  cert: ./certs/server.pem\n  key: ./certs/server-key.pem\n  ca: ./certs/corporate-ca-chain.pem\n\nproxy:\n  write_dir: ./captured\n  strip_headers:\n    - Authorization\n    - Cookie\n    - Set-Cookie\n  dedupe: overwrite\n  filter: \"/api/*\"\n```\n\n## Coat file format\n\nCoat files define one or more request/response mock definitions in YAML or JSON. Format is determined by file extension (`.yaml`/`.yml` or `.json`).\n\n```yaml\ncoats:\n  - name: \"get-users\"                  # optional, used in logging\n    request:\n      method: GET                      # optional, default: GET (use ANY to match all methods)\n      uri: \"/api/v1/users\"             # required — exact, glob (*/?) or regex (~/)\n      headers:                         # optional, subset match with glob support on values\n        Accept: \"application/json\"\n        Authorization: \"Bearer *\"\n      query:                           # optional, map with glob values or raw query string\n        page: \"1\"\n        limit: \"*\"\n\n    response:\n      code: 200                        # optional, default: 200\n      headers:\n        Content-Type: \"application/json\"\n      body: |                          # inline body, mutually exclusive with body_file\n        {\"users\": [{\"id\": 1, \"name\": \"Alice\"}]}\n      # body_file: \"./fixtures/users.json\"  # load body from file (relative to coat file)\n      delay_ms: 0                      # optional artificial delay in ms\n```\n\n### URI matching modes\n\n| Mode | Syntax | Example | Matches |\n|---|---|---|---|\n| Exact | Plain string | `/api/v1/users` | Only `/api/v1/users` |\n| Glob | Contains `*` or `?` | `/api/v1/users/*` | `/api/v1/users/123`, `/api/v1/users/abc` |\n| Regex | Prefixed with `~/` | `~/api/v1/users/\\d+` | `/api/v1/users/123` but not `/api/v1/users/abc` |\n\nWhen multiple coats match, the most specific wins: exact beats glob (longer literal prefix wins), glob beats regex, and method-specific beats `ANY`.\n\n### Response sequences\n\nUse `responses` (plural) instead of `response` (singular) to serve a stateful sequence of responses. The two forms are mutually exclusive.\n\n```yaml\ncoats:\n  - name: \"flaky-health\"\n    request:\n      uri: \"/health\"\n    responses:\n      - code: 503\n        body: \"Service Unavailable\"\n      - code: 503\n        body: \"Service Unavailable\"\n      - code: 200\n        body: '{\"status\": \"ok\"}'\n    sequence: cycle  # cycle (default) loops forever, once returns 404 after exhaustion\n```\n\n## Go test integration\n\nTrenchcoat provides a Go package for spinning up mock servers directly in test suites. This is particularly useful in Terraform provider acceptance tests or any integration test that needs to mock an upstream HTTP API.\n\n```sh\ngo get github.com/yesdevnull/trenchcoat\n```\n\n### Basic usage\n\n```go\nfunc TestMyAPI(t *testing.T) {\n    srv := trenchcoat.NewServer(\n        trenchcoat.WithCoat(trenchcoat.Coat{\n            Name: \"get-users\",\n            Request: trenchcoat.Request{\n                Method: \"GET\",\n                URI:    \"/api/v1/users\",\n            },\n            Response: \u0026trenchcoat.Response{\n                Code:    200,\n                Headers: map[string]string{\"Content-Type\": \"application/json\"},\n                Body:    `{\"users\": [{\"id\": 1, \"name\": \"Alice\"}]}`,\n            },\n        }),\n    )\n    srv.Start(t) // starts on an ephemeral port, registers t.Cleanup for shutdown\n    defer srv.Stop()\n\n    resp, err := http.Get(srv.URL + \"/api/v1/users\")\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer resp.Body.Close()\n\n    if resp.StatusCode != 200 {\n        t.Fatalf(\"expected 200, got %d\", resp.StatusCode)\n    }\n}\n```\n\nKey points:\n\n- `srv.Start(t)` binds to `127.0.0.1:0` (ephemeral port), so tests run in parallel without port conflicts.\n- `srv.URL` contains the base URL (e.g. `http://127.0.0.1:54321`) after `Start` is called.\n- Cleanup is registered via `t.Cleanup`, so the server shuts down automatically when the test finishes.\n\n### Loading coats from files\n\n```go\nsrv := trenchcoat.NewServer(\n    trenchcoat.WithCoatFile(\"testdata/mocks.yaml\"),\n)\n```\n\n### Multiple inline coats\n\n```go\nsrv := trenchcoat.NewServer(\n    trenchcoat.WithCoats(\n        trenchcoat.Coat{\n            Name:     \"list-users\",\n            Request:  trenchcoat.Request{Method: \"GET\", URI: \"/api/users\"},\n            Response: \u0026trenchcoat.Response{Code: 200, Body: `{\"users\": []}`},\n        },\n        trenchcoat.Coat{\n            Name:     \"create-user\",\n            Request:  trenchcoat.Request{Method: \"POST\", URI: \"/api/users\"},\n            Response: \u0026trenchcoat.Response{Code: 201, Body: `{\"id\": 2}`},\n        },\n    ),\n)\n```\n\n### Terraform provider acceptance tests\n\nTrenchcoat works well as a mock backend in Terraform provider acceptance tests. Point the provider's base URL at `srv.URL` and define coats for each API call the provider makes during the plan/apply cycle.\n\n```go\nfunc TestAccResourceWidget_basic(t *testing.T) {\n    srv := trenchcoat.NewServer(\n        trenchcoat.WithCoats(\n            trenchcoat.Coat{\n                Name:    \"create-widget\",\n                Request: trenchcoat.Request{Method: \"POST\", URI: \"/api/v1/widgets\"},\n                Response: \u0026trenchcoat.Response{\n                    Code:    201,\n                    Headers: map[string]string{\"Content-Type\": \"application/json\"},\n                    Body:    `{\"id\": \"widget-1\", \"name\": \"test-widget\"}`,\n                },\n            },\n            trenchcoat.Coat{\n                Name:    \"read-widget\",\n                Request: trenchcoat.Request{Method: \"GET\", URI: \"/api/v1/widgets/widget-1\"},\n                Response: \u0026trenchcoat.Response{\n                    Code:    200,\n                    Headers: map[string]string{\"Content-Type\": \"application/json\"},\n                    Body:    `{\"id\": \"widget-1\", \"name\": \"test-widget\"}`,\n                },\n            },\n            trenchcoat.Coat{\n                Name:    \"delete-widget\",\n                Request: trenchcoat.Request{Method: \"DELETE\", URI: \"/api/v1/widgets/widget-1\"},\n                Response: \u0026trenchcoat.Response{Code: 204},\n            },\n        ),\n    )\n    srv.Start(t)\n    defer srv.Stop()\n\n    resource.Test(t, resource.TestCase{\n        ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,\n        Steps: []resource.TestStep{\n            {\n                Config: testAccWidgetConfig(srv.URL),\n                Check: resource.ComposeAggregateTestCheckFunc(\n                    resource.TestCheckResourceAttr(\"myprovider_widget.test\", \"name\", \"test-widget\"),\n                ),\n            },\n        },\n    })\n}\n\nfunc testAccWidgetConfig(baseURL string) string {\n    return fmt.Sprintf(`\nprovider \"myprovider\" {\n  base_url = %q\n}\n\nresource \"myprovider_widget\" \"test\" {\n  name = \"test-widget\"\n}\n`, baseURL)\n}\n```\n\nFor providers that make multiple calls to the same endpoint (e.g. reading a resource during plan and again during apply), response sequences let you return different responses on successive calls:\n\n```go\ntrenchcoat.Coat{\n    Name:    \"read-widget-sequence\",\n    Request: trenchcoat.Request{Method: \"GET\", URI: \"/api/v1/widgets/widget-1\"},\n    Responses: []trenchcoat.Response{\n        {Code: 404, Body: `{\"error\": \"not found\"}`},           // pre-create read\n        {Code: 200, Body: `{\"id\": \"widget-1\", \"name\": \"w1\"}`}, // post-create read\n        {Code: 200, Body: `{\"id\": \"widget-1\", \"name\": \"w1\"}`}, // refresh\n    },\n    Sequence: \"once\",\n}\n```\n\nMore examples can be found in [`examples/go-tests/example_test.go`](examples/go-tests/example_test.go).\n\n## Building from source\n\n```sh\ngit clone https://github.com/yesdevnull/trenchcoat.git\ncd trenchcoat\nmake build\n```\n\nAvailable Makefile targets:\n\n| Target | Description |\n|---|---|\n| `make build` | Build the `trenchcoat` binary. |\n| `make test` | Run all tests with race detection. |\n| `make coverage` | Run tests and generate `coverage.html`. |\n| `make lint` | Run `golangci-lint`. |\n| `make clean` | Remove build artifacts and test cache. |\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyesdevnull%2Ftrenchcoat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyesdevnull%2Ftrenchcoat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyesdevnull%2Ftrenchcoat/lists"}