diff --git a/code-server/main.test.ts b/code-server/main.test.ts new file mode 100644 index 0000000..daf3ac1 --- /dev/null +++ b/code-server/main.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from "bun:test"; +import { runTerraformInit, testRequiredVariables } from "../test"; + +describe("code-server", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + // More tests depend on shebang refactors +}); diff --git a/coder-login/main.tf b/coder-login/main.tf index 2d3ac8b..58d1bf0 100644 --- a/coder-login/main.tf +++ b/coder-login/main.tf @@ -23,7 +23,7 @@ resource "coder_script" "coder-login" { CODER_DEPLOYMENT_URL : data.coder_workspace.me.access_url }) display_name = "Coder Login" - icon = "http://svgur.com/i/y5G.svg" + icon = "/icon/coder.svg" run_on_start = true start_blocks_login = true } diff --git a/dotfiles/main.test.ts b/dotfiles/main.test.ts new file mode 100644 index 0000000..69eda32 --- /dev/null +++ b/dotfiles/main.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("dotfiles", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("default output", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + expect(state.outputs.dotfiles_uri.value).toBe(""); + }); +}); diff --git a/fly-region/main.test.ts b/fly-region/main.test.ts index 86f6bfc..7e72586 100644 --- a/fly-region/main.test.ts +++ b/fly-region/main.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { - executeScriptInContainer, runTerraformApply, runTerraformInit, testRequiredVariables, @@ -22,4 +21,12 @@ describe("fly-region", async () => { }); expect(state.outputs.value.value).toBe("atl"); }); + + it("region filter", async () => { + const state = await runTerraformApply(import.meta.dir, { + default: "atl", + regions: '["arn", "ams", "bos"]', + }); + expect(state.outputs.value.value).toBe(""); + }); }); diff --git a/gcp-region/README.md b/gcp-region/README.md index 0ca76e1..ab5daf3 100644 --- a/gcp-region/README.md +++ b/gcp-region/README.md @@ -28,6 +28,8 @@ resource "google_compute_instance" "example" { ### Add only GPU zones in the US West 1 region +Note: setting `gpu_only = true` and using a default region without GPU support, the default will be set to `null`. + ```hcl module "gcp_region" { source = "https://registry.coder.com/modules/gcp-region" diff --git a/gcp-region/main.test.ts b/gcp-region/main.test.ts new file mode 100644 index 0000000..2ec623b --- /dev/null +++ b/gcp-region/main.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("gcp-region", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, {}); + + it("default output", async () => { + const state = await runTerraformApply(import.meta.dir, {}); + expect(state.outputs.value.value).toBe(""); + }); + + it("customized default", async () => { + const state = await runTerraformApply(import.meta.dir, { + regions: '["asia"]', + default: "asia-east1-a", + }); + expect(state.outputs.value.value).toBe("asia-east1-a"); + }); + + it("gpu only invalid default", async () => { + const state = await runTerraformApply(import.meta.dir, { + regions: '["us-west2"]', + default: "us-west2-a", + gpu_only: "true", + }); + expect(state.outputs.value.value).toBe(""); + }); + + it("gpu only valid default", async () => { + const state = await runTerraformApply(import.meta.dir, { + regions: '["us-west2"]', + default: "us-west2-b", + gpu_only: "true", + }); + expect(state.outputs.value.value).toBe("us-west2-b"); + }); +}); diff --git a/gcp-region/main.tf b/gcp-region/main.tf index 4d675c8..e9f549d 100644 --- a/gcp-region/main.tf +++ b/gcp-region/main.tf @@ -714,7 +714,7 @@ data "coder_parameter" "region" { description = var.description icon = "/icon/gcp.png" mutable = var.mutable - default = var.default != null && var.default != "" ? var.default : null + default = var.default != null && var.default != "" && (!var.gpu_only || try(local.zones[var.default].gpu, false)) ? var.default : null dynamic "option" { for_each = { for k, v in local.zones : k => v diff --git a/jetbrains-gateway/main.test.ts b/jetbrains-gateway/main.test.ts index ffd970f..9a0628f 100644 --- a/jetbrains-gateway/main.test.ts +++ b/jetbrains-gateway/main.test.ts @@ -1,7 +1,12 @@ -import { describe } from "bun:test"; -import { runTerraformInit, testRequiredVariables } from "../test"; +import { it, expect, describe } from "bun:test"; +import { + runTerraformInit, + testRequiredVariables, + executeScriptInContainer, + runTerraformApply, +} from "../test"; -describe("jetbrains-gateway`", async () => { +describe("jetbrains-gateway", async () => { await runTerraformInit(import.meta.dir); await testRequiredVariables(import.meta.dir, { @@ -10,4 +15,16 @@ describe("jetbrains-gateway`", async () => { folder: "/baz/", jetbrains_ides: '["IU", "IC", "PY"]', }); + + it("default to first ide", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + agent_name: "bar", + folder: "/baz/", + jetbrains_ides: '["IU", "IC", "PY"]', + }); + expect(state.outputs.jetbrains_ides.value).toBe( + '["IU","232.9921.47","https://download.jetbrains.com/idea/ideaIU-2023.2.2.tar.gz"]', + ); + }); }); diff --git a/jupyterlab/main.test.ts b/jupyterlab/main.test.ts new file mode 100644 index 0000000..2597dc2 --- /dev/null +++ b/jupyterlab/main.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, + findResourceInstance, + runContainer, + TerraformState, + execContainer, +} from "../test"; + +// executes the coder script after installing pip +const executeScriptInContainerWithPip = async ( + state: TerraformState, + image: string, + shell: string = "sh", +): Promise<{ + exitCode: number; + stdout: string[]; + stderr: string[]; +}> => { + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer(image); + const respPip = await execContainer(id, [shell, "-c", "apk add py3-pip"]); + const resp = await execContainer(id, [shell, "-c", instance.script]); + const stdout = resp.stdout.trim().split("\n"); + const stderr = resp.stderr.trim().split("\n"); + return { + exitCode: resp.exitCode, + stdout, + stderr, + }; +}; + +describe("jupyterlab", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("fails without pip3", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + const output = await executeScriptInContainer(state, "alpine"); + expect(output.exitCode).toBe(1); + expect(output.stdout).toEqual([ + "\u001B[0;1mInstalling jupyterlab!", + "pip3 is not installed", + "Please install pip3 in your Dockerfile/VM image before running this script", + ]); + }); + + // TODO: Add faster test to run with pip3. + // currently times out. + // it("runs with pip3", async () => { + // ... + // const output = await executeScriptInContainerWithPip(state, "alpine"); + // ... + // }); +}); diff --git a/personalize/main.test.ts b/personalize/main.test.ts new file mode 100644 index 0000000..9c8134e --- /dev/null +++ b/personalize/main.test.ts @@ -0,0 +1,33 @@ +import { readableStreamToText, spawn } from "bun"; +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, + runContainer, + execContainer, + findResourceInstance, +} from "../test"; + +describe("personalize", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("warns without personalize script", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + const output = await executeScriptInContainer(state, "alpine"); + expect(output.exitCode).toBe(0); + expect(output.stdout).toEqual([ + "✨ \u001b[0;1mYou don't have a personalize script!", + "", + "Run \u001b[36;40;1mtouch ~/personalize && chmod +x ~/personalize\u001b[0m to create one.", + "It will run every time your workspace starts. Use it to install personal packages!", + ]); + }); +}); diff --git a/test.ts b/test.ts index 6546490..a32a995 100644 --- a/test.ts +++ b/test.ts @@ -129,7 +129,7 @@ export const findResourceInstance = ( return resource.instances[0].attributes as any; }; -// assertRequiredVariables creates a test-case +// testRequiredVariables creates a test-case // for each variable provided and ensures that // the apply fails without it. export const testRequiredVariables = ( diff --git a/vscode-desktop/main.test.ts b/vscode-desktop/main.test.ts new file mode 100644 index 0000000..304655d --- /dev/null +++ b/vscode-desktop/main.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("vscode-desktop", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("default output", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + expect(state.outputs.vscode_url.value).toBe( + "vscode://coder.coder-remote/open?owner=default&workspace=default&token=$SESSION_TOKEN", + ); + }); +}); diff --git a/vscode-desktop/main.tf b/vscode-desktop/main.tf index 9edae25..715fb14 100644 --- a/vscode-desktop/main.tf +++ b/vscode-desktop/main.tf @@ -16,7 +16,7 @@ variable "agent_id" { variable "folder" { type = string - description = "The folder to opne in VS Code." + description = "The folder to open in VS Code." default = "" } @@ -44,3 +44,8 @@ resource "coder_app" "vscode" { "&token=$SESSION_TOKEN", ]) } + +output "vscode_url" { + value = coder_app.vscode.url + description = "VS Code Desktop URL." +} diff --git a/vscode-web/main.test.ts b/vscode-web/main.test.ts new file mode 100644 index 0000000..57277df --- /dev/null +++ b/vscode-web/main.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, +} from "../test"; + +describe("vscode-web", async () => { + await runTerraformInit(import.meta.dir); + + // replaces testRequiredVariables due to license variable + // may add a testRequiredVariablesWithLicense function later + it("missing agent_id", async () => { + try { + await runTerraformApply(import.meta.dir, { + accept_license: "true", + }); + } catch (ex) { + expect(ex.message).toContain('input variable "agent_id" is not set'); + } + }); + + it("invalid license_agreement", async () => { + try { + await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + } catch (ex) { + expect(ex.message).toContain( + "You must accept the VS Code license agreement by setting accept_license=true", + ); + } + }); + + it("fails without curl", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + accept_license: "true", + }); + const output = await executeScriptInContainer(state, "alpine"); + expect(output.exitCode).toBe(1); + expect(output.stdout).toEqual([ + "\u001b[0;1mInstalling vscode-cli!", + "Failed to install vscode-cli:", // TODO: manually test error log + ]); + }); + + it("runs with curl", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + accept_license: "true", + }); + const output = await executeScriptInContainer(state, "alpine/curl"); + expect(output.exitCode).toBe(0); + expect(output.stdout).toEqual([ + "\u001b[0;1mInstalling vscode-cli!", + "🥳 vscode-cli has been installed.", + "", + "👷 Running /tmp/vscode-cli/bin/code serve-web --port 13338 --without-connection-token --accept-server-license-terms in the background...", + "Check logs at /tmp/vscode-web.log!", + ]); + }); +});