Skip to content

fix: observation timestamp normalization to float8 - #373

Merged
cyclonite69 merged 3 commits into
masterfrom
fix/observation-timestamp-normalization
Jul 8, 2026
Merged

fix: observation timestamp normalization to float8#373
cyclonite69 merged 3 commits into
masterfrom
fix/observation-timestamp-normalization

Conversation

@cyclonite69

@cyclonite69 cyclonite69 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Overview

Normalizes observation endpoint timestamps to float8 numeric type for JavaScript-safe epoch millisecond values.

Changes

  1. observationService.ts: Cast EXTRACT(EPOCH FROM o.time) * 1000 to float8 to ensure numeric epoch milliseconds in API responses
  2. API_REFERENCE.md: Document that observations[].time is numeric epoch milliseconds
  3. openapi.yaml: Update schema to reflect type: number, format: double
  4. Test assertions: Add type checks for numeric time field in integration and unit tests

Validation

  • All 5009 relevant tests pass
  • Type-check passes (no TypeScript errors)
  • Lint passes (ESLint clean)

Note

This branch previously had a buried Vite build fix (NODE_ENV=production) in the same commit. That fix has been extracted and already landed on master (commit 7917b37). This PR contains only the observation timestamp normalization.


Summary by cubic

Return observation timestamps as numeric epoch milliseconds (float8) across API and WiGLE endpoints to fix JavaScript date handling. Updates schema/docs and tests; removes WiGLE popup debug logs and aligns the WiGLE network route to :bssid.

  • Bug Fixes
    • Cast (EXTRACT(EPOCH FROM time) * 1000)::float8 in all observation queries (core API and WiGLE) to return JSON numbers in ms.
    • Update docs/openapi.yaml and API reference to type: number/format: double; add unit/integration type checks and a deterministic E2E diagnostic; clarify E2E Compose steps.
    • Remove WiGLE popup instrumentation logs; rename network page route param to :bssid (no URL or behavior change).

Written for commit 7de1a0d. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Observation timestamps now consistently return JavaScript-safe numeric epoch milliseconds.
    • WiGLE popup enrichment has cleaner rendering and improved fallback behavior when enrichment fails.
    • WiGLE network page routing now correctly uses the BSSID parameter.
  • Documentation
    • Updated API reference and OpenAPI schema to clarify observations[].time is a JSON number in milliseconds.
    • Expanded E2E run instructions with a canonical compose workflow.
  • Tests
    • Added/updated API and E2E checks to validate timestamp typing and WiGLE “seen” enrichment behavior.

cyclonite01 and others added 2 commits July 8, 2026 09:11
Cast EXTRACT(EPOCH FROM ...) * 1000 to float8 for both WiGLE observation
queries to ensure numeric timestamps are returned to client for proper
temporal rendering in Geospatial Explorer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cast EXTRACT(EPOCH FROM o.time) to float8 before multiplication to ensure
numeric epoch millisecond timestamps in API responses. Update OpenAPI and
API reference documentation to reflect numeric time field. Add focused
test assertions for type correctness in service and integration layers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR normalizes observation timestamps to numeric epoch-millisecond values across server queries, API docs, and OpenAPI schema, renames a WiGLE route parameter to bssid, removes client-side popup instrumentation logs, adds an E2E diagnostic for the popup timestamp path, and updates changelog and metrics docs.

Changes

Observation Timestamp and Route Fixes

Layer / File(s) Summary
Observation time projection changed to float8
server/src/services/observationService.ts, tests/unit/observationService.test.ts, tests/integration/api/v1/observations.test.ts
The time SQL projection in getObservationsByBSSID, getWigleObservationsByBSSID, and getWigleObservationsBatch now casts (EXTRACT(EPOCH ...) * 1000)::float8 instead of ::BIGINT, and tests assert numeric time values and the updated SQL expression.
API docs and OpenAPI schema updated for numeric time
docs/API_REFERENCE.md, docs/openapi.yaml
Documentation and schema for Observation.time are updated from integer/int64 to number/double, describing it as a JS-safe epoch millisecond timestamp.
Route param renamed from netid to bssid
server/src/api/routes/v1/wigle/database.ts
GET /page/network/:netid becomes GET /page/network/:bssid, with the handler using bssid for both MV and fallback lookups.
Client tooltip logging cleanup and e2e diagnostics
client/src/components/wigle/mapHandlers.ts, tests/e2e/wigleTimestampDiag.spec.ts
Instrumentation console.log calls are removed from popup/enrichment tooltip flows, replaced with a consolidated failure warning; a new Playwright spec verifies the rendered "Seen" timestamp and absence of enrichment failures.
Changelog and metrics documentation updates
CHANGELOG.md, docs/metrics/lines-of-code.md, tests/e2e/README.md
Changelog entries document the timestamp fix and dependency reference update, LOC metrics are refreshed, and the e2e README explains the compose workflow to avoid recreating the API container.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • cyclonite69/shadowcheck-web#279: Both PRs refactor the WiGLE observation popup/tooltip rendering to use the shared normalizeTooltipData + renderNetworkTooltip pipeline in mapHandlers.ts.
  • cyclonite69/shadowcheck-web#292: Both PRs modify the WiGLE tooltip/popup rendering path to enrich data via the shared renderNetworkTooltip flow.
  • cyclonite69/shadowcheck-web#293: Both PRs align on WiGLE tooltip/enrichment rendering changes in mapHandlers.ts using renderNetworkTooltip(normalizeTooltipData(...)).
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: normalizing observation timestamps to float8.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/observation-timestamp-normalization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Normalize observation timestamps to float8 epoch milliseconds

🐞 Bug fix 📝 Documentation 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Return observations[].time as a JSON number (float8 epoch milliseconds) for JS-safe timestamps.
• Update OpenAPI + API reference docs to specify numeric time (double).
• Add targeted unit/integration assertions and clean up WiGLE tooltip diagnostics.
Diagram

graph TD
  Client["Explorer UI"] --> ObsAPI["GET /api/observations"] --> ObsSvc["observationService"] --> DB[("Postgres")]
  ObsAPI --> Spec["OpenAPI / API docs"]
  Tests["Unit/Integration tests"] --> ObsSvc
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Configure int8 parsing globally in DB client
  • ➕ Keeps SQL simpler (can continue returning BIGINT)
  • ➕ Centralizes behavior for all endpoints returning int8
  • ➖ Global behavior change can unintentionally affect other endpoints/queries
  • ➖ Still requires validating JSON serialization across the codebase
2. Return timestamps as strings (RFC3339 or numeric string)
  • ➕ Avoids any numeric precision ambiguity across languages
  • ➕ Clear contract for consumers that parse explicitly
  • ➖ Requires client changes and can complicate sorting/formatting logic
  • ➖ Breaks existing numeric consumers if any (contract change)

Recommendation: The PR’s targeted SQL cast to float8 is the lowest-risk, most localized fix: it preserves a numeric JSON contract while avoiding Node/driver int8 stringification pitfalls. The alternatives are viable but either introduce global behavior risk (int8 parser) or increase client/API friction (string timestamps).

Files changed (11) +270 / -71

Bug fix (2) +7 / -7
database.tsRename WiGLE page route param from netid to bssid +4/-4

Rename WiGLE page route param from netid to bssid

• Renames the route path parameter and corresponding handler variable from 'netid' to 'bssid' while preserving the lookup behavior via 'wigleService' calls.

server/src/api/routes/v1/wigle/database.ts

observationService.tsCast extracted epoch-ms timestamps to float8 for JSON numeric output +3/-3

Cast extracted epoch-ms timestamps to float8 for JSON numeric output

• Updates local and WiGLE observation queries to compute '(EXTRACT(EPOCH FROM ...)*1000)::float8' so timestamps serialize as JSON numbers and remain JS-safe for epoch milliseconds.

server/src/services/observationService.ts

Refactor (1) +3 / -55
mapHandlers.tsRemove WiGLE tooltip instrumentation logs and standardize warning message +3/-55

Remove WiGLE tooltip instrumentation logs and standardize warning message

• Strips verbose console logging used for timestamp pipeline diagnostics and replaces the enrichment failure log with a stable, namespaced warning. Keeps tooltip rendering behavior while reducing noise.

client/src/components/wigle/mapHandlers.ts

Tests (3) +185 / -0
wigleTimestampDiag.spec.tsAdd Playwright diagnostic test for WiGLE popup timestamp propagation +182/-0

Add Playwright diagnostic test for WiGLE popup timestamp propagation

• Introduces a diagnostic spec that intercepts the WiGLE enrichment API, captures popup boundary logs, and asserts the rendered 'Seen' value is populated (not '—'). Intended for investigating timestamp propagation issues deterministically.

tests/e2e/wigleTimestampDiag.spec.ts

observations.test.tsAssert observations[].time is a number in API response +1/-0

Assert observations[].time is a number in API response

• Adds a focused integration assertion verifying the first observation's 'time' field is returned as a JavaScript number.

tests/integration/api/v1/observations.test.ts

observationService.test.tsVerify SQL includes float8 epoch-ms cast in observation service +2/-0

Verify SQL includes float8 epoch-ms cast in observation service

• Extends the unit test to assert the generated SQL contains the '(EXTRACT(EPOCH FROM o.time) * 1000)::float8 as time' expression, locking in the intended normalization.

tests/unit/observationService.test.ts

Documentation (5) +75 / -9
CHANGELOG.mdDocument timestamp normalization bug fix in changelog +4/-2

Document timestamp normalization bug fix in changelog

• Bumps the release date and records the API/WiGLE fixes for returning numeric epoch timestamps in observations.

CHANGELOG.md

API_REFERENCE.mdClarify observations.time is numeric epoch milliseconds +3/-0

Clarify observations.time is numeric epoch milliseconds

• Adds explicit documentation that 'observations[].time' is a JavaScript-safe numeric epoch timestamp in milliseconds.

docs/API_REFERENCE.md

lines-of-code.mdRefresh generated LOC metrics snapshot +5/-5

Refresh generated LOC metrics snapshot

• Updates the generated timestamp/SHA and associated cloc totals to reflect the current revision.

docs/metrics/lines-of-code.md

openapi.yamlUpdate Observation.time schema to number/double with ms-epoch description +3/-2

Update Observation.time schema to number/double with ms-epoch description

• Changes 'time' from 'integer(int64)' to 'number(double)' and documents that it is returned as a JSON number in epoch milliseconds.

docs/openapi.yaml

README.mdDocument canonical docker-compose procedure for e2e runs +60/-0

Document canonical docker-compose procedure for e2e runs

• Adds guidance to run e2e tests against the base dev API stack and avoid recreating the API container via the e2e overlay, preventing session loss and misleading 401s.

tests/e2e/README.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
tests/e2e/wigleTimestampDiag.spec.ts (2)

1-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the file header to reflect removed instrumentation.

The header comment says "Captures console logs at every instrumentation boundary to identify exactly where wigle_v3_last_seen stops propagating to the SEEN cell," but the [popup:*] instrumentation logs were removed from mapHandlers.ts in this same PR. The test's log capture is now vestigial for its stated purpose. Consider updating the description to reflect that the test primarily verifies the rendered SEEN timestamp and mocks enrichment, rather than tracing instrumentation boundaries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/wigleTimestampDiag.spec.ts` around lines 1 - 9, Update the header
comment in wigleTimestampDiag.spec.ts to match the current behavior, since the
popup instrumentation referenced by the diagnostic note was removed from
mapHandlers.ts. Revise the description around the test’s purpose so it reflects
that wigleTimestampDiag.spec.ts now verifies the rendered SEEN timestamp and
mocked enrichment flow rather than capturing console logs at instrumentation
boundaries.

141-142: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace waitForTimeout with a deterministic wait.

await page.waitForTimeout(600) is used to let async console.log promise handlers resolve before checking logs. This is non-deterministic and can flake under load. Consider waiting for a specific condition instead, such as waiting for the logs array to reach an expected length or using page.waitForFunction.

♻️ Suggested fix
-  // Let async console.log promises resolve
-  await page.waitForTimeout(600);
+  // Let async console.log promise handlers resolve
+  await page.waitForFunction(
+    () => (window as any).__e2eLogsReady !== false,
+    { timeout: 5000 }
+  ).catch(() => {});

Alternatively, restructure the log capture to resolve a promise when the enrichment warning is received, then await that promise directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/wigleTimestampDiag.spec.ts` around lines 141 - 142, Replace the
non-deterministic `page.waitForTimeout(600)` in `wigleTimestampDiag.spec.ts`
with a deterministic synchronization point. Update the test around the
log-capture flow so it waits on a concrete condition tied to the `logs` array or
a `page.waitForFunction`, or resolve a promise when the expected enrichment
warning is captured and await that promise before asserting.
tests/unit/observationService.test.ts (1)

135-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding float8 SQL assertions for WiGLE observation functions.

The getObservationsByBSSID test now verifies the SQL contains (EXTRACT(EPOCH FROM o.time) * 1000)::float8 as time, but the getWigleObservationsByBSSID and getWigleObservationsBatch test blocks don't have equivalent assertions for their ::float8 cast. Adding them would ensure consistency if the cast is accidentally reverted in one query.

♻️ Suggested additions
 describe('getWigleObservationsByBSSID', () => {
   it('should return enriched WiGLE observations', async () => {
     // ... existing mock setup ...
     const result = await getWigleObservationsByBSSID('AA:BB');
     expect(result).toEqual(mockRows);
+    const sql = (query as jest.Mock).mock.calls[0][0];
+    expect(sql).toContain('(EXTRACT(EPOCH FROM we.time) * 1000)::float8 as time');
     expect(query).toHaveBeenCalledWith(expect.stringContaining('app.wigle_v3_observations'), [
       'AA:BB',
     ]);
   });
 });
 describe('getWigleObservationsBatch', () => {
   it('should return batch of enriched WiGLE observations', async () => {
     // ... existing mock setup ...
     const result = await getWigleObservationsBatch(['AA:BB', 'CC:DD']);
     expect(result).toEqual(mockRows);
+    const sql = (query as jest.Mock).mock.calls[0][0];
+    expect(sql).toContain('(EXTRACT(EPOCH FROM we.time) * 1000)::float8 as time');
     expect(query).toHaveBeenCalledWith(expect.stringContaining('ANY($1)'), [['AA:BB', 'CC:DD']]);
   });
 });

Also applies to: 175-186

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/observationService.test.ts` around lines 135 - 153, Add SQL string
assertions in the getWigleObservationsByBSSID and getWigleObservationsBatch
tests to verify the query includes the same ::float8 cast used in
getObservationsByBSSID, so all WiGLE observation paths are covered consistently.
Update the existing expect(query).toHaveBeenCalledWith checks in
observationService.test.ts to match the relevant SQL built by
getWigleObservationsByBSSID and getWigleObservationsBatch, specifically
asserting the time expression contains (EXTRACT(EPOCH FROM o.time) *
1000)::float8 as time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/api/routes/v1/wigle/database.ts`:
- Around line 73-81: The route handler now uses the updated `:bssid` parameter
in `wigle/database.ts`, but the client-facing and documentation artifacts still
reference `:netid`. Propagate the rename in
`client/src/config/apiTestEndpoints.ts`, `docs/API_REFERENCE.md`,
`docs/api/route-inventory.md`, and the `tests/unit/wigleDatabase.test.ts`
describe block so all endpoint metadata stays consistent with the `page/network`
route and its `macParamMiddleware`/handler contract. Ensure the client config
entry, docs, and test names all match the current route param name used by
`getWiglePageNetworkFromMv` and `getWiglePageNetwork`.

In `@tests/e2e/wigleTimestampDiag.spec.ts`:
- Around line 74-87: The enrichment failure check in wigleTimestampDiag.spec.ts
is filtering for the wrong console prefix, so the warning emitted by
mapHandlers.ts is never recorded in logs. Update the page.on('console') handler
to match the actual warning tag from the enrichment path (the [wigle-tooltip]
enrichment fetch failed message), and keep the structured-args parsing so the
warning payload is captured. Then ensure enrichmentFailed is derived from the
captured warning entry so the assertion at the end reflects real enrichment
failures.

---

Nitpick comments:
In `@tests/e2e/wigleTimestampDiag.spec.ts`:
- Around line 1-9: Update the header comment in wigleTimestampDiag.spec.ts to
match the current behavior, since the popup instrumentation referenced by the
diagnostic note was removed from mapHandlers.ts. Revise the description around
the test’s purpose so it reflects that wigleTimestampDiag.spec.ts now verifies
the rendered SEEN timestamp and mocked enrichment flow rather than capturing
console logs at instrumentation boundaries.
- Around line 141-142: Replace the non-deterministic `page.waitForTimeout(600)`
in `wigleTimestampDiag.spec.ts` with a deterministic synchronization point.
Update the test around the log-capture flow so it waits on a concrete condition
tied to the `logs` array or a `page.waitForFunction`, or resolve a promise when
the expected enrichment warning is captured and await that promise before
asserting.

In `@tests/unit/observationService.test.ts`:
- Around line 135-153: Add SQL string assertions in the
getWigleObservationsByBSSID and getWigleObservationsBatch tests to verify the
query includes the same ::float8 cast used in getObservationsByBSSID, so all
WiGLE observation paths are covered consistently. Update the existing
expect(query).toHaveBeenCalledWith checks in observationService.test.ts to match
the relevant SQL built by getWigleObservationsByBSSID and
getWigleObservationsBatch, specifically asserting the time expression contains
(EXTRACT(EPOCH FROM o.time) * 1000)::float8 as time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83718142-9837-4a84-b788-d5aee5f99870

📥 Commits

Reviewing files that changed from the base of the PR and between aa983f6 and 519bd4a.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • client/src/components/wigle/mapHandlers.ts
  • docs/API_REFERENCE.md
  • docs/metrics/lines-of-code.md
  • docs/openapi.yaml
  • server/src/api/routes/v1/wigle/database.ts
  • server/src/services/observationService.ts
  • tests/e2e/README.md
  • tests/e2e/wigleTimestampDiag.spec.ts
  • tests/integration/api/v1/observations.test.ts
  • tests/unit/observationService.test.ts

Comment on lines +73 to +81
'/page/network/:bssid',
macParamMiddleware,
asyncHandler(async (req: Request, res: Response) => {
const { netid } = req.params;
const { bssid } = req.params;
// Try MV first (single-row read); fall back to live 4-query fan-out if MV
// is unavailable (pre-migration deployment) or returns no row.
let network = await wigleService.getWiglePageNetworkFromMv(netid);
let network = await wigleService.getWiglePageNetworkFromMv(bssid);
if (!network) {
network = await wigleService.getWiglePageNetwork(netid);
network = await wigleService.getWiglePageNetwork(bssid);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Cross-file contract drift: param rename not propagated to client config, docs, or route inventory.

The route param rename from :netid to :bssid is server-internal and doesn't change the URL path, so existing clients continue to work. However, multiple downstream artifacts still reference :netid:

  • client/src/config/apiTestEndpoints.ts:1081-1086 — path and params still use netid
  • docs/API_REFERENCE.md:943-951 — documents :netid
  • docs/api/route-inventory.md:306-310 — lists :netid
  • tests/unit/wigleDatabase.test.ts:52-63 — describe block still uses :netid

Per coding guidelines, every endpoint requires an entry in client/src/config/apiTestEndpoints.ts and a JSDoc comment on the route handler. While the JSDoc was updated, the client config and documentation were not, creating inconsistency that will confuse future maintainers and API consumers.

As per coding guidelines: "Every new endpoint requires: entry in client/src/config/apiTestEndpoints.ts, JSDoc comment on route handler, and note in relevant docs/schema/ file if touching DB schema."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/api/routes/v1/wigle/database.ts` around lines 73 - 81, The route
handler now uses the updated `:bssid` parameter in `wigle/database.ts`, but the
client-facing and documentation artifacts still reference `:netid`. Propagate
the rename in `client/src/config/apiTestEndpoints.ts`, `docs/API_REFERENCE.md`,
`docs/api/route-inventory.md`, and the `tests/unit/wigleDatabase.test.ts`
describe block so all endpoint metadata stays consistent with the `page/network`
route and its `macParamMiddleware`/handler contract. Ensure the client config
entry, docs, and test names all match the current route param name used by
`getWiglePageNetworkFromMv` and `getWiglePageNetwork`.

Source: Coding guidelines

Comment on lines +74 to +87
page.on('console', (msg) => {
const text = msg.text();
if (!text.includes('[popup:')) return;
// Playwright serialises structured console.log args — grab them
const args = msg.args();
// tag is first arg, traceId is second, data object is third
Promise.all(args.map((a) => a.jsonValue().catch(() => String(a)))).then((vals) => {
logs.push({
tag: String(vals[0] ?? ''),
traceId: String(vals[1] ?? ''),
data: vals[2] ?? {},
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enrichment failure detection is broken — the console filter doesn't match the actual warning tag.

The console listener at line 76 filters with if (!text.includes('[popup:')) return;, but the actual enrichment failure warning in mapHandlers.ts (line 125) is console.warn('[wigle-tooltip] enrichment fetch failed', err) — which does not contain [popup:. As a result, the warning is never captured in the logs array, enrichmentFailed at line 167 is always undefined, and the assertion at line 181 (expect(enrichmentFailed, 'Enrichment must not fail').toBeUndefined()) will always pass regardless of whether enrichment actually failed.

This gives false confidence that the test verifies enrichment success.

🐛 Proposed fix — broaden the console filter
   page.on('console', (msg) => {
     const text = msg.text();
-    if (!text.includes('[popup:')) return;
+    if (!text.includes('[popup:') && !text.includes('[wigle-tooltip]')) return;
     // Playwright serialises structured console.log args — grab them
     const args = msg.args();
     // tag is first arg, traceId is second, data object is third
     Promise.all(args.map((a) => a.jsonValue().catch(() => String(a)))).then((vals) => {
       logs.push({
         tag: String(vals[0] ?? ''),
         traceId: String(vals[1] ?? ''),
         data: vals[2] ?? {},
       });
     });
   });

Also applies to: 167-181

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/wigleTimestampDiag.spec.ts` around lines 74 - 87, The enrichment
failure check in wigleTimestampDiag.spec.ts is filtering for the wrong console
prefix, so the warning emitted by mapHandlers.ts is never recorded in logs.
Update the page.on('console') handler to match the actual warning tag from the
enrichment path (the [wigle-tooltip] enrichment fetch failed message), and keep
the structured-args parsing so the warning payload is captured. Then ensure
enrichmentFailed is derived from the captured warning entry so the assertion at
the end reflects real enrichment failures.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files

Confidence score: 3/5

  • In tests/e2e/wigleTimestampDiag.spec.ts, the enrichmentFailed check depends on log data filled by a fire-and-forget async .then() in the console handler, so the assertion can run before logs are populated and intermittently fail or miss real regressions — make the log capture/processing awaitable (or assert via a deterministic signal) before merging.
  • In tests/e2e/wigleTimestampDiag.spec.ts, the console filter only watches [popup: while the warning now emits [wigle-tooltip], so the test may never observe the expected failure path and can pass/fail for the wrong reason — align the filter with current prefixes and replace page.waitForTimeout(600) with a deterministic wait condition to reduce CI flakiness.
  • In server/src/api/routes/v1/wigle/database.ts, renaming the route param from :netid to :bssid without updating client/src/config/apiTestEndpoints.ts, docs/API_REFERENCE.md, docs/api/route-inventory.md, and tests/unit/wigleDat... risks endpoint mismatches (broken tests/callers and stale docs) after merge — update all referenced clients/tests/docs to the same param name before merging.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/e2e/wigleTimestampDiag.spec.ts">

<violation number="1" location="tests/e2e/wigleTimestampDiag.spec.ts:74">
P2: The enrichmentFailed assertion reads a logs array populated by a fire-and-forget async promise chain inside the console event handler. Since the .then() callback isn't awaited before the assertion on line 130, a slow promise resolution (common in CI) would cause logs.find() to miss entries even if a failure event was emitted — resulting in a false-negative pass. Await the promise chain or use page.waitForEvent('console') instead.</violation>

<violation number="2" location="tests/e2e/wigleTimestampDiag.spec.ts:76">
P2: The console filter here only captures messages containing `[popup:`, but the enrichment failure warning was changed to use `[wigle-tooltip]` prefix (in `mapHandlers.ts`). Since the `[wigle-tooltip] enrichment fetch failed` message doesn't pass this filter, it never enters the `logs` array. Consequently, the `enrichmentFailed` check at line 167 will always be `undefined`, and the assertion at line 181 will always pass — even if enrichment actually fails. The filter should also match `[wigle-tooltip]` to detect real failures.</violation>

<violation number="3" location="tests/e2e/wigleTimestampDiag.spec.ts:142">
P2: Using page.waitForTimeout(600) instead of a deterministic wait condition introduces flakiness (too short under CI load, unnecessarily slow otherwise) and deviates from the repo convention of waiting for visible elements or text. Replace with a targeted condition — for example, await a specific log count or the presence of a known log entry.</violation>
</file>

<file name="server/src/api/routes/v1/wigle/database.ts">

<violation number="1" location="server/src/api/routes/v1/wigle/database.ts:73">
P2: The route param was renamed from `:netid` to `:bssid` here, but the corresponding references in `client/src/config/apiTestEndpoints.ts`, `docs/API_REFERENCE.md`, `docs/api/route-inventory.md`, and `tests/unit/wigleDatabase.test.ts` still use `:netid`. While the URL path itself is unchanged (so clients still work), the inconsistency across the codebase will confuse future maintainers and may cause test/config mismatches if someone searches by param name.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as Client (Browser)
    participant Router as API Router v1
    participant ObsSvc as Observation Service
    participant DB as PostgreSQL
    participant Frontend as Frontend Map Handlers

    Note over Client,Frontend: CHANGED: Timestamp normalized to float8 numeric ms

    alt Core network observations
        Client->>Router: GET /api/networks/:bssid/observations
        Router->>ObsSvc: getObservationsByBSSID(bssid, lat, lon)
        ObsSvc->>DB: SELECT ... (EXTRACT(EPOCH FROM o.time)*1000)::float8 as time ...
        DB-->>ObsSvc: Rows with time as float8 (e.g. 1.72e12)
        ObsSvc-->>Router: { observations: [{ time: 1720000000000 }, ...] }
        Router-->>Client: JSON response (time is numeric)
    else WiGLE observations (popup enrichment)
        Client->>Router: GET /api/wigle/page/network/:bssid
        Note over Router: Route param renamed to :bssid (no URL change)
        Router->>ObsSvc: getWiglePageNetwork(bssid)
        opt If materialized view returns no row
            ObsSvc->>ObsSvc: Fallback to getWiglePageNetwork(bssid)
        end
        ObsSvc->>DB: SELECT ... (EXTRACT(EPOCH FROM we.time)*1000)::float8 as time ...
        DB-->>ObsSvc: Rows with time as float8
        ObsSvc->>ObsSvc: getWigleObservationsBatch/ByBSSID (same cast)
        ObsSvc-->>Router: Enrichment data (wigle_v3_last_seen, wigle_v2_lasttime as float8)
        Router-->>Client: { wigle: { wigle_v3_last_seen: 1720000000000, ... } }
        Client->>Frontend: normalizeTooltipData(mergedData)
        Frontend-->>Client: Render popup with numeric timestamps
    end

    Note over Client,Router: CHANGED: Removed [popup:*] debug console.log calls
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

await expect(page.locator('.sc-popup')).toContainText('42', { timeout: 8000 });

// Let async console.log promises resolve
await page.waitForTimeout(600);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using page.waitForTimeout(600) instead of a deterministic wait condition introduces flakiness (too short under CI load, unnecessarily slow otherwise) and deviates from the repo convention of waiting for visible elements or text. Replace with a targeted condition — for example, await a specific log count or the presence of a known log entry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/e2e/wigleTimestampDiag.spec.ts, line 142:

<comment>Using page.waitForTimeout(600) instead of a deterministic wait condition introduces flakiness (too short under CI load, unnecessarily slow otherwise) and deviates from the repo convention of waiting for visible elements or text. Replace with a targeted condition — for example, await a specific log count or the presence of a known log entry.</comment>

<file context>
@@ -0,0 +1,182 @@
+  await expect(page.locator('.sc-popup')).toContainText('42', { timeout: 8000 });
+
+  // Let async console.log promises resolve
+  await page.waitForTimeout(600);
+
+  // Expand the Timestamps <details> section if present
</file context>

// Collect every [popup:*] console message in order
const logs: { tag: string; traceId: string; data: any }[] = [];

page.on('console', (msg) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The enrichmentFailed assertion reads a logs array populated by a fire-and-forget async promise chain inside the console event handler. Since the .then() callback isn't awaited before the assertion on line 130, a slow promise resolution (common in CI) would cause logs.find() to miss entries even if a failure event was emitted — resulting in a false-negative pass. Await the promise chain or use page.waitForEvent('console') instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/e2e/wigleTimestampDiag.spec.ts, line 74:

<comment>The enrichmentFailed assertion reads a logs array populated by a fire-and-forget async promise chain inside the console event handler. Since the .then() callback isn't awaited before the assertion on line 130, a slow promise resolution (common in CI) would cause logs.find() to miss entries even if a failure event was emitted — resulting in a false-negative pass. Await the promise chain or use page.waitForEvent('console') instead.</comment>

<file context>
@@ -0,0 +1,182 @@
+  // Collect every [popup:*] console message in order
+  const logs: { tag: string; traceId: string; data: any }[] = [];
+
+  page.on('console', (msg) => {
+    const text = msg.text();
+    if (!text.includes('[popup:')) return;
</file context>


page.on('console', (msg) => {
const text = msg.text();
if (!text.includes('[popup:')) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The console filter here only captures messages containing [popup:, but the enrichment failure warning was changed to use [wigle-tooltip] prefix (in mapHandlers.ts). Since the [wigle-tooltip] enrichment fetch failed message doesn't pass this filter, it never enters the logs array. Consequently, the enrichmentFailed check at line 167 will always be undefined, and the assertion at line 181 will always pass — even if enrichment actually fails. The filter should also match [wigle-tooltip] to detect real failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/e2e/wigleTimestampDiag.spec.ts, line 76:

<comment>The console filter here only captures messages containing `[popup:`, but the enrichment failure warning was changed to use `[wigle-tooltip]` prefix (in `mapHandlers.ts`). Since the `[wigle-tooltip] enrichment fetch failed` message doesn't pass this filter, it never enters the `logs` array. Consequently, the `enrichmentFailed` check at line 167 will always be `undefined`, and the assertion at line 181 will always pass — even if enrichment actually fails. The filter should also match `[wigle-tooltip]` to detect real failures.</comment>

<file context>
@@ -0,0 +1,182 @@
+
+  page.on('console', (msg) => {
+    const text = msg.text();
+    if (!text.includes('[popup:')) return;
+    // Playwright serialises structured console.log args — grab them
+    const args = msg.args();
</file context>
Suggested change
if (!text.includes('[popup:')) return;
if (!text.includes('[popup:') && !text.includes('[wigle-tooltip]')) return;

*/
router.get(
'/page/network/:netid',
'/page/network/:bssid',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The route param was renamed from :netid to :bssid here, but the corresponding references in client/src/config/apiTestEndpoints.ts, docs/API_REFERENCE.md, docs/api/route-inventory.md, and tests/unit/wigleDatabase.test.ts still use :netid. While the URL path itself is unchanged (so clients still work), the inconsistency across the codebase will confuse future maintainers and may cause test/config mismatches if someone searches by param name.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/api/routes/v1/wigle/database.ts, line 73:

<comment>The route param was renamed from `:netid` to `:bssid` here, but the corresponding references in `client/src/config/apiTestEndpoints.ts`, `docs/API_REFERENCE.md`, `docs/api/route-inventory.md`, and `tests/unit/wigleDatabase.test.ts` still use `:netid`. While the URL path itself is unchanged (so clients still work), the inconsistency across the codebase will confuse future maintainers and may cause test/config mismatches if someone searches by param name.</comment>

<file context>
@@ -70,15 +70,15 @@ const validateWigleNetworksQuery = validateQuery({
  */
 router.get(
-  '/page/network/:netid',
+  '/page/network/:bssid',
   macParamMiddleware,
   asyncHandler(async (req: Request, res: Response) => {
</file context>

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 232 rules

Grey Divider


Action required

1. API_ENDPOINTS still uses :netid 📘 Rule violation ≡ Correctness
Description
The WiGLE DB page route parameter was renamed from :netid to :bssid, but the Admin API Testing
endpoint registry/preset still references the old :netid path. This leaves the testing UI out of
sync with the backend and causes Bulk Endpoint Verification to generate non-matching/invalid URLs
that consistently fail MAC validation, violating the compliance requirement to register modified API
routes.
Code

server/src/api/routes/v1/wigle/database.ts[R73-81]

+  '/page/network/:bssid',
  macParamMiddleware,
  asyncHandler(async (req: Request, res: Response) => {
-    const { netid } = req.params;
+    const { bssid } = req.params;
    // Try MV first (single-row read); fall back to live 4-query fan-out if MV
    // is unavailable (pre-migration deployment) or returns no row.
-    let network = await wigleService.getWiglePageNetworkFromMv(netid);
+    let network = await wigleService.getWiglePageNetworkFromMv(bssid);
    if (!network) {
-      network = await wigleService.getWiglePageNetwork(netid);
+      network = await wigleService.getWiglePageNetwork(bssid);
Evidence
The PR updates the v1 WiGLE database router route from /page/network/:netid to
/page/network/:bssid, and the request is guarded by macParamMiddleware, which explicitly reads
and validates req.params.bssid, proving the backend now expects a bssid path param. However, the
Admin API Testing registry still defines the endpoint as /api/wigle/page/network/:netid, so the
tester uses a placeholder name that no longer matches the implemented route; in bulk verification,
the missing FALLBACK_PARAMS.netid causes the tester to substitute '1', yielding URLs like
/api/wigle/page/network/1 that fail MAC validation and produce false-negative failures for that
preset.

Rule 416744: Register new API endpoints in apiTestEndpoints config
server/src/api/routes/v1/wigle/database.ts[72-82]
client/src/config/apiTestEndpoints.ts[1080-1086]
server/src/api/routes/v1/wigle/database.ts[72-87]
server/src/validation/middleware.ts[226-240]
client/src/config/apiTestEndpoints.ts[1074-1086]
client/src/components/admin/hooks/useApiTesting.ts[220-267]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The WiGLE DB Page endpoint preset in the Admin API Testing registry is out of sync with the backend: the backend route now uses `:bssid` and the MAC validation middleware reads `req.params.bssid`, but the registry/preset still uses `:netid`. This mismatch breaks the compliance requirement to register modified API routes and makes Bulk Endpoint Verification generate invalid URLs (often substituting `'1'`) that fail MAC validation and report false failures.

## Issue Context
- Backend route changed to `/api/wigle/page/network/:bssid` (previously `:netid`).
- `macParamMiddleware` validates `req.params.bssid`, so requests using `:netid` placeholders won’t map correctly.
- Admin bulk tester fills missing path params via `FALLBACK_PARAMS[paramName] || '1'`; because the preset still uses `netid`, it can fall back to `'1'`, producing `/api/wigle/page/network/1` and consistent 400 failures from MAC validation.

## Fix Focus Areas
- server/src/api/routes/v1/wigle/database.ts[72-82]
- client/src/config/apiTestEndpoints.ts[1074-1086]
- client/src/components/admin/hooks/useApiTesting.ts[220-267]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +73 to +81
'/page/network/:bssid',
macParamMiddleware,
asyncHandler(async (req: Request, res: Response) => {
const { netid } = req.params;
const { bssid } = req.params;
// Try MV first (single-row read); fall back to live 4-query fan-out if MV
// is unavailable (pre-migration deployment) or returns no row.
let network = await wigleService.getWiglePageNetworkFromMv(netid);
let network = await wigleService.getWiglePageNetworkFromMv(bssid);
if (!network) {
network = await wigleService.getWiglePageNetwork(netid);
network = await wigleService.getWiglePageNetwork(bssid);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. api_endpoints still uses :netid 📘 Rule violation ≡ Correctness

The WiGLE DB page route parameter was renamed from :netid to :bssid, but the Admin API Testing
endpoint registry/preset still references the old :netid path. This leaves the testing UI out of
sync with the backend and causes Bulk Endpoint Verification to generate non-matching/invalid URLs
that consistently fail MAC validation, violating the compliance requirement to register modified API
routes.
Agent Prompt
## Issue description
The WiGLE DB Page endpoint preset in the Admin API Testing registry is out of sync with the backend: the backend route now uses `:bssid` and the MAC validation middleware reads `req.params.bssid`, but the registry/preset still uses `:netid`. This mismatch breaks the compliance requirement to register modified API routes and makes Bulk Endpoint Verification generate invalid URLs (often substituting `'1'`) that fail MAC validation and report false failures.

## Issue Context
- Backend route changed to `/api/wigle/page/network/:bssid` (previously `:netid`).
- `macParamMiddleware` validates `req.params.bssid`, so requests using `:netid` placeholders won’t map correctly.
- Admin bulk tester fills missing path params via `FALLBACK_PARAMS[paramName] || '1'`; because the preset still uses `netid`, it can fall back to `'1'`, producing `/api/wigle/page/network/1` and consistent 400 failures from MAC validation.

## Fix Focus Areas
- server/src/api/routes/v1/wigle/database.ts[72-82]
- client/src/config/apiTestEndpoints.ts[1074-1086]
- client/src/components/admin/hooks/useApiTesting.ts[220-267]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@cyclonite69 cyclonite69 closed this Jul 8, 2026
@cyclonite69
cyclonite69 merged commit f0491d4 into master Jul 8, 2026
5 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants