prop/test/microwaveprop/cache_test.exs
Graham McIntire 6c334d6e18 Cache /contacts, fix map blink, make mode optional
- Add contacts_inserted_at_desc_id_desc_idx so /contacts list runs as an
  Index Scan (~0.4ms) instead of a Seq Scan + top-N heapsort (~77ms on
  58k rows)
- Add Microwaveprop.Cache: tiny ETS-backed TTL cache for memoizing
  expensive-but-stale-tolerant values
- Cache total_entries for unsearched /contacts page loads (30s TTL,
  invalidated on insert)
- Cache the entire /contacts/map payload (pre-encoded JSON + band list),
  moving load_contacts into Radio.contact_map_payload; invalidated on
  new contact insert. Mount goes from ~150-300ms to ~5ms.
- Fix /map overlay blinking on load: reuse the Leaflet GridLayer across
  renderScores calls and just redraw() against the updated ScoreGrid,
  instead of removeLayer + new layer which flashed between tile regens
- Make mode optional on submission: schema change (null: true),
  submission_changeset no longer requires it, blank strings normalise to
  nil, CSV import accepts 6-column (no mode) or 7-column layouts
- core_components .input adds a red asterisk to labels when required,
  giving users a visual cue for which fields must be filled on /submit
2026-04-12 12:47:25 -05:00

46 lines
1.5 KiB
Elixir

defmodule Microwaveprop.CacheTest do
use ExUnit.Case, async: false
alias Microwaveprop.Cache
setup do
Cache.clear()
:ok
end
describe "fetch_or_store/3" do
test "invokes the function and returns the value on a cold cache" do
assert Cache.fetch_or_store(:the_key, 1_000, fn -> 42 end) == 42
end
test "returns the cached value without invoking the function on a warm hit" do
Cache.fetch_or_store(:the_key, 1_000, fn -> 42 end)
assert Cache.fetch_or_store(:the_key, 1_000, fn -> raise "should not run" end) == 42
end
test "re-invokes the function once the TTL expires" do
Cache.put(:the_key, 1, -1)
assert Cache.fetch_or_store(:the_key, 1_000, fn -> 2 end) == 2
end
test "isolates values by key" do
assert Cache.fetch_or_store(:a, 1_000, fn -> 1 end) == 1
assert Cache.fetch_or_store(:b, 1_000, fn -> 2 end) == 2
assert Cache.fetch_or_store(:a, 1_000, fn -> raise "no" end) == 1
assert Cache.fetch_or_store(:b, 1_000, fn -> raise "no" end) == 2
end
end
describe "put/3 and invalidate/1" do
test "put/3 stores a value with the given TTL" do
Cache.put(:k, "hello", 1_000)
assert Cache.fetch_or_store(:k, 1_000, fn -> "other" end) == "hello"
end
test "invalidate/1 removes an entry, forcing the next fetch to recompute" do
Cache.put(:k, "stale", 60_000)
Cache.invalidate(:k)
assert Cache.fetch_or_store(:k, 1_000, fn -> "fresh" end) == "fresh"
end
end
end