Skip to content

Commit 35d78ce

Browse files
authored
test(shared): add basic tests for fetching available versions (#1857)
Part of #1855. Prior to making functional changes to the production code, let's add tests that will help us ensure the issue will get fixed in the next PR(s).
1 parent ec05b73 commit 35d78ce

File tree

6 files changed

+186
-12
lines changed

6 files changed

+186
-12
lines changed

action-binding-generator/api/action-binding-generator.api

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ public synthetic class io/github/typesafegithub/workflows/actionbindinggenerator
116116
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
117117
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input;)V
118118
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
119-
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
120119
}
121120

122121
public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input$Companion {
@@ -150,7 +149,6 @@ public synthetic class io/github/typesafegithub/workflows/actionbindinggenerator
150149
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
151150
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;)V
152151
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
153-
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
154152
}
155153

156154
public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata$Companion {
@@ -184,7 +182,6 @@ public synthetic class io/github/typesafegithub/workflows/actionbindinggenerator
184182
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
185183
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Output;)V
186184
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
187-
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
188185
}
189186

190187
public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Output$Companion {

shared-internal/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ version = rootProject.version
1111
dependencies {
1212
// we cannot use a BOM due to limitation in kotlin scripting when resolving the transitive KMM variant dependencies
1313
// note: see https://youtrack.jetbrains.com/issue/KT-67618
14-
implementation("io.ktor:ktor-client-core:3.1.1")
14+
api("io.ktor:ktor-client-core:3.1.1")
1515
implementation("io.ktor:ktor-client-cio:3.1.1")
1616
implementation("io.ktor:ktor-client-content-negotiation:3.1.1")
1717
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.1")
@@ -24,4 +24,6 @@ dependencies {
2424
// I'm bumping kotlinx-io to 0.6.0 in kotlinx.serialization here: https://github.com/Kotlin/kotlinx.serialization/pull/2933
2525
// Here's a ticket to remember to remove this workaround: https://github.com/typesafegithub/github-workflows-kt/issues/1832
2626
runtimeOnly("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0")
27+
28+
testImplementation("io.ktor:ktor-client-mock:3.1.1")
2729
}

shared-internal/src/main/kotlin/io/github/typesafegithub/workflows/shared/internal/GithubApi.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package io.github.typesafegithub.workflows.shared.internal
33
import io.github.typesafegithub.workflows.shared.internal.model.Version
44
import io.ktor.client.HttpClient
55
import io.ktor.client.call.body
6+
import io.ktor.client.engine.HttpClientEngine
7+
import io.ktor.client.engine.cio.CIO
68
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
79
import io.ktor.client.request.bearerAuth
810
import io.ktor.client.request.get
@@ -15,14 +17,20 @@ suspend fun fetchAvailableVersions(
1517
owner: String,
1618
name: String,
1719
githubToken: String?,
18-
): List<Version> =
19-
listOf(
20+
httpClientEngine: HttpClientEngine = CIO.create(),
21+
): List<Version> {
22+
val httpClient = buildHttpClient(engine = httpClientEngine)
23+
return listOf(
2024
apiTagsUrl(owner = owner, name = name),
2125
apiBranchesUrl(owner = owner, name = name),
22-
).flatMap { url -> fetchGithubRefs(url, githubToken) }
23-
.versions(githubToken)
26+
).flatMap { url -> fetchGithubRefs(url, githubToken, httpClient) }
27+
.versions(githubToken, httpClient)
28+
}
2429

25-
private fun List<GithubRef>.versions(githubToken: String?): List<Version> =
30+
private fun List<GithubRef>.versions(
31+
githubToken: String?,
32+
httpClient: HttpClient,
33+
): List<Version> =
2634
this.map { githubRef ->
2735
val version = githubRef.ref.substringAfterLast("/")
2836
Version(version) {
@@ -46,6 +54,7 @@ private fun List<GithubRef>.versions(githubToken: String?): List<Version> =
4654
private suspend fun fetchGithubRefs(
4755
url: String,
4856
githubToken: String?,
57+
httpClient: HttpClient,
4958
): List<GithubRef> =
5059
httpClient
5160
.get(urlString = url) {
@@ -91,8 +100,8 @@ private data class Person(
91100
val date: String,
92101
)
93102

94-
private val httpClient by lazy {
95-
HttpClient {
103+
private fun buildHttpClient(engine: HttpClientEngine) =
104+
HttpClient(engine) {
96105
install(ContentNegotiation) {
97106
json(
98107
Json {
@@ -101,4 +110,3 @@ private val httpClient by lazy {
101110
)
102111
}
103112
}
104-
}

shared-internal/src/main/kotlin/io/github/typesafegithub/workflows/shared/internal/model/Version.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ data class Version(
3434
return version.compareTo(other.version)
3535
}
3636

37+
override fun equals(other: Any?): Boolean = this.compareTo(other as Version) == 0
38+
3739
override fun toString(): String = version
3840

3941
fun isMajorVersion(): Boolean = versionIntParts.singleOrNull() != null
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package io.github.typesafegithub.workflows.shared.internal.model
2+
3+
import io.github.typesafegithub.workflows.shared.internal.fetchAvailableVersions
4+
import io.kotest.assertions.throwables.shouldThrow
5+
import io.kotest.core.spec.style.FunSpec
6+
import io.kotest.matchers.shouldBe
7+
import io.kotest.matchers.string.shouldContain
8+
import io.ktor.client.engine.mock.MockEngine
9+
import io.ktor.client.engine.mock.respond
10+
import io.ktor.http.HttpHeaders
11+
import io.ktor.http.HttpStatusCode
12+
import io.ktor.http.fullPath
13+
import io.ktor.http.headersOf
14+
import io.ktor.serialization.JsonConvertException
15+
import io.ktor.utils.io.ByteReadChannel
16+
17+
class GithubApiTest :
18+
FunSpec({
19+
test("branches with major versions and tags with other versions") {
20+
// Given
21+
val mockEngine =
22+
MockEngine { request ->
23+
if ("matching-refs/tags" in request.url.fullPath) {
24+
respond(
25+
// language=json
26+
content =
27+
ByteReadChannel(
28+
"""
29+
[
30+
{
31+
"ref":"refs/tags/v1.0.0",
32+
"node_id":"MDM6UmVmMTk3ODE0NjI5OnJlZnMvdGFncy92MQ==",
33+
"url":"https://api.github.com/repos/some-owner/some-name/git/refs/tags/v1",
34+
"object": {
35+
"sha":"544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9",
36+
"type":"tag",
37+
"url":"https://api.github.com/repos/actions/some-name/git/tags/544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9"
38+
}
39+
},
40+
{
41+
"ref":"refs/tags/v1.0.1",
42+
"node_id":"MDM6UmVmMTk3ODE0NjI5OnJlZnMvdGFncy92MQ==",
43+
"url":"https://api.github.com/repos/some-owner/some-name/git/refs/tags/v1.0.1",
44+
"object": {
45+
"sha":"af513c7a016048ae468971c52ed77d9562c7c819",
46+
"type":"tag",
47+
"url":"https://api.github.com/repos/actions/some-name/git/tags/af513c7a016048ae468971c52ed77d9562c7c819"
48+
}
49+
}
50+
]
51+
""".trimIndent(),
52+
),
53+
status = HttpStatusCode.OK,
54+
headers = headersOf(HttpHeaders.ContentType, "application/json"),
55+
)
56+
} else if ("matching-refs/heads" in request.url.fullPath) {
57+
respond(
58+
// language=json
59+
content =
60+
ByteReadChannel(
61+
"""
62+
[
63+
{
64+
"ref":"refs/heads/v1",
65+
"node_id":"MDM6UmVmMTk3ODE0NjI5OnJlZnMvaGVhZHMvdm1qb3NlcGgvc2lsZW50LXJldi1wYXJzZQ==",
66+
"url":"https://api.github.com/repos/some-owner/some-name/git/refs/heads/v1",
67+
"object": {
68+
"sha":"af5130cb8882054eda385840657dcbd1e19ab8f4",
69+
"type":"commit",
70+
"url":"https://api.github.com/repos/some-owner/some-name/git/commits/af5130cb8882054eda385840657dcbd1e19ab8f4"
71+
}
72+
},
73+
{
74+
"ref":"refs/heads/v2",
75+
"node_id":"MDM6UmVmMTk3ODE0NjI5OnJlZnMvaGVhZHMvdm1qb3NlcGgvdG9vbGtpdC13aW5kb3dzLWV4ZWM=",
76+
"url":"https://api.github.com/repos/some-owner/some-name/git/refs/heads/v2",
77+
"object": {
78+
"sha":"c22ccee38a13e34cb01a103c324adb1db665821e",
79+
"type":"commit",
80+
"url":"https://api.github.com/repos/some-owner/some-name/git/commits/c22ccee38a13e34cb01a103c324adb1db665821e"
81+
}
82+
}
83+
]
84+
""".trimIndent(),
85+
),
86+
status = HttpStatusCode.OK,
87+
headers = headersOf(HttpHeaders.ContentType, "application/json"),
88+
)
89+
} else {
90+
respond(
91+
content = ByteReadChannel("The mock client wasn't prepared for this request"),
92+
status = HttpStatusCode.NotFound,
93+
)
94+
}
95+
}
96+
97+
// When
98+
val versions =
99+
fetchAvailableVersions(
100+
owner = "some-owner",
101+
name = "some-name",
102+
githubToken = "token",
103+
httpClientEngine = mockEngine,
104+
)
105+
106+
// Then
107+
versions shouldBe
108+
listOf(
109+
Version("v1.0.0"),
110+
Version("v1.0.1"),
111+
Version("v1"),
112+
Version("v2"),
113+
)
114+
}
115+
116+
test("error occurs when fetching branches and tags") {
117+
// Given
118+
val mockEngine =
119+
MockEngine { request ->
120+
respond(
121+
// language=json
122+
content = ByteReadChannel("""{"message": "There was a problem!"}"""),
123+
status = HttpStatusCode.Forbidden,
124+
headers = headersOf(HttpHeaders.ContentType, "application/json"),
125+
)
126+
}
127+
128+
// Then
129+
// TODO: fix - right now, the logic fails if it gets something unparseable.
130+
// The test just shows the current behavior, not the intended behavior.
131+
// To be fixed in https://github.com/typesafegithub/github-workflows-kt/issues/1855
132+
shouldThrow<JsonConvertException> {
133+
// When
134+
fetchAvailableVersions(
135+
owner = "some-owner",
136+
name = "some-name",
137+
githubToken = "token",
138+
httpClientEngine = mockEngine,
139+
)
140+
}.also {
141+
it.message shouldContain "Unexpected JSON token"
142+
}
143+
}
144+
})

shared-internal/src/test/kotlin/io/github/typesafegithub/workflows/shared/internal/model/VersionTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import io.kotest.assertions.assertSoftly
44
import io.kotest.core.spec.style.FunSpec
55
import io.kotest.matchers.comparables.shouldBeGreaterThan
66
import io.kotest.matchers.shouldBe
7+
import io.kotest.matchers.shouldNotBe
78

89
class VersionTest :
910
FunSpec(
@@ -65,5 +66,25 @@ class VersionTest :
6566
}
6667
}
6768
}
69+
70+
context("equals") {
71+
listOf(
72+
Triple("v1", "v1", true),
73+
Triple("v1", "v2", false),
74+
Triple("v1.0", "v1", false),
75+
Triple("v1.2", "v1.2", true),
76+
Triple("v1.2", "v1.3", false),
77+
Triple("v1.2.3", "v1.2.3", true),
78+
Triple("v1.2", "v1.2.3", false),
79+
).forEach { (left, right, equals) ->
80+
test("equals works correctly for $left vs. $right") {
81+
if (equals) {
82+
Version(left) shouldBe Version(right)
83+
} else {
84+
Version(right) shouldNotBe Version(left)
85+
}
86+
}
87+
}
88+
}
6889
},
6990
)

0 commit comments

Comments
 (0)