Files
LibreMail/app/src/main/kotlin/org/libremail/push/PushStatusNotification.kt
T
JMR-devandClaude Opus 4.8 0b44276909 test(reporting): ReportUpload testability seams + push IdleService E2E
Coverage lane 4 (#249) flagged reporting/push classes as unreachable by tests.
Add minimal, behaviour-preserving seams and the tests they unblock (issue #257):

- ReportUploadScheduler: inject Provider<WorkManager> (mirroring SyncScheduler)
  instead of calling the WorkManager.getInstance static that MockK can't stub on
  the abstract WorkManager (AbstractMethodError). New ReportUploadSchedulerTest
  pins the per-report unique-work name + REPLACE policy.
- 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. New ReportUploadWorkerHttpTest
  drives the transmit path against an in-process JDK HttpServer on loopback and
  covers 2xx success + delete, 4xx failure, 5xx retry/attempt-cap, and network
  error. Production value is unchanged (empty by default).
- IdleService: extract the foreground-notification channel + push-mode-to-text
  logic into PushStatusNotification. New PushStatusNotificationInstrumentedTest
  asserts channel importance and the IDLE/POLLING notification text with a real
  application Context (never a mocked Context).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 23:58:00 -05:00

56 lines
2.2 KiB
Kotlin

// SPDX-License-Identifier: GPL-3.0-or-later
package org.libremail.push
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import org.libremail.R
import org.libremail.data.sync.PushMode
/**
* Builds the persistent foreground notification for [IdleService]. Extracted so the push-mode → text
* choice and the channel definition — the only non-lifecycle logic the service carries — can be
* verified with a real `Context` off the service (see `PushStatusNotificationInstrumentedTest`),
* without standing up the foreground service, its Hilt graph, or live IMAP connections. Behaviour is
* identical to the inline builder it replaced (issue #257).
*/
internal object PushStatusNotification {
const val CHANNEL_ID = "push_status"
const val FOREGROUND_ID = 1002
/** Creates (idempotently) the low-importance channel the status notification posts on. */
fun ensureChannel(context: Context) {
NotificationManagerCompat.from(context).createNotificationChannel(
NotificationChannel(
CHANNEL_ID,
context.getString(R.string.notif_channel_push_status),
NotificationManager.IMPORTANCE_LOW,
),
)
}
/**
* The ongoing status notification. Its text tells the truth per [mode]: "connected for instant
* delivery" versus the low-battery 15-minute polling fallback (#90).
*/
fun build(context: Context, mode: PushMode): Notification {
val text = if (mode == PushMode.POLLING) {
context.getString(R.string.notif_push_status_text_low_battery)
} else {
context.getString(R.string.notif_push_status_text)
}
return NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_monochrome)
.setContentTitle(context.getString(R.string.notif_push_status_title))
.setContentText(text)
.setOngoing(true)
.setShowWhen(false)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()
}
}