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 {
5 createService,
6 listServices,
7 restartService,
8 startService,
9 stopService,
10 deleteService,
11} from "./service";
12
13vi.mock("../lib/sdk", () => ({ configureSdk: vi.fn() }));
14vi.mock("../lib/getAccessToken", () => ({
15 default: vi.fn().mockResolvedValue("test-access-token"),
16}));
17vi.mock("../lib/env", () => ({
18 env: { POCKETENV_TOKEN: "", POCKETENV_API_URL: "https://api.pocketenv.io" },
19}));
20vi.mock("../theme", () => ({
21 c: {
22 primary: (s: string | number) => String(s),
23 secondary: (s: string | number) => String(s),
24 highlight: (s: string | number) => String(s),
25 },
26}));
27vi.mock("../client", () => ({
28 client: { post: vi.fn().mockResolvedValue({ data: {} }) },
29}));
30vi.mock("consola", () => ({
31 default: { log: vi.fn(), success: vi.fn(), error: vi.fn() },
32}));
33vi.mock("@pocketenv/sdk", () => ({
34 Sandbox: { get: vi.fn(), configure: vi.fn() },
35}));
36
37import { client } from "../client";
38
39describe("service commands", () => {
40 const mockExit = vi
41 .spyOn(process, "exit")
42 .mockImplementation(() => undefined as never);
43
44 beforeEach(() => {
45 vi.clearAllMocks();
46 vi.mocked(client.post).mockResolvedValue({ data: {} });
47 });
48
49 describe("createService", () => {
50 it("creates a service and logs success", async () => {
51 const mockSandbox = {
52 service: { add: vi.fn().mockResolvedValue(undefined) },
53 };
54 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
55
56 await createService("my-sandbox", "web", ["npm", "start"], {
57 ports: ["3000"],
58 description: "Web server",
59 });
60
61 expect(mockSandbox.service.add).toHaveBeenCalledWith("web", "npm start", {
62 description: "Web server",
63 ports: [3000],
64 });
65 expect(consola.success).toHaveBeenCalledWith(
66 expect.stringContaining("web"),
67 );
68 });
69
70 it("logs error and exits when creation fails", async () => {
71 vi.mocked(Sandbox.get).mockRejectedValue(new Error("Not found"));
72
73 await createService("my-sandbox", "web", ["npm", "start"], {});
74
75 expect(consola.error).toHaveBeenCalledWith(
76 "Failed to create service",
77 expect.any(Error),
78 );
79 expect(mockExit).toHaveBeenCalledWith(1);
80 });
81 });
82
83 describe("listServices", () => {
84 it("lists services and logs a table", async () => {
85 const mockSandbox = {
86 service: {
87 list: vi.fn().mockResolvedValue({
88 services: [
89 {
90 id: "svc-1",
91 name: "web",
92 command: "npm start",
93 status: "RUNNING",
94 createdAt: new Date().toISOString(),
95 },
96 ],
97 }),
98 },
99 };
100 vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as any);
101
102 await listServices("my-sandbox");
103
104 expect(mockSandbox.service.list).toHaveBeenCalledOnce();
105 expect(consola.log).toHaveBeenCalledOnce();
106 });
107
108 it("logs error and exits when listing fails", async () => {
109 vi.mocked(Sandbox.get).mockRejectedValue(new Error("Not found"));
110
111 await listServices("my-sandbox");
112
113 expect(consola.error).toHaveBeenCalledWith(
114 "Failed to list services",
115 expect.any(Error),
116 );
117 expect(mockExit).toHaveBeenCalledWith(1);
118 });
119 });
120
121 describe("restartService", () => {
122 it("restarts a service via API", async () => {
123 await restartService("svc-1");
124
125 expect(client.post).toHaveBeenCalledWith(
126 "/xrpc/io.pocketenv.service.restartService",
127 undefined,
128 expect.objectContaining({ params: { serviceId: "svc-1" } }),
129 );
130 });
131
132 it("logs error and exits when restart fails", async () => {
133 vi.mocked(client.post).mockRejectedValue(new Error("API error"));
134
135 await restartService("svc-1");
136
137 expect(consola.error).toHaveBeenCalledWith(
138 expect.stringContaining("Failed to restart service"),
139 expect.any(Error),
140 );
141 expect(mockExit).toHaveBeenCalledWith(1);
142 });
143 });
144
145 describe("startService", () => {
146 it("starts a service via API", async () => {
147 await startService("svc-1");
148
149 expect(client.post).toHaveBeenCalledWith(
150 "/xrpc/io.pocketenv.service.startService",
151 undefined,
152 expect.objectContaining({ params: { serviceId: "svc-1" } }),
153 );
154 });
155
156 it("logs error and exits when start fails", async () => {
157 vi.mocked(client.post).mockRejectedValue(new Error("API error"));
158
159 await startService("svc-1");
160
161 expect(consola.error).toHaveBeenCalledWith(
162 expect.stringContaining("Failed to start service"),
163 expect.any(Error),
164 );
165 expect(mockExit).toHaveBeenCalledWith(1);
166 });
167 });
168
169 describe("stopService", () => {
170 it("stops a service via API", async () => {
171 await stopService("svc-1");
172
173 expect(client.post).toHaveBeenCalledWith(
174 "/xrpc/io.pocketenv.service.stopService",
175 undefined,
176 expect.objectContaining({ params: { serviceId: "svc-1" } }),
177 );
178 });
179
180 it("logs error and exits when stop fails", async () => {
181 vi.mocked(client.post).mockRejectedValue(new Error("API error"));
182
183 await stopService("svc-1");
184
185 expect(consola.error).toHaveBeenCalledWith(
186 expect.stringContaining("Failed to stop service"),
187 expect.any(Error),
188 );
189 expect(mockExit).toHaveBeenCalledWith(1);
190 });
191 });
192
193 describe("deleteService", () => {
194 it("deletes a service via API and logs success", async () => {
195 await deleteService("svc-1");
196
197 expect(client.post).toHaveBeenCalledWith(
198 "/xrpc/io.pocketenv.service.deleteService",
199 undefined,
200 expect.objectContaining({ params: { serviceId: "svc-1" } }),
201 );
202 expect(consola.success).toHaveBeenCalledWith(
203 expect.stringContaining("svc-1"),
204 );
205 });
206
207 it("logs error and exits when deletion fails", async () => {
208 vi.mocked(client.post).mockRejectedValue(new Error("API error"));
209
210 await deleteService("svc-1");
211
212 expect(consola.error).toHaveBeenCalledWith(
213 expect.stringContaining("Failed to delete service"),
214 expect.any(Error),
215 );
216 expect(mockExit).toHaveBeenCalledWith(1);
217 });
218 });
219});