Skip to content

test: fix flaky unit tests that compare time-dependent fields - #2800

Open
kimurakazuhiro-c wants to merge 9 commits into
mainfrom
fix/flaky-time-dependent-tests
Open

test: fix flaky unit tests that compare time-dependent fields#2800
kimurakazuhiro-c wants to merge 9 commits into
mainfrom
fix/flaky-time-dependent-tests

Conversation

@kimurakazuhiro-c

@kimurakazuhiro-c kimurakazuhiro-c commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2793, Related #1023

What this PR does

Removes the flakiness from unit tests that compared server-stamped time fields (CreatedAt / UpdatedAt / RequestedAt / StartAt / StoppedAt) against a time.Now() captured in the test setup. Covers all locations listed in #2793: autoops, feature, subscription, account, experiment, and api.

Background / Why this PR is needed

These tests built their expected value with time.Now().Unix() before invoking the code under test, and the code under test stamps its own time.Now().Unix(). The two values are equal only when no second boundary is crossed in between, so the tests failed intermittently in CI — the original report was #1023 for TestGrpcGetEvaluationsValidation, and pkg/api/api/api_test.go even carried a // FIXME: This is a flaky test. CreateAt may not be equal ocasionally. comment acknowledging the problem — the assertion was left active, so the test kept failing intermittently.

For TestUpdateExperiment the window was worse than a single call: now was captured once at the top of the function and reused across every table case, so the whole test function was the race window.

Points

  • Two fix shapes are used depending on what the test asserts:
    • Field-by-field tests (autoops, feature, account, experiment): the exact comparison is replaced with assert.InDelta(t, time.Now().Unix(), actual, 5).
    • Whole-struct comparisons (subscription, api): the stamped field is asserted with InDelta first, then copied into expected so every other field is still compared exactly. This keeps the strict assert.Equal on the full struct rather than weakening it.
  • Cases that must NOT stamp the field keep their exact comparison. TestChangeRulesOrder and TestUpdateExperiment express this with an explicit flag (expectUpdatedAtNow, expectStartAtNow, expectStoppedAtNow) so the error paths still verify the timestamp is left untouched — a behaviour the old expectedUpdateAt: f.UpdatedAt form was checking, and it is preserved.
  • assert.GreaterOrEqual(t, aor.UpdatedAt, aor.CreatedAt) is added in TestNewAutoOpsRule because AddDatetimeClause / AddOpsEventRateClause re-stamp UpdatedAt inside the constructor, so the ordering is a real invariant worth asserting.
  • In pkg/account/domain/account_test.go the fixture UpdatedAt was never actually reaching the account under test — UpdatedAt is not an argument of NewAccountV2 — so the old assertion only passed by coincidence. The field is dropped from the fixtures.
  • The // FIXME: This is a flaky test comment in TestGetEvaluationsValidation is resolved at its source: CreatedAt is dropped from the emptyUserEvaluationsForREST fixture and asserted with assertEvaluationsCreatedAtNowForREST, so the full-struct assert.Equal stays strict and is no longer time-dependent. The comment is removed.
  • No production code is touched; this PR is test-only.

s35560 and others added 8 commits September 1, 2026 17:01
…edAt

NewAutoOpsRule stamps CreatedAt and UpdatedAt at call time, so comparing
them with values built before the call fails whenever the second boundary
is crossed. Assert both are close to now instead, and check that UpdatedAt
is not older than CreatedAt because AddDatetimeClause and
AddOpsEventRateClause re-stamp UpdatedAt inside the constructor.

Refs #2793

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChangeRulesOrder stamps UpdatedAt at call time, so comparing it with a
value built before the call fails whenever the second boundary is crossed.
Replace the expected timestamp with a flag describing the expected
behaviour: the success case asserts UpdatedAt is close to now, and the
error cases assert it is left untouched.

Refs #2793

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
UpdateSubscription stamps UpdatedAt at call time, so the whole-struct
comparison fails whenever the second boundary is crossed between building
the expectation and running the call. Assert the actual UpdatedAt is close
to now, then copy it into the expectation so every other field is still
compared exactly.

Refs #2793

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fixture stamped UpdatedAt with time.Now() and the tests compared it
with the value NewAccountV2 stamps at call time, so they failed whenever
the second boundary was crossed. UpdatedAt is not an argument of
NewAccountV2, so the fixture field never reached the account under test
and the comparison only passed by coincidence.

Drop the field from the fixtures and assert the account under test has
UpdatedAt close to now, which is what AddSearchFilter and the other search
filter operations actually guarantee.

Refs #2793

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…oppedAt

Update stamps StartAt and StoppedAt at call time, and the test captured its
own now at the top of the function, so the comparison failed whenever the
second boundary was crossed. The flakiness window was the whole test
function rather than a single call.

Describe the expected behaviour with flags instead: the cases that trigger
a status transition assert the stamped field is close to now, and the
remaining cases keep the exact comparison that verifies the field is left
untouched. StopAt is not stamped by Update, so it is unchanged.

Refs #2793

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Server-stamped timestamps were compared against a time captured in the
test setup, so the assertions failed whenever the second boundary was
crossed. Assert those fields against the current time with a tolerance,
then copy the actual value into expected for the full-struct comparison.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In account_test.go the InDelta assertions could not fail: NewAccountV2
and the setup AddSearchFilter calls already stamp UpdatedAt with the
current time, so removing the stamp from the mutator under test still
passed. Backdate UpdatedAt before the call and branch on whether the
mutator is expected to stamp it, which also covers the error paths
leaving the field untouched.

In TestChangeRulesOrder the expected value was captured once before the
loop over a shared, mutated feature, so the assertion only held because
the stamping case happened to be last. Read the previous value inside
the loop instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All three patterns in TestAddSearchFilter have at least one expected
filter, so `len(p.expectedFilters) > 0` was always true and the else
branch was never exercised. Drop the branch and keep the plain
InDelta assertion; backdateUpdatedAt is still called so the assertion
stays falsifiable if AddSearchFilter ever stops stamping UpdatedAt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kimurakazuhiro-c
kimurakazuhiro-c marked this pull request as ready for review September 3, 2026 02:14
@t-kikuc
t-kikuc requested a balanced review from Copilot September 3, 2026 03:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The test-only changes resolve flaky timestamp assertions without weakening other checks.

Pull request overview

Stabilizes flaky unit tests that compare server-generated timestamps. No production code is changed.

Changes:

  • Uses bounded assertions for generated timestamps.
  • Preserves exact checks for unchanged timestamps and other fields.
  • Normalizes timestamps before whole-structure comparisons.
File summaries
File Description
pkg/subscription/domain/subscription_test.go Stabilizes UpdatedAt assertions.
pkg/feature/domain/feature_test.go Handles conditional UpdatedAt stamping.
pkg/experiment/domain/experiment_test.go Stabilizes start and stop timestamp checks.
pkg/autoops/domain/auto_ops_rule_test.go Validates generated timestamps and their ordering.
pkg/api/api/api_test.go Stabilizes REST evaluation timestamps.
pkg/api/api/api_grpc_test.go Stabilizes gRPC evaluation and request timestamps.
pkg/account/domain/account_test.go Verifies successful updates and error-path timestamp preservation.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…ent-tests

# Conflicts:
#	pkg/api/api/api_grpc_test.go
#	pkg/api/api/api_test.go
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.

Fix flaky unit tests caused by time-dependent fields (CreatedAt/UpdatedAt)

2 participants