README.md

# ExUnitOpenAPI

Automatically generate OpenAPI 3.0 specifications from your Phoenix controller tests. Zero annotations required - just run your tests and get documentation.

## The Problem

API documentation is tedious to maintain. Existing solutions require either:
- **Manual OpenAPI specs** that drift from reality
- **Heavy DSL annotations** (OpenApiSpex, PhoenixSwagger) that clutter your code
- **Separate schema definitions** that duplicate what's already in your tests

## The Solution

ExUnitOpenAPI captures HTTP request/response data during your test runs and generates an OpenAPI spec automatically. Your tests become your documentation.

```elixir
# Your existing test - no changes needed
test "returns user by id", %{conn: conn} do
  user = insert(:user, name: "Alice")

  conn = get(conn, "/api/users/#{user.id}")

  assert %{"id" => _, "name" => "Alice"} = json_response(conn, 200)
end
```

Run `OPENAPI=1 mix test` and get a complete OpenAPI spec with paths, parameters, and response schemas inferred from your actual test data.

## Installation

Add `exunit_openapi` to your test dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:exunit_openapi, "~> 0.1.0", only: :test}
  ]
end
```

Also add the preferred environment for the mix task:

```elixir
def cli do
  [preferred_envs: ["openapi.generate": :test]]
end
```

## Quick Start

### 1. Configure in `config/test.exs`

```elixir
config :exunit_openapi,
  router: MyAppWeb.Router,
  output: "priv/static/openapi.json",
  info: [
    title: "My API",
    version: "1.0.0",
    description: "My awesome API"
  ]
```

### 2. Add to `test/test_helper.exs`

```elixir
ExUnitOpenAPI.start()
ExUnit.start()
```

### 3. Generate the spec

```bash
# Option 1: Environment variable
OPENAPI=1 mix test

# Option 2: Mix task
mix openapi.generate
```

That's it! Your OpenAPI spec will be generated at the configured output path.

## How It Works

1. **Telemetry Capture**: ExUnitOpenAPI attaches to Phoenix's built-in telemetry events (`[:phoenix, :router_dispatch, :stop]`)

2. **Request Collection**: When your tests make requests via `Phoenix.ConnTest`, the library captures:
   - Request method, path, and parameters
   - Request body (for POST/PUT/PATCH)
   - Response status and JSON body

3. **Route Matching**: Captured requests are matched against your Phoenix router to get path patterns (e.g., `/users/:id`)

4. **Type Inference**: JSON response bodies are analyzed to generate schemas:
   - Primitive types (string, integer, boolean)
   - Objects with properties
   - Arrays with item types
   - Format detection (date-time, uuid, email, uri)

5. **Spec Generation**: Everything is combined into a valid OpenAPI 3.0 specification

## Configuration Options

```elixir
config :exunit_openapi,
  # Required: Your Phoenix router module
  router: MyAppWeb.Router,

  # Output file path (default: "openapi.json")
  output: "priv/static/openapi.json",

  # Output format: :json or :yaml (default: :json)
  format: :json,

  # OpenAPI info object
  info: [
    title: "My API",
    version: "1.0.0",
    description: "API description"
  ],

  # Server URLs (optional)
  servers: [
    %{url: "https://api.example.com", description: "Production"},
    %{url: "https://staging-api.example.com", description: "Staging"}
  ],

  # Security schemes (optional)
  security_schemes: %{
    "BearerAuth" => %{
      "type" => "http",
      "scheme" => "bearer",
      "bearerFormat" => "JWT"
    }
  },

  # Preserve manual edits when regenerating (default: true)
  merge_with_existing: true
```

## Generated Output

Given tests for a users API, ExUnitOpenAPI generates:

```json
{
  "openapi": "3.0.3",
  "info": {
    "title": "My API",
    "version": "1.0.0"
  },
  "paths": {
    "/api/users/{id}": {
      "get": {
        "operationId": "User.show",
        "tags": ["User"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {"type": "integer"}
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {"type": "integer"},
                    "name": {"type": "string"},
                    "email": {"type": "string", "format": "email"},
                    "created_at": {"type": "string", "format": "date-time"}
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    }
  }
}
```

## Tips

### Test Coverage = Documentation Coverage

Only endpoints exercised in your tests will appear in the generated spec. This is a feature, not a bug - it encourages comprehensive testing.

### Multiple Response Codes

Test both success and error cases to document all response types:

```elixir
test "returns user", %{conn: conn} do
  # Documents 200 response
end

test "returns 404 for missing user", %{conn: conn} do
  # Documents 404 response
end
```

### Manual Edits Are Preserved

By default, ExUnitOpenAPI merges with the existing spec file, preserving any manual additions like descriptions or examples. Set `merge_with_existing: false` to always overwrite.

## Roadmap

- [x] Basic request/response capture
- [x] Type inference from JSON
- [x] Router analysis for path patterns
- [x] OpenAPI 3.0 generation
- [ ] Schema deduplication with `$ref`
- [ ] YAML output format
- [ ] Test metadata for descriptions/tags
- [ ] Request validation mode
- [ ] Coverage reporting

## Inspiration

This library is inspired by [rspec-openapi](https://github.com/exoego/rspec-openapi) for Ruby/Rails, which pioneered the "generate docs from tests" approach.

## License

MIT License