Skip to content

test(notify): anchor dedupe keys to today — CI is red on every PR because a date aged out - #207

Open
abdulsaheel wants to merge 1 commit into
mainfrom
fix/retention-window-timebomb-tests
Open

test(notify): anchor dedupe keys to today — CI is red on every PR because a date aged out#207
abdulsaheel wants to merge 1 commit into
mainfrom
fix/retention-window-timebomb-tests

Conversation

@abdulsaheel

@abdulsaheel abdulsaheel commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

This is currently blocking CI on every open PR. Nothing is wrong with any of them, and nothing is wrong with lib/ — the calendar advanced.

What happened

notification_dedupe_test builds date-prefixed dedupe keys from a hardcoded 2026-07-23. FiredKeyStore prunes dated flags older than retentionDays (14). While that date was recent the keys stayed inside the window and the guard deduped correctly. Once it aged past 14 days, every key was pruned the instant it was written, so the second and third emit fired again:

same dedupeKey fires the OS notification exactly once
  Expected: <1>
    Actual: <3>

main passed CI on 2026-08-04, when that date was 12 days old. It has been failing ever since the window closed — on the same commit, with no code change and nothing to point at.

Proof before fix

Substituting today's date into the unmodified file on origin/main turns all 15 tests green. That isolates the cause completely: the guard was never broken.

The fix

Anchor to todayLabel() (already imported in this file) plus a small _dayLabelOffset helper for the next-day case, so the keys sit inside the retention window permanently — which is the condition the dedupe guard is actually specified against.

No lib/ change. Full suite on this branch: 1201 passing, 0 failing.

Why it matters beyond these 6 tests

flutter test --concurrency=1 is exactly what test.yml runs. From the moment that window closed, every open PR's CI went red regardless of content. Merging this unblocks all of them.

Follow-up worth considering

Other suites hardcode dates too — ai_briefing_test, day_nav_test, readiness_freeze_test, core_screens_test, sleep_profile_policy_test, db_storage_hygiene_test. Most aren't retention-sensitive, but any test whose subject prunes or windows on age carries the same failure mode latent. A lint or a shared todayLabel()-based fixture helper would close the class.

Also note: fork PRs are currently sitting at action_required — they need "Approve and run workflows" before any of them can show a green check.

Summary by CodeRabbit

  • Tests
    • Updated notification deduplication tests to use dynamically generated dates.
    • Preserved coverage for persistence, permissions, category gating, quiet hours, concurrency, retention, and high-volume alerts.
    • Improved test reliability across different calendar dates.

The 6 failures in notification_dedupe_test are a TIME BOMB, not a regression.
Nothing in the code changed: the calendar did.

The suite builds date-PREFIXED dedupe keys from a hardcoded `2026-07-23`, and
`FiredKeyStore` prunes dated flags older than `retentionDays` (14). While that
date was recent the keys stayed inside the window and the guard deduped
correctly. Once it aged past 14 days, every key was pruned the moment it was
written, so the second and third emit fired again:

    same dedupeKey fires the OS notification exactly once
      Expected: <1>  Actual: <3>

main passed CI on 2026-08-04, when the date was 12 days old. It has been
failing since the window closed, on the same commit, with no code change and
nothing to point at.

Proved it directly before fixing: substituting today's date into the
unmodified file on origin/main turns all 15 tests green.

Fix is to anchor to `todayLabel()` (already imported here) plus a
`_dayLabelOffset` helper for the next-day case, so the keys sit inside the
retention window permanently -- which is the condition the dedupe guard is
actually specified against. No lib/ change; the guard itself was always correct.

Full suite on this branch: 1201 passing, 0 failing.

WHY THIS MATTERS BEYOND THE 6 TESTS: `flutter test --concurrency=1` is exactly
what test.yml runs, so from the moment that window closed EVERY open PR's CI
went red regardless of content. This unblocks them all.

Worth a follow-up: other suites hardcode dates too (ai_briefing_test,
day_nav_test, readiness_freeze_test, core_screens_test,
sleep_profile_policy_test, db_storage_hygiene_test). Most are not
retention-sensitive, but any test whose subject prunes or windows on age has
the same failure mode latent in it.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The notification dedupe tests now generate date labels and dedupe keys from today and tomorrow. Existing coverage remains for persistence, permissions, categories, quiet hours, concurrency, retention, and high-stress alerts.

Changes

Notification dedupe test updates

Layer / File(s) Summary
Dynamic date generation and dedupe coverage
test/notification_dedupe_test.dart
Test helpers generate today- and tomorrow-based dates. All deduplication scenarios use dynamic keys and dates.

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

Possibly related PRs

  • OpenStrap/edge#137: Introduced the notification deduplication test suite updated by this change.

Suggested reviewers: dannymcc

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the test fix and explains that outdated hardcoded dates caused recurring CI failures.
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.

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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 `@test/notification_dedupe_test.dart`:
- Around line 80-87: Update the test label initialization around _today,
_tomorrow, and _dayLabelOffset to capture a single DateTime value, derive both
offsets from that timestamp, and reuse the canonical todayLabel formatter from
lib/data/day_label.dart. Remove the manual formatting in _dayLabelOffset while
preserving the expected today and tomorrow labels.
🪄 Autofix

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 28b68b73-480d-4557-bccc-5a4655fd164b

📥 Commits

Reviewing files that changed from the base of the PR and between d911f60 and c88f589.

📒 Files selected for processing (1)
  • test/notification_dedupe_test.dart

Comment on lines +80 to +87
final String _tomorrow = _dayLabelOffset(1);

String _dayLabelOffset(int days) {
final d = DateTime.now().add(Duration(days: days));
return '${d.year.toString().padLeft(4, '0')}-'
'${d.month.toString().padLeft(2, '0')}-'
'${d.day.toString().padLeft(2, '0')}';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Derive both labels from one timestamp and the canonical formatter.

_dayLabelOffset calls DateTime.now() independently from _today. If initialization crosses local midnight, _tomorrow can skip a calendar day. The manual formatter can also diverge from todayLabel in lib/data/day_label.dart at Line [24].

Capture one DateTime and derive both labels with todayLabel.

Proposed fix
-final String _today = todayLabel();
-final String _tomorrow = _dayLabelOffset(1);
+final _testNow = DateTime.now();
+final String _today = todayLabel(_testNow);
+final String _tomorrow = _dayLabelOffset(_testNow, 1);

-String _dayLabelOffset(int days) {
-  final d = DateTime.now().add(Duration(days: days));
-  return '${d.year.toString().padLeft(4, '0')}-'
-      '${d.month.toString().padLeft(2, '0')}-'
-      '${d.day.toString().padLeft(2, '0')}';
-}
+String _dayLabelOffset(DateTime base, int days) =>
+    todayLabel(DateTime(base.year, base.month, base.day + days));
🤖 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 `@test/notification_dedupe_test.dart` around lines 80 - 87, Update the test
label initialization around _today, _tomorrow, and _dayLabelOffset to capture a
single DateTime value, derive both offsets from that timestamp, and reuse the
canonical todayLabel formatter from lib/data/day_label.dart. Remove the manual
formatting in _dayLabelOffset while preserving the expected today and tomorrow
labels.

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.

1 participant