Testing

September 17, 2026 ยท View on GitHub

Nothing below handle_answer/3 touches the network, so most tests need no transport at all. For the ones that do, Req.Test stands in for the API.

Routing logic

handle_answer/3 is a function. Call it with a literal reply:

test "a security flag wins" do
  from = {self(), make_ref()}
  assert {:noreply, _} = Triage.handle_answer(%{security: 0.9, kind: :feature, confidence: %{kind: 0.99}}, from, %{})
  assert_receive {_, [:security]}
end

The reply map is documented in Questions. Only the keys a clause matches on need to be present.

The transport

Point the transport at a Req.Test plug in the test config. Disabling the retry delay keeps retry tests fast:

# config/config.exs
if config_env() == :test do
  config :jev,
    api_key: "test-key",
    req_options: [plug: {Req.Test, Jev.HTTP}, retry_delay: 0, retry_log_level: false]
end

Then stub responses shaped like the API:

Req.Test.stub(Jev.HTTP, fn conn ->
  conn
  |> Plug.Conn.put_resp_content_type("application/json")
  |> Plug.Conn.send_resp(200, JSON.encode!(%{
    "model" => "jev-1.13.0",
    "answers" => %{"kind" => %{"type" => "choice", "choice" => "bug", "confidence" => 0.9, "probabilities" => %{"bug" => 0.9, "other" => 0.1}}},
    "usage" => %{"input_tokens" => 100, "output_tokens" => 0}
  }))
end)

assert {:ok, %{kind: :bug}} = Jev.HTTP.post("state", kind: {"Kind?", %{bug: nil, other: nil}})

Req.Test.transport_error/2 simulates a connection failure, and a stub that returns 429 or 529 exercises the retry path.

Servers

A Jev.Server posts from a task process, so the stub must be visible outside the test process. Use shared mode and async: false:

defmodule TriageTest do
  use ExUnit.Case, async: false

  setup {Req.Test, :set_req_test_to_shared}

  test "routes a crash report" do
    Req.Test.stub(Jev.HTTP, &crash_report/1)
    pid = start_supervised!({Triage, []})
    assert GenServer.call(pid, {:labels, %{title: "crash"}}) == [:bug, :"priority:high"]
  end
end

A stub can read the request body to answer differently per call, which is how to test a server with several requests in flight or a recursive workflow that asks a second question based on the first answer.

Live

The repository's examples/triage.exs is a live smoke test against the API:

TYPESAFE_API_KEY=... mix run examples/triage.exs