Skip to content

Manual Test Plan — Projection & subscription actions

Covers the write surface: pause, restart, rebuild, rewind. Long-running rebuild scenarios are in the dedicated long-running rebuilds plan; this one stays at the second-or-two scale.

A-1 — Pause a running projection

FieldValue
Setupdotnet run --project src/BffHost (Full scenario). Wait for TripService and TripPublisher to be Running. Confirm TripProjection shows Updated action and an advancing sequence on the Projections page.
ActionOn the Projection Detail page, click Pause. Confirm the operator confirmation modal.
Expected observationWithin 1–2 seconds the projection's state badge changes to Paused, the agent status pill turns amber, the Pause Reason field populates (operator-driven if the extended-progression-tracking columns are enabled). The sequence freezes at the value it had when paused. The Pause button is replaced by Restart.
How to verifyUI: el-tag:has-text("Paused") on the projection's row. The agent_status column in mt_event_progression shows Paused. API: service.shardStates[<shardName>].action === 'Paused'. SQL: SELECT name, agent_status, pause_reason FROM trips.mt_event_progression WHERE name = 'Trip:All';

A-2 — Restart a paused projection

FieldValue
SetupContinue from A-1 with TripProjection paused.
ActionClick Restart.
Expected observationState badge transitions Paused → Updated within ~1 second. Sequence resumes advancing from where it paused — no replay. Agent status returns to Running. Sparkline shows a flat segment during the pause then resumes the climb.
How to verifyUI: state badge change. API: service.shardStates[<shardName>].action === 'Updated'. SQL: agent_status column flips back to Running. The first post-restart sequence value should equal the last pre-pause value (no gap, no replay).

A-3 — Rebuild a small projection from scratch

FieldValue
SetupFull scenario. Confirm DistanceProjection (smallest of the Trip projections) shows Updated and an established sequence ≥ ~50 from prior publisher activity.
ActionClick Rebuild on the Distance projection's detail page. Confirm the (heavy) operator confirmation modal.
Expected observationProjection's state badge changes through RebuildingUpdated. Sequence drops to 0 then climbs back to HWM as the rebuild replays the event stream. For a small projection this completes in 1–5 seconds; the sparkline shows the entire run. The Pause / Restart buttons are disabled during rebuild.
How to verifyUI: state badge transitions visible in the State Transitions Timeline card. SQL: SELECT name, last_seq_id, rebuild_threshold FROM trips.mt_event_progression WHERE name = 'Distance:All';last_seq_id resets to 0 then climbs. API: service.shardStates[<shardName>].action cycles through the rebuild states.

A-4 — Rewind a subscription to a specific sequence

FieldValue
SetupFull scenario with publisher idle (or paused) so the HWM is stable. Pick a projection with > 100 events advanced (TripProjection typically).
ActionOn the Projection Detail page, open the Rewind Subscription modal. Pick mode To Sequence, enter a target sequence ≤ current sequence (e.g. current - 50). Submit.
Expected observationProjection sequence drops to the target value and starts re-advancing. State briefly shows Stopped during the rewind, then resumes Updated. Events between the target sequence and the previous high-water mark are re-applied — visible side effects depend on the projection (idempotent projections converge; non-idempotent projections see duplicates, which the rewind action explicitly warns about).
How to verifyAPI: service.shardStates[<shardName>].sequence drops to the requested value then climbs. The Rewind Result card on the page shows Success=true with the target sequence echoed. The pre-existing integration test Tests.Integration.subscription_rewind.rewind_subscription_to_sequence_via_critterwatch exercises the same flow against DistanceProjection.

A-5 — Rewind to a timestamp

FieldValue
SetupSame as A-4. Note the current wall-clock time before the action.
ActionRewind modal → To Timestamp → pick a timestamp 30 seconds ago. Submit.
Expected observationThe daemon resolves the timestamp into a sequence floor (via IEventDatabase.FindEventStoreFloorAtTimeAsync), then rewinds to that floor. The Rewind Result card surfaces both the requested timestamp and the resolved sequence floor.
How to verifyThe resolved sequence in the result must be ≤ the sequence the projection was at 30s ago. The API echoes both the timestamp and the resolved floor. SQL: the projection's last_seq_id after rewind matches the resolved floor.

A-6 — Per-tenant projection action (phase 3a)

FieldValue
SetupSwitch BffHost to CRITTERWATCH_SCENARIO=tenancy to load the MTTrip family. Wait for MTTripService and MTTripPublisher to be Running. Open the Projection Detail page for a tenant-aware projection (MTTripProjection).
ActionIn the Pause / Restart / Rebuild modal, scope the action to a specific tenant id (e.g. tenant1). Submit.
Expected observationOnly the tenant-scoped shard (e.g. MTTrip:All:tenant1) transitions; other tenants' shards stay Updated. The tenant-scoped agent URI is event-subscription://MTTripService/MTTrip:All/tenant1 (matches the JasperFx ShardName tenant-grammar).
How to verifyUI: the per-tenant view selector on the Projections page shows the tenant-scoped shard with a Paused badge while other tenants under the same projection name keep their Updated badge. SQL: SELECT name, last_seq_id, agent_status FROM mttrips.mt_event_progression WHERE name LIKE 'MTTrip:%'; — only the row for the targeted tenant has agent_status = Paused.

A-7 — Pause-then-rebuild lock-out (negative case)

FieldValue
SetupFull scenario. Pause TripProjection via A-1.
ActionTry to click Rebuild on the paused projection.
Expected observationThe Rebuild button is disabled with a tooltip explaining "Restart the projection before rebuilding". Or — if the button is clickable — the rebuild fails with a clear error message and no state change. The point of the test is the lockout exists at one layer or the other.
How to verifyUI: data-testid="rebuild-button" has disabled attribute, or clicking surfaces an el-message-box with an error. The RebuildProjection integration test in Tests.Integration.projection_commands covers the happy path; the lockout is the operator-facing affordance on top of that.

A-8 — Cross-scenario admin command on an ancillary store (PS#2 regression gate)

FieldValue
SetupFull scenario or multi-store scenario. Wait for MultiStoreHost to be Running with three event stores surfaced.
ActionPause the IncidentsByCategory projection (lives on the IIncidentsStore ancillary).
Expected observationOnly that ancillary projection pauses. The main Trips projection (Itinerary) and the other ancillary's projection (TelehealthComposite) continue advancing.
How to verifyUI: only the IncidentsByCategory:All row shows Paused. SQL across schemas:
SELECT name, agent_status FROM multistore_incidents.mt_event_progression; — paused row visible.
SELECT name, agent_status FROM multistore_trips.mt_event_progression; — all rows Running.
SELECT name, agent_status FROM multistore_telehealth.mt_event_progression; — all rows Running. The PS#2 PR-D integration test in Tests.Integration.multi_store_admin_commands automates this flow.

Cross-reference

  • Pre-existing automated coverage: Tests.Integration.projection_commands, Tests.Integration.subscription_rewind, Tests.Integration.multi_store_admin_commands (PS#2 PR-D).
  • The per-tenant action surface depends on JasperFx Phase 3a + #209 — both landed; see CW7 for the per-tenant rewind handler fix.

Released under the MIT License.