759 lines
29 KiB
Kotlin
759 lines
29 KiB
Kotlin
package de.hoennig.werkator.config
|
|
|
|
import io.kotest.assertions.throwables.shouldThrow
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.matchers.booleans.shouldBeFalse
|
|
import io.kotest.matchers.booleans.shouldBeTrue
|
|
import io.kotest.matchers.maps.shouldBeEmpty
|
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
|
import io.kotest.matchers.shouldBe
|
|
import io.kotest.matchers.string.shouldContain
|
|
import io.mockk.every
|
|
import io.mockk.mockk
|
|
import org.springframework.beans.factory.ObjectProvider
|
|
import org.springframework.boot.info.BuildProperties
|
|
import java.nio.file.Files
|
|
import java.util.Properties
|
|
|
|
class ConfigLoaderTest : FunSpec() {
|
|
private val loader = ConfigLoader()
|
|
|
|
/** A loader that knows which werkator it is, for the `werkator.version` checks. */
|
|
private fun loaderRunning(version: String): ConfigLoader {
|
|
val provider = mockk<ObjectProvider<BuildProperties>>()
|
|
every { provider.getIfAvailable() } returns BuildProperties(Properties().apply { setProperty("version", version) })
|
|
return ConfigLoader(provider)
|
|
}
|
|
|
|
init {
|
|
test("returns defaults when no config files exist") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
loader.load(dir) shouldBe WerkatorConfig()
|
|
}
|
|
|
|
test("loadRaw returns empty map when no config files exist") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
loader.loadRaw(dir).shouldBeEmpty()
|
|
}
|
|
|
|
test("reads gitea config from .werkator.yml") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
gitea:
|
|
owner: my-org
|
|
repo: my-repo
|
|
""".trimIndent(),
|
|
)
|
|
val config = loader.load(dir)
|
|
config.gitea.owner shouldBe "my-org"
|
|
config.gitea.repo shouldBe "my-repo"
|
|
}
|
|
|
|
test("reads executor.maxConcurrent and defaults it to 1") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
loader.load(dir).executor.maxConcurrent shouldBe 1
|
|
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
executor:
|
|
maxConcurrent: 3
|
|
""".trimIndent(),
|
|
)
|
|
loader.load(dir).executor.maxConcurrent shouldBe 3
|
|
}
|
|
|
|
test("the builds section holds named build definitions") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
executor:
|
|
maxConcurrent: 2
|
|
builds:
|
|
pitest:
|
|
trigger:
|
|
atTimes: ["01:00"]
|
|
branches: ["master", "release/*"]
|
|
activeWithin: 24h
|
|
buildCommand: ./gradlew piTestFull
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val config = loader.load(dir)
|
|
|
|
config.executor.maxConcurrent shouldBe 2
|
|
config.buildDefinitions shouldBe
|
|
mapOf(
|
|
"pitest" to
|
|
BuildDefinition(
|
|
trigger =
|
|
TriggerConfig(
|
|
atTimes = listOf("01:00"),
|
|
branches = listOf("master", "release/*"),
|
|
activeWithin = "24h",
|
|
),
|
|
buildCommand = "./gradlew piTestFull",
|
|
),
|
|
)
|
|
// the implicit default build (onPush over all branches) stays in place
|
|
config.effectiveBuildDefinitions()["default"] shouldBe BuildDefinition(trigger = TriggerConfig(onPush = true))
|
|
}
|
|
|
|
test("an explicit builds.default entry overrides the implicit default build") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
default:
|
|
trigger:
|
|
onPush: false
|
|
""".trimIndent(),
|
|
)
|
|
|
|
loader.load(dir).effectiveBuildDefinitions()["default"] shouldBe BuildDefinition(trigger = TriggerConfig(onPush = false))
|
|
}
|
|
|
|
test("a config that needs a newer werkator is refused, naming the file and both versions") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
werkator:
|
|
version:
|
|
since: "0.9.16"
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val error = shouldThrow<ConfigVersionException> { loaderRunning("0.9.15").load(dir) }
|
|
|
|
error.message.shouldNotBeNull().let {
|
|
it shouldContain ".werkator.yml"
|
|
it shouldContain "0.9.16"
|
|
it shouldContain "0.9.15"
|
|
it shouldContain "roll back"
|
|
}
|
|
}
|
|
|
|
test("a config within its declared range loads, and exceeding only the ceiling still loads") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
werkator:
|
|
version:
|
|
since: "0.9.16"
|
|
below: "1.0"
|
|
gitea:
|
|
owner: my-org
|
|
""".trimIndent(),
|
|
)
|
|
|
|
loaderRunning("0.9.16").load(dir).gitea.owner shouldBe "my-org"
|
|
// beyond `below`: a warning, never a refusal — an unmaintained marker must not stop a CI
|
|
loaderRunning("1.4.0").load(dir).gitea.owner shouldBe "my-org"
|
|
loaderRunning("0.9.16").load(dir).werkator.version shouldBe
|
|
VersionRequirement(since = "0.9.16", below = "1.0")
|
|
}
|
|
|
|
test("an incompatible branch config is refused as the branch's problem, not the server's") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText("gitea:\n owner: my-org")
|
|
|
|
val error =
|
|
shouldThrow<ConfigVersionException> {
|
|
loaderRunning("0.9.15").loadWithBranchLayer(
|
|
dir,
|
|
"""
|
|
werkator:
|
|
version:
|
|
since: "2.0.0"
|
|
""".trimIndent(),
|
|
)
|
|
}
|
|
|
|
error.message.shouldNotBeNull().let {
|
|
it shouldContain "branch"
|
|
it shouldContain "the other branches keep building"
|
|
}
|
|
// the primary config alone is untouched by the branch's declaration
|
|
loaderRunning("0.9.15").load(dir).gitea.owner shouldBe "my-org"
|
|
}
|
|
|
|
test("the machine config is checked as its own file") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".git/werkator").toFile().mkdirs()
|
|
dir.resolve(".git/werkator/.werkator.yml").toFile().writeText(
|
|
"""
|
|
werkator:
|
|
version:
|
|
since: "1.0.0"
|
|
""".trimIndent(),
|
|
)
|
|
|
|
shouldThrow<ConfigVersionException> {
|
|
loaderRunning("0.9.16").load(dir)
|
|
}.message.shouldNotBeNull() shouldContain ".git/werkator/.werkator.yml"
|
|
}
|
|
|
|
test("a leftover builds.maxConcurrent is ignored instead of failing the config") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
maxConcurrent: 1
|
|
pitest:
|
|
buildCommand: ./gradlew piTestFull
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val config = loader.load(dir)
|
|
|
|
config.executor.maxConcurrent shouldBe 1
|
|
config.buildDefinitions.keys shouldBe setOf("pitest")
|
|
}
|
|
|
|
test("a branch may redefine the builds section for its own builds") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
pitest:
|
|
trigger:
|
|
atTimes: ["01:00"]
|
|
buildCommand: ./gradlew piTestPartial
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-test-worktree")
|
|
worktree.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
pitest:
|
|
buildCommand: ./gradlew piTestFull
|
|
experiment:
|
|
trigger:
|
|
onPush: true
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val config = loader.loadForWorktree(dir, worktree)
|
|
|
|
// the branch layer merges into the definition instead of replacing it
|
|
config.buildDefinitions.getValue("pitest").buildCommand shouldBe "./gradlew piTestFull"
|
|
config.buildDefinitions
|
|
.getValue("pitest")
|
|
.trigger.atTimes shouldBe listOf("01:00")
|
|
config.buildDefinitions
|
|
.getValue("experiment")
|
|
.trigger.onPush shouldBe true
|
|
}
|
|
|
|
test("a branch cannot raise the concurrency limit or reach the sandbox policy through a build definition") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
default:
|
|
requirePullRequest: true
|
|
statusContext: werkator
|
|
docker:
|
|
enabled: true
|
|
network: none
|
|
image: host-image
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-test-worktree")
|
|
worktree.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
executor:
|
|
maxConcurrent: 99
|
|
watcher:
|
|
pullRequestGate: false
|
|
builds:
|
|
default:
|
|
requirePullRequest: false
|
|
statusContext: werkator/impersonated
|
|
docker:
|
|
enabled: false
|
|
network: host
|
|
image: attacker-image
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val config = loader.loadForWorktree(dir, worktree)
|
|
val settings = config.buildSettings("any-branch", "default")
|
|
|
|
config.executor.maxConcurrent shouldBe 1
|
|
config.watcher.pullRequestGate shouldBe true
|
|
settings.requirePullRequest shouldBe true
|
|
settings.docker.enabled shouldBe true
|
|
settings.docker.network shouldBe "none"
|
|
settings.statusContext shouldBe "werkator"
|
|
// everything that describes the build itself stays the branch's own business
|
|
settings.docker.image shouldBe "attacker-image"
|
|
}
|
|
|
|
test("a build the branch invents inherits the host's sandbox policy") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
default:
|
|
requirePullRequest: true
|
|
docker:
|
|
enabled: true
|
|
network: none
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-test-worktree")
|
|
worktree.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
invented:
|
|
trigger:
|
|
atTimes: ["03:00"]
|
|
buildCommand: ./gradlew whatever
|
|
docker:
|
|
enabled: false
|
|
network: host
|
|
""".trimIndent(),
|
|
)
|
|
|
|
// the host has never heard of this build, so there is no lower layer to fall
|
|
// back to — it must inherit the policy from builds.default, not the data class
|
|
val settings = loader.loadForWorktree(dir, worktree).buildSettings("any-branch", "invented")
|
|
|
|
settings.buildCommand shouldBe "./gradlew whatever"
|
|
settings.docker.enabled shouldBe true
|
|
settings.docker.network shouldBe "none"
|
|
settings.requirePullRequest shouldBe true
|
|
}
|
|
|
|
test("an exclusion pattern takes a branch out of a build that would otherwise select it") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
default:
|
|
trigger:
|
|
onPush: true
|
|
branches: ["*", "!master"]
|
|
release:
|
|
trigger:
|
|
onPush: true
|
|
branches: ["master"]
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val definitions = loader.load(dir).buildDefinitions
|
|
|
|
definitions
|
|
.getValue("default")
|
|
.trigger
|
|
.selectsByName("mihoe/feature")
|
|
.shouldBeTrue()
|
|
definitions
|
|
.getValue("default")
|
|
.trigger
|
|
.selectsByName("master")
|
|
.shouldBeFalse()
|
|
definitions
|
|
.getValue("release")
|
|
.trigger
|
|
.selectsByName("master")
|
|
.shouldBeTrue()
|
|
definitions
|
|
.getValue("release")
|
|
.trigger
|
|
.selectsByName("mihoe/feature")
|
|
.shouldBeFalse()
|
|
}
|
|
|
|
test("an exclusion wins over a matching pattern, whatever their order") {
|
|
val trigger = TriggerConfig(branches = listOf("!release/hotfix", "release/*"))
|
|
|
|
trigger.selectsByName("release/1.0").shouldBeTrue()
|
|
trigger.selectsByName("release/hotfix").shouldBeFalse()
|
|
}
|
|
|
|
test("a trigger key written outside the trigger block is refused, naming the definition") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
nightly:
|
|
atTimes: ["01:00"]
|
|
buildCommand: ./gradlew check
|
|
""".trimIndent(),
|
|
)
|
|
|
|
// ignoring it would leave the build without a trigger — a job that silently
|
|
// stops running is worse than a configuration that refuses to load
|
|
val thrown = shouldThrow<ConfigFormatException> { loader.load(dir) }
|
|
|
|
thrown.message.shouldContain(".werkator.yml")
|
|
thrown.message.shouldContain("builds.nightly: atTimes")
|
|
thrown.message.shouldContain("trigger:")
|
|
}
|
|
|
|
test("a branch writing its trigger flat fails only its own builds") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
default:
|
|
trigger:
|
|
onPush: true
|
|
""".trimIndent(),
|
|
)
|
|
|
|
shouldThrow<ConfigFormatException> {
|
|
loader.loadWithBranchLayer(
|
|
dir,
|
|
"""
|
|
builds:
|
|
experiment:
|
|
onPush: true
|
|
""".trimIndent(),
|
|
)
|
|
}.message.shouldContain("this branch")
|
|
// the primary config is untouched, so every other branch keeps building
|
|
loader
|
|
.load(dir)
|
|
.buildDefinitions
|
|
.getValue("default")
|
|
.trigger.onPush
|
|
.shouldBeTrue()
|
|
}
|
|
|
|
test("builds.default is the base of every other build, but never its trigger") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
builds:
|
|
default:
|
|
trigger:
|
|
onPush: true
|
|
branches: ["master"]
|
|
buildCommand: ./gradlew check
|
|
artifactDirs: [build/reports]
|
|
docker:
|
|
image: shared-image
|
|
nightly:
|
|
trigger:
|
|
atTimes: ["01:00"]
|
|
artifactDirs: [build/reports, build/libs]
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val nightly = loader.load(dir).buildDefinitions.getValue("nightly")
|
|
|
|
nightly.buildCommand shouldBe "./gradlew check"
|
|
nightly.docker?.image shouldBe "shared-image"
|
|
nightly.artifactDirs shouldBe listOf("build/reports", "build/libs")
|
|
// a trigger says when *this* build runs; inheriting it would fire every job at once
|
|
nightly.trigger.onPush shouldBe false
|
|
nightly.trigger.branches shouldBe emptyList<String>()
|
|
nightly.trigger.atTimes shouldBe listOf("01:00")
|
|
}
|
|
|
|
test("branches is honored while no build is defined and ignored as soon as one is") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
val legacy =
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: from-branches
|
|
docker:
|
|
enabled: true
|
|
""".trimIndent()
|
|
dir.resolve(".werkator.yml").toFile().writeText(legacy)
|
|
|
|
// the leftover execution key is not a definition, so the legacy section still wins
|
|
loader.load(dir).buildSettings("main", "default").buildCommand shouldBe "from-branches"
|
|
dir.resolve(".werkator.yml").toFile().writeText("builds:\n maxConcurrent: 1\n" + legacy)
|
|
loader.load(dir).buildSettings("main", "default").buildCommand shouldBe "from-branches"
|
|
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
legacy +
|
|
"\n" +
|
|
"""
|
|
builds:
|
|
default:
|
|
buildCommand: from-builds
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val settings = loader.load(dir).buildSettings("main", "default")
|
|
settings.buildCommand shouldBe "from-builds"
|
|
settings.docker.enabled shouldBe false
|
|
}
|
|
|
|
test("repo install config overrides project config for same keys") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
gitea:
|
|
owner: original-org
|
|
git:
|
|
token: from-project
|
|
""".trimIndent(),
|
|
)
|
|
dir.resolve(".git/werkator").toFile().mkdirs()
|
|
dir.resolve(".git/werkator/.werkator.yml").toFile().writeText(
|
|
"""
|
|
gitea:
|
|
owner: override-org
|
|
git:
|
|
token: from-repo-install
|
|
""".trimIndent(),
|
|
)
|
|
val config = loader.load(dir)
|
|
config.gitea.owner shouldBe "override-org"
|
|
config.git.token shouldBe "from-repo-install"
|
|
}
|
|
|
|
test("loadRaw only returns explicitly set values") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: ./mvnw test
|
|
""".trimIndent(),
|
|
)
|
|
val raw = loader.loadRaw(dir)
|
|
|
|
@Suppress("UNCHECKED_CAST")
|
|
val default = (raw["branches"] as Map<String, Any?>)["default"] as Map<String, Any?>
|
|
default["buildCommand"] shouldBe "./mvnw test"
|
|
default.containsKey("cleanCommand") shouldBe false
|
|
}
|
|
|
|
test("branch-specific config inherits from branches.default") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: ./mvnw test
|
|
main:
|
|
autoBuild:
|
|
enabled: true
|
|
""".trimIndent(),
|
|
)
|
|
val config = loader.load(dir)
|
|
config.branches["main"]!!.buildCommand shouldBe "./mvnw test"
|
|
config.branches["main"]!!.autoBuild.enabled shouldBe true
|
|
}
|
|
|
|
test("branch-specific buildCommand overrides branches.default") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: ./mvnw test
|
|
release:
|
|
buildCommand: ./mvnw -P release test
|
|
""".trimIndent(),
|
|
)
|
|
val config = loader.load(dir)
|
|
config.branches["release"]!!.buildCommand shouldBe "./mvnw -P release test"
|
|
}
|
|
|
|
test("empty publicBaseUrl defaults to https://<nginx.serverName>/ when set") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
server:
|
|
nginx:
|
|
serverName: ci.example.org
|
|
""".trimIndent(),
|
|
)
|
|
loader.load(dir).server.publicBaseUrl shouldBe "https://ci.example.org/"
|
|
}
|
|
|
|
test("explicit publicBaseUrl wins over the nginx.serverName default") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
server:
|
|
publicBaseUrl: https://other.example.org/
|
|
nginx:
|
|
serverName: ci.example.org
|
|
""".trimIndent(),
|
|
)
|
|
loader.load(dir).server.publicBaseUrl shouldBe "https://other.example.org/"
|
|
}
|
|
|
|
test("publicBaseUrl stays empty without an nginx.serverName") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
loader.load(dir).server.publicBaseUrl shouldBe ""
|
|
}
|
|
|
|
test("loadForWorktree lets the worktree override build config (worktree > .git > project)") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: from-project
|
|
""".trimIndent(),
|
|
)
|
|
dir.resolve(".git/werkator").toFile().mkdirs()
|
|
dir.resolve(".git/werkator/.werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: from-git
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-worktree")
|
|
worktree.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: from-worktree
|
|
""".trimIndent(),
|
|
)
|
|
loader.loadForWorktree(dir, worktree).branches["default"]!!.buildCommand shouldBe "from-worktree"
|
|
}
|
|
|
|
test("loadForWorktree falls back to .git over project when the worktree sets nothing") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: from-project
|
|
""".trimIndent(),
|
|
)
|
|
dir.resolve(".git/werkator").toFile().mkdirs()
|
|
dir.resolve(".git/werkator/.werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: from-git
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-worktree")
|
|
loader.loadForWorktree(dir, worktree).branches["default"]!!.buildCommand shouldBe "from-git"
|
|
}
|
|
|
|
test("loadForWorktree pins secrets and the docker sandbox policy to .git, but allows docker.image") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".git/werkator").toFile().mkdirs()
|
|
dir.resolve(".git/werkator/.werkator.yml").toFile().writeText(
|
|
"""
|
|
git:
|
|
token: real-secret
|
|
server:
|
|
port: 9000
|
|
branches:
|
|
default:
|
|
docker:
|
|
enabled: true
|
|
network: host
|
|
image: trusted-image
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-worktree")
|
|
worktree.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
git:
|
|
token: stolen
|
|
server:
|
|
port: 1234
|
|
branches:
|
|
default:
|
|
docker:
|
|
enabled: false
|
|
network: none
|
|
image: attacker-image
|
|
""".trimIndent(),
|
|
)
|
|
val config = loader.loadForWorktree(dir, worktree)
|
|
// pinned: never taken from the worktree
|
|
config.git.token shouldBe "real-secret"
|
|
config.server.port shouldBe 9000
|
|
config.branches["default"]!!.docker.enabled shouldBe true
|
|
config.branches["default"]!!.docker.network shouldBe "host"
|
|
// overridable: the worktree wins
|
|
config.branches["default"]!!.docker.image shouldBe "attacker-image"
|
|
}
|
|
|
|
test("loadWithBranchLayer applies a branch config read from git, pinning the same keys") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".git/werkator").toFile().mkdirs()
|
|
dir.resolve(".git/werkator/.werkator.yml").toFile().writeText(
|
|
"""
|
|
git:
|
|
token: real-secret
|
|
builds:
|
|
default:
|
|
buildCommand: from-git
|
|
""".trimIndent(),
|
|
)
|
|
|
|
val config =
|
|
loader.loadWithBranchLayer(
|
|
dir,
|
|
"""
|
|
git:
|
|
token: stolen
|
|
builds:
|
|
default:
|
|
buildCommand: from-branch
|
|
pitest:
|
|
trigger:
|
|
atTimes: ["03:00"]
|
|
buildCommand: ./gradlew piTestFull
|
|
""".trimIndent(),
|
|
)
|
|
|
|
config.git.token shouldBe "real-secret"
|
|
config.buildSettings("main", "default").buildCommand shouldBe "from-branch"
|
|
config.buildDefinitions
|
|
.getValue("pitest")
|
|
.trigger.atTimes shouldBe listOf("03:00")
|
|
}
|
|
|
|
test("loadWithBranchLayer without a branch config equals load") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
branches:
|
|
default:
|
|
buildCommand: ./mvnw test
|
|
""".trimIndent(),
|
|
)
|
|
|
|
loader.loadWithBranchLayer(dir, null) shouldBe loader.load(dir)
|
|
loader.loadWithBranchLayer(dir, "") shouldBe loader.load(dir)
|
|
}
|
|
|
|
test("loadForWorktree without a worktree config equals load") {
|
|
val dir = Files.createTempDirectory("werkator-test")
|
|
dir.resolve(".werkator.yml").toFile().writeText(
|
|
"""
|
|
gitea:
|
|
owner: my-org
|
|
branches:
|
|
default:
|
|
buildCommand: ./mvnw test
|
|
""".trimIndent(),
|
|
)
|
|
val worktree = Files.createTempDirectory("werkator-worktree")
|
|
loader.loadForWorktree(dir, worktree) shouldBe loader.load(dir)
|
|
}
|
|
|
|
test("toYaml serializes werkatorConfig with all sections") {
|
|
val yaml = loader.toYaml(WerkatorConfig())
|
|
yaml shouldContain "server:"
|
|
yaml shouldContain "git:"
|
|
yaml shouldContain "gitea:"
|
|
yaml shouldContain "artifacts:"
|
|
yaml shouldContain "watcher:"
|
|
yaml shouldContain "branches:"
|
|
}
|
|
}
|
|
}
|