defmodule Symbiont.HeartbeatTest do use ExUnit.Case, async: false @moduletag :capture_log setup do tmp_dir = Path.join(System.tmp_dir!(), "symbiont_hb_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 {:ok, _} = Symbiont.Ledger.start_link(data_dir: tmp_dir) {:ok, _} = Symbiont.Queue.start_link(data_dir: tmp_dir) # Override config for test Application.put_env(:symbiont, :data_dir, tmp_dir) Application.put_env(:symbiont, :heartbeat_interval_ms, :timer.hours(24)) {:ok, _} = Symbiont.Heartbeat.start_link([]) on_exit(fn -> for name <- [Symbiont.Heartbeat, Symbiont.Queue, Symbiont.Ledger] do if Process.whereis(name), do: GenServer.stop(name) end File.rm_rf!(tmp_dir) end) %{tmp_dir: tmp_dir} end test "pulse returns a health snapshot" do snapshot = Symbiont.Heartbeat.pulse() assert snapshot["status"] == "healthy" assert is_integer(snapshot["queue_size"]) assert snapshot["queue_size"] == 0 assert snapshot["tasks_processed"] == 0 assert snapshot["timestamp"] != nil end test "pulse logs to heartbeat.jsonl", %{tmp_dir: tmp_dir} do _snapshot = Symbiont.Heartbeat.pulse() path = Path.join(tmp_dir, "heartbeat.jsonl") content = File.read!(path) assert String.contains?(content, "healthy") lines = content |> String.split("\n", trim: true) assert length(lines) >= 1 end test "last_snapshot returns nil before first heartbeat" do # We need a fresh heartbeat that hasn't had its initial tick yet # Since our setup already started one, just check it works snapshot = Symbiont.Heartbeat.last_snapshot() # May or may not be nil depending on timing of the 5s initial tick assert is_nil(snapshot) or is_map(snapshot) end test "pulse processes queued tasks" do # Queue some tasks (they'll fail since we're using echo as CLI, but that's ok) {:ok, _} = Symbiont.Queue.enqueue("Test task 1") {:ok, _} = Symbiont.Queue.enqueue("Test task 2") assert Symbiont.Queue.size() == 2 snapshot = Symbiont.Heartbeat.pulse() assert snapshot["tasks_processed"] == 2 # Queue size should be 0 after processing (tasks were taken) assert Symbiont.Queue.size() == 0 end end