Skip to content

test(habits): cover end date reactivation allowance in handler - #759

Merged
thomasluizon merged 1 commit into
mainfrom
thomasluizon/ticket-262-reactivation-gate-test
Oct 19, 2026
Merged

thomasluizon merged 1 commit into
mainfrom
thomasluizon/ticket-262-reactivation-gate-test

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Closes thomasluizon/orbit-tickets#262 (ORB-256).

End-date reactivation had helper coverage without exercising the handler's allowance wiring. Add five cases in tests/Orbit.Application.Tests/Commands/Habits/UpdateHabitCommandHandlerTests.cs, using the real handler and domain entities with a stubbed allowance port:

  • Clearing and extending the end date each return the exact refused result, preserve completion, end date, due date, title, description and update timestamp, and do not save.
  • Both requests reactivate and save when allowance is granted, with exactly one allowance call.
  • A title update without reactivation succeeds and records no allowance call, even with a refusing stub configured.

The fixture completes a recurring root habit through Habit.Log. Stubbing the port keeps these tests independent of account plans, configured ceilings and message copy. No production behavior changed, and no defect was exposed. The build regenerated src/Orbit.Api/openapi.json without a diff.

Contract confirmed

Read the production source at starting head e2909b6f: UpdateHabitCommandHandler.Handle uses HabitCeilingLock.ExecuteEntryAsync and HabitLiveRootEntry.FromUpdate. The allowance is Orbit.Domain.Interfaces.IPayGateService.CanCreateHabits(userId, 1, cancellationToken). PayGateService returns Result.Failure(ErrorMessages.HabitLimitReached.Format(maxHabits)); the error is backed by ErrorCodes.HabitLimitReached (HABIT_LIMIT_REACHED). The refusal assertions use that constant and do not assert message text.

PayGateServiceTests.EndDateReactivation_RequiresLiveRootAllowance already has the requested neutral name and arrangement, so that file needs no change. No new external interface fields are read.

Assumptions

  • Follow the final verification-budget instruction to leave mutation testing to CI, rather than the earlier instruction to remove and restore the end-date branch locally.

Test evidence

All commands below exited with code 0:

dotnet build Orbit.slnx
19 warnings, 0 errors

dotnet test tests/Orbit.Application.Tests --no-build --filter 'FullyQualifiedName~UpdateHabitCommandHandlerTests|FullyQualifiedName~EndDateReactivation_RequiresLiveRootAllowance'
Passed: 44, failed: 0, skipped: 0

dotnet test tests/Orbit.Domain.Tests --no-build
Passed: 736, failed: 0, skipped: 0

dotnet test tests/Orbit.Application.Tests --no-build
Passed: 5569, failed: 0, skipped: 0

dotnet test tests/Orbit.Infrastructure.Tests --no-build
Passed: 3601, failed: 0, skipped: 0

dotnet test tests/Orbit.Analyzers.Tests --no-build
Passed: 32, failed: 0, skipped: 0

Build and focused tests passed before committing; the broader suites passed after the commit. The normal pre-commit formatting, dash, timelessness, root allowlist and suppression checks passed. The pre-push branch protection hook also passed.

Mutation verification is deferred to CI as required by the final work-order note. No local mutation was run, so there is no unchanged-test result with the branch removed, strengthened-test failure, or restored-branch output to report. The removed-gate regression has not been observed locally.

@thomasluizon

Copy link
Copy Markdown
Owner Author

Guard proof by the orchestrator (the removed-gate run the body says was deferred): at head 7cb806fa, replacing the reactivation predicate in UpdateHabitCommand.cs:47 (state => HabitLiveRootEntry.FromUpdate(state.Habit, state.Update)) with state => false and running dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~UpdateHabitCommandHandlerTests' exits 1: 5 failed, 37 passed. The four new end-date cases (refused and granted, clear and extend) fail, plus the existing Handle_IsGeneralReactivationAtCeiling_IsRefusedWithoutMutation; the new title-only case passes as it should. The file was restored with git checkout and nothing was committed.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes Reviewed the handler-level end-date reactivation tests and traced their assertions through the allowance gate and domain completion rules.

  • Refused reactivation: Clearing and extending the end date both preserve the completed habit's state, return the exact allowance refusal, and do not save.
  • Granted reactivation: Both requests reactivate the habit, apply the requested edits, and call allowance and save exactly once.
  • Non-reactivating edit: A title-only update succeeds without consulting the refusing allowance stub.
  • Verification: All 44 focused handler and end-date allowance tests passed; no production code or API contracts changed. Full-solution and mutation testing were not run during this review.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit 16203d9 into main Oct 19, 2026
21 checks passed
@thomasluizon
thomasluizon deleted the thomasluizon/ticket-262-reactivation-gate-test branch October 19, 2026 00:57
thomasluizon added a commit that referenced this pull request Oct 19, 2026
…, slip contract and the Supabase sign-in removal (#768)

* Stop Stripe and Play renewals when account deletion is confirmed (#746)

* fix: stop subscription renewal when account deletion is confirmed

* fix: restrict renewal restoration to an unexpired paid period

* fix: keep free account deletion independent of billing services

* fix: bound billing cancellation delays during account erasure

* fix: preserve renewal intent during deletion reconciliation

(cherry picked from commit 85d8824)

* docs: name Render web services in version floor preconditions (#755)

(cherry picked from commit 21615aa)

* fix(infra): use Render for production database connections (#754)

(cherry picked from commit e2909b6)

* test(habits): cover end date reactivation allowance through handler (#759)

(cherry picked from commit 16203d9)

* test(mcp): verify Pro access for OAuth and API keys (#760)

(cherry picked from commit 0a90f67)

* Cover calendar auto-sync scheduling and import reconciliation (ORB-199) (#761)

* test(calendar): cover auto-sync scheduler eligibility and batching

* test(calendar): cover import suggestion reconciliation filters

* test(calendar): isolate free plan eligibility from expiry

(cherry picked from commit 664c756)

* Clarify controller action cross-references in the agent catalog (#762)

(cherry picked from commit d9cda52)

* refactor(calendar): share legacy match key for sync and suggestions (#763)

(cherry picked from commit 8e754c4)

* Contract habit log slip classification schema (#718) (#764)

(cherry picked from commit b0d3ff4)

* fix(calendar): gate recurring event reconciliation (#765)

(cherry picked from commit c900dba)

* Gate rolling schema contractions behind deployment barriers (#766)

* feat: require deployment barriers for rolling schema contractions

* fix: keep quoted SQL comment markers from hiding contractions

* test: record the deployed habit log contract barrier

* ci: run the rolling contract gate only when its inputs change

The gate builds the infrastructure test project, which costs minutes on every
pull request. It now runs only when a migration, the barrier file or the gate
itself changes; the step before it already reads the same diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: require barriers for unclassified ALTER TABLE actions

* test: isolate barrier unit coverage from checkout history

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 626c525)

* fix: require a session claim on access tokens (#767)

(cherry picked from commit 517ed1b)

* Deprecate legacy Google auth routes (#1528, part 1) (#753)

* Deprecate legacy Google auth routes for #1528

* Explain deprecated Google auth routes and annotate legacy tests

(cherry picked from commit 21f810f)

* Remove deleted Supabase sign-in path (#1528) (#752)

* Remove deleted Supabase sign-in path (#1528)

* Remove unused access-token validation catalog entries (#1528)

(cherry picked from commit 141a4a2)

* fix: bind carried schema barrier to the deployed redesign

Record the observed staging deployment and its drained predecessors without changing the migration gate. Regenerate the tracked gating matrix for the combined source. Refs thomasluizon/orbit-tickets#746.

* fix: retain deletion cancellation ownership across provider retries (#770)

* fix: persist deletion cancellation ownership before provider updates

* test: cover deletion intent commit and account changes

* fix: identify deletion-owned Stripe cancellations

Reconcile provider metadata before restoring renewal or retaining ownership, reject open cancellation transactions, and log skipped erasure rechecks.

Refs thomasluizon/orbit-tickets#1547

(cherry picked from commit a7d1cf2)

* test: drop the deprecation assertions the legacy Google route removal supersedes

The base merge brought #769's assertions that POST /api/Auth/google and
POST /api/Auth/operations/google stay available and deprecated. This carry
removes both routes (main's #1528 part 2), so those assertions no longer
have a subject.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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