trackslash
TRACK-64 P3

Flaky: completion history test depends on Postgres and Go clocks agreeing

0
All issues

Description

Problem

TestGetProjectCompletionHistoryCountsClosedAsCompleted (internal/store/project_completion_history_integration_test.go:86) fails intermittently. Observed on 2026-09-13:

--- FAIL: TestGetProjectCompletionHistoryCountsClosedAsCompleted (9.41s)
    project_completion_history_integration_test.go:101: closed current point = {PeriodStart:2026-09-07 00:00:00 +0000 UTC AsOf:2026-09-13 15:12:51.834473 +0000 UTC Total:0 Completed:0 Rate:0}

It passed on an immediate re-run with no code change.

Cause

The test calls GetProjectCompletionHistory without a Now, so project_completion_history.go:40 falls back to time.Now().UTC() — the Go process clock. The issue's created_at was written by Postgres now() — the container clock. The final point's AsOf is that Go now, and project_completion_history.go:77 drops any issue whose CreatedAt is after AsOf.

When the Postgres clock runs even microseconds ahead of the host clock, the just-created issue sorts after AsOf and is excluded, giving Total:0 instead of Total:1. Docker Desktop's VM clock drifting from the macOS host clock is enough to trigger it.

TestGetProjectCompletionHistoryPoints does not have this problem because it passes an explicit Now. TestGetProjectCompletionHistoryEmptyDefaultAndMissingProject uses the default clock but asserts zeros, so skew cannot change its result.

Proposed direction

Remove the cross-clock comparison from the test rather than widening a tolerance. Either pin created_at with the existing setCompletionIssueCreatedAt helper, or pass an explicit Now sourced from the database so both sides of the comparison come from one clock.

Acceptance criteria

  • The test no longer compares a Postgres-written timestamp against a Go-generated one.
  • It passes under a simulated clock skew where Postgres now() is ahead of the Go process clock.
  • Any sibling test in the file relying on the default-Now path is checked for the same failure mode.
  • No production behavior change; GetProjectCompletionHistory keeps defaulting to time.Now().

Sub-issues

0

Linked issues

0

GitHub

0

No branches or pull requests linked.

Comments

1
Bradley

Cause confirmed by reproduction, not inference. Stamping the issue 5 seconds into the future (emulating a Postgres clock ahead of the Go clock) reproduces the reported failure exactly:

default-clock last point = {PeriodStart:2026-09-14 ... Total:0 Completed:0 Rate:0}   <- the reported failure
pinned last point        = {PeriodStart:2026-04-20 ... Total:1 Completed:1 Rate:100}

Fix (#148):

  • Pinned created_at, the close event, and Now in the failing test — same shape TestGetProjectCompletionHistoryReplaysLifecycleEvents already uses. Nothing reads a wall clock, so no skew reaches it.
  • While checking siblings: TestUIProjectCompletionHistoryVisibleToReadonlyAndPublicReaders (internal/server/ui_project_integration_test.go) has the same defect — it asserts 100% (1 of 1 tickets completed) against handler-dated output, and a skewed clock would either drop the issue or replay its completion away. The handler takes no Now, so its rows are backdated an hour out of the contested window instead. Outside the file this ticket named, but the same bug, so it is fixed here.
  • TestGetProjectCompletionHistoryEmptyDefaultAndMissingProject keeps the default-clock path for coverage. It holds no issues, which is what makes it immune — now stated in a comment so an issue is not added there later.

No production change; the time.Now() default and its coverage are untouched. Both packages pass in full; the two touched tests ran at -count=5.