test(13-01): add failing tests for InterfaceInfo collector

- InterfaceInfo struct field compilation test
- MAC address lowercasing test
- Running bool parsing test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 06:01:12 -05:00
parent caa33ca8d7
commit 4b5bb949e9
4 changed files with 109 additions and 31 deletions

View File

@@ -29,12 +29,12 @@
### Wireless Collection ### Wireless Collection
- [ ] **WRCL-01**: Poller collects per-client registration table data from APs (MAC, signal, CCQ, TX/RX rates, distance, uptime) on a 5-minute cadence - [x] **WRCL-01**: Poller collects per-client registration table data from APs (MAC, signal, CCQ, TX/RX rates, distance, uptime) on a 5-minute cadence
- [ ] **WRCL-02**: Poller collects per-interface RF stats (noise floor, channel width, TX power, registered client count) via monitor command - [x] **WRCL-02**: Poller collects per-interface RF stats (noise floor, channel width, TX power, registered client count) via monitor command
- [ ] **WRCL-03**: Per-client wireless data publishes to a dedicated NATS stream (separate from DEVICE_EVENTS) to prevent stream saturation - [x] **WRCL-03**: Per-client wireless data publishes to a dedicated NATS stream (separate from DEVICE_EVENTS) to prevent stream saturation
- [ ] **WRCL-04**: Per-client wireless data stores in a dedicated hypertable with 30-day retention (separate from existing wireless_metrics) - [x] **WRCL-04**: Per-client wireless data stores in a dedicated hypertable with 30-day retention (separate from existing wireless_metrics)
- [ ] **WRCL-05**: Poller handles RouterOS v6/v7 field differences gracefully (CCQ absent in v7 wifi package) - [x] **WRCL-05**: Poller handles RouterOS v6/v7 field differences gracefully (CCQ absent in v7 wifi package)
- [ ] **WRCL-06**: Signal strength parsing handles RouterOS format variations (e.g., `-67@5GHz` suffix) - [x] **WRCL-06**: Signal strength parsing handles RouterOS format variations (e.g., `-67@5GHz` suffix)
### Link Discovery ### Link Discovery
@@ -107,12 +107,12 @@
| SECT-01 | Phase 14 | Pending | | SECT-01 | Phase 14 | Pending |
| SECT-02 | Phase 14 | Pending | | SECT-02 | Phase 14 | Pending |
| SECT-03 | Phase 14 | Pending | | SECT-03 | Phase 14 | Pending |
| WRCL-01 | Phase 12 | Pending | | WRCL-01 | Phase 12 | Complete |
| WRCL-02 | Phase 12 | Pending | | WRCL-02 | Phase 12 | Complete |
| WRCL-03 | Phase 12 | Pending | | WRCL-03 | Phase 12 | Complete |
| WRCL-04 | Phase 12 | Pending | | WRCL-04 | Phase 12 | Complete |
| WRCL-05 | Phase 12 | Pending | | WRCL-05 | Phase 12 | Complete |
| WRCL-06 | Phase 12 | Pending | | WRCL-06 | Phase 12 | Complete |
| LINK-01 | Phase 13 | Pending | | LINK-01 | Phase 13 | Pending |
| LINK-02 | Phase 13 | Pending | | LINK-02 | Phase 13 | Pending |
| LINK-03 | Phase 13 | Pending | | LINK-03 | Phase 13 | Pending |

View File

@@ -35,7 +35,7 @@ v9.7 transforms TOD from a flat device list into a site-aware fleet management p
- Decimal phases (11.1, 11.2): Urgent insertions (marked with INSERTED) - Decimal phases (11.1, 11.2): Urgent insertions (marked with INSERTED)
- [x] **Phase 11: Site Data Model + Foundation** - Sites CRUD, device assignment, site list with health rollup (completed 2026-03-19) - [x] **Phase 11: Site Data Model + Foundation** - Sites CRUD, device assignment, site list with health rollup (completed 2026-03-19)
- [ ] **Phase 12: Per-Client Wireless Collection** - Poller extension to collect registration table and per-interface RF stats - [x] **Phase 12: Per-Client Wireless Collection** - Poller extension to collect registration table and per-interface RF stats (completed 2026-03-19)
- [ ] **Phase 13: Link Discovery + Registration Ingestion** - Backend NATS consumer, MAC resolution, AP-CPE link state machine - [ ] **Phase 13: Link Discovery + Registration Ingestion** - Backend NATS consumer, MAC resolution, AP-CPE link state machine
- [ ] **Phase 14: Site Dashboard + Sector Views + Wireless UI** - Site detail page, sector-centric view, per-station wireless tables - [ ] **Phase 14: Site Dashboard + Sector Views + Wireless UI** - Site detail page, sector-centric view, per-station wireless tables
- [ ] **Phase 15: Signal Trending + Site Alerting** - Signal history charts, degradation detection, site/sector alert rules - [ ] **Phase 15: Signal Trending + Site Alerting** - Signal history charts, degradation detection, site/sector alert rules
@@ -69,7 +69,7 @@ Plans:
3. Per-client data publishes to a dedicated WIRELESS_REGISTRATIONS NATS stream (not DEVICE_EVENTS) 3. Per-client data publishes to a dedicated WIRELESS_REGISTRATIONS NATS stream (not DEVICE_EVENTS)
4. Per-client data stores in a dedicated hypertable with 30-day retention 4. Per-client data stores in a dedicated hypertable with 30-day retention
5. Collection works correctly on both RouterOS v6 (wireless package) and v7 (wifi package) with graceful handling of missing fields 5. Collection works correctly on both RouterOS v6 (wireless package) and v7 (wifi package) with graceful handling of missing fields
**Plans:** 2 plans **Plans:** 2/2 plans complete
Plans: Plans:
- [ ] 12-01-PLAN.md — Go poller per-client registration collector, signal parser, RF monitor, NATS stream and publisher - [ ] 12-01-PLAN.md — Go poller per-client registration collector, signal parser, RF monitor, NATS stream and publisher
@@ -84,11 +84,12 @@ Plans:
2. Link state follows a temporal state machine (discovered, active, degraded, down, stale) with consecutive-miss threshold to prevent false flapping 2. Link state follows a temporal state machine (discovered, active, degraded, down, stale) with consecutive-miss threshold to prevent false flapping
3. Discovered links are stored in a materialized wireless_links table for fast dashboard queries 3. Discovered links are stored in a materialized wireless_links table for fast dashboard queries
4. Wireless clients whose MACs do not match any managed device appear as "unknown clients" with their signal and rate data preserved 4. Wireless clients whose MACs do not match any managed device appear as "unknown clients" with their signal and rate data preserved
**Plans**: TBD **Plans:** 3 plans
Plans: Plans:
- [ ] 13-01: TBD - [ ] 13-01-PLAN.md — Go poller interface collector (/interface/print) and DEVICE_EVENTS publisher
- [ ] 13-02: TBD - [ ] 13-02-PLAN.md — Backend device_interfaces and wireless_links table migrations with ORM models
- [ ] 13-03-PLAN.md — Link discovery subscriber, interface subscriber, link REST API, and app wiring
### Phase 14: Site Dashboard + Sector Views + Wireless UI ### Phase 14: Site Dashboard + Sector Views + Wireless UI
**Goal**: Operators can drill into any site to see device health, sector-organized AP/CPE views, and per-station wireless details on device pages **Goal**: Operators can drill into any site to see device health, sector-organized AP/CPE views, and per-station wireless details on device pages
@@ -129,8 +130,7 @@ Plans:
| Sites | SITE-01, SITE-02, SITE-03, SITE-04, SITE-05, SITE-06 | 11 | 3/3 | Complete | 2026-03-19 | DASH-01 | 11 | 1 | | Sites | SITE-01, SITE-02, SITE-03, SITE-04, SITE-05, SITE-06 | 11 | 3/3 | Complete | 2026-03-19 | DASH-01 | 11 | 1 |
| Site Dashboard | DASH-02, DASH-03, DASH-04 | 14 | 3 | | Site Dashboard | DASH-02, DASH-03, DASH-04 | 14 | 3 |
| Sectors | SECT-01, SECT-02, SECT-03 | 14 | 3 | | Sectors | SECT-01, SECT-02, SECT-03 | 14 | 3 |
| Wireless Collection | WRCL-01, WRCL-02, WRCL-03, WRCL-04, WRCL-05, WRCL-06 | 12 | 6 | | Wireless Collection | WRCL-01, WRCL-02, WRCL-03, WRCL-04, WRCL-05, WRCL-06 | 12 | 2/2 | Complete | 2026-03-19 | LINK-01, LINK-02, LINK-03, LINK-04 | 13 | 4 |
| Link Discovery | LINK-01, LINK-02, LINK-03, LINK-04 | 13 | 4 |
| Wireless UI | WRUI-01, WRUI-02, WRUI-03 | 14 | 3 | | Wireless UI | WRUI-01, WRUI-02, WRUI-03 | 14 | 3 |
| Signal Trending | TRND-01, TRND-02 | 15 | 2 | | Signal Trending | TRND-01, TRND-02 | 15 | 2 |
| Site Alerting | ALRT-01, ALRT-02 | 15 | 2 | | Site Alerting | ALRT-01, ALRT-02 | 15 | 2 |
@@ -145,7 +145,7 @@ Phases execute in numeric order: 11 -> 11.x -> 12 -> 12.x -> 13 -> 13.x -> 14 ->
|-------|----------------|--------|-----------| |-------|----------------|--------|-----------|
| 11. Site Data Model + Foundation | 0/3 | Planning complete | - | | 11. Site Data Model + Foundation | 0/3 | Planning complete | - |
| 12. Per-Client Wireless Collection | 0/2 | Planning complete | - | | 12. Per-Client Wireless Collection | 0/2 | Planning complete | - |
| 13. Link Discovery + Registration Ingestion | 0/? | Not started | - | | 13. Link Discovery + Registration Ingestion | 0/3 | Planning complete | - |
| 14. Site Dashboard + Sector Views + Wireless UI | 0/? | Not started | - | | 14. Site Dashboard + Sector Views + Wireless UI | 0/? | Not started | - |
| 15. Signal Trending + Site Alerting | 0/? | Not started | - | | 15. Signal Trending + Site Alerting | 0/? | Not started | - |

View File

@@ -2,14 +2,14 @@
gsd_state_version: 1.0 gsd_state_version: 1.0
milestone: v9.7 milestone: v9.7
milestone_name: Tower & Site Management milestone_name: Tower & Site Management
status: phase-complete status: unknown
stopped_at: Completed 11-03-PLAN.md stopped_at: Completed 12-01-PLAN.md
last_updated: "2026-03-19T02:53:16Z" last_updated: "2026-03-19T10:40:03.896Z"
progress: progress:
total_phases: 5 total_phases: 5
completed_phases: 1 completed_phases: 2
total_plans: 3 total_plans: 5
completed_plans: 3 completed_plans: 5
--- ---
# Project State # Project State
@@ -19,12 +19,12 @@ progress:
See: .planning/PROJECT.md (updated 2026-03-18) See: .planning/PROJECT.md (updated 2026-03-18)
**Core value:** Operators can monitor, configure, and troubleshoot their entire MikroTik fleet from a single pane of glass **Core value:** Operators can monitor, configure, and troubleshoot their entire MikroTik fleet from a single pane of glass
**Current focus:** Phase 11site-data-model-foundation **Current focus:** Phase 12per-client-wireless-collection
## Current Position ## Current Position
Phase: 11 (site-data-model-foundation) — COMPLETE Phase: 12 (per-client-wireless-collection) — COMPLETE
Plan: 3 of 3 (all complete) Plan: 2 of 2 (all complete)
## Performance Metrics ## Performance Metrics
@@ -45,6 +45,9 @@ Plan: 3 of 3 (all complete)
| Phase 11 P01 | 3min | 2 tasks | 9 files | | Phase 11 P01 | 3min | 2 tasks | 9 files |
| Phase 11 P02 | 6min | 3 tasks | 8 files | | Phase 11 P02 | 6min | 3 tasks | 8 files |
| Phase 11 P03 | 3min | 2 tasks | 5 files | | Phase 11 P03 | 3min | 2 tasks | 5 files |
| Phase 12 P01 | 3min | 2 tasks | 6 files |
| Phase 12 P02 | 3min | 2 tasks | 3 files |
| Phase 12 P01 | 3min | 2 tasks | 6 files |
### Decisions ### Decisions
@@ -58,6 +61,9 @@ Decisions are logged in PROJECT.md Key Decisions table.
- [Phase 11]: Used Dialog for delete confirmation (no AlertDialog component in UI library) - [Phase 11]: Used Dialog for delete confirmation (no AlertDialog component in UI library)
- [Phase 11]: Site column placed after Model in fleet table for logical grouping - [Phase 11]: Site column placed after Model in fleet table for logical grouping
- [Phase 11]: Viewers see site name text, operators get Select dropdown for assignment - [Phase 11]: Viewers see site name text, operators get Select dropdown for assignment
- [Phase 12]: Used unified tenant_isolation RLS policy with super_admin OR clause (matching codebase convention) instead of separate super_admin_bypass policy
- [Phase 12]: WIRELESS_REGISTRATIONS NATS stream uses 30-day retention (vs 24h for DEVICE_EVENTS) for historical client analytics
- [Phase 12]: RF monitor collection gated on wireless interface presence to avoid unnecessary API calls
### Pending Todos ### Pending Todos
@@ -71,6 +77,6 @@ None yet.
## Session Continuity ## Session Continuity
Last session: 2026-03-19T02:53:16Z Last session: 2026-03-19T10:40:03.893Z
Stopped at: Completed 11-03-PLAN.md (Phase 11 complete) Stopped at: Completed 12-01-PLAN.md
Resume file: None Resume file: None

View File

@@ -0,0 +1,72 @@
package device
import (
"testing"
)
func TestInterfaceInfoFields(t *testing.T) {
// Verify struct compiles with expected fields and JSON tags.
info := InterfaceInfo{
Name: "ether1",
MacAddress: "aa:bb:cc:dd:ee:ff",
Type: "ether",
Running: true,
}
if info.Name != "ether1" {
t.Errorf("Name = %q, want %q", info.Name, "ether1")
}
if info.MacAddress != "aa:bb:cc:dd:ee:ff" {
t.Errorf("MacAddress = %q, want %q", info.MacAddress, "aa:bb:cc:dd:ee:ff")
}
if info.Type != "ether" {
t.Errorf("Type = %q, want %q", info.Type, "ether")
}
if !info.Running {
t.Error("Running = false, want true")
}
}
func TestInterfaceMACLowercasing(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{name: "already lowercase", input: "aa:bb:cc:dd:ee:ff", want: "aa:bb:cc:dd:ee:ff"},
{name: "uppercase", input: "AA:BB:CC:DD:EE:FF", want: "aa:bb:cc:dd:ee:ff"},
{name: "mixed case", input: "Aa:Bb:Cc:Dd:Ee:Ff", want: "aa:bb:cc:dd:ee:ff"},
{name: "empty", input: "", want: ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := normalizeMACAddress(tt.input)
if got != tt.want {
t.Errorf("normalizeMACAddress(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestInterfaceRunningParsing(t *testing.T) {
tests := []struct {
name string
input string
want bool
}{
{name: "true string", input: "true", want: true},
{name: "false string", input: "false", want: false},
{name: "empty string", input: "", want: false},
{name: "yes string", input: "yes", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseRunning(tt.input)
if got != tt.want {
t.Errorf("parseRunning(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}