defmodule ServerWeb.HostChannelTest do use ServerWeb.ChannelCase, async: false alias Server.Hosts alias ServerWeb.AgentSocket setup do {:ok, {host, token}} = Hosts.create_host("pve-01") %{host: host, token: token} end describe "join" do test "succeeds with valid token and marks host online", %{host: host, token: token} do {:ok, socket} = connect(AgentSocket, %{}) assert {:ok, _reply, socket} = subscribe_and_join(socket, "host:pve-01", %{ "token" => token, "agent_version" => "0.1.0" }) assert socket.assigns.host_id == host.id reloaded = Server.Repo.reload!(host) assert reloaded.status == "online" assert reloaded.agent_version == "0.1.0" assert reloaded.last_seen_at != nil end test "rejects invalid token" do {:ok, socket} = connect(AgentSocket, %{}) assert {:error, %{reason: "invalid_token"}} = subscribe_and_join(socket, "host:pve-01", %{ "token" => "garbage", "agent_version" => "0.1.0" }) end test "rejects unknown host name" do {:ok, socket} = connect(AgentSocket, %{}) assert {:error, %{reason: "unknown_host"}} = subscribe_and_join(socket, "host:nope", %{ "token" => "x", "agent_version" => "0.1.0" }) end end describe "metric events persist to DB" do setup %{token: token, host: host} do {:ok, socket} = connect(AgentSocket, %{}) {:ok, _reply, joined} = subscribe_and_join(socket, "host:pve-01", %{ "token" => token, "agent_version" => "0.1.0" }) %{socket: joined, host: host} end test "metric:fast is stored with interval=fast", %{socket: socket, host: host} do ts = "2026-04-21T12:00:00.123456Z" ref = push(socket, "metric:fast", %{ "collected_at" => ts, "data" => %{"cpu_percent" => 12.3, "load1" => 0.2} }) assert_reply ref, :ok sample = Server.Metrics.latest_sample(host.id, "fast") assert sample != nil assert sample.payload == %{"cpu_percent" => 12.3, "load1" => 0.2} {:ok, expected, _} = DateTime.from_iso8601(ts) assert DateTime.compare(sample.collected_at, expected) == :eq end test "metric:medium is stored with interval=medium", %{socket: socket, host: host} do ref = push(socket, "metric:medium", %{ "collected_at" => "2026-04-21T12:05:00Z", "data" => %{"vms_detail" => []} }) assert_reply ref, :ok sample = Server.Metrics.latest_sample(host.id, "medium") assert sample != nil assert sample.payload == %{"vms_detail" => []} end test "metric:slow is stored with interval=slow", %{socket: socket, host: host} do ref = push(socket, "metric:slow", %{ "collected_at" => "2026-04-21T12:30:00Z", "data" => %{"system_info" => %{"pveversion" => "8.3.0"}} }) assert_reply ref, :ok sample = Server.Metrics.latest_sample(host.id, "slow") assert sample != nil assert sample.payload == %{"system_info" => %{"pveversion" => "8.3.0"}} end test "replies :error when collected_at is missing", %{socket: socket} do ref = push(socket, "metric:fast", %{"data" => %{}}) assert_reply ref, :error, %{reason: "missing_collected_at"} end test "replies :error when data is missing", %{socket: socket} do ref = push(socket, "metric:fast", %{"collected_at" => "2026-04-21T12:00:00Z"}) assert_reply ref, :error, %{reason: "missing_data"} end end describe "terminate" do test "marks host offline when channel process exits", %{host: host, token: token} do {:ok, socket} = connect(AgentSocket, %{}) {:ok, _, joined} = subscribe_and_join(socket, "host:pve-01", %{ "token" => token, "agent_version" => "0.1.0" }) Process.unlink(joined.channel_pid) ref = Process.monitor(joined.channel_pid) close(joined) assert_receive {:DOWN, ^ref, :process, _, _}, 1_000 reloaded = Server.Repo.reload!(host) assert reloaded.status == "offline" end end end