defmodule OpenAgents.PostHogTest do
use ExUnit.Case, async: false
alias OpenAgents.PostHog
setup do
original = Application.get_env(:openagents, :posthog_analytics)
Application.put_env(:openagents, :posthog_analytics, personal_api_key: nil, project_id: nil)
on_exit(fn ->
if original == nil,
do: Application.delete_env(:openagents, :posthog_analytics),
else: Application.put_env(:openagents, :posthog_analytics, original)
end)
:ok
end
defp configure(overrides \\ []) do
Application.put_env(
:openagents,
:posthog_analytics,
Keyword.merge(
[
personal_api_key: "phx_test_key",
project_id: 303_178,
app_host: "https://posthog-api.internal",
request_options: [plug: {Req.Test, __MODULE__}]
],
overrides
)
)
end
describe "enabled?/0" do
test "requires both a key and a positive numeric project id" do
refute PostHog.enabled?()
configure(personal_api_key: nil)
refute PostHog.enabled?()
configure(project_id: "0")
refute PostHog.enabled?()
configure(project_id: 303_178)
assert PostHog.enabled?()
configure(personal_api_key: " ")
refute PostHog.enabled?()
end
end
describe "overview/0" do
test "is not configured without credentials and sends nothing" do
assert {:error, :not_configured} = PostHog.overview()
end
test "shapes the eight projections from one pull" do
configure()
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "uniq(person_id)"
assert ["Bearer phx_test_key"] = Plug.Conn.get_req_header(conn, "authorization")
assert conn.request_path == "/api/projects/303178/query/"
Req.Test.json(conn, %{
"columns" => ["event", "count", "people"],
"results" => [["$pageview", 118, 8], ["chat_opened", 17, 3]]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "'auth_started'"
Req.Test.json(conn, %{
"columns" => [
"auth_started",
"user_signed_up",
"user_signed_in",
"identified_signups",
"chat_message_sent"
],
"results" => [[3, 1, 2, 1, 6]]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "'chat_turn_completed'"
Req.Test.json(conn, %{
"columns" => [
"turns",
"completed",
"failed",
"cancelled",
"avg_duration_ms",
"max_duration_ms"
],
"results" => [[6, 6, 0, 0, 4525.0, 7414]]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "'chat_message_received'"
assert body =~ "turn_failure_percent"
Req.Test.json(conn, %{
"columns" => [
"messages_received",
"messages_queued",
"turns_failed",
"turns_finished",
"turn_failure_percent"
],
"results" => [[24, 2, 1, 24, 4.2]]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "'chat_tokens_used'"
assert body =~ "GROUP BY model, provider"
Req.Test.json(conn, %{
"columns" => [
"model",
"provider",
"turns",
"input_tokens",
"output_tokens",
"total_tokens"
],
"results" => [
["anthropic/claude-sonnet-4.5", "anthropic", 6, 4200, 1800, 6000],
[nil, nil, 1, 10, 5.0, 15.0]
]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "'$pageview'"
Req.Test.json(conn, %{
"columns" => ["url", "views"],
"results" => [["https://openagents.com/", 20], [nil, 4]]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "properties.is_maintainer"
assert body =~ "unlabeled_after_24h_percent"
Req.Test.json(conn, %{
"columns" => [
"median_first_maintainer_response_hours",
"eligible_issues",
"unlabeled_issues",
"unlabeled_after_24h_percent"
],
"results" => [[12.5, 20, 3, 15.0]]
})
end)
Req.Test.expect(__MODULE__, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
assert body =~ "toStartOfWeek"
assert body =~ "properties.issue_state_changed"
Req.Test.json(conn, %{
"columns" => ["week", "created", "closed"],
"results" => [["2026-08-10", 8, 5], ["2026-08-17", 11, 9]]
})
end)
assert {:ok, overview} = PostHog.overview()
assert %DateTime{} = overview.generated_at
assert [%{"event" => "$pageview", "count" => 118, "people" => 8}] =
Enum.slice(overview.event_counts, 0, 1)
assert overview.funnel["user_signed_up"] == 1
assert overview.funnel["chat_message_sent"] == 6
assert overview.chat_turns == %{
"turns" => 6,
"completed" => 6,
"failed" => 0,
"cancelled" => 0,
"avg_duration_ms" => 4525,
"max_duration_ms" => 7414
}
assert overview.chat_lifecycle == %{
"messages_received" => 24,
"messages_queued" => 2,
"turns_failed" => 1,
"turns_finished" => 24,
"turn_failure_percent" => 4.2
}
assert overview.chat_token_usage == [
%{
"model" => "anthropic/claude-sonnet-4.5",
"provider" => "anthropic",
"turns" => 6,
"input_tokens" => 4200,
"output_tokens" => 1800,
"total_tokens" => 6000
},
%{
"model" => "unknown",
"provider" => "unknown",
"turns" => 1,
"input_tokens" => 10,
"output_tokens" => 5,
"total_tokens" => 15
}
]
assert [%{"url" => "https://openagents.com/", "views" => 20} | _] = overview.top_pages
assert overview.triage_health == %{
"median_first_maintainer_response_hours" => 12.5,
"eligible_issues" => 20,
"unlabeled_issues" => 3,
"unlabeled_after_24h_percent" => 15.0
}
assert overview.weekly_issue_flow == [
%{"week" => "2026-08-10", "created" => 8, "closed" => 5},
%{"week" => "2026-08-17", "created" => 11, "closed" => 9}
]
end
test "a rejected key is unavailable, never raised" do
configure()
Req.Test.expect(__MODULE__, fn conn ->
Plug.Conn.send_resp(conn, 401, "unauthorized")
end)
assert {:error, :unavailable} = PostHog.overview()
end
test "an unreachable host is unavailable, never raised" do
configure(request_options: [])
assert {:error, :unavailable} = PostHog.overview()
end
end
end