|
| 1 | +import assert from "node:assert/strict" |
| 2 | +import { execFile } from "node:child_process" |
| 3 | +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises" |
| 4 | +import { tmpdir } from "node:os" |
| 5 | +import { dirname, join, resolve } from "node:path" |
| 6 | +import { fileURLToPath } from "node:url" |
| 7 | +import { promisify } from "node:util" |
| 8 | + |
| 9 | +const execFileAsync = promisify(execFile) |
| 10 | +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..") |
| 11 | +const root = await mkdtemp(join(tmpdir(), "wp-codebox-browser-recipe-files-")) |
| 12 | +const recipeDirectory = join(root, "recipe") |
| 13 | +const invocationDirectory = join(root, "invocation") |
| 14 | +const cli = join(repositoryRoot, "packages/cli/dist/index.js") |
| 15 | + |
| 16 | +try { |
| 17 | + await Promise.all([mkdir(recipeDirectory), mkdir(invocationDirectory)]) |
| 18 | + await writeFile(join(recipeDirectory, "action-steps.json"), JSON.stringify([{ kind: "evaluate", expression: "document.documentElement.dataset.actionRecipe = 'ready'", assert: "ready" }])) |
| 19 | + await writeFile(join(recipeDirectory, "scenario.json"), JSON.stringify({ url: "about:blank", captures: ["steps"], steps: [{ kind: "evaluate", expression: "document.documentElement.dataset.scenarioRecipe = 'ready'", assert: "ready" }] })) |
| 20 | + await writeFile(join(recipeDirectory, "scenario-steps.json"), JSON.stringify([{ kind: "evaluate", expression: "document.documentElement.dataset.scenarioStepsRecipe = 'ready'", assert: "ready" }])) |
| 21 | + await writeFile(join(recipeDirectory, "valid-recipe.json"), JSON.stringify({ |
| 22 | + schema: "wp-codebox/workspace-recipe/v1", |
| 23 | + runtime: { backend: "wordpress-playground", wp: "latest", blueprint: { steps: [] } }, |
| 24 | + workflow: { steps: [ |
| 25 | + { command: "wordpress.browser-actions", args: ["url=about:blank", "steps-json=@action-steps.json", "capture=steps"] }, |
| 26 | + { command: "wordpress.browser-scenario", args: ["scenario-json=@scenario.json"] }, |
| 27 | + { command: "wordpress.browser-scenario", args: ["url=about:blank", "steps-json=@scenario-steps.json", "capture=steps"] }, |
| 28 | + ] }, |
| 29 | + })) |
| 30 | + |
| 31 | + const validDryRun = await execFileAsync(process.execPath, [cli, "recipe-run", "--recipe", join(recipeDirectory, "valid-recipe.json"), "--dry-run", "--json"], { |
| 32 | + cwd: invocationDirectory, |
| 33 | + timeout: 120_000, |
| 34 | + maxBuffer: 4 * 1024 * 1024, |
| 35 | + }) |
| 36 | + const validDryRunOutput = JSON.parse(validDryRun.stdout) |
| 37 | + assert.equal(validDryRunOutput.plan.policy.commands.includes("wordpress.browser-actions.evaluate"), true) |
| 38 | + |
| 39 | + const valid = await execFileAsync(process.execPath, [cli, "recipe-run", "--recipe", join(recipeDirectory, "valid-recipe.json"), "--artifacts", join(root, "valid-artifacts"), "--json"], { |
| 40 | + cwd: invocationDirectory, |
| 41 | + timeout: 300_000, |
| 42 | + maxBuffer: 4 * 1024 * 1024, |
| 43 | + }) |
| 44 | + const validOutput = JSON.parse(valid.stdout) |
| 45 | + assert.equal(validOutput.success, true, valid.stdout) |
| 46 | + assert.equal(validOutput.executions.length, 3) |
| 47 | + |
| 48 | + await writeFile(join(recipeDirectory, "malformed.json"), "{") |
| 49 | + await writeFile(join(recipeDirectory, "malformed-recipe.json"), JSON.stringify({ |
| 50 | + schema: "wp-codebox/workspace-recipe/v1", |
| 51 | + workflow: { steps: [ |
| 52 | + { command: "wordpress.browser-actions", args: ["url=/", "steps-json=@malformed.json"] }, |
| 53 | + { command: "wordpress.browser-scenario", args: ["scenario-json=@malformed.json"] }, |
| 54 | + { command: "wordpress.browser-scenario", args: ["url=/", "steps-json=@malformed.json"] }, |
| 55 | + ] }, |
| 56 | + })) |
| 57 | + |
| 58 | + let malformedStdout = "" |
| 59 | + try { |
| 60 | + await execFileAsync(process.execPath, [cli, "recipe-run", "--recipe", join(recipeDirectory, "malformed-recipe.json"), "--dry-run", "--json"], { |
| 61 | + cwd: invocationDirectory, |
| 62 | + timeout: 120_000, |
| 63 | + maxBuffer: 4 * 1024 * 1024, |
| 64 | + }) |
| 65 | + assert.fail("Malformed file-backed browser payloads should fail recipe validation") |
| 66 | + } catch (error) { |
| 67 | + malformedStdout = String((error as { stdout?: string }).stdout ?? "") |
| 68 | + } |
| 69 | + const malformedOutput = JSON.parse(malformedStdout) |
| 70 | + assert.equal(malformedOutput.success, false) |
| 71 | + assert.deepEqual(malformedOutput.validation.issues.map((issue: { code: string }) => issue.code), ["invalid-steps-json", "invalid-scenario-json", "invalid-steps-json"]) |
| 72 | +} finally { |
| 73 | + await rm(root, { recursive: true, force: true }) |
| 74 | +} |
| 75 | + |
| 76 | +console.log("browser recipe file payload integration passed") |
0 commit comments