Context
Several domain entities have rich state machines with transition guards that are completely untested. These are the core business rules of the system.
Entities and Their State Machines
CommandRun (6 states, 5 transitions, 0 tests)
State machine: Queued → Running → Completed/Failed/TimedOut/Cancelled
Test matrix:
| From |
Action |
Expected |
| Queued |
Start() |
Running |
| Queued |
Fail("msg") |
Failed |
| Queued |
Cancel() |
Cancelled |
| Running |
Complete(0) |
Completed |
| Running |
Complete(1) |
Completed (non-zero exit) |
| Running |
Fail("msg") |
Failed |
| Running |
Timeout() |
TimedOut |
| Running |
Cancel() |
Cancelled |
| Completed |
Start() |
throws |
| Completed |
Complete(0) |
throws |
| Failed |
Start() |
throws |
| TimedOut |
Cancel() |
throws |
| Cancelled |
Start() |
throws |
Also test:
- Constructor rejects empty
templateName, Guid.Empty userId, empty correlationId
Fail("") throws (empty error message)
SetOutputPreview with string > 1000 chars throws
SetOutputPreview(null) succeeds (nullable)
SetTruncated() is idempotent
AddLog() adds to Logs collection and calls Touch()
ArchiveItem (4 states, 4 transitions, 0 tests)
State machine: Available → Restored/Expired/Conflict
Test matrix:
| From |
Action |
Expected |
| Available |
MarkAsRestored(userId) |
Restored |
| Available |
MarkAsExpired() |
Expired |
| Available |
MarkAsConflict() |
Conflict |
| Restored |
ResetToAvailable() |
throws |
| Expired |
ResetToAvailable() |
Available |
| Conflict |
ResetToAvailable() |
Available |
| Restored |
MarkAsExpired() |
throws |
| Expired |
MarkAsRestored(userId) |
throws |
Also test:
- Constructor validates
entityType must be "board", "column", or "card" — test "task", "", null
name.Length > 200 throws
Guid.Empty for various ID parameters throws
- Empty
snapshotJson throws
MarkAsRestored(Guid.Empty) throws
UserPreference (untested)
DismissOnboarding() / ReplayOnboarding() / RecordOnboardingCompletion() idempotent guard
- Verify
RecordOnboardingCompletion can only be called once
NotificationPreference (untested)
Update() method validates new values
ChatSession (untested)
- Session lifecycle and message accumulation
- Any state-dependent behavior
CardLabel and CardCommentMention (untested)
Implementation Notes
- These are pure domain tests — no mocking needed, just construct entities and assert
- Follow existing pattern in
Taskdeck.Domain.Tests (e.g., BoardTests, CardTests)
- One test class per entity
- Use
[Theory] with [InlineData] for the transition matrix
- Verify
Touch() is called (UpdatedAt changes) on every mutation
Context
Several domain entities have rich state machines with transition guards that are completely untested. These are the core business rules of the system.
Entities and Their State Machines
CommandRun(6 states, 5 transitions, 0 tests)State machine:
Queued → Running → Completed/Failed/TimedOut/CancelledTest matrix:
Also test:
templateName,Guid.EmptyuserId, emptycorrelationIdFail("")throws (empty error message)SetOutputPreviewwith string > 1000 chars throwsSetOutputPreview(null)succeeds (nullable)SetTruncated()is idempotentAddLog()adds toLogscollection and callsTouch()ArchiveItem(4 states, 4 transitions, 0 tests)State machine:
Available → Restored/Expired/ConflictTest matrix:
Also test:
entityTypemust be "board", "column", or "card" — test "task", "", nullname.Length > 200throwsGuid.Emptyfor various ID parameters throwssnapshotJsonthrowsMarkAsRestored(Guid.Empty)throwsUserPreference(untested)DismissOnboarding()/ReplayOnboarding()/RecordOnboardingCompletion()idempotent guardRecordOnboardingCompletioncan only be called onceNotificationPreference(untested)Update()method validates new valuesChatSession(untested)CardLabelandCardCommentMention(untested)Implementation Notes
Taskdeck.Domain.Tests(e.g.,BoardTests,CardTests)[Theory]with[InlineData]for the transition matrixTouch()is called (UpdatedAt changes) on every mutation