prop/test/microwaveprop_web/controllers/page_controller_test.exs
Graham McIntire e7fb34eb6f
test: add /qsos→/contacts redirect + scorer conversion properties
- PageController: cover the legacy `/qsos` → `/contacts` redirect pair
  (both the index and the id-preserving per-resource redirect), which
  had no coverage since the route rename.
- Scorer property tests:
  - f_to_c / c_to_f are inverses up to float epsilon, both propagate
    nil, and the C→F slope is exactly 5/9.
  - wind_speed_kts is non-negative, propagates nil on either axis,
    and matches sqrt(u²+v²)·1.94384 (the 5-digit kts factor the
    scorer uses).
  - precip_to_rate_mmhr is non-negative, 0 for nil/zero/negative.
  - dbz_to_rain_rate_mmhr is 0 below 5 dBZ (and for nil), and
    monotonically non-decreasing above — guards against accidental
    inversion of the Marshall-Palmer lookup.

Suite: 2,382 tests + 155 properties (was 2,380 + 148); coverage
71.08% → 71.12%; credo strict clean.
2026-04-23 14:03:02 -05:00

90 lines
3.3 KiB
Elixir

defmodule MicrowavepropWeb.PageControllerTest do
use MicrowavepropWeb.ConnCase
test "GET / redirects to /map", %{conn: conn} do
conn = get(conn, ~p"/")
assert redirected_to(conn) == "/map"
end
describe "legacy /qsos redirects" do
# The app was renamed `qsos` → `contacts` in the 2025 refactor; the
# old routes stay around so bookmarked links don't 404.
test "GET /qsos permanent-redirects to /contacts", %{conn: conn} do
conn = get(conn, "/qsos")
assert redirected_to(conn) == "/contacts"
end
test "GET /qsos/:id permanent-redirects to /contacts/:id preserving the id", %{conn: conn} do
id = "7d3cf5c8-6fd4-44b5-9b8e-84d9ef2c1b10"
conn = get(conn, "/qsos/#{id}")
assert redirected_to(conn) == "/contacts/#{id}"
end
end
# RFC 8288 Link headers for agent discovery. These advertise resources
# that actually exist in this app to crawlers/agents that look at
# response headers before parsing HTML.
describe "Link headers (RFC 8288)" do
test "homepage response carries Link headers", %{conn: conn} do
conn = get(conn, ~p"/")
[link] = get_resp_header(conn, "link")
assert link =~ ~s(</algo>; rel="service-doc")
assert link =~ ~s(</about>; rel="about")
assert link =~ ~s(</privacy>; rel="privacy-policy")
assert link =~ ~s(</sitemap.xml>; rel="sitemap")
end
test "browser-pipeline pages carry the same Link headers", %{conn: conn} do
conn = get(conn, ~p"/about")
[link] = get_resp_header(conn, "link")
assert link =~ ~s(rel="service-doc")
assert link =~ ~s(rel="about")
end
end
# Markdown for Agents: content negotiation so agents that send
# `Accept: text/markdown` get a real markdown document instead of a 406.
# We only serve markdown for paths where we actually have markdown source
# (the homepage summary and /algo). Other paths still 406 rather than lie
# with a converted-on-the-fly body.
describe "Markdown for Agents" do
test "GET / with Accept: text/markdown returns a markdown document", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "text/markdown")
|> get(~p"/")
assert conn.status == 200
assert ["text/markdown; charset=utf-8"] = get_resp_header(conn, "content-type")
[tokens] = get_resp_header(conn, "x-markdown-tokens")
assert String.to_integer(tokens) > 0
assert conn.resp_body =~ "# "
end
test "GET /algo with Accept: text/markdown returns algo.md", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "text/markdown")
|> get(~p"/algo")
assert conn.status == 200
assert ["text/markdown; charset=utf-8"] = get_resp_header(conn, "content-type")
assert conn.resp_body =~ "Microwave Propagation Algorithm"
end
test "GET / without markdown Accept still redirects to /map", %{conn: conn} do
conn = get(conn, ~p"/")
assert redirected_to(conn) == "/map"
end
test "Accept lists with both html and markdown prefer markdown when specified", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "text/markdown, text/html;q=0.9")
|> get(~p"/")
assert ["text/markdown; charset=utf-8"] = get_resp_header(conn, "content-type")
end
end
end