c366fbd9a3
- SyncEngineTest: 429 stops with Retry(rate_limited) before pulling; 400, 413, 409, 422 push refusals are no progress, the cycle pulls, succeeds and records last_error_code, one push only. - WorkManagerSyncWorkTest on work-testing's test WorkManager: kvitt-sync APPEND_OR_REPLACE (second request chained), kvitt-sync-periodic KEEP every 15 minutes, CONNECTED constraint. Adds androidx.work:work-testing (testImplementation, same version as work-runtime). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
89 lines
3.9 KiB
Kotlin
89 lines
3.9 KiB
Kotlin
// :data — Room database, Ktor client, token store, sync engine, WorkManager workers, the real repositories and the APK
|
|
// updater (SPEC §6.2, §14.2, §14.4; owned by M5). Hilt modules here provide the implementations; :app (M8) binds the
|
|
// :core:model interfaces to them.
|
|
plugins {
|
|
id("kvitt.android.library")
|
|
id("kvitt.android.hilt")
|
|
alias(libs.plugins.ksp)
|
|
alias(libs.plugins.room3)
|
|
alias(libs.plugins.kotlin.serialization)
|
|
}
|
|
|
|
room3 {
|
|
schemaDirectory("$projectDir/schemas")
|
|
}
|
|
|
|
dependencies {
|
|
implementation(project(":core:model"))
|
|
implementation(project(":domain"))
|
|
implementation(libs.androidx.room3.runtime)
|
|
ksp(libs.androidx.room3.compiler)
|
|
implementation(libs.androidx.sqlite.bundled)
|
|
implementation(libs.androidx.datastore.preferences)
|
|
implementation(libs.androidx.work.runtime.ktx)
|
|
implementation(libs.ktor.client.core)
|
|
implementation(libs.ktor.client.okhttp)
|
|
implementation(libs.ktor.client.content.negotiation)
|
|
implementation(libs.ktor.serialization.kotlinx.json)
|
|
implementation(libs.kotlinx.serialization.json)
|
|
implementation(libs.kotlinx.coroutines.core)
|
|
implementation(libs.kotlinx.datetime)
|
|
implementation(libs.coil)
|
|
implementation(libs.coil.network.ktor3)
|
|
|
|
testImplementation(libs.ktor.client.mock)
|
|
testImplementation(libs.androidx.work.testing)
|
|
testImplementation(libs.kotlinx.coroutines.test)
|
|
testImplementation(libs.robolectric)
|
|
}
|
|
|
|
// Unit tests read the shared examples (SPEC §14.3.5) and the exported Room schemas (§14.2.3) in place; both are inputs,
|
|
// so a changed contract file re-runs the tests.
|
|
val contractsExamples = rootProject.layout.projectDirectory.dir("../contracts/examples")
|
|
val roomSchemas = layout.projectDirectory.dir("schemas")
|
|
val categoryKeywordsTable = rootProject.layout.projectDirectory.file("../contracts/category-keywords.csv")
|
|
tasks.withType<Test>().configureEach {
|
|
inputs.dir(contractsExamples).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("contractsExamples")
|
|
inputs.dir(roomSchemas).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("roomSchemas")
|
|
systemProperty("kvitt.contracts.examples", contractsExamples.asFile.absolutePath)
|
|
systemProperty("kvitt.room.schemas", roomSchemas.asFile.absolutePath)
|
|
inputs.file(categoryKeywordsTable).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("categoryKeywords")
|
|
systemProperty("kvitt.contracts.keywords", categoryKeywordsTable.asFile.absolutePath)
|
|
}
|
|
|
|
// The keyword table of the category suggestion (UX-3, SPEC §9.11 CS-6) is bundled as the Java resource
|
|
// se/hagstedt/kvitt/data/category-keywords.csv, copied from contracts/category-keywords.csv at build time (never
|
|
// hand-copied). A missing table fails the build: the task's input file must exist.
|
|
abstract class CopyCategoryKeywords : DefaultTask() {
|
|
@get:InputFile
|
|
@get:PathSensitive(PathSensitivity.NONE)
|
|
abstract val table: RegularFileProperty
|
|
|
|
@get:Input
|
|
abstract val resourcePath: Property<String>
|
|
|
|
@get:OutputDirectory
|
|
abstract val outputDir: DirectoryProperty
|
|
|
|
@TaskAction
|
|
fun copy() {
|
|
val source = table.get().asFile
|
|
if (!source.isFile) throw GradleException("missing ${source.path}")
|
|
val root = outputDir.get().asFile
|
|
root.deleteRecursively()
|
|
val target = root.resolve(resourcePath.get())
|
|
target.parentFile.mkdirs()
|
|
source.copyTo(target)
|
|
}
|
|
}
|
|
|
|
val copyCategoryKeywords = tasks.register<CopyCategoryKeywords>("copyCategoryKeywords") {
|
|
description = "Copies contracts/category-keywords.csv into the main Java resources."
|
|
table.set(categoryKeywordsTable)
|
|
resourcePath.set("se/hagstedt/kvitt/data/category-keywords.csv")
|
|
outputDir.set(layout.buildDirectory.dir("generated/category-keywords"))
|
|
}
|
|
androidComponents {
|
|
onVariants { variant -> variant.sources.resources?.addGeneratedSourceDirectory(copyCategoryKeywords, CopyCategoryKeywords::outputDir) }
|
|
}
|