Add tests for proto decode/encode validation edge cases, assert/401 plugs, authorization policy, identifier normalization, telemetry handlers, GeoIP parsing, agent helpers, maintenance helpers, and Ecto query modules.
210 lines
5.2 KiB
Elixir
210 lines
5.2 KiB
Elixir
defmodule ToweropsWeb.TelemetryTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
import ExUnit.CaptureLog
|
|
|
|
alias ToweropsWeb.Telemetry
|
|
|
|
describe "parse_redis_info/1" do
|
|
test "parses a minimal Redis INFO response" do
|
|
info = """
|
|
# Stats
|
|
total_connections_received:12
|
|
total_commands_processed:500
|
|
instantaneous_ops_per_sec:5
|
|
"""
|
|
|
|
result = Telemetry.parse_redis_info(info)
|
|
assert result["total_connections_received"] == "12"
|
|
assert result["total_commands_processed"] == "500"
|
|
assert result["instantaneous_ops_per_sec"] == "5"
|
|
end
|
|
|
|
test "skips comment/empty lines" do
|
|
info = "\n# Comment\n\nkey:value\n# Another\n"
|
|
assert %{"key" => "value"} == Telemetry.parse_redis_info(info)
|
|
end
|
|
|
|
test "returns empty map for empty input" do
|
|
assert %{} == Telemetry.parse_redis_info("")
|
|
end
|
|
|
|
test "handles CRLF line endings" do
|
|
info = "a:1\r\nb:2\r\n"
|
|
assert %{"a" => "1", "b" => "2"} == Telemetry.parse_redis_info(info)
|
|
end
|
|
|
|
test "splits on first colon only" do
|
|
info = "url:redis://localhost:6379"
|
|
assert %{"url" => "redis://localhost:6379"} == Telemetry.parse_redis_info(info)
|
|
end
|
|
|
|
test "drops malformed entries without colon" do
|
|
info = "valid:1\njunk_no_colon\nanother:2"
|
|
assert %{"valid" => "1", "another" => "2"} == Telemetry.parse_redis_info(info)
|
|
end
|
|
end
|
|
|
|
describe "metrics/0" do
|
|
test "returns a list of metric specs" do
|
|
metrics = Telemetry.metrics()
|
|
assert is_list(metrics)
|
|
refute Enum.empty?(metrics)
|
|
end
|
|
end
|
|
|
|
describe "handle_router_exception/4" do
|
|
test "logs an error with exception metadata" do
|
|
conn = %Plug.Conn{
|
|
method: "GET",
|
|
request_path: "/api/devices",
|
|
assigns: %{request_id: "req-123"}
|
|
}
|
|
|
|
metadata = %{
|
|
conn: conn,
|
|
kind: :error,
|
|
reason: "something went wrong",
|
|
stacktrace: [],
|
|
plug: MyTestPlug
|
|
}
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
Telemetry.handle_router_exception(:event, %{}, metadata, nil)
|
|
end)
|
|
|
|
assert log =~ "Router exception on Elixir.MyTestPlug GET /api/devices"
|
|
assert log =~ "[error]"
|
|
end
|
|
end
|
|
|
|
describe "handle_endpoint_stop/4" do
|
|
setup do
|
|
previous = Logger.level()
|
|
Logger.configure(level: :warning)
|
|
on_exit(fn -> Logger.configure(level: previous) end)
|
|
:ok
|
|
end
|
|
|
|
test "logs warning for slow requests over 5 seconds" do
|
|
conn = %Plug.Conn{
|
|
method: "POST",
|
|
request_path: "/slow-endpoint",
|
|
status: 200,
|
|
assigns: %{}
|
|
}
|
|
|
|
# Duration > 5000ms in native units
|
|
duration_native = System.convert_time_unit(6_000, :millisecond, :native)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
Telemetry.handle_endpoint_stop(
|
|
:stop,
|
|
%{duration: duration_native},
|
|
%{conn: conn},
|
|
nil
|
|
)
|
|
end)
|
|
|
|
assert log =~ "Slow request: POST /slow-endpoint"
|
|
assert log =~ "6000ms"
|
|
assert log =~ "[warning]"
|
|
end
|
|
|
|
test "logs error for 5xx status codes" do
|
|
conn = %Plug.Conn{
|
|
method: "GET",
|
|
request_path: "/api/broken",
|
|
status: 500,
|
|
assigns: %{request_id: "req-456"}
|
|
}
|
|
|
|
duration_native = System.convert_time_unit(100, :millisecond, :native)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
Telemetry.handle_endpoint_stop(
|
|
:stop,
|
|
%{duration: duration_native},
|
|
%{conn: conn},
|
|
nil
|
|
)
|
|
end)
|
|
|
|
assert log =~ "Server error: GET /api/broken returned 500"
|
|
assert log =~ "[error]"
|
|
end
|
|
|
|
test "does not log for fast 2xx requests" do
|
|
conn = %Plug.Conn{
|
|
method: "GET",
|
|
request_path: "/fast-endpoint",
|
|
status: 200,
|
|
assigns: %{}
|
|
}
|
|
|
|
duration_native = System.convert_time_unit(50, :millisecond, :native)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
Telemetry.handle_endpoint_stop(
|
|
:stop,
|
|
%{duration: duration_native},
|
|
%{conn: conn},
|
|
nil
|
|
)
|
|
end)
|
|
|
|
assert log == ""
|
|
end
|
|
|
|
test "logs both warning and error for slow 5xx request" do
|
|
conn = %Plug.Conn{
|
|
method: "DELETE",
|
|
request_path: "/api/resource",
|
|
status: 503,
|
|
assigns: %{request_id: "req-789"}
|
|
}
|
|
|
|
duration_native = System.convert_time_unit(7_000, :millisecond, :native)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
Telemetry.handle_endpoint_stop(
|
|
:stop,
|
|
%{duration: duration_native},
|
|
%{conn: conn},
|
|
nil
|
|
)
|
|
end)
|
|
|
|
assert log =~ "Slow request: DELETE /api/resource"
|
|
assert log =~ "Server error: DELETE /api/resource returned 503"
|
|
end
|
|
|
|
test "does not log for fast 4xx requests" do
|
|
conn = %Plug.Conn{
|
|
method: "GET",
|
|
request_path: "/not-found",
|
|
status: 404,
|
|
assigns: %{}
|
|
}
|
|
|
|
duration_native = System.convert_time_unit(100, :millisecond, :native)
|
|
|
|
log =
|
|
capture_log(fn ->
|
|
Telemetry.handle_endpoint_stop(
|
|
:stop,
|
|
%{duration: duration_native},
|
|
%{conn: conn},
|
|
nil
|
|
)
|
|
end)
|
|
|
|
assert log == ""
|
|
end
|
|
end
|
|
end
|