# Bugs Found 2026-05-12 All three fixed; `mix test --seed 949374` now passes (3893 tests, 0 failures). ## 1. Public profile leaks private contacts and pending beacons [FIXED] **Severity:** High **Category:** Privacy / authorization bypass **Files:** - `lib/microwaveprop_web/live/user_profile_live.ex:21` - `lib/microwaveprop_web/live/user_profile_live.ex:22` - `lib/microwaveprop_web/live/user_profile_live.ex:92` - `lib/microwaveprop_web/live/user_profile_live.ex:130` - `lib/microwaveprop/beacons.ex:61` `/u/:callsign` is mounted in the public LiveView session, but it loads `Radio.list_contacts_for_user(user)` and `Beacons.list_beacons_for_user(user)` without checking whether the visitor is the profile owner or an admin. `list_contacts_for_user/1` returns every submitted contact for that user, including contacts where `private == true`. The profile template then renders those rows and links to `/contacts/:id`. The contact detail page correctly 404s for unauthorized viewers, but the public profile has already exposed the contact timestamp, stations, band, mode, and distance. `list_beacons_for_user/1` explicitly returns approved and pending beacons for the public profile page. The profile template renders pending beacon callsign, frequency, grid, keying, status, and link. This contradicts the beacon visibility model where pending beacons should only be visible to the submitter and admins. Suggested fix: split the profile query by viewer. Anonymous/non-owner visitors should see only non-private contacts and approved beacons. Owners/admins can see their private contacts and pending beacons. Add LiveView tests for anonymous, owner, and admin views. ## 2. `Radio.create_contact/2` rejects authenticated submissions unless callers also pass an email [FIXED] **Severity:** Medium **Category:** Broken context contract / validation ordering **Files:** - `lib/microwaveprop/radio.ex:629` - `lib/microwaveprop/radio.ex:632` - `lib/microwaveprop/radio.ex:633` - `lib/microwaveprop/radio/contact.ex:109` - `lib/microwaveprop/radio/contact.ex:173` `Radio.create_contact(attrs, user_id)` adds `user_id` after `Contact.submission_changeset/2` has already run `validate_user_or_email/1`. That validation only sees fields cast from `attrs`, so a valid authenticated create with `user_id` but no `submitter_email` returns an invalid changeset with `submitter_email: can't be blank`. Confirmed with: ```text Radio.create_contact(valid_attrs_without_submitter_email, user.id) #=> {:error, %Ecto.Changeset{errors: [submitter_email: {"can't be blank", []}]}} ``` Current web/API call sites mask this by inserting the user's email into params before calling the context, but the public function's contract and implementation disagree. Any future trusted caller that relies on the `user_id` argument alone will fail unexpectedly. Suggested fix: put `user_id` into the struct or attrs before calling `Contact.submission_changeset/2`, or move the ownership validation to after `maybe_put_user_id/2`. Add a context-level regression test that passes `user_id` without `submitter_email`. ## 3. Full test suite is order-dependent around Valkey-backed `GridCache.clear/0` [FIXED] **Severity:** Medium **Category:** Test isolation / flaky suite **Files:** - `test/support/conn_case.ex:45` - `test/microwaveprop/weather/grid_cache_valkey_test.exs:26` - `test/microwaveprop/weather/grid_cache_valkey_test.exs:35` - `lib/microwaveprop/weather/grid_cache.ex:477` One `mix test` run failed with 3 setup failures. `ConnCase` calls `GridCache.clear/0` for every web test. If a Valkey cache test has registered `Microwaveprop.Valkey.Conn` and swapped `:valkey_adapter`, `GridCache.clear/0` takes the Valkey path and calls `Valkey.scan_match/1`. In the failed run, that reached `Microwaveprop.Valkey.MockAdapter.command/3` without a `SCAN` expectation, crashing unrelated web tests during setup. Failure shape: ```text Mox.UnexpectedCallError: no expectation defined for Microwaveprop.Valkey.MockAdapter.command/3 args: [Microwaveprop.Valkey.Conn, ["SCAN", "0", "MATCH", "prop:wg:*", "COUNT", "500"], ...] ``` Verification: ```text mix test --seed 949374 # 3880 tests, 3 failures, 6 skipped mix precommit # 3880 tests, 0 failures, 6 skipped ``` The subsequent passing `mix precommit` run makes this look order-dependent rather than a deterministic failure. Suggested fix: make Valkey test setup restore global process/env state before other tests can observe it, or avoid global `Process.register/2` for `Conn`. Also consider making `ConnCase` force the ETS fallback or installing a default Valkey mock stub for `GridCache.clear/0`. ## Verification Notes - `mix compile --warnings-as-errors` passed. - `mix credo --strict` reported design/refactoring/style issues only; no bug-class findings were added from Credo. - `mix test --seed 949374` failed as described in bug 3. - `mix precommit` passed afterward, confirming the checked-in code still clears the normal project gate for this run.