test(coverage): raise unit coverage past the 50% gate (phases 0-2)
All checks were successful
PR Checks / android-build (pull_request) Successful in 7m19s
All checks were successful
PR Checks / android-build (pull_request) Successful in 7m19s
Executes COVERAGE_PLAN.md phases 0-2 to clear the SonarQube new-code coverage gate (was 16.4%, threshold 50%). Estimated new-code coverage after this change is ~57%. 109 new tests across 19 files; full suite is 264 tests, all green. Phase 0 — coverage exclusions (sonar-project.properties): drop code a JVM unit test can't execute from the *coverage* denominator (still analysed for bugs/smells) — pure-@Composable UI the `*Screen.kt` glob missed (ui/components/**, BlockRenderer, ShardComponents), Android-framework glue (push services, Keystore-backed Encrypted* stores, Hilt di/**). Phase 1 — DTO serialization tests: AdminDto, PublicDto, WikiDto, PostDto/PageDto/ ContactDto, SsoDto, the shard board DTOs and player game-data DTOs, and the mobile-auth request bodies — decode + encode + computed helpers (isPublished/isMaintenance/ActorDto.label/ShardStatusDto.isOnline). Phase 2 — ViewModel tests: a MainDispatcherRule harness + hand-written API fakes (FakePublicApi/FakeAdminApi/FakePlayerShardApi/FakeShardStream) drive real repositories into the ViewModels. Covers the admin (dashboard/content/moderation/ support), content (news/post/page/wiki/home/contact), player (characters/ vendors/character/my-houses) and shard-board (champs/guilds/governors/houses/ hub) ViewModels — load success/error, form validation, role/status-aware feedback, and live-frame merging. To make the shard boards testable, extract a small `ShardStream` interface from `ShardStreamClient` (bound in NetworkModule) so `ShardRepository` depends on the capability, not the OkHttp client — lets a fake stream replace the perpetual SSE reconnect loop in tests. No production behaviour change. Phases 3 (repositories) and 4 (core net/auth top-up) are follow-ups; the deep-dependency auth family (Login/Account/TrustedDevices ViewModels, AuthRepository) lands with them. See docs/android/COVERAGE_PLAN.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.runicgateway.app.core.net
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* The live shard SSE feed as a cold flow of lifecycle + frame events (PLAN.md §6.2,
|
||||
* §7). Extracted as an interface so consumers (e.g. [com.runicgateway.app.data.repository.ShardRepository])
|
||||
* depend on the capability, not the OkHttp-backed [ShardStreamClient] — the boards
|
||||
* can then be unit-tested against a fake stream instead of a real network connection.
|
||||
*/
|
||||
interface ShardStream {
|
||||
fun events(): Flow<ShardStreamEvent>
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class ShardStreamClient @Inject constructor(
|
||||
baseClient: OkHttpClient,
|
||||
private val baseUrlHolder: BaseUrlHolder,
|
||||
private val json: Json,
|
||||
) {
|
||||
) : ShardStream {
|
||||
// SSE is a long-lived, mostly-idle connection (keepalive comments every ~25s),
|
||||
// so the read timeout must be disabled or the idle stream would be killed.
|
||||
private val sseClient: OkHttpClient = baseClient.newBuilder()
|
||||
@@ -56,7 +56,7 @@ class ShardStreamClient @Inject constructor(
|
||||
* drive a live/offline indicator; [ShardStreamEvent.Frame] carries a decoded
|
||||
* `{ kind, … }` payload the boards merge in place.
|
||||
*/
|
||||
fun events(): Flow<ShardStreamEvent> = channelFlow {
|
||||
override fun events(): Flow<ShardStreamEvent> = channelFlow {
|
||||
var backoffMs = INITIAL_BACKOFF_MS
|
||||
while (isActive) {
|
||||
val url = baseUrlHolder.current?.resolve(STREAM_PATH)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
package com.runicgateway.app.data.repository
|
||||
|
||||
import com.runicgateway.app.core.net.ShardStreamClient
|
||||
import com.runicgateway.app.core.net.ShardStream
|
||||
import com.runicgateway.app.core.net.ShardStreamEvent
|
||||
import com.runicgateway.app.core.result.ApiResult
|
||||
import com.runicgateway.app.core.result.safeApiCall
|
||||
@@ -35,7 +35,7 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class ShardRepository @Inject constructor(
|
||||
private val api: PublicApi,
|
||||
private val stream: ShardStreamClient,
|
||||
private val stream: ShardStream,
|
||||
private val json: Json,
|
||||
) {
|
||||
// ── Snapshots ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.runicgateway.app.BuildConfig
|
||||
import com.runicgateway.app.core.net.AuthInterceptor
|
||||
import com.runicgateway.app.core.net.BaseUrlHolder
|
||||
import com.runicgateway.app.core.net.HostSelectionInterceptor
|
||||
import com.runicgateway.app.core.net.ShardStream
|
||||
import com.runicgateway.app.core.net.ShardStreamClient
|
||||
import com.runicgateway.app.core.net.TokenAuthenticator
|
||||
import com.runicgateway.app.core.net.UserAgentInterceptor
|
||||
import com.runicgateway.app.data.api.AuthApi
|
||||
@@ -94,6 +96,12 @@ object NetworkModule {
|
||||
@Singleton
|
||||
fun providePublicApi(retrofit: Retrofit): PublicApi = retrofit.create(PublicApi::class.java)
|
||||
|
||||
/** Expose the live SSE feed as the [ShardStream] capability so repositories depend
|
||||
* on the interface (unit-testable against a fake), not the OkHttp-backed client. */
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShardStream(client: ShardStreamClient): ShardStream = client
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthApi(retrofit: Retrofit): AuthApi = retrofit.create(AuthApi::class.java)
|
||||
|
||||
Reference in New Issue
Block a user