From ce504178c6b903b2525fc4ad9c0bec81a1e8183c Mon Sep 17 00:00:00 2001 From: yacine Bouraroui Date: Mon, 7 Oct 2024 20:08:07 +0200 Subject: [PATCH] api tests for preview flows --- backend/tests/api/README.md | 11 +++++ backend/tests/api/deno.json | 9 ++++ backend/tests/api/deno.lock | 40 +++++++++++++++++ backend/tests/api/main_test.ts | 80 ++++++++++++++++++++++++++++++++++ backend/tests/api/utils.ts | 51 ++++++++++++++++++++++ 5 files changed, 191 insertions(+) create mode 100644 backend/tests/api/README.md create mode 100644 backend/tests/api/deno.json create mode 100644 backend/tests/api/deno.lock create mode 100644 backend/tests/api/main_test.ts create mode 100644 backend/tests/api/utils.ts diff --git a/backend/tests/api/README.md b/backend/tests/api/README.md new file mode 100644 index 0000000000000..7e64c2a4e1199 --- /dev/null +++ b/backend/tests/api/README.md @@ -0,0 +1,11 @@ +# Api integration tests + +You'll need deno to be installed and a windmill api endpoint. + +By default the tests use the local environment endpoint . You can override it using `BASE_URL` environment variable. + +By default the api authentication uses default local env user credentials. You can override api token using `WM_TOKEN` environment variable. + +## Running the api tests + +`deno test --allow-env --allow-net` diff --git a/backend/tests/api/deno.json b/backend/tests/api/deno.json new file mode 100644 index 0000000000000..59fb06642f1f9 --- /dev/null +++ b/backend/tests/api/deno.json @@ -0,0 +1,9 @@ +{ + "tasks": { + "dev": "deno run --watch main.ts", + "test": "deno test --allow-env --allow-net" + }, + "imports": { + "@std/assert": "jsr:@std/assert@1" + } +} diff --git a/backend/tests/api/deno.lock b/backend/tests/api/deno.lock new file mode 100644 index 0000000000000..2fd0b2cc13434 --- /dev/null +++ b/backend/tests/api/deno.lock @@ -0,0 +1,40 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@std/assert": "jsr:@std/assert@1.0.6", + "jsr:@std/assert@1": "jsr:@std/assert@1.0.6", + "jsr:@std/internal@^1.0.4": "jsr:@std/internal@1.0.4" + }, + "jsr": { + "@std/assert@1.0.6": { + "integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207", + "dependencies": [ + "jsr:@std/internal@^1.0.4" + ] + }, + "@std/internal@1.0.4": { + "integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422" + } + } + }, + "redirects": { + "https://cdn.skypack.dev/human-id@3.0.0": "https://cdn.skypack.dev/new/human-id@v3.0.0/dist=es2019", + "https://cdn.skypack.dev/new/human-id@v3.0.0/dist=es2019": "https://cdn.skypack.dev/error/build:human-id@v3.0.0-ktOXHm1YV4tQ5io95RTp", + "https://deno.land/x/nanoid/mod.ts": "https://deno.land/x/nanoid@v3.0.0/mod.ts" + }, + "remote": { + "https://cdn.skypack.dev/error/build:human-id@v3.0.0-ktOXHm1YV4tQ5io95RTp": "54c50c8a33ab04f2a14c966809df583d6c59f0f8b549c270d1127afc8d4c652b", + "https://deno.land/x/nanoid@v3.0.0/customAlphabet.ts": "1cfd7cfd2f07ca8d78a7e7855fcc9f59abf01ef2a127484ef94328fadf940ead", + "https://deno.land/x/nanoid@v3.0.0/customRandom.ts": "af56e19038c891a4b4ef2be931554c27579bd407ee5bbea5cb64f6ee1347cbe3", + "https://deno.land/x/nanoid@v3.0.0/mod.ts": "3ead610e40c58d8fdca21d5da9ec661445a2b82526e19c34d05de5f90be8a1be", + "https://deno.land/x/nanoid@v3.0.0/nanoid.ts": "8d119bc89a0f34e7bbe0c2dbdc280d01753e431af553d189663492310a31085d", + "https://deno.land/x/nanoid@v3.0.0/random.ts": "4da71d5f72f2bfcc6a4ee79b5d4e72f48dcf4fe4c3835fd5ebab08b9f33cd598", + "https://deno.land/x/nanoid@v3.0.0/urlAlphabet.ts": "8b1511deb1ecb23c66202b6000dc10fb68f9a96b5550c6c8cef5009324793431" + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1" + ] + } +} diff --git a/backend/tests/api/main_test.ts b/backend/tests/api/main_test.ts new file mode 100644 index 0000000000000..b6e09a0f8e0b7 --- /dev/null +++ b/backend/tests/api/main_test.ts @@ -0,0 +1,80 @@ + +import { assertEquals } from "jsr:@std/assert"; +import type { FlowPreview } from "../../../cli/gen/index.ts"; +import * as api from "../../../cli/gen/index.ts"; +import { awaitJobCompletion, setup } from "./utils.ts"; + +const flowPreview: FlowPreview = { + "args": { + "a": 4, + "b": 9 + }, + "value": { + "modules": [ + { + "id": "a", + "value": { + "type": "rawscript", + "content": "// import * as wmill from \"windmill-client\"\n\nexport async function main(a: number, b : number) {\n return a + b\n}\n", + "language": "bun", + "input_transforms": { + "a": { + "type": "javascript", + "expr": "flow_input.a" + }, + "b": { + "type": "javascript", + "expr": "flow_input.b" + } + }, + "tag": "" + } + }, + { + "id": "b", + "value": { + "type": "rawscript", + "content": "// import * as wmill from \"windmill-client\"\n\nexport async function main(n: number) {\n return n / 2.0\n}\n", + "language": "bun", + "input_transforms": { + "n": { + "type": "javascript", + "expr": "results.a" + } + }, + "tag": "" + } + } + ] + }, + "path": "u/admin/beauteous_flow" +}; + +Deno.test("Run a flow preview and check its completion", async () => { + const { workspace } = await setup() + + const id = await api.runFlowPreview({ + workspace, + requestBody: flowPreview + }); + + const result = await awaitJobCompletion({ id, workspace, timeoutMs: 2000 }); + assertEquals(result.result, 6.5); + assertEquals(result.canceled, false); + assertEquals(result.success, true); + +}); + +Deno.test("Run a flow preview - Immediately cancel and check cancellation status", async () => { + const { workspace } = await setup() + + const id = await api.runFlowPreview({ + workspace, + requestBody: flowPreview + }); + + await api.cancelQueuedJob({ id, workspace, requestBody: { reason: "test cancellation"}}); + const result = await awaitJobCompletion({ id, workspace, timeoutMs: 200}); + assertEquals(result.canceled, true); + assertEquals(result.success, false); +}); \ No newline at end of file diff --git a/backend/tests/api/utils.ts b/backend/tests/api/utils.ts new file mode 100644 index 0000000000000..4b9f3f9989120 --- /dev/null +++ b/backend/tests/api/utils.ts @@ -0,0 +1,51 @@ +import { OpenAPI, createWorkspace, getJob, login } from "../../../cli/gen/index.ts"; + +// Setup method for each api test +export async function setup() { + await generateApiToken() + const workspace = await genTestWorkspace(); + + return { workspace } +} + +async function generateApiToken() { + // if WM_TOKEN env variable not set + if (!OpenAPI.TOKEN) { + const email = Deno.env.get("LOGIN_WM_EMAIL") || "admin@windmill.dev"; + const password = Deno.env.get("LOGIN_WM_PASSWORD") || "changeme"; + const token = await login({ requestBody: { email, password } }); + OpenAPI.TOKEN = token; + } + +} + +/** + * Ensure a workspace named "api-tests" is created + */ +async function genTestWorkspace() { + const id = "api-tests" + + await createWorkspace({ requestBody: { id, name: id}}).catch(()=> null) + + return id +} + +/** + * + * @returns The job result + */ +export async function awaitJobCompletion(data: { workspace: string, id: string, timeoutMs: number }) { + const start = Date.now(); + while (true) { + const result = await getJob(data).catch(()=> null); + + + if (!!result && result.type === "CompletedJob") { + return result; + } + + if ((Date.now() - start) > data.timeoutMs) { + throw new Error("Job did not finish before the specified timeout: " + data.timeoutMs) + } + } +} \ No newline at end of file