diff --git a/packages/cli/tests/cli/dev-media.spec.ts b/packages/cli/tests/cli/dev-media.spec.ts new file mode 100644 index 00000000..fd41e981 --- /dev/null +++ b/packages/cli/tests/cli/dev-media.spec.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; +import { waitForDevServer } from "./testkit/dev-utils.js"; +import { fixture, setupCLITests } from "./testkit/index.js"; + +describe("media in dev", () => { + const t = setupCLITests(); + + it("should upload public file and serve it", async () => { + await t.givenLoggedInWithProject(fixture("basic")); + + const handle = await t.runLive("dev"); + const url = await waitForDevServer(handle); + + const form = new FormData(); + form.append( + "file", + new Blob(["hello world"], { type: "text/plain" }), + "test.txt", + ); + + const uploadRes = await fetch( + `${url}/api/apps/${t.kit.api.appId}/integration-endpoints/Core/UploadFile`, + { method: "POST", body: form }, + ); + expect(uploadRes.status).toBe(200); + + const { file_url } = (await uploadRes.json()) as { file_url: string }; + expect(file_url).toMatch(/\/media\/.+\.txt$/); + + const fileRes = await fetch(file_url); + expect(fileRes.status).toBe(200); + expect(await fileRes.text()).toBe("hello world"); + + const result = await handle.stop(); + t.expectResult(result).toSucceed(); + }); + + it("should upload secret file and serve it with token", async () => { + await t.givenLoggedInWithProject(fixture("basic")); + + const handle = await t.runLive("dev"); + const url = await waitForDevServer(handle); + + const form = new FormData(); + form.append( + "file", + new Blob(["secret content"], { type: "text/plain" }), + "secret.txt", + ); + + const uploadRes = await fetch( + `${url}/api/apps/${t.kit.api.appId}/integration-endpoints/Core/UploadPrivateFile`, + { method: "POST", body: form }, + ); + expect(uploadRes.status).toBe(200); + + const { file_uri } = (await uploadRes.json()) as { file_uri: string }; + expect(file_uri).toMatch(/\.txt$/); + + // Get a signed URL for the private file + const signedUrlRes = await fetch( + `${url}/api/apps/${t.kit.api.appId}/integration-endpoints/Core/CreateFileSignedUrl`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ file_uri }), + }, + ); + expect(signedUrlRes.status).toBe(200); + + const { signed_url } = (await signedUrlRes.json()) as { + signed_url: string; + }; + expect(signed_url).toContain("/media/private/"); + expect(signed_url).toContain("token="); + + // Fetch with valid token succeeds + const fileRes = await fetch(signed_url); + expect(fileRes.status).toBe(200); + expect(await fileRes.text()).toBe("secret content"); + + // Fetch with wrong token returns 400 + const badUrl = signed_url.replace(/([?&]token=)[^&]+/, "$1invalid"); + const badRes = await fetch(badUrl); + expect(badRes.status).toBe(400); + const badBody = (await badRes.json()) as { error: string }; + expect(badBody.error).toBe("InvalidJWT"); + + // Fetch with no token returns 401 + const urlWithToken = new URL(signed_url); + urlWithToken.searchParams.delete("token"); + const noTokenUrl = urlWithToken.toString(); + const noTokenRes = await fetch(noTokenUrl); + expect(noTokenRes.status).toBe(401); + const noTokenBody = (await noTokenRes.json()) as { error: string }; + expect(noTokenBody.error).toBe("Missing token"); + + const result = await handle.stop(); + t.expectResult(result).toSucceed(); + }); +}); diff --git a/packages/cli/tests/cli/dev.spec.ts b/packages/cli/tests/cli/dev.spec.ts index 3e2780a0..cbb9bea1 100644 --- a/packages/cli/tests/cli/dev.spec.ts +++ b/packages/cli/tests/cli/dev.spec.ts @@ -1,14 +1,6 @@ import { describe, it } from "vitest"; -import { fixture, type RunLiveHandle, setupCLITests } from "./testkit/index.js"; - -const waitForDevServer = async ( - runLiveHandle: RunLiveHandle, -): Promise => { - const pattern = /Dev server is available at (http\S+)/; - await runLiveHandle.waitForOutput(pattern); - const match = runLiveHandle.stdout.join("").match(pattern)!; - return match[1]; -}; +import { waitForDevServer } from "./testkit/dev-utils.js"; +import { fixture, setupCLITests } from "./testkit/index.js"; describe("dev command", () => { const t = setupCLITests(); diff --git a/packages/cli/tests/cli/testkit/dev-utils.ts b/packages/cli/tests/cli/testkit/dev-utils.ts new file mode 100644 index 00000000..16766b67 --- /dev/null +++ b/packages/cli/tests/cli/testkit/dev-utils.ts @@ -0,0 +1,13 @@ +import type { RunLiveHandle } from "./CLITestkit"; + +export const waitForDevServer = async ( + runLiveHandle: RunLiveHandle, +): Promise => { + const pattern = /Dev server is available at (http\S+)/; + await runLiveHandle.waitForOutput(pattern); + const match = runLiveHandle.stdout.join("").match(pattern); + if (!match) { + throw new Error(`Can't find dev server url`); + } + return match[1]; +};