Add Tier 1 test coverage: EnrichmentStatus, Markdown, health, RemoteIp, Maidenhead

EnrichmentStatus 0→100%, HealthController 0→100%, Maidenhead 74→96%,
RemoteIp 56→94%, Markdown 0→92%. Total coverage 53→55%.
This commit is contained in:
Graham McIntire 2026-04-06 10:11:52 -05:00
parent f1db86e5b1
commit 7e52c6660d
5 changed files with 405 additions and 0 deletions

View file

@ -0,0 +1,169 @@
defmodule Microwaveprop.MarkdownTest do
use ExUnit.Case, async: true
alias Microwaveprop.Markdown
describe "headings" do
test "renders h1" do
assert Markdown.to_html!("# Hello") =~ "<h1>Hello</h1>"
end
test "renders h2 through h6" do
for level <- 2..6 do
hashes = String.duplicate("#", level)
assert Markdown.to_html!("#{hashes} Title") =~ "<h#{level}>Title</h#{level}>"
end
end
test "caps at h6 for excessive hashes" do
assert Markdown.to_html!("####### Too many") =~ "<h6>"
end
end
describe "paragraphs" do
test "renders simple paragraph" do
assert Markdown.to_html!("Hello world") =~ "<p>Hello world</p>"
end
test "joins multi-line paragraphs" do
result = Markdown.to_html!("Line one\nLine two")
assert result =~ "<p>Line one Line two</p>"
end
test "separates paragraphs by blank lines" do
result = Markdown.to_html!("Para one\n\nPara two")
assert result =~ "<p>Para one</p>"
assert result =~ "<p>Para two</p>"
end
end
describe "fenced code blocks" do
test "renders code block" do
md = "```\nfoo\nbar\n```"
result = Markdown.to_html!(md)
assert result =~ "<pre><code>foo\nbar</code></pre>"
end
test "renders code block with language" do
md = "```elixir\nIO.puts(\"hi\")\n```"
result = Markdown.to_html!(md)
assert result =~ ~s(<code class="language-elixir">)
end
test "escapes HTML in code blocks" do
md = "```\n<script>alert('xss')</script>\n```"
result = Markdown.to_html!(md)
assert result =~ "&lt;script&gt;"
refute result =~ "<script>"
end
end
describe "tables" do
test "renders table with header" do
md = "| A | B |\n|---|---|\n| 1 | 2 |"
result = Markdown.to_html!(md)
assert result =~ "<thead>"
assert result =~ "<th>A</th>"
assert result =~ "<td>1</td>"
end
end
describe "lists" do
test "renders unordered list" do
md = "- one\n- two\n- three"
result = Markdown.to_html!(md)
assert result =~ "<ul>"
assert result =~ "<li>one</li>"
assert result =~ "<li>two</li>"
assert result =~ "<li>three</li>"
end
test "renders ordered list" do
md = "1. first\n2. second"
result = Markdown.to_html!(md)
assert result =~ "<ol>"
assert result =~ "<li>first</li>"
assert result =~ "<li>second</li>"
end
end
describe "inline formatting" do
test "renders bold with **" do
assert Markdown.to_html!("**bold**") =~ "<strong>bold</strong>"
end
test "renders bold with __" do
assert Markdown.to_html!("__bold__") =~ "<strong>bold</strong>"
end
test "renders italic with *" do
assert Markdown.to_html!("*italic*") =~ "<em>italic</em>"
end
test "renders inline code" do
assert Markdown.to_html!("`code`") =~ "<code>code</code>"
end
test "renders links" do
assert Markdown.to_html!("[text](http://example.com)") =~ ~s(<a href="http://example.com">text</a>)
end
end
describe "list continuation" do
test "continues unordered list items with indentation" do
md = "- item one\n continued\n- item two"
result = Markdown.to_html!(md)
assert result =~ "<li>item one continued</li>"
assert result =~ "<li>item two</li>"
end
end
describe "table without separator" do
test "renders table body only when no separator row" do
md = "| A | B |"
result = Markdown.to_html!(md)
assert result =~ "<tbody>"
refute result =~ "<thead>"
end
end
describe "CRLF handling" do
test "normalizes CRLF to LF" do
result = Markdown.to_html!("Hello\r\nworld")
assert result =~ "<p>Hello world</p>"
end
end
describe "empty input" do
test "handles empty string" do
assert Markdown.to_html!("") == ""
end
end
describe "horizontal rules" do
test "renders --- as hr" do
assert Markdown.to_html!("---") =~ "<hr />"
end
test "renders *** as hr" do
assert Markdown.to_html!("***") =~ "<hr />"
end
test "renders ___ as hr" do
assert Markdown.to_html!("___") =~ "<hr />"
end
end
describe "HTML escaping" do
test "escapes angle brackets in paragraphs" do
result = Markdown.to_html!("x < y > z")
assert result =~ "&lt;"
assert result =~ "&gt;"
end
test "escapes ampersands" do
result = Markdown.to_html!("a & b")
assert result =~ "&amp;"
end
end
end

View file

@ -0,0 +1,130 @@
defmodule Microwaveprop.Radio.EnrichmentStatusTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Radio.EnrichmentStatus
describe "states/0" do
test "returns all valid states" do
assert EnrichmentStatus.states() == [:pending, :queued, :processing, :complete, :failed, :unavailable]
end
end
describe "fields/0" do
test "returns all status fields" do
assert EnrichmentStatus.fields() == [:hrrr_status, :weather_status, :terrain_status, :iemre_status]
end
end
describe "valid_transition?/2" do
test "pending can transition to queued" do
assert EnrichmentStatus.valid_transition?(:pending, :queued)
end
test "pending can transition to unavailable" do
assert EnrichmentStatus.valid_transition?(:pending, :unavailable)
end
test "pending cannot transition to complete" do
refute EnrichmentStatus.valid_transition?(:pending, :complete)
end
test "queued can transition to processing" do
assert EnrichmentStatus.valid_transition?(:queued, :processing)
end
test "queued can transition to failed" do
assert EnrichmentStatus.valid_transition?(:queued, :failed)
end
test "queued cannot transition to complete" do
refute EnrichmentStatus.valid_transition?(:queued, :complete)
end
test "processing can transition to complete" do
assert EnrichmentStatus.valid_transition?(:processing, :complete)
end
test "processing can transition to failed" do
assert EnrichmentStatus.valid_transition?(:processing, :failed)
end
test "processing cannot transition to pending" do
refute EnrichmentStatus.valid_transition?(:processing, :pending)
end
test "failed can transition to queued (retry)" do
assert EnrichmentStatus.valid_transition?(:failed, :queued)
end
test "failed cannot transition to complete" do
refute EnrichmentStatus.valid_transition?(:failed, :complete)
end
test "unavailable can transition to queued (retry)" do
assert EnrichmentStatus.valid_transition?(:unavailable, :queued)
end
test "complete can transition to pending (reset)" do
assert EnrichmentStatus.valid_transition?(:complete, :pending)
end
test "complete cannot transition to queued" do
refute EnrichmentStatus.valid_transition?(:complete, :queued)
end
end
describe "transition/3" do
test "applies valid transition to changeset" do
changeset =
%Contact{}
|> Ecto.Changeset.change(%{hrrr_status: :pending})
|> EnrichmentStatus.transition(:hrrr_status, :queued)
assert Ecto.Changeset.get_change(changeset, :hrrr_status) == :queued
assert changeset.valid?
end
test "adds error for invalid transition" do
changeset =
%Contact{}
|> Ecto.Changeset.change(%{hrrr_status: :pending})
|> EnrichmentStatus.transition(:hrrr_status, :complete)
assert {:hrrr_status, {"invalid transition from pending to complete", []}} in changeset.errors
end
test "works for all status fields" do
for field <- EnrichmentStatus.fields() do
changeset =
%Contact{}
|> Ecto.Changeset.change(%{field => :pending})
|> EnrichmentStatus.transition(field, :queued)
assert Ecto.Changeset.get_change(changeset, field) == :queued
end
end
end
describe "force_status/3" do
test "sets status without transition validation" do
changeset =
%Contact{}
|> Ecto.Changeset.change(%{hrrr_status: :pending})
|> EnrichmentStatus.force_status(:hrrr_status, :complete)
assert Ecto.Changeset.get_change(changeset, :hrrr_status) == :complete
assert changeset.valid?
end
test "allows normally invalid transitions" do
changeset =
%Contact{}
|> Ecto.Changeset.change(%{weather_status: :pending})
|> EnrichmentStatus.force_status(:weather_status, :failed)
assert Ecto.Changeset.get_change(changeset, :weather_status) == :failed
assert changeset.valid?
end
end
end

View file

@ -77,6 +77,19 @@ defmodule Microwaveprop.Radio.MaidenheadTest do
assert :error = Maidenhead.to_latlon(nil)
end
test "returns :error for non-binary" do
assert :error = Maidenhead.to_latlon(123)
end
test "handles 8-char extended grid EM12kp37" do
assert {:ok, {lat, lon}} = Maidenhead.to_latlon("EM12kp37")
assert is_float(lat)
assert is_float(lon)
# Should be within EM12kp subsquare
assert lat > 32.0 and lat < 33.0
assert lon > -98.0 and lon < -96.0
end
test "AA00 returns southwest corner center" do
# lon = 0*20 - 180 + 0*2 + 1 = -179, lat = 0*10 - 90 + 0*1 + 0.5 = -89.5
assert {:ok, {lat, lon}} = Maidenhead.to_latlon("AA00")

View file

@ -0,0 +1,10 @@
defmodule MicrowavepropWeb.HealthControllerTest do
use MicrowavepropWeb.ConnCase, async: true
test "GET /health returns 200 ok", %{conn: conn} do
conn = get(conn, "/health")
assert response(conn, 200) == "ok"
assert response_content_type(conn, :text) =~ "text/plain"
end
end

View file

@ -0,0 +1,83 @@
defmodule MicrowavepropWeb.Plugs.RemoteIpTest do
use ExUnit.Case, async: true
import Plug.Conn
import Plug.Test
alias MicrowavepropWeb.Plugs.RemoteIp
defp call_plug(conn) do
RemoteIp.call(conn, RemoteIp.init([]))
end
describe "call/2" do
test "extracts IP from cf-connecting-ip header" do
conn =
conn(:get, "/")
|> put_req_header("cf-connecting-ip", "1.2.3.4")
|> call_plug()
assert conn.remote_ip == {1, 2, 3, 4}
end
test "falls back to x-forwarded-for when no cf-connecting-ip" do
conn =
conn(:get, "/")
|> put_req_header("x-forwarded-for", "5.6.7.8")
|> call_plug()
assert conn.remote_ip == {5, 6, 7, 8}
end
test "prefers cf-connecting-ip over x-forwarded-for" do
conn =
conn(:get, "/")
|> put_req_header("cf-connecting-ip", "1.2.3.4")
|> put_req_header("x-forwarded-for", "5.6.7.8")
|> call_plug()
assert conn.remote_ip == {1, 2, 3, 4}
end
test "takes first IP from comma-separated x-forwarded-for" do
conn =
conn(:get, "/")
|> put_req_header("x-forwarded-for", "10.0.0.1, 10.0.0.2, 10.0.0.3")
|> call_plug()
assert conn.remote_ip == {10, 0, 0, 1}
end
test "preserves original remote_ip when no headers present" do
original_ip = {127, 0, 0, 1}
conn =
conn(:get, "/")
|> Map.put(:remote_ip, original_ip)
|> call_plug()
assert conn.remote_ip == original_ip
end
test "handles IPv6 addresses" do
conn =
conn(:get, "/")
|> put_req_header("cf-connecting-ip", "::1")
|> call_plug()
assert conn.remote_ip == {0, 0, 0, 0, 0, 0, 0, 1}
end
test "ignores malformed IP addresses" do
original_ip = {127, 0, 0, 1}
conn =
conn(:get, "/")
|> Map.put(:remote_ip, original_ip)
|> put_req_header("cf-connecting-ip", "not-an-ip")
|> call_plug()
assert conn.remote_ip == original_ip
end
end
end