typescript-virtual-container 1.2.9 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +0 -1
- package/README.md +141 -50
- package/biome.json +7 -0
- package/dist/SSHMimic/exec.d.ts.map +1 -1
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +32 -16
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +20 -6
- package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
- package/dist/VirtualFileSystem/binaryPack.js +29 -6
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +36 -13
- package/dist/VirtualPackageManager/index.d.ts.map +1 -1
- package/dist/VirtualPackageManager/index.js +192 -43
- package/dist/VirtualShell/index.d.ts +10 -4
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +18 -7
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +3 -1
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts +6 -0
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +6 -0
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +32 -9
- package/dist/commands/awk.d.ts +11 -0
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +15 -2
- package/dist/commands/base64.d.ts +5 -0
- package/dist/commands/base64.d.ts.map +1 -1
- package/dist/commands/base64.js +9 -1
- package/dist/commands/cat.d.ts +5 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +10 -2
- package/dist/commands/cd.d.ts +5 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +5 -0
- package/dist/commands/chmod.d.ts +5 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +5 -0
- package/dist/commands/cp.d.ts +5 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +5 -0
- package/dist/commands/curl.d.ts +5 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +34 -6
- package/dist/commands/cut.d.ts +5 -0
- package/dist/commands/cut.d.ts.map +1 -1
- package/dist/commands/cut.js +8 -1
- package/dist/commands/date.d.ts +5 -0
- package/dist/commands/date.d.ts.map +1 -1
- package/dist/commands/date.js +7 -1
- package/dist/commands/declare.d.ts +3 -0
- package/dist/commands/declare.d.ts.map +1 -0
- package/dist/commands/declare.js +39 -0
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +5 -0
- package/dist/commands/dpkg.d.ts +5 -0
- package/dist/commands/dpkg.d.ts.map +1 -1
- package/dist/commands/dpkg.js +24 -7
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +8 -2
- package/dist/commands/echo.d.ts +5 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +13 -4
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +11 -1
- package/dist/commands/exit.d.ts +5 -0
- package/dist/commands/exit.d.ts.map +1 -1
- package/dist/commands/exit.js +12 -2
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -1
- package/dist/commands/find.d.ts +5 -0
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +5 -0
- package/dist/commands/free.d.ts +5 -0
- package/dist/commands/free.d.ts.map +1 -1
- package/dist/commands/free.js +5 -0
- package/dist/commands/grep.d.ts +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +12 -2
- package/dist/commands/gzip.d.ts +5 -0
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +18 -2
- package/dist/commands/head.d.ts +5 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +5 -0
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +98 -45
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +5 -0
- package/dist/commands/hostname.d.ts +5 -0
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +5 -0
- package/dist/commands/id.d.ts.map +1 -1
- package/dist/commands/id.js +4 -1
- package/dist/commands/index.d.ts +2 -17
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -340
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +3 -1
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +8 -2
- package/dist/commands/nano.js +1 -1
- package/dist/commands/neofetch.js +1 -1
- package/dist/commands/node.d.ts +9 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +316 -0
- package/dist/commands/npm.d.ts +19 -0
- package/dist/commands/npm.d.ts.map +1 -0
- package/dist/commands/npm.js +109 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +3 -1
- package/dist/commands/printf.d.ts +3 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +113 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +4 -1
- package/dist/commands/python.d.ts +30 -0
- package/dist/commands/python.d.ts.map +1 -0
- package/dist/commands/python.js +2058 -0
- package/dist/commands/read.d.ts +3 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +34 -0
- package/dist/commands/registry.d.ts +8 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +229 -0
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +280 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +11 -3
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +9 -3
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +57 -36
- package/dist/commands/shift.d.ts +5 -0
- package/dist/commands/shift.d.ts.map +1 -0
- package/dist/commands/shift.js +52 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +4 -2
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -2
- package/dist/commands/sudo.js +1 -1
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +11 -3
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +8 -6
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +46 -24
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +3 -1
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +14 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +1 -1
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +4 -1
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +4 -1
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +32 -7
- package/dist/commands/which.d.ts.map +1 -1
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -9
- package/dist/modules/linuxRootfs.d.ts +18 -1
- package/dist/modules/linuxRootfs.d.ts.map +1 -1
- package/dist/modules/linuxRootfs.js +160 -17
- package/dist/standalone-wo-sftp.d.ts +2 -0
- package/dist/standalone-wo-sftp.d.ts.map +1 -0
- package/dist/standalone-wo-sftp.js +30 -0
- package/dist/utils/expand.d.ts +50 -0
- package/dist/utils/expand.d.ts.map +1 -0
- package/dist/utils/expand.js +183 -0
- package/dist/utils/vfsDiff.d.ts +90 -0
- package/dist/utils/vfsDiff.d.ts.map +1 -0
- package/dist/utils/vfsDiff.js +177 -0
- package/package.json +2 -1
- package/src/SSHMimic/exec.ts +10 -1
- package/src/SSHMimic/executor.ts +104 -18
- package/src/SSHMimic/index.ts +49 -15
- package/src/VirtualFileSystem/binaryPack.ts +35 -8
- package/src/VirtualFileSystem/index.ts +78 -28
- package/src/VirtualPackageManager/index.ts +208 -49
- package/src/VirtualShell/index.ts +35 -7
- package/src/VirtualShell/shell.ts +23 -3
- package/src/VirtualShell/shellParser.ts +134 -36
- package/src/VirtualUserManager/index.ts +7 -2
- package/src/commands/adduser.ts +6 -0
- package/src/commands/alias.ts +5 -1
- package/src/commands/apt.ts +47 -17
- package/src/commands/awk.ts +20 -6
- package/src/commands/base64.ts +13 -2
- package/src/commands/cat.ts +13 -5
- package/src/commands/cd.ts +5 -0
- package/src/commands/chmod.ts +5 -0
- package/src/commands/cp.ts +5 -0
- package/src/commands/curl.ts +56 -12
- package/src/commands/cut.ts +8 -1
- package/src/commands/date.ts +7 -1
- package/src/commands/declare.ts +44 -0
- package/src/commands/diff.ts +17 -3
- package/src/commands/dpkg.ts +33 -11
- package/src/commands/du.ts +17 -5
- package/src/commands/echo.ts +22 -9
- package/src/commands/env.ts +11 -1
- package/src/commands/exit.ts +12 -2
- package/src/commands/export.ts +3 -1
- package/src/commands/find.ts +5 -0
- package/src/commands/free.ts +9 -2
- package/src/commands/grep.ts +12 -2
- package/src/commands/gzip.ts +28 -4
- package/src/commands/head.ts +5 -0
- package/src/commands/help.ts +121 -47
- package/src/commands/history.ts +7 -2
- package/src/commands/hostname.ts +5 -0
- package/src/commands/id.ts +4 -1
- package/src/commands/index.ts +9 -360
- package/src/commands/ls.ts +5 -3
- package/src/commands/lsb-release.ts +8 -2
- package/src/commands/nano.ts +1 -1
- package/src/commands/neofetch.ts +1 -1
- package/src/commands/node.ts +341 -0
- package/src/commands/npm.ts +132 -0
- package/src/commands/ping.ts +6 -2
- package/src/commands/printf.ts +112 -0
- package/src/commands/ps.ts +21 -9
- package/src/commands/python.ts +2229 -0
- package/src/commands/read.ts +41 -0
- package/src/commands/registry.ts +244 -0
- package/src/commands/runtime.ts +353 -0
- package/src/commands/sed.ts +27 -9
- package/src/commands/set.ts +9 -3
- package/src/commands/sh.ts +159 -55
- package/src/commands/shift.ts +53 -0
- package/src/commands/sleep.ts +2 -1
- package/src/commands/sort.ts +10 -6
- package/src/commands/source.ts +15 -3
- package/src/commands/sudo.ts +1 -1
- package/src/commands/tar.ts +28 -7
- package/src/commands/tee.ts +7 -1
- package/src/commands/test.ts +61 -26
- package/src/commands/tr.ts +3 -1
- package/src/commands/true.ts +17 -0
- package/src/commands/type.ts +6 -3
- package/src/commands/uname.ts +5 -1
- package/src/commands/uniq.ts +8 -2
- package/src/commands/uptime.ts +4 -1
- package/src/commands/wget.ts +51 -12
- package/src/commands/which.ts +5 -2
- package/src/commands/xargs.ts +11 -2
- package/src/index.ts +23 -24
- package/src/modules/linuxRootfs.ts +233 -30
- package/src/standalone-wo-sftp.ts +38 -0
- package/src/utils/expand.ts +238 -0
- package/src/utils/vfsDiff.ts +275 -0
- package/standalone-wo-sftp.js +507 -0
- package/standalone-wo-sftp.js.map +7 -0
- package/standalone.js +253 -191
- package/standalone.js.map +4 -4
- package/tests/bun-test-shim.ts +9 -1
- package/tests/command-helpers.test.ts +1 -5
- package/tests/new-features.test.ts +415 -5
- package/tests/parser-executor.test.ts +27 -27
- package/tests/sftp.test.ts +122 -42
- package/tests/users.test.ts +23 -5
- package/CHANGELOG.md +0 -150
package/tests/bun-test-shim.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
getArg,
|
|
4
|
-
getFlag,
|
|
5
|
-
ifFlag,
|
|
6
|
-
} from "../src/commands/command-helpers";
|
|
2
|
+
import { getArg, getFlag, ifFlag } from "../src/commands/command-helpers";
|
|
7
3
|
|
|
8
4
|
describe("command-helpers", () => {
|
|
9
5
|
test("ifFlag detects plain and inline flag forms", () => {
|
|
@@ -254,7 +254,7 @@ describe("curl / wget (pure fetch)", () => {
|
|
|
254
254
|
test("curl fetches real URL and returns body", async () => {
|
|
255
255
|
const r = await client.exec("curl https://httpbin.org/get");
|
|
256
256
|
// In sandboxed env network may be blocked — accept 0 (ok), 6 (dns), 22 (http err), or 1 (fetch error)
|
|
257
|
-
expect([0, 1, 6, 22]).toContain(r.exitCode);
|
|
257
|
+
expect([0, 1, 6, 22]).toContain(r.exitCode ?? -1);
|
|
258
258
|
});
|
|
259
259
|
|
|
260
260
|
test("curl -o saves to VFS", async () => {
|
|
@@ -479,7 +479,7 @@ describe("Bug fixes", () => {
|
|
|
479
479
|
test("ls -a shows dotfiles", async () => {
|
|
480
480
|
await c.exec("touch /tmp/.hidden && touch /tmp/visible");
|
|
481
481
|
const normal = await c.exec("ls /tmp");
|
|
482
|
-
const all
|
|
482
|
+
const all = await c.exec("ls -a /tmp");
|
|
483
483
|
expect(normal.stdout).not.toContain(".hidden");
|
|
484
484
|
expect(all.stdout).toContain(".hidden");
|
|
485
485
|
expect(all.stdout).toContain("visible");
|
|
@@ -500,7 +500,7 @@ describe("Bug fixes", () => {
|
|
|
500
500
|
await c.exec("chmod u+x /tmp/u.sh");
|
|
501
501
|
const mode = shell2.vfs.stat("/tmp/u.sh").mode;
|
|
502
502
|
expect(mode & 0o100).toBe(0o100); // user x set
|
|
503
|
-
expect(mode & 0o010).toBe(0);
|
|
503
|
+
expect(mode & 0o010).toBe(0); // group x not set
|
|
504
504
|
});
|
|
505
505
|
|
|
506
506
|
test("chmod go-r removes group+other read", async () => {
|
|
@@ -521,7 +521,9 @@ describe("Bug fixes", () => {
|
|
|
521
521
|
// ping -c
|
|
522
522
|
test("ping -c 2 sends exactly 2 packets", async () => {
|
|
523
523
|
const r = await c.exec("ping -c 2 localhost");
|
|
524
|
-
const dataLines = r.stdout
|
|
524
|
+
const dataLines = r.stdout
|
|
525
|
+
?.split("\n")
|
|
526
|
+
.filter((l) => l.includes("icmp_seq="));
|
|
525
527
|
expect(dataLines?.length).toBe(2);
|
|
526
528
|
});
|
|
527
529
|
|
|
@@ -538,7 +540,9 @@ describe("Bug fixes", () => {
|
|
|
538
540
|
});
|
|
539
541
|
|
|
540
542
|
test("[ -f path ] returns 1 for non-existent file", async () => {
|
|
541
|
-
const r = await c.exec(
|
|
543
|
+
const r = await c.exec(
|
|
544
|
+
"[ -f /tmp/doesnotexist999 ] && echo yes || echo no",
|
|
545
|
+
);
|
|
542
546
|
expect(r.stdout?.trim()).toBe("no");
|
|
543
547
|
});
|
|
544
548
|
|
|
@@ -624,3 +628,409 @@ describe("Bug fixes", () => {
|
|
|
624
628
|
expect(r.stdout?.trim()).toBe("3");
|
|
625
629
|
});
|
|
626
630
|
});
|
|
631
|
+
|
|
632
|
+
// ─── Roadmap features ────────────────────────────────────────────────────────
|
|
633
|
+
|
|
634
|
+
describe("/proc/self and /proc/<pid>", () => {
|
|
635
|
+
let shell4: VirtualShell;
|
|
636
|
+
let c4: InstanceType<typeof SshClient>;
|
|
637
|
+
|
|
638
|
+
beforeAll(async () => {
|
|
639
|
+
shell4 = new VirtualShell("proc-vm");
|
|
640
|
+
await shell4.ensureInitialized();
|
|
641
|
+
c4 = new SshClient(shell4, "root");
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test("/proc/self exists and has comm file", async () => {
|
|
645
|
+
expect(shell4.vfs.exists("/proc/self")).toBe(true);
|
|
646
|
+
expect(shell4.vfs.exists("/proc/self/comm")).toBe(true);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test("/proc/self/status has Name field", async () => {
|
|
650
|
+
const r = await c4.exec("cat /proc/self/status");
|
|
651
|
+
expect(r.exitCode).toBe(0);
|
|
652
|
+
expect(r.stdout).toContain("Name:");
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test("/proc/self/cmdline is readable", async () => {
|
|
656
|
+
const r = await c4.exec("cat /proc/self/cmdline");
|
|
657
|
+
expect(r.exitCode).toBe(0);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test("/proc/1 (init) exists", async () => {
|
|
661
|
+
expect(shell4.vfs.exists("/proc/1")).toBe(true);
|
|
662
|
+
expect(shell4.vfs.exists("/proc/1/status")).toBe(true);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("/proc/1/status has correct pid", async () => {
|
|
666
|
+
const r = await c4.exec("cat /proc/1/status");
|
|
667
|
+
expect(r.stdout).toContain("Pid:");
|
|
668
|
+
expect(r.stdout).toContain("1");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("refreshProcFs updates /proc contents", () => {
|
|
672
|
+
shell4.refreshProcFs();
|
|
673
|
+
expect(shell4.vfs.exists("/proc/uptime")).toBe(true);
|
|
674
|
+
expect(shell4.vfs.exists("/proc/self")).toBe(true);
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
import { diffSnapshots, formatDiff, assertDiff } from "../src";
|
|
679
|
+
|
|
680
|
+
describe("VFS snapshot diff tooling", () => {
|
|
681
|
+
let shell5: VirtualShell;
|
|
682
|
+
let c5: InstanceType<typeof SshClient>;
|
|
683
|
+
|
|
684
|
+
beforeAll(async () => {
|
|
685
|
+
shell5 = new VirtualShell("diff-vm");
|
|
686
|
+
await shell5.ensureInitialized();
|
|
687
|
+
c5 = new SshClient(shell5, "root");
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("diffSnapshots returns clean diff for identical snapshots", () => {
|
|
691
|
+
const snap = shell5.vfs.toSnapshot();
|
|
692
|
+
const diff = diffSnapshots(snap, snap);
|
|
693
|
+
expect(diff.clean).toBe(true);
|
|
694
|
+
expect(diff.added).toHaveLength(0);
|
|
695
|
+
expect(diff.removed).toHaveLength(0);
|
|
696
|
+
expect(diff.modified).toHaveLength(0);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test("diffSnapshots detects added file", async () => {
|
|
700
|
+
const before = shell5.vfs.toSnapshot();
|
|
701
|
+
await c5.exec("echo test > /tmp/diff-test.txt");
|
|
702
|
+
const after = shell5.vfs.toSnapshot();
|
|
703
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
704
|
+
const paths = diff.added.map((e) => e.path);
|
|
705
|
+
expect(paths).toContain("/tmp/diff-test.txt");
|
|
706
|
+
expect(diff.clean).toBe(false);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test("diffSnapshots detects added directory", async () => {
|
|
710
|
+
const before = shell5.vfs.toSnapshot();
|
|
711
|
+
shell5.vfs.mkdir("/tmp/diff-newdir", 0o755);
|
|
712
|
+
const after = shell5.vfs.toSnapshot();
|
|
713
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
714
|
+
const paths = diff.added.map((e) => e.path);
|
|
715
|
+
expect(paths).toContain("/tmp/diff-newdir");
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test("diffSnapshots detects modified file", async () => {
|
|
719
|
+
shell5.vfs.writeFile("/tmp/modtest.txt", "before");
|
|
720
|
+
const before = shell5.vfs.toSnapshot();
|
|
721
|
+
shell5.vfs.writeFile("/tmp/modtest.txt", "after");
|
|
722
|
+
const after = shell5.vfs.toSnapshot();
|
|
723
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
724
|
+
const mod = diff.modified.find((e) => e.path === "/tmp/modtest.txt");
|
|
725
|
+
expect(mod).toBeDefined();
|
|
726
|
+
expect(mod?.before).toBe("before");
|
|
727
|
+
expect(mod?.after).toBe("after");
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("diffSnapshots detects removed file", async () => {
|
|
731
|
+
shell5.vfs.writeFile("/tmp/toremove.txt", "bye");
|
|
732
|
+
const before = shell5.vfs.toSnapshot();
|
|
733
|
+
shell5.vfs.remove("/tmp/toremove.txt");
|
|
734
|
+
const after = shell5.vfs.toSnapshot();
|
|
735
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
736
|
+
const paths = diff.removed.map((e) => e.path);
|
|
737
|
+
expect(paths).toContain("/tmp/toremove.txt");
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test("diffSnapshots ignores specified prefixes", async () => {
|
|
741
|
+
shell5.vfs.writeFile("/proc/uptime", "999.00 888.00\n");
|
|
742
|
+
const before = shell5.vfs.toSnapshot();
|
|
743
|
+
shell5.vfs.writeFile("/proc/uptime", "1000.00 900.00\n");
|
|
744
|
+
const after = shell5.vfs.toSnapshot();
|
|
745
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
746
|
+
expect(diff.modified.map((e) => e.path)).not.toContain("/proc/uptime");
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test("formatDiff returns (no changes) for clean diff", () => {
|
|
750
|
+
const snap = shell5.vfs.toSnapshot();
|
|
751
|
+
const diff = diffSnapshots(snap, snap);
|
|
752
|
+
expect(formatDiff(diff)).toBe("(no changes)");
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test("formatDiff includes change summary", async () => {
|
|
756
|
+
const before = shell5.vfs.toSnapshot();
|
|
757
|
+
shell5.vfs.writeFile("/tmp/format-test.txt", "x");
|
|
758
|
+
const after = shell5.vfs.toSnapshot();
|
|
759
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
760
|
+
const formatted = formatDiff(diff);
|
|
761
|
+
expect(formatted).toContain("added");
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test("assertDiff passes when paths match", async () => {
|
|
765
|
+
shell5.vfs.writeFile("/tmp/assert-test.txt", "hello");
|
|
766
|
+
const before = shell5.vfs.toSnapshot();
|
|
767
|
+
shell5.vfs.writeFile("/tmp/assert-new.txt", "new");
|
|
768
|
+
shell5.vfs.remove("/tmp/assert-test.txt");
|
|
769
|
+
const after = shell5.vfs.toSnapshot();
|
|
770
|
+
const diff = diffSnapshots(before, after, { ignore: ["/proc"] });
|
|
771
|
+
expect(() =>
|
|
772
|
+
assertDiff(diff, {
|
|
773
|
+
added: ["/tmp/assert-new.txt"],
|
|
774
|
+
removed: ["/tmp/assert-test.txt"],
|
|
775
|
+
}),
|
|
776
|
+
).not.toThrow();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
test("assertDiff throws when expected path is missing", () => {
|
|
780
|
+
const snap = shell5.vfs.toSnapshot();
|
|
781
|
+
const diff = diffSnapshots(snap, snap);
|
|
782
|
+
expect(() => assertDiff(diff, { added: ["/nonexistent"] })).toThrow();
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
describe("node and python3 REPL stubs", () => {
|
|
787
|
+
let shell6: VirtualShell;
|
|
788
|
+
let c6: InstanceType<typeof SshClient>;
|
|
789
|
+
|
|
790
|
+
beforeAll(async () => {
|
|
791
|
+
shell6 = new VirtualShell("repl-vm");
|
|
792
|
+
await shell6.ensureInitialized();
|
|
793
|
+
c6 = new SshClient(shell6, "root");
|
|
794
|
+
await c6.exec("apt install nodejs python3");
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test("node --version returns v18", async () => {
|
|
798
|
+
const r = await c6.exec("node --version");
|
|
799
|
+
expect(r.exitCode).toBe(0);
|
|
800
|
+
expect(r.stdout?.trim()).toBe("v18.19.0");
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test("node -e evaluates arithmetic", async () => {
|
|
804
|
+
const r = await c6.exec("node -e '2 + 3'");
|
|
805
|
+
expect(r.exitCode).toBe(0);
|
|
806
|
+
expect(r.stdout?.trim()).toBe("5");
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test("node -e console.log works", async () => {
|
|
810
|
+
const r = await c6.exec("node -e 'console.log(42)'");
|
|
811
|
+
expect(r.stdout?.trim()).toBe("42");
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("node executes VFS file", async () => {
|
|
815
|
+
shell6.vfs.writeFile("/tmp/test.js", "console.log(10 + 5);\n");
|
|
816
|
+
const r = await c6.exec("node /tmp/test.js");
|
|
817
|
+
expect(r.stdout?.trim()).toBe("15");
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("node missing file returns exit 1", async () => {
|
|
821
|
+
const r = await c6.exec("node /tmp/nonexistent.js");
|
|
822
|
+
expect(r.exitCode).toBe(1);
|
|
823
|
+
expect(r.stderr).toContain("No such file or directory");
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test("python3 --version returns Python 3.11", async () => {
|
|
827
|
+
const r = await c6.exec("python3 --version");
|
|
828
|
+
expect(r.exitCode).toBe(0);
|
|
829
|
+
expect(r.stdout?.trim()).toContain("Python 3.11");
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test("python3 -c print arithmetic", async () => {
|
|
833
|
+
const r = await c6.exec("python3 -c 'print(2 + 3)'");
|
|
834
|
+
expect(r.stdout?.trim()).toBe("5");
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
test("python3 -c f-string", async () => {
|
|
838
|
+
const r = await c6.exec("python3 -c 'print(f\"result: {1+1}\")'");
|
|
839
|
+
expect(r.stdout?.trim()).toBe("result: 2");
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
test("python3 executes VFS script", async () => {
|
|
843
|
+
shell6.vfs.writeFile("/tmp/test.py", "print(10 + 5)\n");
|
|
844
|
+
const r = await c6.exec("python3 /tmp/test.py");
|
|
845
|
+
expect(r.stdout?.trim()).toBe("15");
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("python alias works", async () => {
|
|
849
|
+
const r = await c6.exec("python --version");
|
|
850
|
+
expect(r.exitCode).toBe(0);
|
|
851
|
+
expect(r.stdout?.trim()).toContain("Python");
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test("python3 missing file returns exit 2", async () => {
|
|
855
|
+
const r = await c6.exec("python3 /tmp/nonexistent.py");
|
|
856
|
+
expect(r.exitCode).toBe(2);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
test("node is available as a shell command", async () => {
|
|
860
|
+
const r = await c6.exec("node --version");
|
|
861
|
+
expect(r.exitCode).toBe(0);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
// ─── Enhanced REPL tests (post-rewrite) ──────────────────────────────────────
|
|
866
|
+
|
|
867
|
+
describe("node enhanced REPL", () => {
|
|
868
|
+
let shell7: VirtualShell;
|
|
869
|
+
let c7: InstanceType<typeof SshClient>;
|
|
870
|
+
|
|
871
|
+
beforeAll(async () => {
|
|
872
|
+
shell7 = new VirtualShell("node-vm");
|
|
873
|
+
await shell7.ensureInitialized();
|
|
874
|
+
c7 = new SshClient(shell7, "root");
|
|
875
|
+
await c7.exec("apt install nodejs");
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test("node -e string methods", async () => {
|
|
879
|
+
shell7.vfs.writeFile("/tmp/str.js", "'hello'.toUpperCase()");
|
|
880
|
+
const r = await c7.exec("node /tmp/str.js");
|
|
881
|
+
expect(r.stdout?.trim()).toBe("HELLO");
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
test("node -e array methods", async () => {
|
|
885
|
+
shell7.vfs.writeFile("/tmp/arr.js", "[1,2,3].map(x => x*2).join(',')");
|
|
886
|
+
const r = await c7.exec("node /tmp/arr.js");
|
|
887
|
+
expect(r.stdout?.trim()).toBe("2,4,6");
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test("node process.version is virtual", async () => {
|
|
891
|
+
const r = await c7.exec("node -e 'process.version'");
|
|
892
|
+
expect(r.stdout?.trim()).toBe("v18.19.0");
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
test("node require path works", async () => {
|
|
896
|
+
shell7.vfs.writeFile(
|
|
897
|
+
"/tmp/path.js",
|
|
898
|
+
"const p = require('path'); console.log(p.join('a', 'b'))",
|
|
899
|
+
);
|
|
900
|
+
const r = await c7.exec("node /tmp/path.js");
|
|
901
|
+
expect(r.stdout?.trim()).toBe("a/b");
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test("node require fs throws gracefully", async () => {
|
|
905
|
+
shell7.vfs.writeFile(
|
|
906
|
+
"/tmp/fs.js",
|
|
907
|
+
"try { require('fs') } catch(e) { console.log('blocked') }",
|
|
908
|
+
);
|
|
909
|
+
const r = await c7.exec("node /tmp/fs.js");
|
|
910
|
+
expect(r.stdout?.trim()).toBe("blocked");
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test("node console.log output", async () => {
|
|
914
|
+
shell7.vfs.writeFile("/tmp/log.js", "console.log('hello', 'world')");
|
|
915
|
+
const r = await c7.exec("node /tmp/log.js");
|
|
916
|
+
expect(r.stdout?.trim()).toBe("hello world");
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test("node Math methods", async () => {
|
|
920
|
+
const r = await c7.exec("node -e 'Math.floor(3.9)'");
|
|
921
|
+
expect(r.stdout?.trim()).toBe("3");
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
test("node JSON.stringify", async () => {
|
|
925
|
+
const r = await c7.exec("node -e 'JSON.stringify({x:1})'");
|
|
926
|
+
expect(r.stdout?.trim()).toBe('{"x":1}');
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
describe("python3 enhanced interpreter", () => {
|
|
931
|
+
let shell8: VirtualShell;
|
|
932
|
+
let c8: InstanceType<typeof SshClient>;
|
|
933
|
+
|
|
934
|
+
beforeAll(async () => {
|
|
935
|
+
shell8 = new VirtualShell("python-vm");
|
|
936
|
+
await shell8.ensureInitialized();
|
|
937
|
+
c8 = new SshClient(shell8, "root");
|
|
938
|
+
await c8.exec("apt install python3");
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
const py = async (code: string) => {
|
|
942
|
+
shell8.vfs.writeFile("/tmp/t.py", code);
|
|
943
|
+
return c8.exec("python3 /tmp/t.py");
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
test("str.upper()", async () => {
|
|
947
|
+
const r = await py("print('hello'.upper())");
|
|
948
|
+
expect(r.stdout?.trim()).toBe("HELLO");
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
test("str.split()", async () => {
|
|
952
|
+
const r = await py("print('a,b,c'.split(','))");
|
|
953
|
+
expect(r.stdout?.trim()).toBe("['a', 'b', 'c']");
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test("str.join()", async () => {
|
|
957
|
+
const r = await py("print(','.join(['a','b','c']))");
|
|
958
|
+
expect(r.stdout?.trim()).toBe("a,b,c");
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
test("list comprehension", async () => {
|
|
962
|
+
const r = await py("print([x**2 for x in range(5)])");
|
|
963
|
+
expect(r.stdout?.trim()).toBe("[0, 1, 4, 9, 16]");
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test("list.sort() in-place", async () => {
|
|
967
|
+
const r = await py("nums=[3,1,4,1,5]\nnums.sort()\nprint(nums)");
|
|
968
|
+
expect(r.stdout?.trim()).toBe("[1, 1, 3, 4, 5]");
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
test("dict access and methods", async () => {
|
|
972
|
+
const r = await py("d={'x':1,'y':2}\nprint(d['x'])\nprint(list(d.keys()))");
|
|
973
|
+
expect(r.stdout).toContain("1");
|
|
974
|
+
expect(r.stdout).toContain("x");
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
test("class with __init__ and methods", async () => {
|
|
978
|
+
const r = await py(
|
|
979
|
+
"class Dog:\n def __init__(self, name):\n self.name = name\n def bark(self):\n return f'Woof! I am {self.name}'\nd = Dog('Rex')\nprint(d.bark())",
|
|
980
|
+
);
|
|
981
|
+
expect(r.stdout?.trim()).toBe("Woof! I am Rex");
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
test("import math and use functions", async () => {
|
|
985
|
+
const r = await py(
|
|
986
|
+
"import math\nprint(math.floor(3.9))\nprint(round(math.sqrt(16), 0))",
|
|
987
|
+
);
|
|
988
|
+
expect(r.stdout).toContain("3");
|
|
989
|
+
expect(r.stdout).toContain("4");
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
test("import os and getcwd", async () => {
|
|
993
|
+
const r = await py("import os\nprint(os.getcwd())");
|
|
994
|
+
expect(r.exitCode).toBe(0);
|
|
995
|
+
expect(r.stdout?.trim().length).toBeGreaterThan(0);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
test("import sys and version", async () => {
|
|
999
|
+
const r = await py("import sys\nprint(sys.version[:5])");
|
|
1000
|
+
expect(r.stdout?.trim()).toBe("3.11.");
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
test("import json dumps/loads", async () => {
|
|
1004
|
+
const r = await py(
|
|
1005
|
+
"import json\nd={'x':1}\nprint(json.dumps(d))\nprint(json.loads('{\"a\":2}')['a'])",
|
|
1006
|
+
);
|
|
1007
|
+
expect(r.stdout).toContain('"x"');
|
|
1008
|
+
expect(r.stdout).toContain("2");
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
test("try/except handles errors", async () => {
|
|
1012
|
+
const r = await py(
|
|
1013
|
+
"try:\n x = 1/0\nexcept ZeroDivisionError:\n print('caught')",
|
|
1014
|
+
);
|
|
1015
|
+
expect(r.stdout?.trim()).toBe("caught");
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test("f-string interpolation", async () => {
|
|
1019
|
+
const r = await py("name='world'\nprint(f'hello {name}')");
|
|
1020
|
+
expect(r.stdout?.trim()).toBe("hello world");
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
test("sorted() and reversed()", async () => {
|
|
1024
|
+
const r = await py(
|
|
1025
|
+
"print(sorted([3,1,2]))\nprint(list(reversed([1,2,3])))",
|
|
1026
|
+
);
|
|
1027
|
+
expect(r.stdout).toContain("[1, 2, 3]");
|
|
1028
|
+
expect(r.stdout).toContain("[3, 2, 1]");
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test("enumerate()", async () => {
|
|
1032
|
+
const r = await py("for i,v in enumerate(['a','b']):\n print(i, v)");
|
|
1033
|
+
expect(r.stdout).toContain("0 a");
|
|
1034
|
+
expect(r.stdout).toContain("1 b");
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
@@ -4,34 +4,34 @@ import { executePipeline } from "../src/SSHMimic/executor";
|
|
|
4
4
|
import { parseShellPipeline } from "../src/VirtualShell/shellParser";
|
|
5
5
|
|
|
6
6
|
describe("Pipeline parser and executor", () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
test("parses simple pipeline", () => {
|
|
8
|
+
const pipeline = parseShellPipeline("echo hello | grep h");
|
|
9
|
+
expect(pipeline.isValid).toBe(true);
|
|
10
|
+
expect(pipeline.commands).toHaveLength(2);
|
|
11
|
+
expect(pipeline.commands[0]?.name).toBe("echo");
|
|
12
|
+
expect(pipeline.commands[1]?.name).toBe("grep");
|
|
13
|
+
});
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
test("handles invalid syntax", () => {
|
|
16
|
+
const pipeline = parseShellPipeline("echo hello |");
|
|
17
|
+
expect(pipeline.isValid).toBe(false);
|
|
18
|
+
expect(pipeline.error).toBeDefined();
|
|
19
|
+
});
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
test("executes simple pipeline", async () => {
|
|
22
|
+
const shell = new VirtualShell("localhost");
|
|
23
|
+
const pipeline = parseShellPipeline("echo hello | grep h");
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
const result = await executePipeline(
|
|
26
|
+
pipeline,
|
|
27
|
+
"root",
|
|
28
|
+
"localhost",
|
|
29
|
+
"shell",
|
|
30
|
+
"/",
|
|
31
|
+
shell,
|
|
32
|
+
);
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
});
|
|
34
|
+
expect(result.exitCode).toBe(0);
|
|
35
|
+
expect(result.stdout).toContain("hello");
|
|
36
|
+
});
|
|
37
|
+
});
|