test(arch): keep Room and query strings inside the data layer
The seam is only worth having if something checks it. This fails the build if androidx.room, an entity or a query string appears outside data/, rather than leaving it to review to notice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
package de.jeanlucmakiola.clockula
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* docs/PLAN.md §0's boundary, made mechanical: the app talks to repository
|
||||||
|
* interfaces and plain-Kotlin models, and only `data/` knows Room exists.
|
||||||
|
* A grep is a weaker tool than a compiler, but it is the one that fails the
|
||||||
|
* build when someone reaches through the seam.
|
||||||
|
*/
|
||||||
|
class ArchitectureRulesTest {
|
||||||
|
|
||||||
|
private val mainSourceRoot: File = listOf(File("src/main/java"), File("app/src/main/java"))
|
||||||
|
.firstOrNull { it.isDirectory }
|
||||||
|
?: error("Could not resolve the main source root from ${File("").absolutePath}")
|
||||||
|
|
||||||
|
private val kotlinSources: List<File> =
|
||||||
|
mainSourceRoot.walkTopDown().filter { it.isFile && it.extension == "kt" }.toList()
|
||||||
|
|
||||||
|
private val outsideDataLayer: List<File> =
|
||||||
|
kotlinSources.filterNot { it.invariantSeparatorsPath.contains("de/jeanlucmakiola/clockula/data/") }
|
||||||
|
|
||||||
|
private fun List<File>.containing(vararg needles: String): List<String> =
|
||||||
|
filter { file -> needles.any { it in file.readText() } }.map { it.invariantSeparatorsPath }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Room does not leak above the data layer`() {
|
||||||
|
val offenders = outsideDataLayer.containing("androidx.room")
|
||||||
|
|
||||||
|
assertThat(offenders).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no Dao or Query annotation appears outside the data layer`() {
|
||||||
|
val offenders = outsideDataLayer.containing("@Dao", "@Query")
|
||||||
|
|
||||||
|
assertThat(offenders).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no entity or dao type is named outside the data layer`() {
|
||||||
|
val offenders = outsideDataLayer.containing(
|
||||||
|
"AlarmEntity",
|
||||||
|
"TimerEntity",
|
||||||
|
"WorldClockEntity",
|
||||||
|
"LapEntity",
|
||||||
|
"AlarmDao",
|
||||||
|
"TimerDao",
|
||||||
|
"WorldClockDao",
|
||||||
|
"LapDao",
|
||||||
|
"ClockulaDatabase",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(offenders).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `the domain is free of Android types`() {
|
||||||
|
val domain = kotlinSources
|
||||||
|
.filter { it.invariantSeparatorsPath.contains("de/jeanlucmakiola/clockula/domain/") }
|
||||||
|
val offenders = domain.filter { file ->
|
||||||
|
file.readLines().any { it.startsWith("import android.") || it.startsWith("import androidx.") }
|
||||||
|
}.map { it.invariantSeparatorsPath }
|
||||||
|
|
||||||
|
assertThat(offenders).isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user