README.md

# ReqCassette

[![Hex.pm](https://img.shields.io/hexpm/v/req_cassette.svg)](https://hex.pm/packages/req_cassette)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/req_cassette/)
[![GitHub CI](https://github.com/lostbean/req_cassette/workflows/CI/badge.svg)](https://github.com/lostbean/req_cassette/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

A VCR-style record-and-replay library for Elixir's [Req](https://hexdocs.pm/req)
HTTP client. Record HTTP responses to "cassettes" and replay them in tests for
fast, deterministic, offline-capable testing.

Perfect for testing applications that use external APIs, especially LLM APIs
like Anthropic's Claude!

## Features

- 🎬 **Record & Replay** - Capture real HTTP responses and replay them instantly
- ⚡ **Async-Safe** - Works with `async: true` in ExUnit (unlike ExVCR)
- 🔌 **Built on Req.Test** - Uses Req's native testing infrastructure (no global
  mocking)
- 🤖 **ReqLLM Integration** - Perfect for testing LLM applications (save money
  on API calls!)
- 📝 **Human-Readable** - JSON cassettes you can inspect and edit
- 🎯 **Simple API** - Just add `plug: {ReqCassette.Plug, ...}` to your Req calls

## Quick Start

```elixir
# First call - records to cassette
response = Req.get!(
  "https://api.example.com/users/1",
  plug: {ReqCassette.Plug, %{cassette_dir: "test/cassettes", mode: :record}}
)

# Second call - replays from cassette (instant, no network!)
response = Req.get!(
  "https://api.example.com/users/1",
  plug: {ReqCassette.Plug, %{cassette_dir: "test/cassettes", mode: :record}}
)
```

## Installation

Add to your `mix.exs`:

```elixir
def deps do
  [
    {:req, "~> 0.5.15"},
    {:req_cassette, "~> 0.1.0"}  # or github/path
  ]
end
```

## Usage with ReqLLM

Save money on LLM API calls during testing:

```elixir
# First call costs money
{:ok, response} = ReqLLM.generate_text(
  "anthropic:claude-sonnet-4-20250514",
  "Explain recursion",
  max_tokens: 100,
  req_http_options: [
    plug: {ReqCassette.Plug, %{cassette_dir: "test/cassettes", mode: :record}}
  ]
)

# Second call is FREE - replays from cassette!
{:ok, response} = ReqLLM.generate_text(
  "anthropic:claude-sonnet-4-20250514",
  "Explain recursion",
  max_tokens: 100,
  req_http_options: [
    plug: {ReqCassette.Plug, %{cassette_dir: "test/cassettes", mode: :record}}
  ]
)
```

See [docs/REQ_LLM_INTEGRATION.md](docs/REQ_LLM_INTEGRATION.md) for detailed
ReqLLM integration guide.

## Development

### Quick Commands

```bash
# Development workflow
mix precommit  # Format, check, test (run before commit)
mix ci         # CI checks (read-only format check)
```

### Code Quality

The project uses Elixir formatter and Credo for code quality. The `mix
precommit` command formats code, checks quality with Credo, and runs tests. The
`mix ci` command is designed for CI environments and only checks formatting
without modifying files.

### Testing

```bash
# Run all tests
mix test

# Run specific test suite
mix test test/req_cassette/plug_test.exs

# Run demos
mix run examples/httpbin_demo.exs
ANTHROPIC_API_KEY=sk-... mix run examples/req_llm_demo.exs
```

See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed development guide.

## Documentation

- [SUMMARY.md](docs/SUMMARY.md) - Complete project overview and architecture
- [DEVELOPMENT.md](docs/DEVELOPMENT.md) - Development guide (setup, testing,
  code quality)
- [REQ_LLM_INTEGRATION.md](docs/REQ_LLM_INTEGRATION.md) - ReqLLM integration
  guide
- [Design Specification](docs/DESIGN_SPEC.md) - Full design spec

## Example Test

```elixir
defmodule MyApp.APITest do
  use ExUnit.Case, async: true

  @cassette_dir "test/fixtures/cassettes"

  setup do
    File.mkdir_p!(@cassette_dir)
    :ok
  end

  test "fetches user data" do
    response = Req.get!(
      "https://api.example.com/users/1",
      plug: {ReqCassette.Plug, %{cassette_dir: @cassette_dir, mode: :record}}
    )

    assert response.status == 200
    assert response.body["name"] == "Alice"
  end
end
```

## Why ReqCassette over ExVCR?

| Feature        | ReqCassette     | ExVCR                |
| -------------- | --------------- | -------------------- |
| Async-safe     | ✅ Yes          | ❌ No                |
| HTTP client    | Req only        | hackney, finch, etc. |
| Implementation | Req.Test + Plug | :meck (global)       |
| Maintenance    | Low             | High                 |

## How It Works

1. **Record Mode**: First request → Hits real API → Saves response to JSON file
2. **Replay Mode**: Subsequent requests → Loads from JSON → Returns instantly

The cassette is matched by HTTP method, path, query string, and request body.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Contributing

Contributions welcome! Please open an issue or PR.