|
1 | 1 | import assert from "node:assert/strict" |
2 | 2 | import { execFile } from "node:child_process" |
3 | 3 | import { createHash } from "node:crypto" |
4 | | -import { mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises" |
| 4 | +import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises" |
5 | 5 | import { tmpdir } from "node:os" |
6 | 6 | import { join } from "node:path" |
7 | 7 | import { promisify } from "node:util" |
8 | 8 |
|
9 | 9 | const execFileAsync = promisify(execFile) |
10 | 10 | const root = await mkdtemp(join(tmpdir(), "wp-codebox-readonly-mounts-integration-")) |
| 11 | +const projectSource = join(root, "project") |
| 12 | +const projectConfigSource = join(projectSource, "config.php") |
| 13 | +const overlaySource = join(root, "config-overlay.php") |
11 | 14 | const readonlySource = join(root, "readonly.bin") |
12 | 15 | const readwriteSource = join(root, "readwrite.bin") |
13 | 16 | const recipePath = join(root, "recipe.json") |
14 | 17 | const artifactsPath = join(root, "artifacts") |
| 18 | +const originalConfig = "<?php return 'parent';\n" |
| 19 | +const overlayConfig = "<?php return 'overlay';\n" |
15 | 20 | const readonlyBytes = Buffer.from([0, 255, 1, 2, 3, 127, 128]) |
16 | 21 | const overwrittenBytes = Buffer.from([128, 127, 3, 2, 1, 255, 0]) |
17 | 22 | const stagingDirectoriesBefore = await readonlyStagingDirectories() |
18 | 23 |
|
19 | 24 | try { |
| 25 | + await mkdir(projectSource) |
| 26 | + await writeFile(projectConfigSource, originalConfig) |
| 27 | + await writeFile(overlaySource, overlayConfig) |
20 | 28 | await writeFile(readonlySource, readonlyBytes) |
21 | 29 | await writeFile(readwriteSource, readonlyBytes) |
22 | 30 | await writeFile(recipePath, `${JSON.stringify({ |
23 | 31 | schema: "wp-codebox/workspace-recipe/v1", |
24 | 32 | runtime: { backend: "wordpress-playground", wp: "6.5", blueprint: { steps: [] } }, |
25 | 33 | inputs: { |
26 | 34 | mounts: [ |
| 35 | + { source: projectSource, target: "/home/project", mode: "readwrite" }, |
| 36 | + { source: overlaySource, target: "/home/project/config.php", mode: "readonly" }, |
27 | 37 | { source: readonlySource, target: "/wordpress/readonly.bin", mode: "readonly" }, |
28 | 38 | { source: readwriteSource, target: "/wordpress/readwrite.bin", mode: "readwrite" }, |
29 | 39 | ], |
30 | 40 | }, |
31 | 41 | workflow: { |
32 | 42 | steps: [{ |
33 | 43 | command: "wordpress.run-php", |
34 | | - args: [`code=$contents = base64_decode('${overwrittenBytes.toString("base64")}'); file_put_contents('/wordpress/readonly.bin', $contents); file_put_contents('/wordpress/readwrite.bin', $contents);`], |
| 44 | + args: [`code=$config = file_get_contents('/home/project/config.php'); if ($config !== "<?php return 'overlay';\\n") { fwrite(STDERR, $config); exit(1); } $contents = base64_decode('${overwrittenBytes.toString("base64")}'); file_put_contents('/home/project/config.php', "overwritten"); file_put_contents('/home/project/mutated.txt', 'parent mutation'); file_put_contents('/wordpress/readonly.bin', $contents); file_put_contents('/wordpress/readwrite.bin', $contents);`], |
35 | 45 | }], |
36 | 46 | }, |
37 | 47 | })}\n`) |
|
41 | 51 | assert.equal(output.success, true, JSON.stringify(output)) |
42 | 52 | assert.equal(sha256(await readFile(readonlySource)), sha256(readonlyBytes), "readonly host bytes must survive an actual Playground PHP overwrite") |
43 | 53 | assert.deepEqual(await readFile(readwriteSource), overwrittenBytes, "readwrite host bytes must reflect an actual Playground PHP overwrite") |
| 54 | + assert.equal(await readFile(overlaySource, "utf8"), overlayConfig, "readonly overlay source must survive an actual Playground PHP overwrite") |
| 55 | + assert.equal(await readFile(projectConfigSource, "utf8"), originalConfig, "the parent writeback must exclude the nested overlay path") |
| 56 | + assert.equal(await readFile(join(projectSource, "mutated.txt"), "utf8"), "parent mutation", "parent readwrite mutations must still materialize") |
44 | 57 | assert.deepEqual(await readonlyStagingDirectories(), stagingDirectoriesBefore, "recipe-run cleanup must remove readonly mount staging") |
45 | 58 | } |
46 | 59 | } finally { |
|
0 commit comments