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 { putFile, listFiles, deleteFile } from "./file";
5
6vi.mock("../lib/sdk", () => ({ configureSdk: vi.fn() }));
7vi.mock("../lib/getAccessToken", () => ({
8 default: vi.fn().mockResolvedValue("test-access-token"),
9}));
10vi.mock("../lib/env", () => ({
11 env: { POCKETENV_TOKEN: "", POCKETENV_API_URL: "https://api.pocketenv.io" },
12}));
13vi.mock("../theme", () => ({
14 c: {
15 primary: (s: string | number) => String(s),
16 secondary: (s: string | number) => String(s),
17 error: (s: string | number) => String(s),
18 },
19}));
20vi.mock("../client", () => ({
21 client: { post: vi.fn().mockResolvedValue({ data: {} }) },
22}));
23vi.mock("consola", () => ({
24 default: { log: vi.fn(), success: vi.fn(), error: vi.fn() },
25}));
26vi.mock("@pocketenv/sdk", () => ({
27 Sandbox: { get: vi.fn(), configure: vi.fn() },
28}));
29vi.mock("fs/promises", () => ({
30 default: {
31 access: vi.fn().mockResolvedValue(undefined),
32 readFile: vi.fn().mockResolvedValue("file content"),
33 },
34}));
35vi.mock("@inquirer/prompts", () => ({
36 editor: vi.fn().mockResolvedValue("editor content"),
37}));
38
39import { client } from "../client";
40import fs from "fs/promises";
41
42describe("file commands", () => {
43 beforeEach(() => {
44 vi.clearAllMocks();
45 Object.defineProperty(process.stdin, "isTTY", {
46 value: true,
47 configurable: true,
48 });
49 });
50
51 describe("putFile", () => {
52 it("writes file content from local path", async () => {
53 const mockSandbox = {
54 file: { write: vi.fn().mockResolvedValue(undefined) },
55 };
56 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
57
58 await putFile("my-sandbox", "/remote/path.txt", "./local/file.txt");
59
60 expect(fs.readFile).toHaveBeenCalledWith(
61 expect.stringContaining("local/file.txt"),
62 "utf-8",
63 );
64 expect(mockSandbox.file.write).toHaveBeenCalledWith(
65 "/remote/path.txt",
66 "file content",
67 );
68 expect(consola.success).toHaveBeenCalledWith(
69 expect.stringContaining("/remote/path.txt"),
70 );
71 });
72
73 it("logs error when local file does not exist", async () => {
74 const mockExit = vi
75 .spyOn(process, "exit")
76 .mockImplementation(() => undefined as never);
77 vi.mocked(fs.access).mockRejectedValue(
78 Object.assign(new Error("ENOENT"), { code: "ENOENT" }),
79 );
80
81 await putFile("my-sandbox", "/remote/path.txt", "./missing.txt");
82
83 expect(consola.error).toHaveBeenCalledWith(
84 expect.stringContaining("No such file"),
85 );
86 expect(mockExit).toHaveBeenCalledWith(1);
87 });
88
89 it("logs error when sandbox write fails", async () => {
90 const mockSandbox = {
91 file: { write: vi.fn().mockRejectedValue(new Error("Write failed")) },
92 };
93 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
94
95 await putFile("my-sandbox", "/remote/path.txt", "./local/file.txt");
96
97 expect(consola.error).toHaveBeenCalledWith(
98 expect.stringContaining("Failed to create file"),
99 );
100 });
101 });
102
103 describe("listFiles", () => {
104 it("lists files and logs a table", async () => {
105 const mockSandbox = {
106 file: {
107 list: vi.fn().mockResolvedValue({
108 files: [
109 {
110 id: "file-1",
111 path: "/etc/config.json",
112 createdAt: new Date().toISOString(),
113 },
114 ],
115 }),
116 },
117 };
118 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
119
120 await listFiles("my-sandbox");
121
122 expect(mockSandbox.file.list).toHaveBeenCalledOnce();
123 expect(consola.log).toHaveBeenCalledOnce();
124 });
125 });
126
127 describe("deleteFile", () => {
128 it("deletes a file via API", async () => {
129 await deleteFile("file-1");
130
131 expect(client.post).toHaveBeenCalledWith(
132 "/xrpc/io.pocketenv.file.deleteFile",
133 undefined,
134 expect.objectContaining({ params: { id: "file-1" } }),
135 );
136 expect(consola.success).toHaveBeenCalledWith(
137 expect.stringContaining("file-1"),
138 );
139 });
140
141 it("logs error when deletion fails", async () => {
142 vi.mocked(client.post).mockRejectedValue(new Error("API error"));
143
144 await deleteFile("file-1");
145
146 expect(consola.error).toHaveBeenCalledWith(
147 expect.stringContaining("Failed to delete file"),
148 );
149 });
150 });
151});