diff --git a/CHANGELOG.md b/CHANGELOG.md index 56080d1..68c32fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## Unreleased + +- Tool and prompt handlers run with the dispatching process in `$callers`, as + in a `Task`. Mocking and stubbing libraries and the Ecto SQL sandbox, which + find their owner through `$callers`, now reach handlers called with + `Phantom.Test` without global or shared mode. + ## 0.7.1 (2026-10-08) - A GET SSE stream for a session that already has one replaces it instead of diff --git a/lib/phantom/router.ex b/lib/phantom/router.ex index 0eac457..e90d7f0 100644 --- a/lib/phantom/router.ex +++ b/lib/phantom/router.ex @@ -2028,9 +2028,11 @@ defmodule Phantom.Router do request_id = request.id parent_pid = session.pid task_session = %{session | request: %{request | spec: spec}} + callers = [self() | Process.get(:"$callers", [])] worker = spawn(fn -> + Process.put(:"$callers", callers) # The isolated handler uses these process keys to return its eventual # result to the transport process that owns the current request. Process.put(:phantom_adopter, parent_pid) diff --git a/test/phantom/handler_callers_test.exs b/test/phantom/handler_callers_test.exs new file mode 100644 index 0000000..28017b2 --- /dev/null +++ b/test/phantom/handler_callers_test.exs @@ -0,0 +1,43 @@ +defmodule Phantom.HandlerCallersTest do + use ExUnit.Case, async: true + + defmodule Router do + use Phantom.Router, name: "HandlerCallers", vsn: "1.0" + + require Phantom.Prompt, as: Prompt + alias Phantom.Tool + + tool :stubbed_tool, description: "Replies with a stubbed HTTP response" + prompt :stubbed_prompt, description: "Replies with a stubbed HTTP response" + + def stubbed_tool(_params, session) do + {:reply, Tool.text(fetch_stub()), session} + end + + def stubbed_prompt(_params, session) do + {:reply, Prompt.response(user: Prompt.text(fetch_stub())), session} + end + + defp fetch_stub, do: Req.get!(plug: {Req.Test, __MODULE__}, retry: false).body + end + + import Phantom.Test + + setup do + Phantom.Test.start(router: Router) + Req.Test.stub(Router, &Req.Test.text(&1, "stubbed")) + {:ok, session: build_session(Router)} + end + + test "a tool handler can use stubs owned by the calling test", %{session: session} do + session + |> call_tool(:stubbed_tool, %{}) + |> assert_tool_text("stubbed") + end + + test "a prompt handler can use stubs owned by the calling test", %{session: session} do + session + |> get_prompt(:stubbed_prompt, %{}) + |> assert_prompt_message(text: "stubbed") + end +end