Metadata-Version: 2.4
Name: dubdeck
Version: 0.1.0
Summary: API mocking with record and replay
Keywords: api,http,mocking,pytest,record-replay,testing
Author: Alexander Whillas
Author-email: Alexander Whillas <whillas@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Dist: faker>=40.36.0
Requires-Dist: fastapi>=0.141.1
Requires-Dist: httpx>=0.28.1
Requires-Dist: jinja2>=3.1.6
Requires-Dist: jsonpath-ng>=1.8.0
Requires-Dist: jsonschema>=4.26.0
Requires-Dist: openapi-pydantic>=0.5.1
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pydantic-settings>=2.15.0
Requires-Dist: ruamel-yaml>=0.19.1
Requires-Dist: starlette>=1.6.0
Requires-Dist: typer>=0.27.1
Requires-Dist: uvicorn[standard]>=0.52.4
Requires-Python: >=3.13
Project-URL: Homepage, https://github.com/memphisbourke02/dubdeck
Project-URL: Documentation, https://github.com/memphisbourke02/dubdeck/tree/master/docs/user-guide
Project-URL: Repository, https://github.com/memphisbourke02/dubdeck
Project-URL: Issues, https://github.com/memphisbourke02/dubdeck/issues
Project-URL: Changelog, https://github.com/memphisbourke02/dubdeck/releases
Description-Content-Type: text/markdown

# Dubdeck


Record real HTTP responses from your micro-services into YAML, then replay them in tests with no network. Where there's no recording, synthesise one from an OpenAPI 3.1 document. One process mocks several services, one port each, all configured in YAML — in the spirit of [Mockintosh](https://mockintosh.io/).

> **Status: M0–M9 built and tested.** Replay, record, redaction, the pytest
> fixture, matching, the management API, templating, SSE streaming, spec mode
> from OpenAPI, the drift commands and request validation all work. MCP
> support is phase 2; a handful of smaller gaps are listed in
> [what works today](docs/user-guide/index.md#what-works-today).
> [What works today](docs/user-guide/index.md#what-works-today) has the detail.
> The example below runs against the current build.

Mockintosh mocks *hand-written* stubs, and the expensive part in practice is writing and maintaining those stubs. Record-and-replay removes that step: point dubdeck at the real service once and the fixtures write themselves.

```
record   mode:  test → dubdeck → real service   (persist request + response)
replay   mode:  test → dubdeck ⇢ cassette       (no network)
generate mode:  test → dubdeck ⇢ OpenAPI + Faker rules
```

## Quickstart

### 1. Configure

```yaml
# dubdeck.yaml
services:
  - name: example-api
    port: 0                          # 0 ⇒ ephemeral, chosen at startup
    mode: replay
    upstream: https://app.example.com/api
    cassette: tests/cassettes/example-api.yaml
    env: EXAMPLE_API_URL              # fixtures export this ⇒ the mock's URL
    auth:
      type: bearer
      token: ${EXAMPLE_API_TOKEN}     # from .env, used only when recording
```

```bash
# .env  — gitignored. Only needed to record.
EXAMPLE_API_TOKEN=sk-live-…
```

### 2. Write the test

```python
# tests/conftest.py
import pytest
from dubdeck.testing import Dubdeck


@pytest.fixture(scope="session")
def example_api():
    with Dubdeck.from_config("dubdeck.yaml") as m:
        yield m


@pytest.fixture(autouse=True)
def _wire(example_api, monkeypatch):
    for var, url in example_api.env.items():  # EXAMPLE_API_URL → http://127.0.0.1:53411/api
        monkeypatch.setenv(var, url)
    example_api.reset()  # sequence cursors, per test
    yield
    example_api.assert_no_unhandled()
```

```python
# tests/test_datasets.py
from myservice.example_api import summarise_dataset


def test_summarise_reads_the_dataset(example_api):
    summary = summarise_dataset("abc")  # reads EXAMPLE_API_URL

    assert summary.name == "John Doe"
    example_api.assert_called("GET", "/api/datasets/abc")
```

Paths are unchanged — only the origin moves. Your client code needs no knowledge of dubdeck.

### 3. Record once

```console
$ DUBDECK_MODE=record uv run pytest tests/test_datasets.py
$ git diff tests/cassettes/          # review what the service actually returned
```

Credentials are stripped before anything is written; a recording that trips the secret detector fails and writes nothing.

### 4. Replay forever

```console
$ uv run pytest                      # offline, deterministic, no credentials needed
```

A request with no recording is a **hard error**, not a silent pass-through — CI can never reach a live service by accident.

```
example-api  GET /api/datasets/xyz → 599  MISS sig=9f0e11  (mode=replay)
  ↳ no interaction matched; nearest: GET /api/datasets/abc (path differs)
  ↳ record it:  DUBDECK_MODE=record pytest -k test_summarise
```

## Docs

| | |
|---|---|
| **[User guide](docs/user-guide/index.md)** | **How to use what is built, with runnable examples. Start here** |
| [Getting started](docs/user-guide/01-getting-started.md) | Ten minutes to a passing offline test |
| [Recipes](docs/user-guide/10-recipes.md) | Whole worked use cases |
| [Specification](docs/spec/index.md) | 19 sections on what dubdeck is *for*. Start with [scope](docs/spec/02-scope.md), then [architecture](docs/spec/15-architecture.md) |
| [Consumer API](docs/spec/16-consumer-api.md) | The pytest fixture in full |
| [Cassette format](docs/spec/04-cassettes.md) | What gets committed |
| [Redaction](docs/spec/09-redaction.md) | Why a cassette is safe to put in git |
| [Build order](docs/spec/13-build-order.md) | M0–M9 |

## Development

```console
$ uv sync
$ uv run pytest
$ uv run ruff check && uv run ty check
```
