defmodule Symbiont.APITest do use ExUnit.Case, async: false import Plug.Test import Plug.Conn import Symbiont.TestHelpers @moduletag :capture_log setup do tmp_dir = Path.join(System.tmp_dir!(), "symbiont_api_test_#{:rand.uniform(999_999)}") File.mkdir_p!(tmp_dir) stop_all_services() ensure_task_supervisor() 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 -> stop_all_services() 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