From 36fa871e7ba1354f46b3256f573b8147504fc1f4 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Wed, 1 May 2024 15:19:50 -0400 Subject: [PATCH] add tests --- CONTRIBUTING.md | 2 + github-upload-public-key/main.test.ts | 89 +++++++++++++++++++++++++++ github-upload-public-key/main.tf | 26 +++++++- github-upload-public-key/run.sh | 22 ++++--- slackme/main.test.ts | 10 +-- test.ts | 11 +++- 6 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 github-upload-public-key/main.test.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6aa77cf..198eede 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,8 @@ To create a new module, clone this repository and run: A suite of test-helpers exists to run `terraform apply` on modules with variables, and test script output against containers. +The testing suite must be able to run docker containers with the `--network=host` flag, which typically requires running the tests on linux as this flag does not apply to Docker Desktop for MacOS and Windows. MacOS users can work around this by using something like [Orbstack](https://orbstack.dev/) instead of Docker Desktop. + Reference existing `*.test.ts` files for implementation. ```shell diff --git a/github-upload-public-key/main.test.ts b/github-upload-public-key/main.test.ts new file mode 100644 index 0000000..8b6dca7 --- /dev/null +++ b/github-upload-public-key/main.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from "bun:test"; +import { createJSONResponse, execContainer, findResourceInstance, runContainer, runTerraformApply, runTerraformInit, testRequiredVariables, writeCoder } from "../test"; +import { Server, serve } from "bun"; + +describe("github-upload-public-key", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("creates new key if one does not exist", async () => { + const { instance, id } = await setupContainer(); + await writeCoder(id, "echo foo"); + let exec = await execContainer(id, ["bash", "-c", instance.script]); + expect(exec.stdout).toContain("Coder public SSH key uploaded to GitHub!") + expect(exec.exitCode).toBe(0); + }); + + it("does nothing if one already exists", async () => { + const { instance, id } = await setupContainer(); + await writeCoder(id, "echo findkey"); + let exec = await execContainer(id, ["bash", "-c", instance.script]); + expect(exec.stdout).toContain("Coder public SSH key is already uploaded to GitHub!") + expect(exec.exitCode).toBe(0); + }); +}); + +const setupContainer = async ( + image = "lorello/alpine-bash", + vars: Record = {}, + ) => { + const server = await setupServer(); + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + // trim the trailing slash on the URL + access_url: server.url.toString().slice(0, -1), + owner_session_token: "bar", + github_api_url: server.url.toString().slice(0, -1), + ...vars, + }); + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer(image); + return { id, instance }; +}; + +const setupServer = async (): Promise => { + let url: URL; + const fakeSlackHost = serve({ + fetch: (req) => { + url = new URL(req.url); + if (url.pathname === "/api/v2/users/me/gitsshkey") { + return createJSONResponse({ + public_key: "exists", + }); + } + + if (url.pathname === "/user/keys") { + if (req.method === "POST") { + return createJSONResponse({ + key: "created", + }, 201); + } + + // case: key already exists + if (req.headers.get("Authorization") == "Bearer findkey") { + return createJSONResponse([{ + key: "foo", + }, { + key: "exists", + }]); + } + + // case: key does not exist + return createJSONResponse([{ + key: "foo", + }]); + } + + + return createJSONResponse({ + error: "not_found" + }, 404); + }, + port: 0, + }); + + return fakeSlackHost; +} \ No newline at end of file diff --git a/github-upload-public-key/main.tf b/github-upload-public-key/main.tf index cd22c1f..7d18ddb 100644 --- a/github-upload-public-key/main.tf +++ b/github-upload-public-key/main.tf @@ -20,14 +20,34 @@ variable "external_auth_id" { default = "github" } +variable "github_api_url" { + type = string + description = "The URL of the GitHub instance." + default = "https://api.github.com" +} + +// Optional variables mostly for testing purposes, will normally come from data.coder_workspace.me +variable "access_url" { + type = string + description = "The access URL of the workspace." + default = "" +} + +variable "owner_session_token" { + type = string + description = "The owner session token of the workspace." + default = "" +} + data "coder_workspace" "me" {} resource "coder_script" "github_upload_public_key" { agent_id = var.agent_id script = templatefile("${path.module}/run.sh", { - CODER_OWNER_SESSION_TOKEN : data.coder_workspace.me.owner_session_token, - CODER_ACCESS_URL : data.coder_workspace.me.access_url, - GITHUB_EXTERNAL_AUTH_ID : var.external_auth_id, + CODER_OWNER_SESSION_TOKEN : var.owner_session_token != "" ? var.owner_session_token : data.coder_workspace.me.owner_session_token, + CODER_ACCESS_URL : var.access_url != "" ? var.access_url : data.coder_workspace.me.access_url, + CODER_EXTERNAL_AUTH_ID : var.external_auth_id, + GITHUB_API_URL : var.github_api_url, }) display_name = "Github Upload Public Key" icon = "/icon/github.svg" diff --git a/github-upload-public-key/run.sh b/github-upload-public-key/run.sh index f7c1e16..6dabefa 100755 --- a/github-upload-public-key/run.sh +++ b/github-upload-public-key/run.sh @@ -4,7 +4,8 @@ set -e CODER_ACCESS_URL="${CODER_ACCESS_URL}" CODER_OWNER_SESSION_TOKEN="${CODER_OWNER_SESSION_TOKEN}" -GITHUB_EXTERNAL_AUTH_ID="${GITHUB_EXTERNAL_AUTH_ID}" +CODER_EXTERNAL_AUTH_ID="${CODER_EXTERNAL_AUTH_ID}" +GITHUB_API_URL="${GITHUB_API_URL}" if [ -z "$CODER_ACCESS_URL" ]; then echo "No coder access url specified!" @@ -16,13 +17,18 @@ if [ -z "$CODER_OWNER_SESSION_TOKEN" ]; then exit 1 fi -if [ -z "$GITHUB_EXTERNAL_AUTH_ID" ]; then +if [ -z "$CODER_EXTERNAL_AUTH_ID" ]; then echo "No GitHub external auth id specified!" exit 1 fi +if [ -z "$GITHUB_API_URL" ]; then + echo "No GitHub API URL specified!" + exit 1 +fi + echo "Fetching GitHub token..." -GITHUB_TOKEN=$(coder external-auth access-token $GITHUB_EXTERNAL_AUTH_ID) +GITHUB_TOKEN=$(coder external-auth access-token $CODER_EXTERNAL_AUTH_ID) if [ $? -ne 0 ]; then echo "Failed to fetch GitHub token!" exit 1 @@ -36,7 +42,7 @@ echo "GitHub token found!" echo "Fetching Coder public SSH key..." PUBLIC_KEY_RESPONSE=$( curl -L -s \ - -w "%%{http_code}" \ + -w "\n%%{http_code}" \ -H 'accept: application/json' \ -H "cookie: coder_session_token=$CODER_OWNER_SESSION_TOKEN" \ "$CODER_ACCESS_URL/api/v2/users/me/gitsshkey" @@ -61,11 +67,11 @@ fi echo "Fetching GitHub public SSH keys..." GITHUB_KEYS_RESPONSE=$( curl -L -s \ - -w "%%{http_code}" \ + -w "\n%%{http_code}" \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/user/keys + $GITHUB_API_URL/user/keys ) GITHUB_KEYS_RESPONSE_STATUS=$(tail -n1 <<< "$GITHUB_KEYS_RESPONSE") GITHUB_KEYS_RESPONSE_BODY=$(sed \$d <<< "$GITHUB_KEYS_RESPONSE") @@ -89,11 +95,11 @@ CODER_PUBLIC_KEY_NAME="$CODER_ACCESS_URL Workspaces" UPLOAD_RESPONSE=$( curl -L -s \ -X POST \ - -w "%%{http_code}" \ + -w "\n%%{http_code}" \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/user/keys \ + $GITHUB_API_URL/user/keys \ -d "{\"title\":\"$CODER_PUBLIC_KEY_NAME\",\"key\":\"$PUBLIC_KEY\"}" ) UPLOAD_RESPONSE_STATUS=$(tail -n1 <<< "$UPLOAD_RESPONSE") diff --git a/slackme/main.test.ts b/slackme/main.test.ts index 53c6b2e..402a690 100644 --- a/slackme/main.test.ts +++ b/slackme/main.test.ts @@ -8,6 +8,7 @@ import { runTerraformApply, runTerraformInit, testRequiredVariables, + writeCoder, } from "../test"; describe("slackme", async () => { @@ -119,15 +120,6 @@ const setupContainer = async ( return { id, instance }; }; -const writeCoder = async (id: string, script: string) => { - const exec = await execContainer(id, [ - "sh", - "-c", - `echo '${script}' > /usr/bin/coder && chmod +x /usr/bin/coder`, - ]); - expect(exec.exitCode).toBe(0); -}; - const assertSlackMessage = async (opts: { command: string; format?: string; diff --git a/test.ts b/test.ts index 97416cf..c2eb65e 100644 --- a/test.ts +++ b/test.ts @@ -222,4 +222,13 @@ export const createJSONResponse = (obj: object, statusCode = 200): Response => { }, status: statusCode, }) -} \ No newline at end of file +} + +export const writeCoder = async (id: string, script: string) => { + const exec = await execContainer(id, [ + "sh", + "-c", + `echo '${script}' > /usr/bin/coder && chmod +x /usr/bin/coder`, + ]); + expect(exec.exitCode).toBe(0); +};