157 lines
3.9 KiB
Elixir
157 lines
3.9 KiB
Elixir
defmodule Symbiont.APITest do
|
|
use ExUnit.Case, async: false
|
|
use Plug.Test
|
|
|
|
@moduletag :capture_log
|
|
|
|
setup do
|
|
tmp_dir = Path.join(System.tmp_dir!(), "symbiont_api_test_#{:rand.uniform(999_999)}")
|
|
File.mkdir_p!(tmp_dir)
|
|
|
|
# Start required services
|
|
for name <- [Symbiont.Ledger, Symbiont.Queue, Symbiont.Heartbeat] do
|
|
if Process.whereis(name), do: GenServer.stop(name)
|
|
end
|
|
|
|
unless Process.whereis(Symbiont.TaskSupervisor) do
|
|
Task.Supervisor.start_link(name: Symbiont.TaskSupervisor)
|
|
end
|
|
|
|
Application.put_env(:symbiont, :data_dir, tmp_dir)
|
|
Application.put_env(:symbiont, :heartbeat_interval_ms, :timer.hours(24))
|
|
|
|
{:ok, _} = Symbiont.Ledger.start_link(data_dir: tmp_dir)
|
|
{:ok, _} = Symbiont.Queue.start_link(data_dir: tmp_dir)
|
|
{:ok, _} = Symbiont.Heartbeat.start_link([])
|
|
|
|
on_exit(fn ->
|
|
for name <- [Symbiont.Heartbeat, Symbiont.Ledger, Symbiont.Queue] do
|
|
if Process.whereis(name), do: GenServer.stop(name)
|
|
end
|
|
|
|
File.rm_rf!(tmp_dir)
|
|
end)
|
|
|
|
%{tmp_dir: tmp_dir}
|
|
end
|
|
|
|
defp call_api(method, path, body \\ nil) do
|
|
conn =
|
|
if body do
|
|
conn(method, path, Jason.encode!(body))
|
|
|> put_req_header("content-type", "application/json")
|
|
else
|
|
conn(method, path)
|
|
end
|
|
|
|
Symbiont.API.call(conn, Symbiont.API.init([]))
|
|
end
|
|
|
|
defp json_body(conn) do
|
|
Jason.decode!(conn.resp_body)
|
|
end
|
|
|
|
describe "GET /health" do
|
|
test "returns ok" do
|
|
conn = call_api(:get, "/health")
|
|
assert conn.status == 200
|
|
|
|
body = json_body(conn)
|
|
assert body["status"] == "ok"
|
|
assert body["runtime"] == "elixir/otp"
|
|
end
|
|
end
|
|
|
|
describe "GET /status" do
|
|
test "returns system status" do
|
|
conn = call_api(:get, "/status")
|
|
assert conn.status == 200
|
|
|
|
body = json_body(conn)
|
|
assert body["status"] == "healthy"
|
|
assert body["runtime"] == "elixir/otp"
|
|
assert is_integer(body["queue_size"])
|
|
assert is_integer(body["total_calls"])
|
|
end
|
|
end
|
|
|
|
describe "GET /ledger" do
|
|
test "returns empty ledger initially" do
|
|
conn = call_api(:get, "/ledger")
|
|
assert conn.status == 200
|
|
|
|
body = json_body(conn)
|
|
assert body["entries"] == []
|
|
assert body["count"] == 0
|
|
end
|
|
|
|
test "returns entries after appending" do
|
|
Symbiont.Ledger.append(%{model: "haiku", estimated_cost_usd: 0.01, success: true})
|
|
Process.sleep(50)
|
|
|
|
conn = call_api(:get, "/ledger")
|
|
body = json_body(conn)
|
|
assert body["count"] == 1
|
|
assert hd(body["entries"])["model"] == "haiku"
|
|
end
|
|
end
|
|
|
|
describe "GET /ledger/stats" do
|
|
test "returns aggregate stats" do
|
|
conn = call_api(:get, "/ledger/stats")
|
|
assert conn.status == 200
|
|
|
|
body = json_body(conn)
|
|
assert body["total_calls"] == 0
|
|
assert body["total_cost_estimated_usd"] == 0.0
|
|
end
|
|
end
|
|
|
|
describe "POST /queue" do
|
|
test "enqueues a task" do
|
|
conn = call_api(:post, "/queue", %{task: "Do something later"})
|
|
assert conn.status == 200
|
|
|
|
body = json_body(conn)
|
|
assert body["status"] == "queued"
|
|
assert is_binary(body["id"])
|
|
assert body["position"] == 1
|
|
end
|
|
|
|
test "rejects empty task" do
|
|
conn = call_api(:post, "/queue", %{task: ""})
|
|
assert conn.status == 400
|
|
|
|
body = json_body(conn)
|
|
assert body["error"] =~ "missing"
|
|
end
|
|
|
|
test "rejects missing task field" do
|
|
conn = call_api(:post, "/queue", %{})
|
|
assert conn.status == 400
|
|
end
|
|
end
|
|
|
|
describe "POST /task" do
|
|
test "rejects empty task" do
|
|
conn = call_api(:post, "/task", %{task: ""})
|
|
assert conn.status == 400
|
|
end
|
|
|
|
test "rejects missing task" do
|
|
conn = call_api(:post, "/task", %{})
|
|
assert conn.status == 400
|
|
end
|
|
end
|
|
|
|
describe "unknown routes" do
|
|
test "returns 404" do
|
|
conn = call_api(:get, "/nonexistent")
|
|
assert conn.status == 404
|
|
|
|
body = json_body(conn)
|
|
assert body["error"] == "not found"
|
|
end
|
|
end
|
|
end
|