97 lines
3.2 KiB
Elixir
97 lines
3.2 KiB
Elixir
defmodule Microwaveprop.Radio.BandResolverPropertyTest do
|
|
use ExUnit.Case, async: true
|
|
use ExUnitProperties
|
|
|
|
alias Microwaveprop.Radio.BandResolver
|
|
|
|
describe "resolve/1 output contract" do
|
|
property "result is always nil or a member of allowed_bands/0 (strings)" do
|
|
check all(s <- string(:printable)) do
|
|
case BandResolver.resolve(s) do
|
|
nil -> :ok
|
|
mhz -> assert mhz in BandResolver.allowed_bands()
|
|
end
|
|
end
|
|
end
|
|
|
|
property "result is always nil or a member of allowed_bands/0 (integers)" do
|
|
check all(i <- integer(-1_000..500_000)) do
|
|
case BandResolver.resolve(i) do
|
|
nil -> :ok
|
|
mhz -> assert mhz in BandResolver.allowed_bands()
|
|
end
|
|
end
|
|
end
|
|
|
|
property "result is always nil or a member of allowed_bands/0 (floats)" do
|
|
check all(f <- float(min: -10.0, max: 500_000.0)) do
|
|
case BandResolver.resolve(f) do
|
|
nil -> :ok
|
|
mhz -> assert mhz in BandResolver.allowed_bands()
|
|
end
|
|
end
|
|
end
|
|
|
|
property "never crashes on arbitrary term input" do
|
|
check all(
|
|
term <-
|
|
one_of([
|
|
constant(nil),
|
|
constant(""),
|
|
constant(:atom),
|
|
constant([]),
|
|
constant(%{}),
|
|
string(:printable),
|
|
integer(),
|
|
float()
|
|
])
|
|
) do
|
|
result = BandResolver.resolve(term)
|
|
assert is_nil(result) or is_integer(result)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "resolve/1 is idempotent for canonical bands" do
|
|
property "every allowed band resolves to itself" do
|
|
check all(mhz <- member_of(BandResolver.allowed_bands())) do
|
|
assert BandResolver.resolve(mhz) == mhz
|
|
assert BandResolver.resolve(Integer.to_string(mhz)) == mhz
|
|
end
|
|
end
|
|
|
|
property "resolve_as_string/1 round-trips through Integer.to_string" do
|
|
check all(mhz <- member_of(BandResolver.allowed_bands())) do
|
|
assert BandResolver.resolve_as_string(mhz) == Integer.to_string(mhz)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "resolve/1 snap-to-nearest" do
|
|
property "frequencies within a small neighborhood of a microwave band resolve to that band" do
|
|
# The >= 900 MHz branch uses nearest-neighbor snapping. A small
|
|
# perturbation of a canonical band value must land on the same
|
|
# band — but we clamp the perturbed input to stay ≥ 900 so we
|
|
# don't cross the resolver's lower cutoff for 902 (the smallest
|
|
# microwave band).
|
|
bands_900_plus = Enum.filter(BandResolver.allowed_bands(), &(&1 >= 900))
|
|
|
|
check all(
|
|
mhz <- member_of(bands_900_plus),
|
|
delta_pct <- float(min: -0.001, max: 0.001)
|
|
) do
|
|
perturbed = max(900.01, mhz * (1.0 + delta_pct))
|
|
# Only assert when the perturbed value is still unambiguously
|
|
# closer to `mhz` than to any other allowed band.
|
|
nearest_other =
|
|
bands_900_plus
|
|
|> Enum.reject(&(&1 == mhz))
|
|
|> Enum.min_by(&abs(&1 - perturbed))
|
|
|
|
if abs(perturbed - mhz) < abs(perturbed - nearest_other) do
|
|
assert BandResolver.resolve(perturbed) == mhz
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|