The store now starts empty and agents are added/removed exclusively through the web admin page / admin API, persisted to AGENTS_FILE. No AGENTS env var needed in the sops secret.
175 lines
4.9 KiB
Elixir
175 lines
4.9 KiB
Elixir
defmodule N8nOpenaiAdapter.RouterTest do
|
|
use ExUnit.Case, async: false
|
|
import Plug.Test
|
|
import Plug.Conn
|
|
|
|
alias N8nOpenaiAdapter.AgentRegistry
|
|
alias N8nOpenaiAdapter.Router
|
|
|
|
setup do
|
|
# The app starts with an empty store (no AGENTS env seeding). Seed the two
|
|
# test agents via the registry so the base tests have something to list.
|
|
AgentRegistry.put("scholar-agent", "https://n8n.bueso.eu/webhook/scholar-id/chat")
|
|
AgentRegistry.put("media-agent", "https://n8n.bueso.eu/webhook/media-id/chat")
|
|
:ok
|
|
end
|
|
|
|
test "GET /v1/models lists configured agents" do
|
|
conn =
|
|
conn(:get, "/v1/models")
|
|
|> put_req_header("authorization", "Bearer test-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["object"] == "list"
|
|
ids = Enum.map(body["data"], & &1["id"])
|
|
assert "scholar-agent" in ids
|
|
assert "media-agent" in ids
|
|
end
|
|
|
|
test "GET /v1/models rejects a bad API key" do
|
|
conn =
|
|
conn(:get, "/v1/models")
|
|
|> put_req_header("authorization", "Bearer wrong")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 401
|
|
end
|
|
|
|
test "POST /v1/chat/completions with unknown model returns 400" do
|
|
conn =
|
|
conn(
|
|
:post,
|
|
"/v1/chat/completions",
|
|
Jason.encode!(%{
|
|
"model" => "nope",
|
|
"messages" => [%{"role" => "user", "content" => "hi"}]
|
|
})
|
|
)
|
|
|> put_req_header("authorization", "Bearer test-key")
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 400
|
|
assert Jason.decode!(conn.resp_body)["error"]["message"] == "Unknown model: nope"
|
|
end
|
|
|
|
test "POST /v1/chat/completions without a user message returns 400" do
|
|
conn =
|
|
conn(
|
|
:post,
|
|
"/v1/chat/completions",
|
|
Jason.encode!(%{
|
|
"model" => "scholar-agent",
|
|
"messages" => [%{"role" => "assistant", "content" => "hi"}]
|
|
})
|
|
)
|
|
|> put_req_header("authorization", "Bearer test-key")
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 400
|
|
end
|
|
|
|
test "GET /admin serves the web admin page (admin key)" do
|
|
conn =
|
|
conn(:get, "/admin")
|
|
|> put_req_header("authorization", "Bearer test-admin-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 200
|
|
assert conn.resp_body =~ "n8n OpenAI Adapter"
|
|
assert conn.resp_body =~ "admin/agents"
|
|
end
|
|
|
|
test "GET /admin rejects a non-admin key" do
|
|
conn =
|
|
conn(:get, "/admin")
|
|
|> put_req_header("authorization", "Bearer test-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 401
|
|
end
|
|
|
|
# --- Admin API ---
|
|
|
|
test "GET /admin/agents lists agents (admin key)" do
|
|
conn =
|
|
conn(:get, "/admin/agents")
|
|
|> put_req_header("authorization", "Bearer test-admin-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert Map.has_key?(body["agents"], "scholar-agent")
|
|
end
|
|
|
|
test "GET /admin/agents rejects a non-admin key" do
|
|
conn =
|
|
conn(:get, "/admin/agents")
|
|
|> put_req_header("authorization", "Bearer test-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 401
|
|
end
|
|
|
|
test "POST /admin/agents adds an agent" do
|
|
conn =
|
|
conn(
|
|
:post,
|
|
"/admin/agents",
|
|
Jason.encode!(%{
|
|
"model" => "new-agent",
|
|
"webhook" => "https://n8n.bueso.eu/webhook/new/chat"
|
|
})
|
|
)
|
|
|> put_req_header("authorization", "Bearer test-admin-key")
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 200
|
|
assert Jason.decode!(conn.resp_body)["ok"] == true
|
|
# now visible in /v1/models
|
|
conn2 =
|
|
conn(:get, "/v1/models")
|
|
|> put_req_header("authorization", "Bearer test-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
ids = Enum.map(Jason.decode!(conn2.resp_body)["data"], & &1["id"])
|
|
assert "new-agent" in ids
|
|
end
|
|
|
|
test "DELETE /admin/agents removes an agent" do
|
|
# add then delete
|
|
conn =
|
|
conn(
|
|
:post,
|
|
"/admin/agents",
|
|
Jason.encode!(%{
|
|
"model" => "temp-agent",
|
|
"webhook" => "https://n8n.bueso.eu/webhook/temp/chat"
|
|
})
|
|
)
|
|
|> put_req_header("authorization", "Bearer test-admin-key")
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn.status == 200
|
|
|
|
conn2 =
|
|
conn(:delete, "/admin/agents/temp-agent")
|
|
|> put_req_header("authorization", "Bearer test-admin-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn2.status == 200
|
|
|
|
conn3 =
|
|
conn(:delete, "/admin/agents/temp-agent")
|
|
|> put_req_header("authorization", "Bearer test-admin-key")
|
|
|> Router.call(Router.init([]))
|
|
|
|
assert conn3.status == 404
|
|
end
|
|
end
|