Skip to content

No AppModel test harness, so model-level behaviour is only ever proven through the UI #30

Description

@lightcloud00

What is missing

ParkNudgeTests/TestFixtures.swift provides a ParkingSession builder and a FixedClock, and nothing else. There are no fakes for the nine protocols in ServiceProtocols.swift, so AppModel — which owns most of the app's decision-making — cannot be constructed in a unit test.

Everything it does is therefore either untested or tested indirectly through XCUITest, which is slow, device-dependent, and cannot assert on things that have no visible representation.

Concrete cost, already paid

Issue #15 asked for "UI coverage using a fake requester; verify no prompt is requested on first completion". That could not be written. ReviewPromptPolicy is fully unit-tested as a pure decision, but the wiring — that finishActive() evaluates it after the paywall flag is set, and passes the real completion count — is unverified.

The same gap hides:

  • whether the first-completion paywall and the rating request can ever fire together;
  • whether alertMessage being set actually suppresses the rating request in practice;
  • whether thumbnail(for:)'s updatedAt-keyed cache really retires a replaced photo.

Suggested approach

Add in-memory fakes for ParkingRepository, LocationProviding, NotificationScheduling, PhotoStoring, DirectionsOpening, PurchaseProviding, CSVExporting and ReviewRequesting — the last one recording calls — plus a builder that assembles an AppModel from them with a FixedClock. AppEnvironment.makeUITesting already contains most of these as private types; promoting them into the test target is the smaller move than writing new ones.

Acceptance

  • AppModel constructible in a unit test with all dependencies faked.
  • A test asserts no review is requested on the first completion, and one is on the third.
  • A test asserts no review is requested while isPaywallPresented or alertMessage is set.
  • ./scripts/verify.sh still green.

Deferred deliberately from #15; recorded here rather than left implicit.

Resolution plan

  • Verified: 2026-08-25T04:51:16Z against main@04a6cd5e808720983471ee9595fd40bc94e1f0fe (commit time 2026-08-25T00:53:29Z).
  • Source basis: Original issue body, all 0 comment(s), current default-branch tree, referenced pull-request states, and the protected top-90 dossier revalidated without modifying it.
  • Disposition: Code or verification work - not complete on the verified default branch.
  • Outcome: No AppModel test harness, so model-level behaviour is only ever proven through the UI

Current evidence and corrected premise

Verdict — NOT_STARTED. Confirmed by the shape of the test directory.

ParkNudge/Application/AppModel.swift exists. ParkNudgeTests/ contains 8 files and none of them
is an AppModel test
:

CSVAndMoneyTests  FeatureAccessTests  MeterStateTests  ParkingCoordinatorTests
ReminderPlannerTests  ReviewPromptPolicyTests  SwiftDataParkingRepositoryTests  TestFixtures

The domain layer is well covered — MeterState, ReminderPlanner, ParkingCoordinator,
ReviewPromptPolicy, the repository. The application layer that composes them is not.
P2 · enhancement, area: ios, priority:p2, revenue:r3 · Canonical source: origin/main
Cluster: red or missing test suites — see CLUSTERS.md.
All 0 issue comment(s) were read. There are no comments.
Verified source paths on this head:

  • ParkNudge/Application/AppEnvironment.swift
  • ParkNudge/Application/AppModel.swift
  • ParkNudge/Domain/Services/ServiceProtocols.swift
  • ParkNudgeTests/ParkingCoordinatorTests.swift
  • ParkNudgeTests/TestFixtures.swift
  • scripts/verify.sh
    Filed or dossier path references not present on this head; treat them as stale until relocated:
  • ParkNudgeTests/AppModelTests.swift

Resolution steps

Exact fix

Add ParkNudgeTests/AppModelTests.swift, following the conventions already in the directory:

  1. Reuse ParkNudgeTests/TestFixtures.swift — it exists precisely to build domain objects for
    tests; do not introduce a second fixture style.
  2. Follow ParkingCoordinatorTests.swift for the composition-under-test pattern; it is the closest
    analog (a coordinator over the same repository seam).
  3. Inject rather than construct: AppEnvironment.swift is where ReviewRequester and
    ReviewPromptPolicy are wired (3 references), so AppModel should be constructible with test
    doubles through it. If it currently is not, making it injectable is the first task — and is
    the real content of this issue.
  4. Cover at minimum: starting and ending a parking session updates state; the meter state exposed to
    the UI matches MeterState for the same inputs; the review prompt is requested exactly when
    ReviewPromptPolicy.shouldRequest says so (this closes a real gap — the policy is tested, its
    invocation is not).

Tests and acceptance

Test / proof

xcodebuild test -scheme ParkNudge -only-testing:ParkNudgeTests/AppModelTests

The new tests must fail if AppModel stops calling a domain policy — verify by temporarily removing
one call and confirming a red.

Gate — the repo's unit scheme

  • For every listed xcodebuild argv, submit the equivalent focused job through the shared fleet-build gate after iOS capacity admission; do not run xcodebuild directly.

Dependencies and stop gates

Out of scope — ParkNudgeUITests; #29's unrendered meter states

  • Do not close this issue until every acceptance item is mapped to exact merged-head evidence.

Next AI first action

Reuse ParkNudgeTests/TestFixtures.swift** — it exists precisely to build domain objects for

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: iosiOS application workenhancementNew feature or requestpriority:p2Post-MVP or lower-priority workreadiness:diagnosticExact bounded investigation requiredrevenue:r3Indirect or long-range revenue impact

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions