the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import { vi, describe, it, expect, beforeEach } from "vitest";
2import consola from "consola";
3import { Sandbox } from "@pocketenv/sdk";
4import copy from "./copy";
5
6vi.mock("../lib/sdk", () => ({ configureSdk: vi.fn() }));
7vi.mock("../theme", () => ({
8 c: {
9 primary: (s: string | number) => String(s),
10 },
11}));
12vi.mock("consola", () => ({
13 default: { log: vi.fn(), success: vi.fn(), error: vi.fn() },
14}));
15vi.mock("@pocketenv/sdk", () => ({
16 Sandbox: { get: vi.fn(), configure: vi.fn() },
17}));
18vi.mock("ora", () => ({
19 default: vi.fn(() => ({
20 start: vi.fn().mockReturnThis(),
21 stop: vi.fn(),
22 stopAndPersist: vi.fn(),
23 })),
24}));
25
26describe("copy", () => {
27 const mockExit = vi
28 .spyOn(process, "exit")
29 .mockImplementation(() => undefined as never);
30
31 beforeEach(() => {
32 vi.clearAllMocks();
33 });
34
35 it("copies from local to sandbox", async () => {
36 const mockSandbox = {
37 data: { status: "RUNNING" },
38 copy: { upload: vi.fn().mockResolvedValue(undefined) },
39 };
40 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
41
42 await copy("./local/file.txt", "my-sandbox:/remote/file.txt");
43
44 expect(Sandbox.get).toHaveBeenCalledWith("my-sandbox");
45 expect(mockSandbox.copy.upload).toHaveBeenCalledWith(
46 "./local/file.txt",
47 "/remote/file.txt",
48 expect.objectContaining({ signal: expect.any(AbortSignal) }),
49 );
50 });
51
52 it("copies from sandbox to local", async () => {
53 const mockSandbox = {
54 data: { status: "RUNNING" },
55 copy: { download: vi.fn().mockResolvedValue(undefined) },
56 };
57 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
58
59 await copy("my-sandbox:/remote/file.txt", "./local/file.txt");
60
61 expect(mockSandbox.copy.download).toHaveBeenCalledWith(
62 "/remote/file.txt",
63 "./local/file.txt",
64 expect.objectContaining({ signal: expect.any(AbortSignal) }),
65 );
66 });
67
68 it("copies between two sandboxes", async () => {
69 const mockSourceSandbox = {
70 data: { status: "RUNNING" },
71 copy: { to: vi.fn().mockResolvedValue(undefined) },
72 };
73 const mockDestSandbox = { data: { status: "RUNNING" } };
74 vi.mocked(Sandbox.get)
75 .mockResolvedValueOnce(mockSourceSandbox as any)
76 .mockResolvedValueOnce(mockDestSandbox as any);
77
78 await copy("src-sandbox:/src/file.txt", "dst-sandbox:/dst/file.txt");
79
80 expect(mockSourceSandbox.copy.to).toHaveBeenCalledWith(
81 "dst-sandbox",
82 "/src/file.txt",
83 "/dst/file.txt",
84 expect.objectContaining({ signal: expect.any(AbortSignal) }),
85 );
86 });
87
88 it("exits with error when source and destination are both local", async () => {
89 await copy("./local/src.txt", "./local/dst.txt");
90
91 expect(consola.error).toHaveBeenCalledWith(
92 "Both source and destination cannot be local paths.",
93 );
94 expect(mockExit).toHaveBeenCalledWith(1);
95 });
96
97 it("exits with error when source equals destination", async () => {
98 await copy("same-path", "same-path");
99
100 expect(consola.error).toHaveBeenCalledWith(
101 "Source and destination cannot be the same.",
102 );
103 expect(mockExit).toHaveBeenCalledWith(1);
104 });
105
106 it("exits with error when sandbox is not running", async () => {
107 const mockSandbox = {
108 data: { status: "STOPPED" },
109 copy: { upload: vi.fn() },
110 };
111 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
112
113 await copy("./local/file.txt", "my-sandbox:/remote/file.txt");
114
115 expect(consola.error).toHaveBeenCalledWith(
116 expect.stringContaining("not running"),
117 );
118 expect(mockExit).toHaveBeenCalledWith(1);
119 });
120});