ReportUploadScheduler — inject Provider<WorkManager> (exactly mirroring SyncScheduler) instead of calling the WorkManager.getInstance(context) static that MockK can't stub on the abstract WorkManager (AbstractMethodError). Resolves lazily at enqueue time, so no change to the on-demand-WorkManager init ordering.
ReportUploadWorker — take the ingest endpoint via a new @DebugReportEndpoint qualifier, provided from BuildConfig.DEBUG_REPORT_ENDPOINT in ReportingModule, rather than reading the BuildConfig static inline. Production value is identical (empty by default), so the "no endpoint configured → clean failure" behaviour is unchanged.
IdleService — extract the foreground-notification channel + push-mode→text logic (the service's only non-lifecycle logic) into PushStatusNotification. startAsForeground now just delegates; behaviour is byte-for-byte the same notification.
Coverage added
ReportUploadSchedulerTest (unit) — pins the per-report unique-work name and the REPLACE policy; statusFlow observes the right unique work.
ReportUploadWorkerHttpTest (unit) — drives the previously-unreachable transmit path against an in-process JDK HttpServer on loopback (no new dependency, same "real in-process server" approach as GreenMail): 2xx → success + report deleted (and asserts the JSON POST body/content-type), 4xx → permanent failure, 5xx → retry then failure at the attempt cap, network error → retry.
PushStatusNotificationInstrumentedTest (instrumented/E2E) — asserts the channel importance and the IDLE vs POLLING notification text using the real application Context (a ContextWrapper, never mockk<Context>()), following the #272 / BatteryOptimizationManagerIntentTest patterns.
CrashReporter.terminate (item 4) left as-is per the ticket.
Validation
Compile gate (JDK 21, no emulator): :app:testDebugUnitTest + :app:compileDebugAndroidTestKotlin + :app:ktlintCheck + :app:detekt + :app:assembleDebug — all green.
Instrumented: PushStatusNotificationInstrumentedTest 3/3 green on an API 36 emulator via the merged local helper (#269); teardown verified clean (no orphaned qemu). CI runs the full multi-API matrix.
Closes #257
## Testability seams (minimal, behaviour-preserving)
- **`ReportUploadScheduler`** — inject `Provider<WorkManager>` (exactly mirroring `SyncScheduler`) instead of calling the `WorkManager.getInstance(context)` static that MockK can't stub on the abstract `WorkManager` (`AbstractMethodError`). Resolves lazily at enqueue time, so no change to the on-demand-WorkManager init ordering.
- **`ReportUploadWorker`** — take the ingest endpoint via a new `@DebugReportEndpoint` qualifier, provided from `BuildConfig.DEBUG_REPORT_ENDPOINT` in `ReportingModule`, rather than reading the `BuildConfig` static inline. Production value is identical (empty by default), so the "no endpoint configured → clean failure" behaviour is unchanged.
- **`IdleService`** — extract the foreground-notification channel + push-mode→text logic (the service's only non-lifecycle logic) into `PushStatusNotification`. `startAsForeground` now just delegates; behaviour is byte-for-byte the same notification.
## Coverage added
- **`ReportUploadSchedulerTest`** (unit) — pins the per-report unique-work name and the `REPLACE` policy; `statusFlow` observes the right unique work.
- **`ReportUploadWorkerHttpTest`** (unit) — drives the previously-unreachable transmit path against an in-process JDK `HttpServer` on loopback (no new dependency, same "real in-process server" approach as GreenMail): 2xx → success + report deleted (and asserts the JSON POST body/content-type), 4xx → permanent failure, 5xx → retry then failure at the attempt cap, network error → retry.
- **`PushStatusNotificationInstrumentedTest`** (instrumented/E2E) — asserts the channel importance and the IDLE vs POLLING notification text using the real application `Context` (a `ContextWrapper`, never `mockk<Context>()`), following the `#272` / `BatteryOptimizationManagerIntentTest` patterns.
`CrashReporter.terminate` (item 4) left as-is per the ticket.
## Validation
- Compile gate (JDK 21, no emulator): `:app:testDebugUnitTest` + `:app:compileDebugAndroidTestKotlin` + `:app:ktlintCheck` + `:app:detekt` + `:app:assembleDebug` — all green.
- Unit: `ReportUploadSchedulerTest` 2/2, `ReportUploadWorkerHttpTest` 5/5, `ReportUploadWorkerTest` 3/3.
- Instrumented: `PushStatusNotificationInstrumentedTest` 3/3 green on an API 36 emulator via the merged local helper (`#269`); teardown verified clean (no orphaned qemu). CI runs the full multi-API matrix.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #257
Testability seams (minimal, behaviour-preserving)
ReportUploadScheduler— injectProvider<WorkManager>(exactly mirroringSyncScheduler) instead of calling theWorkManager.getInstance(context)static that MockK can't stub on the abstractWorkManager(AbstractMethodError). Resolves lazily at enqueue time, so no change to the on-demand-WorkManager init ordering.ReportUploadWorker— take the ingest endpoint via a new@DebugReportEndpointqualifier, provided fromBuildConfig.DEBUG_REPORT_ENDPOINTinReportingModule, rather than reading theBuildConfigstatic inline. Production value is identical (empty by default), so the "no endpoint configured → clean failure" behaviour is unchanged.IdleService— extract the foreground-notification channel + push-mode→text logic (the service's only non-lifecycle logic) intoPushStatusNotification.startAsForegroundnow just delegates; behaviour is byte-for-byte the same notification.Coverage added
ReportUploadSchedulerTest(unit) — pins the per-report unique-work name and theREPLACEpolicy;statusFlowobserves the right unique work.ReportUploadWorkerHttpTest(unit) — drives the previously-unreachable transmit path against an in-process JDKHttpServeron loopback (no new dependency, same "real in-process server" approach as GreenMail): 2xx → success + report deleted (and asserts the JSON POST body/content-type), 4xx → permanent failure, 5xx → retry then failure at the attempt cap, network error → retry.PushStatusNotificationInstrumentedTest(instrumented/E2E) — asserts the channel importance and the IDLE vs POLLING notification text using the real applicationContext(aContextWrapper, nevermockk<Context>()), following the#272/BatteryOptimizationManagerIntentTestpatterns.CrashReporter.terminate(item 4) left as-is per the ticket.Validation
:app:testDebugUnitTest+:app:compileDebugAndroidTestKotlin+:app:ktlintCheck+:app:detekt+:app:assembleDebug— all green.ReportUploadSchedulerTest2/2,ReportUploadWorkerHttpTest5/5,ReportUploadWorkerTest3/3.PushStatusNotificationInstrumentedTest3/3 green on an API 36 emulator via the merged local helper (#269); teardown verified clean (no orphaned qemu). CI runs the full multi-API matrix.🤖 Generated with Claude Code