Compare commits

...

1 Commits

Author SHA1 Message Date
efiten bdf5f647d4 fix(ci): freshen all e2e-fixture observation timestamps (unblocks #1630 reach e2e) (#1747)
## Problem

`test-issue-1630-reach-mobile-e2e.js` has been failing on `master` since
~2026-06-16, in its precondition `pickRepeaterWithReach` ("no repeater
with reach links found in fixture") — not in any of its actual
assertions. The same SHA passed on 06-15 and failed on 06-16 with no
code change, i.e. it tracks the wall clock, not the tree.

## Root cause

`tools/freshen-fixture.sh` shifts `nodes`, `transmissions`, `observers`
and `neighbor_edges` timestamps to ~now, but for
`observations.timestamp` it only rewrote rows where `timestamp = 0 OR
timestamp IS NULL`. Real-timestamped observations stayed frozen at
fixture-capture time.

Per-node reach (`/api/nodes/{pk}/reach?days=N` → `scanReachRows`,
`cmd/server/node_reach.go`) windows on `observations.timestamp >=
sinceEpoch`. ~30 days after the fixture was captured, the newest
observation aged out of the 30-day window, so reach returned no links
and the test could find no repeater with reach.

## Fix

Shift all non-zero `observations.timestamp` forward by the same offset
(preserving relative order), mirroring the other tables in the script.
The offset subquery is uncorrelated, so SQLite evaluates `MAX` once on
the pre-update state (same idiom the existing blocks rely on).

## Verification

Ran the updated script against `test-fixtures/e2e-fixture.db`:

```
BEFORE: newest observation 31 days old  → outside the 30-day reach window
AFTER : newest observation  0 days old, all 500 observations within 30 days
```

CI Playwright on this PR is the end-to-end confirmation. Scope is the CI
fixture helper only — no application code, schema, or runtime behaviour
changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Erwin Fiten <erwin.fiten@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:50:08 +02:00
+12 -1
View File
@@ -23,7 +23,18 @@ UPDATE transmissions SET first_seen = strftime('%Y-%m-%dT%H:%M:%SZ', first_seen,
(SELECT printf('+%d seconds', CAST((julianday('now') - julianday(MAX(first_seen))) * 86400 AS INTEGER)) FROM transmissions)
) WHERE first_seen IS NOT NULL;
-- Sync observations.timestamp (Unix seconds) to match their transmission's freshened first_seen.
-- Shift all real (non-zero) observation timestamps (Unix seconds) forward so the
-- newest is ~now, preserving relative ordering — same approach as the columns
-- above. Per-node reach (/api/nodes/{pk}/reach?days=N) filters on
-- observations.timestamp >= sinceEpoch, so without this the fixture's observations
-- age out of the window ~N days after capture and reach returns no links (the
-- #1630 reach-mobile e2e then can't find a repeater with reach and fails). The
-- subquery is uncorrelated, so SQLite evaluates MAX once on the pre-update state.
UPDATE observations SET timestamp = timestamp +
(SELECT CAST(strftime('%s', 'now') AS INTEGER) - MAX(timestamp) FROM observations WHERE timestamp > 0)
WHERE timestamp > 0;
-- Sync observations.timestamp to match their transmission's freshened first_seen.
-- Observations with timestamp=0 break the SQL since-filter in buildTransmissionWhere.
UPDATE observations SET timestamp = CAST(strftime('%s',
(SELECT first_seen FROM transmissions WHERE id = transmission_id)