implemented 03-gitea-client.md: added Gitea API client for commit statuses, status mapping, and extensive tests
This commit is contained in:
@@ -23,6 +23,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter")
|
||||
implementation("org.springframework:spring-web")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("info.picocli:picocli-spring-boot-starter:4.7.6")
|
||||
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
|
||||
|
||||
@@ -38,3 +38,16 @@ WireMock (already a test dependency, see `WireMockSmokeTest`):
|
||||
|
||||
- `./gradlew ktlintFormat` then `./gradlew build` is green.
|
||||
- No call path throws when Gitea is unconfigured or down.
|
||||
|
||||
## Execution Notes (2026-07-07)
|
||||
|
||||
Implemented as designed; deviations and decisions:
|
||||
|
||||
- Added `org.springframework:spring-web` as a dependency; `RestClient` lives there and `spring-boot-starter` alone does not provide it.
|
||||
- `publishStatus` takes a `BuildStatus` and maps it internally instead of a raw Gitea state string.
|
||||
The forward mapping never produces `error` because the `BuildStatus` enum is exhaustive; legacy emitted `error` only for unknown status strings.
|
||||
- `readStatus` returns a sealed `GiteaStatusResult` (`Found`/`None`/`Disabled`/`Error`) so callers get explicit non-fatal error states instead of exceptions.
|
||||
- `resolveUsername` only requires `gitea.baseUrl` and `git.token`; legacy gated it on the full status-enabled check including owner/repo, which the `/api/v1/user` endpoint does not need.
|
||||
- Responses are read as strings and parsed with a dedicated Jackson `ObjectMapper` instead of RestClient message converters, keeping malformed-JSON handling explicit and independent of converter auto-detection.
|
||||
- The legacy "Build status deleted" description marker is not ported; it belongs to the result-delete feature of later steps.
|
||||
- No config changes were needed: `gitea.*` and `git.token` already exist in `GitTallyConfig`, the `init` templates, and `docs/configuration.md`.
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ Foundation:
|
||||
|
||||
- [x] `01-build-state-domain.md` — build result domain model and persistent repository
|
||||
- [x] `02-git-gateway.md` — full git access layer (fetch, branches, commits, checkout)
|
||||
- [ ] `03-gitea-client.md` — Gitea API client for commit statuses
|
||||
- [x] `03-gitea-client.md` — Gitea API client for commit statuses
|
||||
|
||||
Core engine:
|
||||
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package de.hoennig.gittally.gitea
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.config.GitTallyConfig
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.client.RestClient
|
||||
import org.springframework.web.client.RestClientException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
/** Outcome of [GiteaClient.readStatus]; errors are values, never exceptions. */
|
||||
sealed interface GiteaStatusResult {
|
||||
/** The newest status matching the configured context. */
|
||||
data class Found(
|
||||
val status: BuildStatus,
|
||||
) : GiteaStatusResult
|
||||
|
||||
/** Gitea answered, but no status matches the configured context. */
|
||||
data object None : GiteaStatusResult
|
||||
|
||||
/** The client is not configured, see [GiteaClient.isEnabled]. */
|
||||
data object Disabled : GiteaStatusResult
|
||||
|
||||
/** Gitea is unreachable, answered with an error, or sent an unusable response. */
|
||||
data class Error(
|
||||
val message: String,
|
||||
) : GiteaStatusResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Client for the Gitea commit-status API.
|
||||
* All methods are non-fatal: when Gitea is unconfigured, unreachable, or failing,
|
||||
* they log and return a fallback value instead of throwing.
|
||||
*/
|
||||
@Service
|
||||
class GiteaClient(
|
||||
private val configLoader: ConfigLoader,
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(GiteaClient::class.java)
|
||||
|
||||
private val json =
|
||||
ObjectMapper()
|
||||
.registerKotlinModule()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
|
||||
fun isEnabled(workingDir: Path = Paths.get(".")): Boolean = isEnabled(configLoader.load(workingDir))
|
||||
|
||||
/** Publishes a commit status for [sha]; returns false when disabled or the request failed. */
|
||||
fun publishStatus(
|
||||
sha: String,
|
||||
status: BuildStatus,
|
||||
description: String,
|
||||
targetUrl: String? = null,
|
||||
workingDir: Path = Paths.get("."),
|
||||
): Boolean {
|
||||
val config = configLoader.load(workingDir)
|
||||
if (!isEnabled(config)) {
|
||||
log.debug("Gitea status publishing is disabled; not publishing status for {}.", sha)
|
||||
return false
|
||||
}
|
||||
val body = mutableMapOf<String, String>()
|
||||
body["state"] = status.toGiteaState()
|
||||
body["context"] = config.gitea.statusContext
|
||||
body["description"] = description
|
||||
if (!targetUrl.isNullOrBlank()) {
|
||||
body["target_url"] = targetUrl
|
||||
}
|
||||
return try {
|
||||
restClient(config)
|
||||
.post()
|
||||
.uri("/api/v1/repos/{owner}/{repo}/statuses/{sha}", config.gitea.owner, config.gitea.repo, sha)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(json.writeValueAsString(body))
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
true
|
||||
} catch (e: RestClientException) {
|
||||
log.warn("Could not publish Gitea status for {}: {}", sha, e.message)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads the newest commit status for [sha] matching the configured `gitea.statusContext`. */
|
||||
fun readStatus(
|
||||
sha: String,
|
||||
workingDir: Path = Paths.get("."),
|
||||
): GiteaStatusResult {
|
||||
val config = configLoader.load(workingDir)
|
||||
if (!isEnabled(config)) {
|
||||
return GiteaStatusResult.Disabled
|
||||
}
|
||||
val body =
|
||||
try {
|
||||
restClient(config)
|
||||
.get()
|
||||
.uri(
|
||||
"/api/v1/repos/{owner}/{repo}/commits/{sha}/statuses?sort=recentupdate",
|
||||
config.gitea.owner,
|
||||
config.gitea.repo,
|
||||
sha,
|
||||
).retrieve()
|
||||
.body(String::class.java)
|
||||
} catch (e: RestClientException) {
|
||||
log.warn("Could not read Gitea status for {}: {}", sha, e.message)
|
||||
return GiteaStatusResult.Error("Gitea status request failed: ${e.message}")
|
||||
} ?: return GiteaStatusResult.Error("Gitea status request returned an empty response")
|
||||
val statuses =
|
||||
try {
|
||||
json.readValue(body, object : TypeReference<List<GiteaCommitStatus>>() {})
|
||||
} catch (e: JacksonException) {
|
||||
log.warn("Could not parse Gitea status response for {}: {}", sha, e.message)
|
||||
return GiteaStatusResult.Error("Gitea status response is not valid JSON: ${e.message}")
|
||||
}
|
||||
// sort=recentupdate returns newest first, so the first context match is the current status
|
||||
val newest =
|
||||
statuses.firstOrNull { it.context == config.gitea.statusContext }
|
||||
?: return GiteaStatusResult.None
|
||||
val status =
|
||||
buildStatusFromGiteaState(newest.state.orEmpty())
|
||||
?: return GiteaStatusResult.Error("Gitea reported unknown status state '${newest.state}'")
|
||||
return GiteaStatusResult.Found(status)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the username owning `git.token` via the Gitea user API;
|
||||
* used as fallback for `git.account`. Returns null when unconfigured or on failure.
|
||||
* Unlike [isEnabled] this only needs `gitea.baseUrl` and `git.token`.
|
||||
*/
|
||||
fun resolveUsername(workingDir: Path = Paths.get(".")): String? {
|
||||
val config = configLoader.load(workingDir)
|
||||
if (config.gitea.baseUrl.isBlank() || config.git.token.isBlank()) {
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
restClient(config)
|
||||
.get()
|
||||
.uri("/api/v1/user")
|
||||
.retrieve()
|
||||
.body(String::class.java)
|
||||
?.let {
|
||||
json
|
||||
.readTree(it)
|
||||
.path("login")
|
||||
.asText()
|
||||
.ifEmpty { null }
|
||||
}
|
||||
} catch (e: RestClientException) {
|
||||
log.warn("Could not resolve Gitea username: {}", e.message)
|
||||
null
|
||||
} catch (e: JacksonException) {
|
||||
log.warn("Could not parse Gitea user response: {}", e.message)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEnabled(config: GitTallyConfig): Boolean =
|
||||
config.gitea.baseUrl.isNotBlank() &&
|
||||
config.gitea.owner.isNotBlank() &&
|
||||
config.gitea.repo.isNotBlank() &&
|
||||
config.git.token.isNotBlank()
|
||||
|
||||
private fun restClient(config: GitTallyConfig): RestClient =
|
||||
RestClient
|
||||
.builder()
|
||||
.baseUrl(config.gitea.baseUrl.trimEnd('/'))
|
||||
.defaultHeader("Authorization", "token ${config.git.token}")
|
||||
.build()
|
||||
|
||||
private data class GiteaCommitStatus(
|
||||
val context: String? = null,
|
||||
val state: String? = null,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.hoennig.gittally.gitea
|
||||
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
|
||||
/** Gitea commit-status state published for this build status. */
|
||||
fun BuildStatus.toGiteaState(): String =
|
||||
when (this) {
|
||||
BuildStatus.SUCCESS -> "success"
|
||||
BuildStatus.FAILED, BuildStatus.INTERRUPTED, BuildStatus.CANCELLED -> "failure"
|
||||
BuildStatus.PENDING, BuildStatus.RUNNING -> "pending"
|
||||
}
|
||||
|
||||
/**
|
||||
* Build status for a Gitea commit-status state, or null for unknown states.
|
||||
* The reverse mapping is lossy: Gitea only distinguishes success/failure/pending.
|
||||
*/
|
||||
fun buildStatusFromGiteaState(state: String): BuildStatus? =
|
||||
when (state) {
|
||||
"success" -> BuildStatus.SUCCESS
|
||||
"failure", "error", "warning" -> BuildStatus.FAILED
|
||||
"pending" -> BuildStatus.RUNNING
|
||||
else -> null
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package de.hoennig.gittally.gitea
|
||||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.equalTo
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.equalToJson
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.get
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.okJson
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.post
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor
|
||||
import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.config.GitConfig
|
||||
import de.hoennig.gittally.config.GitTallyConfig
|
||||
import de.hoennig.gittally.config.GiteaConfig
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.nulls.shouldBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.types.shouldBeInstanceOf
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class GiteaClientTest :
|
||||
FunSpec({
|
||||
|
||||
val server = WireMockServer(options().dynamicPort())
|
||||
val configLoader = mockk<ConfigLoader>()
|
||||
val client = GiteaClient(configLoader)
|
||||
|
||||
val publishUrl = "/api/v1/repos/the-owner/the-repo/statuses/abc123"
|
||||
val readUrl = "/api/v1/repos/the-owner/the-repo/commits/abc123/statuses?sort=recentupdate"
|
||||
|
||||
fun configure(
|
||||
baseUrl: String = "http://localhost:${server.port()}",
|
||||
owner: String = "the-owner",
|
||||
repo: String = "the-repo",
|
||||
token: String = "secret-token",
|
||||
) {
|
||||
every { configLoader.load(any()) } returns
|
||||
GitTallyConfig(
|
||||
git = GitConfig(token = token),
|
||||
gitea = GiteaConfig(baseUrl = baseUrl, owner = owner, repo = repo, statusContext = "GitTally"),
|
||||
)
|
||||
}
|
||||
|
||||
beforeSpec { server.start() }
|
||||
afterSpec { server.stop() }
|
||||
beforeEach {
|
||||
server.resetAll()
|
||||
configure()
|
||||
}
|
||||
|
||||
context("isEnabled") {
|
||||
test("is true when baseUrl, owner, repo, and token are configured") {
|
||||
client.isEnabled() shouldBe true
|
||||
}
|
||||
|
||||
test("is false when any required value is missing") {
|
||||
configure(baseUrl = "")
|
||||
client.isEnabled() shouldBe false
|
||||
configure(owner = "")
|
||||
client.isEnabled() shouldBe false
|
||||
configure(repo = "")
|
||||
client.isEnabled() shouldBe false
|
||||
configure(token = "")
|
||||
client.isEnabled() shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
context("publishStatus") {
|
||||
test("posts state, context, description, and target_url with token auth") {
|
||||
server.stubFor(post(publishUrl).willReturn(aResponse().withStatus(201)))
|
||||
|
||||
val published =
|
||||
client.publishStatus(
|
||||
sha = "abc123",
|
||||
status = BuildStatus.SUCCESS,
|
||||
description = "Build succeeded",
|
||||
targetUrl = "https://ci.example.org/branches/main/index.html",
|
||||
)
|
||||
|
||||
published shouldBe true
|
||||
server.verify(
|
||||
postRequestedFor(urlEqualTo(publishUrl))
|
||||
.withHeader("Authorization", equalTo("token secret-token"))
|
||||
.withHeader("Content-Type", equalTo("application/json"))
|
||||
.withRequestBody(
|
||||
equalToJson(
|
||||
"""
|
||||
{
|
||||
"state": "success",
|
||||
"context": "GitTally",
|
||||
"description": "Build succeeded",
|
||||
"target_url": "https://ci.example.org/branches/main/index.html"
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
test("omits target_url when none is given") {
|
||||
server.stubFor(post(publishUrl).willReturn(aResponse().withStatus(201)))
|
||||
|
||||
client.publishStatus("abc123", BuildStatus.RUNNING, "Build running") shouldBe true
|
||||
|
||||
server.verify(
|
||||
postRequestedFor(urlEqualTo(publishUrl))
|
||||
.withRequestBody(
|
||||
equalToJson("""{"state": "pending", "context": "GitTally", "description": "Build running"}"""),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
test("maps every build status to the documented Gitea state") {
|
||||
server.stubFor(post(publishUrl).willReturn(aResponse().withStatus(201)))
|
||||
val expectedStates =
|
||||
mapOf(
|
||||
BuildStatus.SUCCESS to "success",
|
||||
BuildStatus.FAILED to "failure",
|
||||
BuildStatus.INTERRUPTED to "failure",
|
||||
BuildStatus.CANCELLED to "failure",
|
||||
BuildStatus.PENDING to "pending",
|
||||
BuildStatus.RUNNING to "pending",
|
||||
)
|
||||
|
||||
expectedStates.forEach { (status, state) ->
|
||||
server.resetRequests()
|
||||
client.publishStatus("abc123", status, "d") shouldBe true
|
||||
server.verify(
|
||||
postRequestedFor(urlEqualTo(publishUrl))
|
||||
.withRequestBody(equalToJson("""{"state": "$state", "context": "GitTally", "description": "d"}""")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test("returns false without a request when disabled") {
|
||||
configure(token = "")
|
||||
|
||||
client.publishStatus("abc123", BuildStatus.SUCCESS, "d") shouldBe false
|
||||
|
||||
server.findAll(postRequestedFor(urlEqualTo(publishUrl))).size shouldBe 0
|
||||
}
|
||||
|
||||
test("returns false on HTTP errors instead of throwing") {
|
||||
server.stubFor(post(publishUrl).willReturn(aResponse().withStatus(500)))
|
||||
|
||||
client.publishStatus("abc123", BuildStatus.SUCCESS, "d") shouldBe false
|
||||
}
|
||||
|
||||
test("returns false when Gitea is unreachable instead of throwing") {
|
||||
configure(baseUrl = "http://localhost:1")
|
||||
|
||||
client.publishStatus("abc123", BuildStatus.SUCCESS, "d") shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
context("readStatus") {
|
||||
test("requests statuses sorted by recentupdate with token auth") {
|
||||
server.stubFor(get(readUrl).willReturn(okJson("[]")))
|
||||
|
||||
client.readStatus("abc123")
|
||||
|
||||
server.verify(
|
||||
getRequestedFor(urlEqualTo(readUrl))
|
||||
.withHeader("Authorization", equalTo("token secret-token")),
|
||||
)
|
||||
}
|
||||
|
||||
test("returns the newest status matching the configured context") {
|
||||
server.stubFor(
|
||||
get(readUrl).willReturn(
|
||||
okJson(
|
||||
"""
|
||||
[
|
||||
{"context": "other-ci", "state": "failure"},
|
||||
{"context": "GitTally", "state": "success", "description": "Build succeeded"},
|
||||
{"context": "GitTally", "state": "pending"}
|
||||
]
|
||||
""".trimIndent(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
client.readStatus("abc123") shouldBe GiteaStatusResult.Found(BuildStatus.SUCCESS)
|
||||
}
|
||||
|
||||
test("returns None when no status matches the context") {
|
||||
server.stubFor(get(readUrl).willReturn(okJson("""[{"context": "other-ci", "state": "success"}]""")))
|
||||
|
||||
client.readStatus("abc123") shouldBe GiteaStatusResult.None
|
||||
}
|
||||
|
||||
test("returns None for an empty status list") {
|
||||
server.stubFor(get(readUrl).willReturn(okJson("[]")))
|
||||
|
||||
client.readStatus("abc123") shouldBe GiteaStatusResult.None
|
||||
}
|
||||
|
||||
test("returns Error for malformed JSON instead of throwing") {
|
||||
server.stubFor(get(readUrl).willReturn(okJson("""{"oops": "not a list"!!!""")))
|
||||
|
||||
client.readStatus("abc123").shouldBeInstanceOf<GiteaStatusResult.Error>()
|
||||
}
|
||||
|
||||
test("returns Error for an unknown state value") {
|
||||
server.stubFor(get(readUrl).willReturn(okJson("""[{"context": "GitTally", "state": "hovering"}]""")))
|
||||
|
||||
client.readStatus("abc123").shouldBeInstanceOf<GiteaStatusResult.Error>()
|
||||
}
|
||||
|
||||
test("returns Error on HTTP 4xx/5xx instead of throwing") {
|
||||
server.stubFor(get(readUrl).willReturn(aResponse().withStatus(404)))
|
||||
client.readStatus("abc123").shouldBeInstanceOf<GiteaStatusResult.Error>()
|
||||
|
||||
server.stubFor(get(readUrl).willReturn(aResponse().withStatus(500)))
|
||||
client.readStatus("abc123").shouldBeInstanceOf<GiteaStatusResult.Error>()
|
||||
}
|
||||
|
||||
test("returns Error when Gitea is unreachable instead of throwing") {
|
||||
configure(baseUrl = "http://localhost:1")
|
||||
|
||||
client.readStatus("abc123").shouldBeInstanceOf<GiteaStatusResult.Error>()
|
||||
}
|
||||
|
||||
test("returns Disabled when unconfigured, without a request") {
|
||||
configure(owner = "")
|
||||
|
||||
client.readStatus("abc123") shouldBe GiteaStatusResult.Disabled
|
||||
|
||||
server.findAll(getRequestedFor(urlEqualTo(readUrl))).size shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
context("resolveUsername") {
|
||||
test("returns the login of the token's user") {
|
||||
server.stubFor(get("/api/v1/user").willReturn(okJson("""{"id": 42, "login": "the-user"}""")))
|
||||
|
||||
client.resolveUsername() shouldBe "the-user"
|
||||
|
||||
server.verify(
|
||||
getRequestedFor(urlEqualTo("/api/v1/user"))
|
||||
.withHeader("Authorization", equalTo("token secret-token")),
|
||||
)
|
||||
}
|
||||
|
||||
test("only needs baseUrl and token, not owner/repo") {
|
||||
configure(owner = "", repo = "")
|
||||
server.stubFor(get("/api/v1/user").willReturn(okJson("""{"login": "the-user"}""")))
|
||||
|
||||
client.resolveUsername() shouldBe "the-user"
|
||||
}
|
||||
|
||||
test("returns null when baseUrl or token is missing") {
|
||||
configure(baseUrl = "")
|
||||
client.resolveUsername().shouldBeNull()
|
||||
|
||||
configure(token = "")
|
||||
client.resolveUsername().shouldBeNull()
|
||||
}
|
||||
|
||||
test("returns null on HTTP errors, missing login, or malformed JSON instead of throwing") {
|
||||
server.stubFor(get("/api/v1/user").willReturn(aResponse().withStatus(401)))
|
||||
client.resolveUsername().shouldBeNull()
|
||||
|
||||
server.stubFor(get("/api/v1/user").willReturn(okJson("""{"id": 42}""")))
|
||||
client.resolveUsername().shouldBeNull()
|
||||
|
||||
server.stubFor(get("/api/v1/user").willReturn(okJson("not json")))
|
||||
client.resolveUsername().shouldBeNull()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.hoennig.gittally.gitea
|
||||
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.nulls.shouldBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
|
||||
class GiteaStateMappingTest :
|
||||
FunSpec({
|
||||
|
||||
test("maps each build status to its Gitea state") {
|
||||
BuildStatus.SUCCESS.toGiteaState() shouldBe "success"
|
||||
BuildStatus.FAILED.toGiteaState() shouldBe "failure"
|
||||
BuildStatus.INTERRUPTED.toGiteaState() shouldBe "failure"
|
||||
BuildStatus.CANCELLED.toGiteaState() shouldBe "failure"
|
||||
BuildStatus.PENDING.toGiteaState() shouldBe "pending"
|
||||
BuildStatus.RUNNING.toGiteaState() shouldBe "pending"
|
||||
}
|
||||
|
||||
test("maps Gitea states back to build statuses") {
|
||||
buildStatusFromGiteaState("success") shouldBe BuildStatus.SUCCESS
|
||||
buildStatusFromGiteaState("failure") shouldBe BuildStatus.FAILED
|
||||
buildStatusFromGiteaState("error") shouldBe BuildStatus.FAILED
|
||||
buildStatusFromGiteaState("warning") shouldBe BuildStatus.FAILED
|
||||
buildStatusFromGiteaState("pending") shouldBe BuildStatus.RUNNING
|
||||
}
|
||||
|
||||
test("maps unknown Gitea states to null") {
|
||||
buildStatusFromGiteaState("").shouldBeNull()
|
||||
buildStatusFromGiteaState("deleted").shouldBeNull()
|
||||
buildStatusFromGiteaState("SUCCESS").shouldBeNull()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user