# `ObanClaude.Testing`
[🔗](https://github.com/genagent/oban_claude/blob/v0.10.2/lib/oban_claude/testing.ex#L1)

Build the values a `:query_fun` returns, without knowing `claude_wrapper`'s
struct shapes.

`ObanClaude.run/2` and `ObanClaude.Worker` call claude through a `:query_fun`
(`t:ObanClaude.query_fun/0`): a 2-arity `(prompt, opts)` returning
`{:ok, %ClaudeWrapper.Result{}}` or `{:error, %ClaudeWrapper.Error{}}`. In a
test you pass a stub `:query_fun` so no real (paid) claude call happens.

Hand-writing that stub means knowing wrapper internals: success is a
`%Result{is_error: false}`; structured output lives at
`extra["structured_output"]` (where `ObanClaude.structured/1` and
`ObanClaude.outcome/1` read it); a failure carries a `%Error{kind: ...}` from
claude_wrapper's kind vocabulary. This module is those stubs, written once, so
a shift in that representation surfaces here rather than silently in every
consumer's tests.

## Value builders

Return the bare wrapper structs -- for a hand-written `:query_fun`, or to feed
`handle_result/2` / `ObanClaude.structured/1` directly in a unit test:

  * `result/1` -- a success `%Result{}`
  * `structured_result/2` -- a `%Result{}` carrying structured output
  * `error/2` -- an `%Error{}` of a given kind

## query_fun builders

Return a ready `:query_fun` (a 2-arity function):

  * `respond/1` -- always succeeds with a result (or a plain string)
  * `fail/1` -- always fails with an error (or a plain kind atom)
  * `sequence/1` -- returns each scripted value in turn, for retry tests

## Examples

Through the full seam with `ObanClaude.run/2`:

    import ObanClaude.Testing

    test "a clean result classifies to {:ok, result}" do
      assert {:ok, %Result{result: "done"}} =
               ObanClaude.run(%{"prompt" => "x"}, query_fun: respond("done"))
    end

    test "an auth failure cancels" do
      assert {{:cancel, :auth}, _} =
               ObanClaude.run(%{"prompt" => "x"}, query_fun: fail(:auth))
    end

Structured output a `handle_result/2` can read back:

    test "reads the structured verdict" do
      result = structured_result(%{"outcome" => "blocked"})
      assert ObanClaude.outcome(result) == "blocked"
    end

A worker's retry path, scripting one failure then a success:

    qf = sequence([error(:auth, reason: :rate_limit), "done"])
    assert {{:error, :rate_limit}, _} = ObanClaude.run(%{"prompt" => "x"}, query_fun: qf)
    assert {:ok, %Result{result: "done"}} = ObanClaude.run(%{"prompt" => "x"}, query_fun: qf)

> #### Only a `:query_fun` runtime value {: .info}
>
> `respond/1`, `fail/1`, and `sequence/1` return function *values*, so pass
> them to `ObanClaude.run/2` (or a worker's `perform`) at runtime. The
> `use ObanClaude.Worker, query_fun: ...` option is captured at compile time
> and needs a function *capture* (`&Mod.fun/2`) -- have that named function
> build its return with `result/1`/`error/2` instead.

# `failable`

```elixir
@type failable() :: ClaudeWrapper.Error.t() | ClaudeWrapper.Error.kind()
```

What `fail/1` (and `sequence/1`) accept: an `%Error{}` or a plain error-kind atom.

# `respondable`

```elixir
@type respondable() :: ClaudeWrapper.Result.t() | String.t()
```

What `respond/1` (and `sequence/1`) accept: a `%Result{}` or a plain result string.

# `error`

```elixir
@spec error(ClaudeWrapper.Error.kind(), keyword()) :: ClaudeWrapper.Error.t()
```

An error `%ClaudeWrapper.Error{}` of `kind`.

`opts` are forwarded to `ClaudeWrapper.Error.new/2` (`:reason`, `:message`,
`:exit_code`, `:stdout`, `:stderr`).

    ObanClaude.Testing.error(:timeout)
    ObanClaude.Testing.error(:auth, reason: :rate_limit)

# `fail`

```elixir
@spec fail(failable()) :: ObanClaude.query_fun()
```

A `:query_fun` that always fails, returning `{:error, error}`.

`err` is an `%Error{}` (from `error/2`) or a plain kind atom (shorthand for
`error(kind)`).

    ObanClaude.run(args, query_fun: ObanClaude.Testing.fail(:auth))

# `respond`

```elixir
@spec respond(respondable()) :: ObanClaude.query_fun()
```

A `:query_fun` that always succeeds, returning `{:ok, result}`.

`resp` is a `%Result{}` (from `result/1` or `structured_result/2`) or a plain
string (shorthand for `result(string)`).

    ObanClaude.run(args, query_fun: ObanClaude.Testing.respond("done"))

# `result`

```elixir
@spec result(String.t() | keyword()) :: ClaudeWrapper.Result.t()
```

A success `%ClaudeWrapper.Result{}`.

Pass a plain string for the result text, or a keyword list to set other fields
(`:result`, `:cost_usd`, `:num_turns`, `:session_id`, `:duration_ms`).

    ObanClaude.Testing.result("done")
    ObanClaude.Testing.result(result: "done", cost_usd: 0.01, num_turns: 2)

# `sequence`

```elixir
@spec sequence([respondable() | failable()]) :: ObanClaude.query_fun()
```

A `:query_fun` that returns each of `values` in turn: call 1 gets the first,
call 2 the second, and so on. For retry tests, where a worker's first attempt
fails and a later one succeeds.

Each value is coerced as in `respond/1`/`fail/1`: a `%Result{}` or string
becomes `{:ok, ...}`; an `%Error{}` or kind atom becomes `{:error, ...}`.
Raises once the script is exhausted, so a test that runs more attempts than it
scripted fails loudly rather than silently reusing the last value.

    # attempt 1 rate-limited, attempt 2 succeeds
    qf = ObanClaude.Testing.sequence([ObanClaude.Testing.error(:auth, reason: :rate_limit), "done"])

Backed by an `Agent` linked to the calling process, so call it from the test
process (not a compile-time `use` default).

# `structured_result`

```elixir
@spec structured_result(map() | list(), keyword()) :: ClaudeWrapper.Result.t()
```

A success `%Result{}` carrying `data` as its structured output.

`data` is planted at `extra["structured_output"]`, exactly where
`ObanClaude.structured/1` and `ObanClaude.outcome/1` read it. `opts` sets the
other `Result` fields, as in `result/1`.

    ObanClaude.Testing.structured_result(%{"outcome" => "blocked"})
    ObanClaude.Testing.structured_result(%{"pr" => 42}, cost_usd: 0.03)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
