@@ -8,6 +8,7 @@ import com.coder.gateway.sdk.v2.models.*
88import com.coder.gateway.services.CoderSettingsState
99import com.coder.gateway.settings.CoderSettings
1010import com.coder.gateway.util.sslContextFromPEMs
11+ import com.google.gson.Gson
1112import com.google.gson.GsonBuilder
1213import com.sun.net.httpserver.HttpExchange
1314import com.sun.net.httpserver.HttpServer
@@ -30,6 +31,17 @@ import javax.net.ssl.SSLHandshakeException
3031import javax.net.ssl.SSLPeerUnverifiedException
3132import kotlin.test.assertFailsWith
3233
34+ enum class SpyAction {
35+ GET_WORKSPACES ,
36+ GET_ME ,
37+ GET_WORKSPACE ,
38+ GET_TEMPLATE ,
39+ GET_RESOURCES ,
40+ STOP_WORKSPACE ,
41+ START_WORKSPACE ,
42+ UPDATE_WORKSPACE ,
43+ }
44+
3345class BaseCoderRestClientTest {
3446 data class TestWorkspace (var workspace : Workspace , var resources : List <WorkspaceResource >? = emptyList())
3547
@@ -40,32 +52,75 @@ class BaseCoderRestClientTest {
4052 * hardcode IDs everywhere since you cannot use variables in the where
4153 * blocks).
4254 */
43- private fun mockServer (workspaces : List <TestWorkspace >, spy : ((exchange: HttpExchange ) -> Unit )? = null): Pair <HttpServer , String > {
55+ private fun mockServer (
56+ workspaces : List <TestWorkspace >,
57+ templates : List <Template > = emptyList(),
58+ spy : ((action: SpyAction , id: UUID ? ) -> Unit )? = null): Pair <HttpServer , String > {
4459 val srv = HttpServer .create(InetSocketAddress (0 ), 0 )
45- addServerContext(srv, workspaces, spy)
60+ addServerContext(srv, workspaces, templates, spy)
4661 srv.start()
4762 return Pair (srv, " http://localhost:" + srv.address.port)
4863 }
4964
5065 private val resourceEndpoint = " /api/v2/templateversions/([^/]+)/resources" .toRegex()
66+ private val templateEndpoint = " /api/v2/templates/([^/]+)" .toRegex()
67+ private val buildEndpoint = " /api/v2/workspaces/([^/]+)/builds" .toRegex()
5168
5269 private fun toJson (src : Any? ): String {
5370 return GsonBuilder ().registerTypeAdapter(Instant ::class .java, InstantConverter ()).create().toJson(src)
5471 }
5572
56- private fun handleExchange (exchange : HttpExchange , workspaces : List <TestWorkspace >): Pair <Int , String > {
57- val matches = resourceEndpoint.find(exchange.requestURI.path)
58- if (matches != null ) {
73+ private fun handleExchange (
74+ exchange : HttpExchange ,
75+ workspaces : List <TestWorkspace >,
76+ templates : List <Template >,
77+ spy : ((action: SpyAction , id: UUID ? ) -> Unit )? ): Pair <Int , String > {
78+ var matches = resourceEndpoint.find(exchange.requestURI.path)
79+ if (exchange.requestMethod == " GET" && matches != null ) {
5980 val templateVersionId = UUID .fromString(matches.destructured.toList()[0 ])
60- val ws = workspaces.first { it.workspace.latestBuild.templateVersionID == templateVersionId }
61- return Pair (HttpURLConnection .HTTP_OK , toJson(ws.resources))
81+ spy?.invoke(SpyAction .GET_RESOURCES , templateVersionId)
82+ val ws = workspaces.firstOrNull { it.workspace.latestBuild.templateVersionID == templateVersionId }
83+ if (ws != null ) {
84+ return Pair (HttpURLConnection .HTTP_OK , toJson(ws.resources))
85+ }
86+ }
87+
88+ matches = templateEndpoint.find(exchange.requestURI.path)
89+ if (exchange.requestMethod == " GET" && matches != null ) {
90+ val templateId = UUID .fromString(matches.destructured.toList()[0 ])
91+ spy?.invoke(SpyAction .GET_TEMPLATE , templateId)
92+ val template = templates.firstOrNull { it.id == templateId }
93+ if (template != null ) {
94+ return Pair (HttpURLConnection .HTTP_OK , toJson(template))
95+ }
96+ }
97+
98+ matches = buildEndpoint.find(exchange.requestURI.path)
99+ if (exchange.requestMethod == " POST" && matches != null ) {
100+ val workspaceId = UUID .fromString(matches.destructured.toList()[0 ])
101+ val json = Gson ().fromJson(InputStreamReader (exchange.requestBody), CreateWorkspaceBuildRequest ::class .java)
102+ if (json.templateVersionID != null ) {
103+ spy?.invoke(SpyAction .UPDATE_WORKSPACE , workspaceId)
104+ } else {
105+ when (json.transition) {
106+ WorkspaceTransition .START -> spy?.invoke(SpyAction .START_WORKSPACE , workspaceId)
107+ WorkspaceTransition .STOP -> spy?.invoke(SpyAction .STOP_WORKSPACE , workspaceId)
108+ WorkspaceTransition .DELETE -> Unit
109+ }
110+ }
111+ val ws = workspaces.firstOrNull { it.workspace.id == workspaceId }
112+ if (ws != null ) {
113+ return Pair (HttpURLConnection .HTTP_CREATED , toJson(ws.workspace))
114+ }
62115 }
63116
64117 when (exchange.requestURI.path) {
65118 " /api/v2/workspaces" -> {
119+ spy?.invoke(SpyAction .GET_WORKSPACES , null )
66120 return Pair (HttpsURLConnection .HTTP_OK , toJson(WorkspacesResponse (workspaces.map{ it.workspace }, workspaces.size)))
67121 }
68122 " /api/v2/users/me" -> {
123+ spy?.invoke(SpyAction .GET_ME , null )
69124 val user = User (
70125 UUID .randomUUID(),
71126 " tester" ,
@@ -83,13 +138,16 @@ class BaseCoderRestClientTest {
83138 return Pair (HttpsURLConnection .HTTP_NOT_FOUND , " not found" )
84139 }
85140
86- private fun addServerContext (srv : HttpServer , workspaces : List <TestWorkspace > = emptyList(), spy : ((exchange: HttpExchange ) -> Unit )? = null) {
141+ private fun addServerContext (
142+ srv : HttpServer ,
143+ workspaces : List <TestWorkspace > = emptyList(),
144+ templates : List <Template > = emptyList(),
145+ spy : ((action: SpyAction , id: UUID ? ) -> Unit )? = null) {
87146 srv.createContext(" /" ) { exchange ->
88- spy?.invoke(exchange)
89147 var code: Int
90148 var response: String
91149 try {
92- val p = handleExchange(exchange, workspaces)
150+ val p = handleExchange(exchange, workspaces, templates, spy )
93151 code = p.first
94152 response = p.second
95153 } catch (ex: Exception ) {
@@ -203,6 +261,49 @@ class BaseCoderRestClientTest {
203261 }
204262 }
205263
264+ @Test
265+ fun testUpdate () {
266+ val actions = mutableListOf<Pair <SpyAction , UUID ?>>()
267+ val template = DataGen .template(" template" )
268+ val workspaces = listOf (TestWorkspace (DataGen .workspace(" ws1" )),
269+ TestWorkspace (DataGen .workspace(" ws2" , transition = WorkspaceTransition .STOP )))
270+ val (srv, url) = mockServer(workspaces, listOf (template)) { action, id ->
271+ actions.add(Pair (action, id))
272+ }
273+ val client = BaseCoderRestClient (URL (url), " token" )
274+
275+ // Fails to stop a non-existent workspace.
276+ val badWorkspaceId = UUID .randomUUID()
277+ assertFailsWith(
278+ exceptionClass = Exception ::class ,
279+ block = { client.updateWorkspace(badWorkspaceId, " name" , WorkspaceTransition .START , template.id) })
280+ assertEquals(listOf (Pair (SpyAction .STOP_WORKSPACE , badWorkspaceId)), actions)
281+ actions.clear()
282+
283+ // When workspace is started it should stop first.
284+ with (workspaces[0 ].workspace) {
285+ client.updateWorkspace(this .id, this .name, this .latestBuild.transition, template.id)
286+ val expected: List <Pair <SpyAction , UUID ?>> = listOf (
287+ Pair (SpyAction .STOP_WORKSPACE , this .id),
288+ Pair (SpyAction .GET_TEMPLATE , template.id),
289+ Pair (SpyAction .UPDATE_WORKSPACE , this .id))
290+ assertEquals(expected, actions)
291+ actions.clear()
292+ }
293+
294+ // When workspace is stopped it will not stop first.
295+ with (workspaces[1 ].workspace) {
296+ client.updateWorkspace(this .id, this .name, this .latestBuild.transition, template.id)
297+ val expected: List <Pair <SpyAction , UUID ?>> = listOf (
298+ Pair (SpyAction .GET_TEMPLATE , template.id),
299+ Pair (SpyAction .UPDATE_WORKSPACE , this .id))
300+ assertEquals(expected, actions)
301+ actions.clear()
302+ }
303+
304+ srv.stop(0 )
305+ }
306+
206307 @Test
207308 fun testValidSelfSignedCert () {
208309 val settings = CoderSettings (CoderSettingsState (
@@ -284,4 +385,3 @@ class BaseCoderRestClientTest {
284385 srv2.stop(0 )
285386 }
286387}
287-
0 commit comments