Commit graph

13 commits

Author SHA1 Message Date
67d8be6b89 test: in-process Redis fake instead of skipping Redis-backed tests
Replaces the previous :requires_redis skip with a real test double so
the suite stays self-contained — no external Redis service needed in
CI, but the call paths through RedisHealthCheck and FourOhFourTracker
still get exercised end-to-end.

- Towerops.Redix.Fake: GenServer that speaks PING / SADD / SCARD /
  SISMEMBER / SMEMBERS / EXPIRE / DEL / FLUSHDB. start_link refuses
  anything other than localhost:6379 so failure-path tests still fail
  the way they did against real Redix.
- Application supervisor starts the fake under name Towerops.Redix in
  test env (instead of the dummy Agent stub).
- RedisHealthCheck and FourOhFourTracker now route Redix calls through
  Application.get_env(:towerops, :redix_module, Redix). Test config
  points it at the fake.
- Drops the test_helper :requires_redis exclusion and the @moduletag
  on the two test files; FourOhFourTrackerTest setup just resets the
  fake between runs.
2026-05-10 11:32:50 -05:00
670516f641 test: skip Redis-dependent tests in CI when Redis isn't reachable
The Forgejo CI test gate has no Redis service, so FourOhFourTracker
and RedisHealthCheck tests fail with Redix.ConnectionError. They've
always been broken in CI but the failures got noticed now.

Mirrors the existing :requires_gdal pattern: probe localhost:6379 in
test_helper.exs and add :requires_redis to extra_excludes when nothing
answers. Locally (where direnv runs Redis) the tag isn't excluded so
the tests still run.
2026-05-10 11:14:47 -05:00
6e4b07026f feat(coverage): tree-canopy clutter via LANDFIRE EVH
Closes the last clutter source. NLOS heatmaps now see trees as
obstacles in addition to buildings:

- New Towerops.Coverages.TreeCanopy module fetches an AAIGrid of
  canopy heights for the coverage bbox via the same /vsicurl/
  + Lidar.Reader pipeline used for terrain. Source defaults to
  the public LANDFIRE 2022 EVH COG (~30 m, all CONUS) and is
  override-able via :tree_canopy_url.
- EVH cells are integer codes; decode_evh maps the canopy range
  (101..199 → height-100 m), the herbaceous range (11..29 →
  (code-10) * 0.1 m), and zeroes everything else.
- Profile.sample/5 takes a :canopy grid alongside :clutter and
  uses max(building_height, canopy_height) so a tree poking above
  a one-storey roof still shadows.
- CoverageWorker fetches the canopy grid once per job (best
  effort — failures log + continue with nil) and threads it
  through the env map. LOS keeps clutter empty + canopy nil
  (height-above-clutter view); NLOS gets both.
- Tests: tree_canopy_test for the disabled / override paths;
  test_helper disables the LANDFIRE fetch by default so the
  rest of the suite doesn't stream remote bytes.
2026-05-06 16:54:59 -05:00
f77c493fa3 feat(lidar): nationwide USGS NED fallback (the actual code)
Earlier commit a9aa8bf9 had the message but only the test tweak made
it through pre-commit stashing. This is the implementation:

- lib/towerops/lidar/sources/usgs_ned.ex
- lib/towerops/lidar.ex (mosaic_from_ned + get_elevation_from_ned
  fallback paths)
- lib/towerops/coverages/building.ex + migration scaffolding
- lib/towerops/workers/coverage_worker.ex error message
- test_helper.exs and the lidar test changes
2026-05-06 16:21:50 -05:00
6bfcbee7fc feat: draggable Leaflet marker for radio location on coverage form
Adds a "Radio location" section to the coverage form with an embedded
Leaflet map. The marker starts at the parent site's coordinates and
the user can drag it to the actual radio location. A "Use site
location" button clears the override.

- New CoverageLocationPicker hook in assets/js/app.ts: creates the
  map, draggable marker, and pushes "location_picked" {lat, lon} on
  dragend. Re-creates the marker when the user picks a site that
  initially had no coords.
- form.html.heex: location section with map div (phx-hook), readout
  ("lat, lon - override" if set), and reset button. Hidden inputs for
  latitude_override / longitude_override so the form's params always
  carry the chosen coordinates.
- form.ex: handle_event "location_picked" updates the override fields
  in the changeset; "use_site_location" clears them. Helper functions
  radio_marker_lat/lon walk the form values, falling back to the
  selected site.

CI test gate fix:
- Worker test now requires GDAL CLI tools to write GeoTIFF + PNG.
  test_helper.exs detects whether gdal_translate / gdaldem are on
  PATH; when missing (CI's lean test image), :requires_gdal tagged
  tests are excluded. Locally with brew install gdal they still run.
2026-05-06 14:57:55 -05:00
5a9381f91a feat: add /coverage RF prediction feature scaffold
End-to-end CRUD scaffold for per-site, per-antenna RF coverage
prediction. Schema, context, antenna registry, Oban worker stub, and
three LiveViews are wired up; the actual ITM + LIDAR + buildings
compute pipeline lands in a follow-up.

- Migration + Coverage schema with full validation (RF params, pixel-
  budget cap, antenna_slug existence). organization_id excluded from
  cast (programmatic-only).
- Coverages context: org-scoped CRUD, validate_site_in_organization
  rejects cross-tenant site_id refs at insert/update, queue_compute
  with PubSub broadcast on per-coverage and per-org topics.
- MSI Planet .ant parser + persistent_term registry loaded at boot
  from priv/antennas/. Two synthetic .ant fixtures (omni + 90deg
  sector) bundled.
- CoverageWorker on new :coverage Oban queue (concurrency 2). Job
  args carry organization_id; worker refuses to run on org mismatch.
  Stub flips status to "failed" with "compute not yet implemented"
  so the UI flow is exercisable.
- LiveViews: index (org-wide, live status updates), form (antenna
  picker grouped by manufacturer), show (status banners, params
  panel, map placeholder). Sidebar nav link added.
- Routes /coverage, /coverage/new, /coverage/:id, /coverage/:id/edit
  under :require_authenticated_user_with_default_org.
- 41 new tests covering schema validation, .ant parsing, context
  CRUD with cross-org guards, Oban job enqueue.
2026-05-06 13:44:14 -05:00
e822bc9986 chore: rate_limit rewrite + on-call redesign plan + handoff_notifications_mode 2026-04-28 12:17:29 -05:00
a8e43b38d6 feat: implement batched SNMP GET for multi-OID requests (#183)
Replace sequential multi-GET with true batched SNMP GET support.
Previously, Client.get_multiple/2 sent N individual SNMP GET PDUs
for N OIDs. Now sends a single PDU with multiple varbinds, reducing
network round-trips by ~80% for multi-OID operations.

Implementation:
- Add get_multiple/3 callback to SnmpBehaviour
- Implement batched GET in Manager using PDU.build_get_request_multi/2
- Update Client.get_multiple/2 to use batched GET with sequential fallback
- Return map format %{oid => {type, value}} for batched results
- Convert to ordered list for backward compatibility
- Handle SNMP exceptions (noSuchObject, noSuchInstance) in result map

Testing:
- Update all test mocks to use get_multiple expectations
- Add individual get stubs for categorize_device_speed and optional fields
- 2249/2251 tests passing (99.87%)

This addresses F1 from LibreNMS feature parity analysis:
"Replace sequential multi-get with batched GET support."

Impact:
- Discovery system info (6 OIDs): 1 SNMP request instead of 6
- Storage polling (3 OIDs/entry): 1 request per entry instead of 3
- Network round-trips reduced by ~80% for all get_multiple operations

Reviewed-on: graham/towerops-web#183
2026-03-26 16:37:34 -05:00
0214c2a100 complete overhaul of snmp engine to C nif 2026-01-30 12:27:12 -06:00
a0ba9285dd
bring in snmpkit 2026-01-23 12:52:17 -06:00
c3303cc194
moar tests 2026-01-06 15:43:54 -06:00
8cbca259fc
snmp bits 2026-01-03 14:41:28 -06:00
ba463dc5a2
init 2025-12-21 11:10:43 -06:00