defmodule TribeOne.TribesPlugin.Supertest.APITest do use ExUnit.Case, async: false setup tags do tribes_owner = Ecto.Adapters.SQL.Sandbox.start_owner!(Tribes.Repo, shared: not tags[:async]) parrhesia_owner = Ecto.Adapters.SQL.Sandbox.start_owner!(Parrhesia.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(parrhesia_owner) Ecto.Adapters.SQL.Sandbox.stop_owner(tribes_owner) end) :ok end test "reports health and schema state" do assert {:ok, %{ok: true, plugin: "tribe-one-supertest"}} = TribeOne.TribesPlugin.Supertest.API.health() assert {:ok, %{ok: true, schema: %{tables: %{cases: true, events: true}}}} = TribeOne.TribesPlugin.Supertest.API.schema() end test "creates linked Ash resources and lists state by run" do run_id = "api-test-#{System.unique_integer([:positive])}" assert {:ok, %{ok: true, case: test_case}} = TribeOne.TribesPlugin.Supertest.API.create_case(%{ "run_id" => run_id, "name" => "primary", "payload" => %{"source" => "api-test"} }) assert test_case.counter == 0 assert test_case.payload == %{"source" => "api-test"} assert {:ok, %{ok: true, event: event}} = TribeOne.TribesPlugin.Supertest.API.create_event(%{ "case_id" => test_case.id, "run_id" => run_id, "sequence" => 1, "body" => "created", "payload" => %{"case" => test_case.name} }) assert event.case_id == test_case.id assert {:ok, %{ok: true, case: incremented}} = TribeOne.TribesPlugin.Supertest.API.increment_case(%{ "id" => test_case.id, "delta" => 3 }) assert incremented.counter == 3 assert {:ok, %{ok: true, cases: [listed_case], events: [listed_event]}} = TribeOne.TribesPlugin.Supertest.API.state(%{"run_id" => run_id}) assert listed_case.id == test_case.id assert listed_case.counter == 3 assert listed_event.id == event.id end test "reset soft-deletes state for one run" do run_id = "reset-test-#{System.unique_integer([:positive])}" assert {:ok, %{case: test_case}} = TribeOne.TribesPlugin.Supertest.API.create_case(%{ "run_id" => run_id, "name" => "reset" }) assert {:ok, %{event: _event}} = TribeOne.TribesPlugin.Supertest.API.create_event(%{ "case_id" => test_case.id, "run_id" => run_id, "sequence" => 1, "body" => "before reset" }) assert {:ok, %{ok: true, deleted: %{cases: 1, events: 1}}} = TribeOne.TribesPlugin.Supertest.API.reset(%{"run_id" => run_id}) assert {:ok, %{ok: true, cases: [], events: []}} = TribeOne.TribesPlugin.Supertest.API.state(%{"run_id" => run_id}) end test "broadcasts and records cluster pubsub probe messages" do pubsub = :"supertest_cluster_pubsub_#{System.unique_integer([:positive])}" start_supervised!({Phoenix.PubSub, name: pubsub}) start_supervised!({TribeOne.TribesPlugin.Supertest.ClusterPubSubProbe, pubsub: pubsub}) run_id = "cluster-pubsub-test-#{System.unique_integer([:positive])}" assert {:ok, %{ok: true, message: message}} = TribeOne.TribesPlugin.Supertest.API.broadcast_cluster_pubsub(%{ "run_id" => run_id, "payload" => %{"phase" => "api-test"} }) assert message.run_id == run_id assert message.payload == %{"phase" => "api-test"} eventually(fn -> assert {:ok, %{ok: true, messages: [received]}} = TribeOne.TribesPlugin.Supertest.API.received_cluster_pubsub(%{"run_id" => run_id}) assert received.id == message.id assert received.payload == %{"phase" => "api-test"} end) assert {:ok, %{ok: true}} = TribeOne.TribesPlugin.Supertest.API.reset_cluster_pubsub(%{"run_id" => run_id}) assert {:ok, %{ok: true, messages: []}} = TribeOne.TribesPlugin.Supertest.API.received_cluster_pubsub(%{"run_id" => run_id}) end test "echoes plugin management context" do context = %Tribes.Plugin.ManagementContext{ plugin: "tribe-one-supertest", method: "supertest.echo", version: "1", actor_pubkey: String.duplicate("a", 64), admin?: true } assert {:ok, %{ ok: true, plugin: "tribe-one-supertest", method: "supertest.echo", version: "1", admin: true, actor_pubkey: actor_pubkey, params: %{"probe" => true} }} = TribeOne.TribesPlugin.Supertest.API.management_echo(context, %{"probe" => true}) assert actor_pubkey == String.duplicate("a", 64) end test "records scheduler probe executions" do run_id = "scheduler-test-#{System.unique_integer([:positive])}" assert {:ok, %{ok: true, control: control}} = TribeOne.TribesPlugin.Supertest.API.start_scheduler_probe(%{ "run_id" => run_id, "expected_nodes" => 2 }) assert control.name == "scheduler:control" assert control.status == "active" assert :ok = TribeOne.TribesPlugin.Supertest.Scheduler.SingletonCounterWorker.perform( scheduler_job("node-a") ) assert :ok = TribeOne.TribesPlugin.Supertest.Scheduler.AllNodesCounterWorker.perform( scheduler_job("node-a") ) assert :ok = TribeOne.TribesPlugin.Supertest.Scheduler.AllNodesCounterWorker.perform( scheduler_job("node-b") ) assert {:ok, %{ ok: true, expected_nodes: 2, passed?: true, singleton: %{counter: 1, node_ids: ["node-a"]}, all_nodes: %{counter: 2, node_ids: ["node-a", "node-b"]} }} = TribeOne.TribesPlugin.Supertest.API.scheduler_probe(%{"run_id" => run_id}) assert {:ok, %{ok: true, stopped: true}} = TribeOne.TribesPlugin.Supertest.API.stop_scheduler_probe(%{"run_id" => run_id}) end test "emits metrics probe telemetry" do ref = make_ref() :telemetry.attach( "supertest-api-metrics-probe-test", [:supertest, :metrics, :probe], fn _event, measurements, metadata, pid -> send(pid, {ref, measurements, metadata}) end, self() ) on_exit(fn -> :telemetry.detach("supertest-api-metrics-probe-test") end) assert {:ok, %{ok: true, value: 41}} = TribeOne.TribesPlugin.Supertest.API.emit_metrics_probe(%{"value" => 41}) assert_receive {^ref, %{value: 41}, %{}} end defp scheduler_job(node_id) do %Tribes.Plugin.Scheduler.Job{ id: "job-#{node_id}", plugin: "tribe-one-supertest", cron_id: "plugin:tribe-one-supertest:scheduler", scheduled_at: ~U[2026-05-28 12:00:00Z], attempt_id: "attempt-#{node_id}", attempt_token: "token-#{node_id}", node_id: node_id, payload: %{}, requirements: %{} } end defp eventually(assertion, attempts \\ 20) defp eventually(assertion, attempts) when attempts > 0 do assertion.() rescue _error in [ExUnit.AssertionError] -> Process.sleep(10) eventually(assertion, attempts - 1) end defp eventually(assertion, 0), do: assertion.() end