# `ALLM.Providers.FakeImages`
[🔗](https://github.com/cykod/ALLM/blob/v0.3.0/lib/allm/providers/fake_images.ex#L1)

Deterministic, scripted adapter for image-generation testing. Implements
`ALLM.ImageAdapter`. See spec §35.8.

Layer B — runtime. FakeImages is the canonical testing image adapter; it
ships in `lib/` (not `test/support/`) because users need it for their
own application tests, mirroring the v0.2 `ALLM.Providers.Fake`
precedent.

## What FakeImages is (and isn't)

FakeImages **mostly ignores the `%ALLM.ImageRequest{}`** passed to
`generate/2`. It does inspect `:operation` to enforce the
`supported_operations/0` gate, and reads `:metadata` to round-trip onto
the response per the `ImageAdapter` contract; otherwise the scripted
response is produced irrespective of the request.

## Script shapes

`opts[:adapter_opts][:image_script]` accepts a list of script entries.
See `script/1` for the full grammar.

    adapter_opts: [
      image_script: [
        {:ok, [%ALLM.Image{...}], usage: %ALLM.ImageUsage{...}},
        {:error, %ALLM.Error.ImageAdapterError{reason: :rate_limited}},
        {:retry_until_call, 3}
      ]
    ]

`{:retry_until_call, n}` (added in 14.3) returns a synthetic
`%ALLM.Error.ImageAdapterError{reason: :rate_limited, retry_after_ms: 0}`
for the first `n - 1` calls against this entry, then advances the
cursor to the next entry on call `n`. Vehicle for testing
`ALLM.Retry.run/3` integration in `ALLM.generate_image/3`.

Multi-call scripting uses the same list — each call advances a
process-local cursor (or an explicit Agent cursor passed via
`adapter_opts[:script_cursor]` from `start_script_cursor/0`).

## Cursor behaviour

By default the cursor lives in the process dictionary at
`{:allm_fake_images_cursor, :erlang.phash2(scripts)}` — isolated per
ExUnit test process (`async: true`), GC'd on pid-down. Pass
`adapter_opts[:script_cursor]` with the pid returned from
`start_script_cursor/0` to share / isolate cursors explicitly across
processes.

## Test-only capture seam

Pass `adapter_opts[:capture_pid]` with a pid to receive a side-channel
message every time `generate/2` is invoked, BEFORE the script is
consulted. The message has the form:

    {ALLM.Providers.FakeImages, :call, %{request: request, opts: opts}}

This is purely a side-channel — it does NOT affect the response. It
exists so test files can assert on what the adapter received without
the `Process.register/2` + named-pid pattern (which forces
`async: false`). With `:capture_pid` in scope, tests stay
`async: true` and use `assert_receive {ALLM.Providers.FakeImages,
:call, _}` to pattern-match on the captured payload.

## Examples

    iex> img = ALLM.Image.from_binary(<<137, 80, 78, 71>>, "image/png")
    iex> req = ALLM.ImageRequest.new(prompt: "a kestrel")
    iex> opts = [adapter_opts: [image_script: [{:ok, [img]}]]]
    iex> {:ok, resp} = ALLM.Providers.FakeImages.generate(req, opts)
    iex> length(resp.images)
    1

# `script_entry`

```elixir
@type script_entry() ::
  {:ok, [ALLM.Image.t()]}
  | {:ok, [ALLM.Image.t()], keyword()}
  | {:error, ALLM.Error.ImageAdapterError.t()}
  | {:retry_until_call, pos_integer()}
```

One scripted image-generation result.

# `cursor_index`

```elixir
@spec cursor_index(pid()) :: non_neg_integer()
```

Read the current cursor index for an Agent-backed cursor. Used in
tests to assert how many calls have been consumed.

# `generate`

```elixir
@spec generate(
  ALLM.ImageRequest.t(),
  keyword()
) :: {:ok, ALLM.ImageResponse.t()} | {:error, ALLM.Error.ImageAdapterError.t()}
```

Execute a scripted image-generation request. See spec §35.3.

Returns `{:error, %ImageAdapterError{reason: :unsupported_operation,
metadata: %{operation: op}}}` BEFORE consulting the script when
`request.operation not in supported_operations()` (entry-point gate per
the conformance contract).

Otherwise reads the script from `opts[:adapter_opts][:image_script]`,
advances the process-local cursor, and returns the entry verbatim.
Empty / exhausted script returns
`{:error, %ImageAdapterError{reason: :unknown, metadata: %{cause:
:no_scripted_image}}}`.

Propagates `opts[:request_id]` onto `response.request_id` when the
script does not already populate it. Round-trips `request.metadata`
onto `response.metadata` when the script does not populate it.

When `opts[:adapter_opts][:capture_pid]` is a pid, sends
`{ALLM.Providers.FakeImages, :call, %{request: request, opts: opts}}`
to that pid BEFORE the operation gate fires (so even rejected calls
are captured). Side-channel only — does not affect the response.

## Examples

    iex> img = ALLM.Image.from_url("https://example.com/x.png")
    iex> req = ALLM.ImageRequest.new(prompt: "a kestrel")
    iex> opts = [adapter_opts: [image_script: [{:ok, [img]}]]]
    iex> {:ok, resp} = ALLM.Providers.FakeImages.generate(req, opts)
    iex> resp.usage.images
    1

    iex> img = ALLM.Image.from_binary(<<1>>, "image/png")
    iex> req = ALLM.ImageRequest.new(prompt: "a kestrel")
    iex> opts = [adapter_opts: [image_script: [{:ok, [img]}], capture_pid: self()]]
    iex> {:ok, _} = ALLM.Providers.FakeImages.generate(req, opts)
    iex> receive do
    ...>   {ALLM.Providers.FakeImages, :call, %{request: ^req}} -> :ok
    ...> after
    ...>   0 -> :no_message
    ...> end
    :ok

# `script`

```elixir
@spec script([script_entry()]) :: :ok
```

Document and validate the script grammar for `adapter_opts[:image_script]`.

Each entry is one of:

  * `{:ok, [%Image{}, ...]}` — return the listed images plus a default
    `%ImageUsage{images: length(images)}`.
  * `{:ok, [%Image{}, ...], usage: %ImageUsage{}}` — return the listed
    images plus the supplied usage.
  * `{:error, %ImageAdapterError{}}` — return the struct verbatim.

Returns `:ok` when the script is well-formed; raises `ArgumentError`
on the first invalid entry. (Validation is opt-in — the runtime
`generate/2` path tolerates a mix of legal entries and surfaces a
cursor-exhausted shape on `nil` lookups.)

## Examples

    iex> img = ALLM.Image.from_binary(<<1>>, "image/png")
    iex> ALLM.Providers.FakeImages.script([{:ok, [img]}])
    :ok

# `start_script_cursor`

```elixir
@spec start_script_cursor() :: pid()
```

Start an Agent-backed script cursor for cross-process multi-call
scripting and for disambiguating content-equal scripts in the same
process.

Pass the returned pid as `adapter_opts[:script_cursor]`; subsequent
calls increment the cursor on the Agent rather than on the process
dictionary.

## Examples

    iex> pid = ALLM.Providers.FakeImages.start_script_cursor()
    iex> ALLM.Providers.FakeImages.cursor_index(pid)
    0

# `supported_operations`

```elixir
@spec supported_operations() :: [ALLM.ImageRequest.operation()]
```

Return the list of operations FakeImages can perform.

Defaults to all three image operations; tests asserting per-adapter
rejection paths typically construct a custom `@behaviour
ALLM.ImageAdapter` module rather than narrowing this list.

## Examples

    iex> ALLM.Providers.FakeImages.supported_operations()
    [:generate, :edit, :variation]

---

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