Skip to content

Commit c7af603

Browse files
committed
chore: fix UTs related to CLI downloading
For one thing some method signature changed, some methods are now suspending functions that will have to run in a coroutine in the tests. The second big issue is that now the download function requests user's input via a dialog
1 parent 345371f commit c7af603

File tree

2 files changed

+94
-28
lines changed

2 files changed

+94
-28
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ dependencies {
5656
testImplementation(kotlin("test"))
5757
// required by the unit tests
5858
testImplementation(kotlin("test-junit5"))
59+
testImplementation("io.mockk:mockk:1.13.12")
5960
// required by IntelliJ test framework
6061
testImplementation("junit:junit:4.13.2")
6162

src/test/kotlin/com/coder/gateway/cli/CoderCLIManagerTest.kt

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.coder.gateway.settings.CODER_SSH_CONFIG_OPTIONS
1010
import com.coder.gateway.settings.CoderSettings
1111
import com.coder.gateway.settings.CoderSettingsState
1212
import com.coder.gateway.settings.Environment
13+
import com.coder.gateway.util.DialogUi
1314
import com.coder.gateway.util.InvalidVersionException
1415
import com.coder.gateway.util.OS
1516
import com.coder.gateway.util.SemVer
@@ -19,6 +20,9 @@ import com.coder.gateway.util.sha1
1920
import com.coder.gateway.util.toURL
2021
import com.squareup.moshi.JsonEncodingException
2122
import com.sun.net.httpserver.HttpServer
23+
import io.mockk.every
24+
import io.mockk.mockkConstructor
25+
import kotlinx.coroutines.runBlocking
2226
import org.junit.jupiter.api.BeforeAll
2327
import org.junit.jupiter.api.assertDoesNotThrow
2428
import org.zeroturnaround.exec.InvalidExitValueException
@@ -28,7 +32,8 @@ import java.net.InetSocketAddress
2832
import java.net.URL
2933
import java.nio.file.AccessDeniedException
3034
import java.nio.file.Path
31-
import java.util.*
35+
import java.util.UUID
36+
import kotlin.test.BeforeTest
3237
import kotlin.test.Test
3338
import kotlin.test.assertContains
3439
import kotlin.test.assertEquals
@@ -37,6 +42,9 @@ import kotlin.test.assertFalse
3742
import kotlin.test.assertNotEquals
3843
import kotlin.test.assertTrue
3944

45+
private const val VERSION_FOR_PROGRESS_REPORTING = "v2.13.1-devel+de07351b8"
46+
private val noOpTextProgress: (String) -> Unit = { _ -> }
47+
4048
internal class CoderCLIManagerTest {
4149
/**
4250
* Return the contents of a script that contains the string.
@@ -65,6 +73,9 @@ internal class CoderCLIManagerTest {
6573
if (exchange.requestURI.path == "/bin/override") {
6674
code = HttpURLConnection.HTTP_OK
6775
response = mkbinVersion("0.0.0")
76+
} else if (exchange.requestURI.path.contains(".asc")) {
77+
code = HttpURLConnection.HTTP_NOT_FOUND
78+
response = "not found"
6879
} else if (!exchange.requestURI.path.startsWith("/bin/coder-")) {
6980
code = HttpURLConnection.HTTP_NOT_FOUND
7081
response = "not found"
@@ -85,6 +96,13 @@ internal class CoderCLIManagerTest {
8596
return Pair(srv, URL("http://localhost:" + srv.address.port))
8697
}
8798

99+
@BeforeTest
100+
fun setup() {
101+
// Mock the DialogUi constructor
102+
mockkConstructor(DialogUi::class)
103+
every { anyConstructed<DialogUi>().confirm(any(), any()) } returns true
104+
}
105+
88106
@Test
89107
fun testServerInternalError() {
90108
val (srv, url) = mockServer(HttpURLConnection.HTTP_INTERNAL_ERROR)
@@ -93,7 +111,7 @@ internal class CoderCLIManagerTest {
93111
val ex =
94112
assertFailsWith(
95113
exceptionClass = ResponseException::class,
96-
block = { ccm.download() },
114+
block = { runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) } }
97115
)
98116
assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, ex.code)
99117

@@ -145,7 +163,7 @@ internal class CoderCLIManagerTest {
145163

146164
assertFailsWith(
147165
exceptionClass = AccessDeniedException::class,
148-
block = { ccm.download() },
166+
block = { runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) } },
149167
)
150168

151169
srv.stop(0)
@@ -168,15 +186,16 @@ internal class CoderCLIManagerTest {
168186
CoderSettings(
169187
CoderSettingsState(
170188
dataDirectory = tmpdir.resolve("real-cli").toString(),
189+
fallbackOnCoderForSignatures = true
171190
),
172191
),
173192
)
174193

175-
assertTrue(ccm.download())
194+
assertTrue(runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
176195
assertDoesNotThrow { ccm.version() }
177196

178197
// It should skip the second attempt.
179-
assertFalse(ccm.download())
198+
assertFalse(runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
180199

181200
// Make sure login failures propagate.
182201
assertFailsWith(
@@ -194,16 +213,17 @@ internal class CoderCLIManagerTest {
194213
CoderSettings(
195214
CoderSettingsState(
196215
dataDirectory = tmpdir.resolve("mock-cli").toString(),
216+
fallbackOnCoderForSignatures = true
197217
),
198218
binaryName = "coder.bat",
199219
),
200220
)
201221

202-
assertEquals(true, ccm.download())
222+
assertEquals(true, runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
203223
assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
204224

205225
// It should skip the second attempt.
206-
assertEquals(false, ccm.download())
226+
assertEquals(false, runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
207227

208228
// Should use the source override.
209229
ccm =
@@ -213,11 +233,12 @@ internal class CoderCLIManagerTest {
213233
CoderSettingsState(
214234
binarySource = "/bin/override",
215235
dataDirectory = tmpdir.resolve("mock-cli").toString(),
236+
fallbackOnCoderForSignatures = true
216237
),
217238
),
218239
)
219240

220-
assertEquals(true, ccm.download())
241+
assertEquals(true, runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
221242
assertContains(ccm.localBinaryPath.toFile().readText(), "0.0.0")
222243

223244
srv.stop(0)
@@ -242,14 +263,15 @@ internal class CoderCLIManagerTest {
242263
}
243264

244265
@Test
245-
fun testOverwitesWrongVersion() {
266+
fun testOverwritesWrongVersion() {
246267
val (srv, url) = mockServer()
247268
val ccm =
248269
CoderCLIManager(
249270
url,
250271
CoderSettings(
251272
CoderSettingsState(
252273
dataDirectory = tmpdir.resolve("overwrite-cli").toString(),
274+
fallbackOnCoderForSignatures = true
253275
),
254276
),
255277
)
@@ -261,7 +283,7 @@ internal class CoderCLIManagerTest {
261283
assertEquals("cli", ccm.localBinaryPath.toFile().readText())
262284
assertEquals(0, ccm.localBinaryPath.toFile().lastModified())
263285

264-
assertTrue(ccm.download())
286+
assertTrue(runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
265287

266288
assertNotEquals("cli", ccm.localBinaryPath.toFile().readText())
267289
assertNotEquals(0, ccm.localBinaryPath.toFile().lastModified())
@@ -279,14 +301,15 @@ internal class CoderCLIManagerTest {
279301
CoderSettings(
280302
CoderSettingsState(
281303
dataDirectory = tmpdir.resolve("clobber-cli").toString(),
304+
fallbackOnCoderForSignatures = true
282305
),
283306
)
284307

285308
val ccm1 = CoderCLIManager(url1, settings)
286309
val ccm2 = CoderCLIManager(url2, settings)
287310

288-
assertTrue(ccm1.download())
289-
assertTrue(ccm2.download())
311+
assertTrue(runBlocking { ccm1.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
312+
assertTrue(runBlocking { ccm2.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
290313

291314
srv1.stop(0)
292315
srv2.stop(0)
@@ -314,8 +337,12 @@ internal class CoderCLIManagerTest {
314337
fun testConfigureSSH() {
315338
val workspace = workspace("foo", agents = mapOf("agent1" to UUID.randomUUID().toString()))
316339
val workspace2 = workspace("bar", agents = mapOf("agent1" to UUID.randomUUID().toString()))
317-
val betterWorkspace = workspace("foo", agents = mapOf("agent1" to UUID.randomUUID().toString()), ownerName = "bettertester")
318-
val workspaceWithMultipleAgents = workspace("foo", agents = mapOf("agent1" to UUID.randomUUID().toString(), "agent2" to UUID.randomUUID().toString()))
340+
val betterWorkspace =
341+
workspace("foo", agents = mapOf("agent1" to UUID.randomUUID().toString()), ownerName = "bettertester")
342+
val workspaceWithMultipleAgents = workspace(
343+
"foo",
344+
agents = mapOf("agent1" to UUID.randomUUID().toString(), "agent2" to UUID.randomUUID().toString())
345+
)
319346

320347
val extraConfig =
321348
listOf(
@@ -331,7 +358,12 @@ internal class CoderCLIManagerTest {
331358
SSHTest(listOf(workspace), "existing-end", "replace-end", "no-blocks"),
332359
SSHTest(listOf(workspace), "existing-end-no-newline", "replace-end-no-newline", "no-blocks"),
333360
SSHTest(listOf(workspace), "existing-middle", "replace-middle", "no-blocks"),
334-
SSHTest(listOf(workspace), "existing-middle-and-unrelated", "replace-middle-ignore-unrelated", "no-related-blocks"),
361+
SSHTest(
362+
listOf(workspace),
363+
"existing-middle-and-unrelated",
364+
"replace-middle-ignore-unrelated",
365+
"no-related-blocks"
366+
),
335367
SSHTest(listOf(workspace), "existing-only", "replace-only", "blank"),
336368
SSHTest(listOf(workspace), "existing-start", "replace-start", "no-blocks"),
337369
SSHTest(listOf(workspace), "no-blocks", "append-no-blocks", "no-blocks"),
@@ -463,7 +495,10 @@ internal class CoderCLIManagerTest {
463495
Path.of("src/test/fixtures/outputs/").resolve(it.output + ".conf").toFile().readText()
464496
.replace(newlineRe, System.lineSeparator())
465497
.replace("/tmp/coder-gateway/test.coder.invalid/config", escape(coderConfigPath.toString()))
466-
.replace("/tmp/coder-gateway/test.coder.invalid/coder-linux-amd64", escape(ccm.localBinaryPath.toString()))
498+
.replace(
499+
"/tmp/coder-gateway/test.coder.invalid/coder-linux-amd64",
500+
escape(ccm.localBinaryPath.toString())
501+
)
467502
.let { conf ->
468503
if (it.sshLogDirectory != null) {
469504
conf.replace("/tmp/coder-gateway/test.coder.invalid/logs", it.sshLogDirectory.toString())
@@ -476,7 +511,10 @@ internal class CoderCLIManagerTest {
476511
Path.of("src/test/fixtures/inputs/").resolve(it.remove + ".conf").toFile().readText()
477512
.replace(newlineRe, System.lineSeparator())
478513
.replace("/tmp/coder-gateway/test.coder.invalid/config", escape(coderConfigPath.toString()))
479-
.replace("/tmp/coder-gateway/test.coder.invalid/coder-linux-amd64", escape(ccm.localBinaryPath.toString()))
514+
.replace(
515+
"/tmp/coder-gateway/test.coder.invalid/coder-linux-amd64",
516+
escape(ccm.localBinaryPath.toString())
517+
)
480518
.let { conf ->
481519
if (it.sshLogDirectory != null) {
482520
conf.replace("/tmp/coder-gateway/test.coder.invalid/logs", it.sshLogDirectory.toString())
@@ -552,7 +590,10 @@ internal class CoderCLIManagerTest {
552590
"new\nline",
553591
)
554592

555-
val workspace = workspace("foo", agents = mapOf("agentid1" to UUID.randomUUID().toString(), "agentid2" to UUID.randomUUID().toString()))
593+
val workspace = workspace(
594+
"foo",
595+
agents = mapOf("agentid1" to UUID.randomUUID().toString(), "agentid2" to UUID.randomUUID().toString())
596+
)
556597
val withAgents = workspace.latestBuild.resources.filter { it.agents != null }.flatMap { it.agents!! }.map {
557598
workspace to it
558599
}
@@ -730,8 +771,24 @@ internal class CoderCLIManagerTest {
730771
EnsureCLITest(null, null, "1.0.0", false, true, true, Result.DL_DATA), // Download to fallback.
731772
EnsureCLITest(null, null, "1.0.0", false, false, true, Result.NONE), // No download, error when used.
732773
EnsureCLITest("1.0.1", "1.0.1", "1.0.0", false, true, true, Result.DL_DATA), // Update fallback.
733-
EnsureCLITest("1.0.1", "1.0.2", "1.0.0", false, false, true, Result.USE_BIN), // No update, use outdated.
734-
EnsureCLITest(null, "1.0.2", "1.0.0", false, false, true, Result.USE_DATA), // No update, use outdated fallback.
774+
EnsureCLITest(
775+
"1.0.1",
776+
"1.0.2",
777+
"1.0.0",
778+
false,
779+
false,
780+
true,
781+
Result.USE_BIN
782+
), // No update, use outdated.
783+
EnsureCLITest(
784+
null,
785+
"1.0.2",
786+
"1.0.0",
787+
false,
788+
false,
789+
true,
790+
Result.USE_DATA
791+
), // No update, use outdated fallback.
735792
EnsureCLITest("1.0.0", null, "1.0.0", false, false, true, Result.USE_BIN), // Use existing.
736793
EnsureCLITest("1.0.1", "1.0.0", "1.0.0", false, false, true, Result.USE_DATA), // Use existing fallback.
737794
)
@@ -746,6 +803,7 @@ internal class CoderCLIManagerTest {
746803
enableBinaryDirectoryFallback = it.enableFallback,
747804
dataDirectory = tmpdir.resolve("ensure-data-dir").toString(),
748805
binaryDirectory = tmpdir.resolve("ensure-bin-dir").toString(),
806+
fallbackOnCoderForSignatures = true
749807
),
750808
)
751809

@@ -777,34 +835,39 @@ internal class CoderCLIManagerTest {
777835
Result.ERROR -> {
778836
assertFailsWith(
779837
exceptionClass = AccessDeniedException::class,
780-
block = { ensureCLI(url, it.buildVersion, settings) },
838+
block = { runBlocking { ensureCLI(url, it.buildVersion, settings, noOpTextProgress) } }
781839
)
782840
}
841+
783842
Result.NONE -> {
784-
val ccm = ensureCLI(url, it.buildVersion, settings)
843+
val ccm = runBlocking { ensureCLI(url, it.buildVersion, settings, noOpTextProgress) }
785844
assertEquals(settings.binPath(url), ccm.localBinaryPath)
786845
assertFailsWith(
787846
exceptionClass = ProcessInitException::class,
788847
block = { ccm.version() },
789848
)
790849
}
850+
791851
Result.DL_BIN -> {
792-
val ccm = ensureCLI(url, it.buildVersion, settings)
852+
val ccm = runBlocking { ensureCLI(url, it.buildVersion, settings, noOpTextProgress) }
793853
assertEquals(settings.binPath(url), ccm.localBinaryPath)
794854
assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
795855
}
856+
796857
Result.DL_DATA -> {
797-
val ccm = ensureCLI(url, it.buildVersion, settings)
858+
val ccm = runBlocking { ensureCLI(url, it.buildVersion, settings, noOpTextProgress) }
798859
assertEquals(settings.binPath(url, true), ccm.localBinaryPath)
799860
assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
800861
}
862+
801863
Result.USE_BIN -> {
802-
val ccm = ensureCLI(url, it.buildVersion, settings)
864+
val ccm = runBlocking { ensureCLI(url, it.buildVersion, settings, noOpTextProgress) }
803865
assertEquals(settings.binPath(url), ccm.localBinaryPath)
804866
assertEquals(SemVer.parse(it.version ?: ""), ccm.version())
805867
}
868+
806869
Result.USE_DATA -> {
807-
val ccm = ensureCLI(url, it.buildVersion, settings)
870+
val ccm = runBlocking { ensureCLI(url, it.buildVersion, settings, noOpTextProgress) }
808871
assertEquals(settings.binPath(url, true), ccm.localBinaryPath)
809872
assertEquals(SemVer.parse(it.fallbackVersion ?: ""), ccm.version())
810873
}
@@ -838,19 +901,21 @@ internal class CoderCLIManagerTest {
838901
CoderSettings(
839902
CoderSettingsState(
840903
dataDirectory = tmpdir.resolve("features").toString(),
904+
fallbackOnCoderForSignatures = true
841905
),
842906
binaryName = "coder.bat",
843907
),
844908
)
845-
assertEquals(true, ccm.download())
909+
assertEquals(true, runBlocking { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) })
846910
assertEquals(it.second, ccm.features, "version: ${it.first}")
847911

848912
srv.stop(0)
849913
}
850914
}
851915

852916
companion object {
853-
private val tmpdir: Path = Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test/cli-manager")
917+
private val tmpdir: Path =
918+
Path.of(System.getProperty("java.io.tmpdir")).resolve("coder-gateway-test/cli-manager")
854919

855920
@JvmStatic
856921
@BeforeAll

0 commit comments

Comments
 (0)