Skip to content

Expand WMFPageViewsDataController test coverage - #5968

Open
davmonk wants to merge 3 commits into
wikimedia:mainfrom
davmonk:add-page-views-controller-tests
Open

Expand WMFPageViewsDataController test coverage#5968
davmonk wants to merge 3 commits into
wikimedia:mainfrom
davmonk:add-page-views-controller-tests

Conversation

@davmonk

@davmonk davmonk commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Adds tests for the previously-untested methods of WMFPageViewsDataController (against a temporary WMFCoreDataStore), raising the file to ~74% (416/559 lines) coverage.

Updated (synced with main): main removed the reading-challenge feature (fetchReadingChallengeState, ReadingChallengeState, and the devReadingChallengeState/readingChallengeUserCompleted user-defaults keys), so the reading-challenge tests this PR originally included were dropped — you can't test deleted code. The PageViews coverage below is unaffected.

What

  • fetchMostRecentTime — latest timestamp, and nil on an empty store
  • fetchTimelinePages — descending timestamp order
  • addPageViewSeconds + fetchPageViewMinutes — seconds summed into minutes
  • fetchPageViewDates — bucketing by day, hour, and month

Notes

  • Date-bucketing assertions use local-calendar dates built the same way the code reads them, so they're stable across time zones.

Known gap: fetchLinkedPageViews (investigated)

fetchLinkedPageViews is intentionally left uncovered. Investigation (full notes in this comment) showed the crash is not in the relationship walk: the method returns [[CDPageView]] — raw Core Data NSManagedObjects — and the crash occurs when the caller reads a property (pageView.page?.title) off the context's queue, which trips -com.apple.CoreData.ConcurrencyDebug 1 (enabled in the WMFData test plan). The walk itself terminates and returns correctly; a control that only inspects array structure passes.

The method is public with zero callers, and is the only method on this controller returning managed objects instead of value types (cf. fetchTimelinePages → [WMFPageWithTimestamp]). It still exists on main as of this update. Recommended fix: remove it (dead code) or extract value types inside the perform block, after which the linked-chain test can be added back. Worth a Phabricator ticket (GitHub issues are disabled on this repo).

Verified locally: 10/10 tests pass across serial runs, SwiftLint --strict clean.

🤖 Generated with Claude Code

@davmonk

davmonk commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Investigation notes: why fetchLinkedPageViews() is left uncovered

(GitHub issues are disabled on this repo, so recording the investigation here next to the in-code NOTE. Worth a Phabricator ticket.)

Finding: WMFPageViewsDataController.fetchLinkedPageViews() returns [[CDPageView]] — raw Core Data NSManagedObjects — out of its context.perform block. Any consumer that reads a property of those objects (e.g. pageView.page?.title) off the owning context's queue trips Core Data's multithreading assertion and crashes. The WMFData test plan runs with -com.apple.CoreData.ConcurrencyDebug 1, so this is a hard crash in tests.

Evidence:

  • A test that builds a Start → Middle → End chain, calls fetchLinkedPageViews(), then reads chains.first?.compactMap { $0.page?.title } crashes the runner. Instrumented logging shows the method itself returns successfully (1 chain, length 3) — the crash is in the property access afterward, not the fetch/walk.
  • A control that calls the same method but only inspects array structure (chains.map { $0.count }, never touching a managed-object property) passes. This isolates the cause to off-queue managed-object access; the previousPageView/nextPageViews inverse relationships are modeled correctly and the walk terminates on well-formed data.

Also notable: the method is public but has zero callers in the codebase, and is the only method on this controller that returns NSManagedObjects instead of value types (cf. fetchTimelinePages()[WMFPageWithTimestamp]). That both violates the data-layer guidance (no managed objects across the boundary) and makes it a latent crash for any future caller.

Recommended fix: either remove it (currently dead), or refactor it to extract value types inside the perform block (e.g. [[WMFPage]] or a small struct). Once it returns Sendable value types, the linked-chain unit test can be added back.

Adds tests for the previously-untested methods of WMFPageViewsDataController
against a temporary WMFCoreDataStore, raising the file from ~26% to ~87%
line coverage:

- fetchMostRecentTime (latest timestamp and empty-store nil)
- fetchTimelinePages (descending timestamp order)
- addPageViewSeconds + fetchPageViewMinutes (seconds summed into minutes)
- fetchPageViewDates (bucketing by day, hour, and month)
- the reading-challenge state machine via injected `now` and seeded page
  views: removed, not-live-yet, not-enrolled, concluded-no-streak,
  enrolled-not-started, ongoing streak (read/not-yet-read today), and
  concluded-incomplete (longest past streak)

The reading-challenge state reads process-wide shared defaults; setUp now
clears the dev-override and completion flags so each test is deterministic,
and the tests never drive the completion path (which would persist state).

fetchLinkedPageViews is left uncovered for now: exercising it with a linked
page-view chain crashes the runner, which appears to be a latent issue in
the relationship walk rather than a test problem. Tracking separately
rather than shipping a crashing test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@davmonk
davmonk force-pushed the add-page-views-controller-tests branch from 3c57d5b to 28c7529 Compare July 14, 2026 15:45
davmonk and others added 2 commits July 22, 2026 15:32
Main removed the reading-challenge feature (fetchReadingChallengeState,
ReadingChallengeState, and the devReadingChallengeState/readingChallengeUserCompleted
user-defaults keys), so those 8 tests no longer compile. Drop them and their
setUp defaults-clearing; the 10 remaining WMFPageViewsDataController tests
(add/delete/import/counts/mostRecentTime/timeline/minutes/dates) are unaffected.

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

davmonk commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up on a change from syncing this branch with main: main removed the reading-challenge feature entirely (fetchReadingChallengeState, the ReadingChallengeState enum, and the devReadingChallengeState/readingChallengeUserCompleted user-defaults keys — all gone from production source). The 8 reading-challenge tests this PR originally added no longer compile against that, so I removed them along with their setUp defaults-clearing.

The 10 remaining WMFPageViewsDataController tests (add/delete/import/counts, fetchMostRecentTime, fetchTimelinePages, fetchPageViewMinutes, fetchPageViewDates) are unaffected and pass — verified locally (serial, iOS 18.5), SwiftLint --strict clean. File coverage is now ~74% (416/559 lines); the earlier ~87% figure included the removed tests.

Note the fetchLinkedPageViews finding above still stands — that method was not part of the removal and still exists on main. Description updated to match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant