ai-spec-dev 0.35.0 → 0.37.0
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/RELEASE_LOG.md +139 -0
- package/cli/commands/config.ts +18 -0
- package/cli/commands/create.ts +16 -1
- package/cli/utils.ts +4 -0
- package/core/code-generator.ts +6 -4
- package/core/dsl-extractor.ts +9 -1
- package/core/dsl-feedback.ts +7 -1
- package/core/dsl-validator.ts +32 -0
- package/core/key-store.ts +5 -4
- package/core/provider-utils.ts +39 -4
- package/dist/cli/index.js +121 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +122 -15
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +77 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +77 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/tests/code-generator.test.ts +253 -0
- package/tests/context-loader.test.ts +207 -0
- package/tests/dsl-validator.test.ts +105 -0
- package/tests/mock-server-generator.test.ts +404 -0
- package/tests/openapi-exporter.test.ts +310 -0
- package/tests/reviewer.test.ts +214 -0
- package/tests/spec-generator.test.ts +228 -0
- package/tests/spec-versioning.test.ts +205 -0
- package/tests/types-generator.test.ts +347 -0
- package/tests/vcr.test.ts +355 -0
- package/.claude/commands/add-lesson.md +0 -34
- package/.claude/commands/check-layers.md +0 -65
- package/.claude/commands/installed-deps.md +0 -35
- package/.claude/commands/recall-lessons.md +0 -40
- package/.claude/commands/scan-singletons.md +0 -45
- package/.claude/commands/verify-imports.md +0 -48
- package/.claude/settings.local.json +0 -24
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as fs from "fs-extra";
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import type { SpecDSL, ApiEndpoint } from "../core/dsl-types";
|
|
6
|
+
|
|
7
|
+
// ─── We need to import the module under test ────────────────────────────────
|
|
8
|
+
// Some functions are not exported — we test via the public API (generateMockAssets)
|
|
9
|
+
// and exported helpers (findLatestDslFile, applyMockProxy, restoreMockProxy).
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
generateMockAssets,
|
|
13
|
+
findLatestDslFile,
|
|
14
|
+
applyMockProxy,
|
|
15
|
+
restoreMockProxy,
|
|
16
|
+
MockServerOptions,
|
|
17
|
+
} from "../core/mock-server-generator";
|
|
18
|
+
|
|
19
|
+
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function makeDsl(overrides: Partial<SpecDSL> = {}): SpecDSL {
|
|
22
|
+
return {
|
|
23
|
+
version: "1.0",
|
|
24
|
+
feature: { id: "user-crud", title: "User CRUD", description: "Basic user management" },
|
|
25
|
+
models: [
|
|
26
|
+
{
|
|
27
|
+
name: "User",
|
|
28
|
+
fields: [
|
|
29
|
+
{ name: "id", type: "String", required: true, unique: true },
|
|
30
|
+
{ name: "email", type: "String", required: true },
|
|
31
|
+
{ name: "name", type: "String", required: true },
|
|
32
|
+
{ name: "age", type: "Int", required: false },
|
|
33
|
+
{ name: "isActive", type: "Boolean", required: true },
|
|
34
|
+
{ name: "createdAt", type: "DateTime", required: true },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
endpoints: [
|
|
39
|
+
{
|
|
40
|
+
id: "EP-001",
|
|
41
|
+
method: "GET",
|
|
42
|
+
path: "/api/users",
|
|
43
|
+
description: "List all users",
|
|
44
|
+
auth: true,
|
|
45
|
+
successStatus: 200,
|
|
46
|
+
successDescription: "Returns list of users",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "EP-002",
|
|
50
|
+
method: "POST",
|
|
51
|
+
path: "/api/users",
|
|
52
|
+
description: "Create a new user",
|
|
53
|
+
auth: true,
|
|
54
|
+
request: { body: { email: "String", name: "String" } },
|
|
55
|
+
successStatus: 201,
|
|
56
|
+
successDescription: "User created",
|
|
57
|
+
errors: [
|
|
58
|
+
{ status: 400, code: "INVALID_INPUT", description: "Bad request" },
|
|
59
|
+
{ status: 409, code: "DUPLICATE_EMAIL", description: "Email already exists" },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "EP-003",
|
|
64
|
+
method: "GET",
|
|
65
|
+
path: "/api/users/:id",
|
|
66
|
+
description: "Get user by ID",
|
|
67
|
+
auth: true,
|
|
68
|
+
request: { params: { id: "String" } },
|
|
69
|
+
successStatus: 200,
|
|
70
|
+
successDescription: "Returns user",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "EP-004",
|
|
74
|
+
method: "DELETE",
|
|
75
|
+
path: "/api/users/:id",
|
|
76
|
+
description: "Delete a user",
|
|
77
|
+
auth: true,
|
|
78
|
+
successStatus: 204,
|
|
79
|
+
successDescription: "User deleted",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
behaviors: [],
|
|
83
|
+
...overrides,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let tmpDir: string;
|
|
88
|
+
|
|
89
|
+
beforeEach(async () => {
|
|
90
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "mock-gen-test-"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(async () => {
|
|
94
|
+
await fs.remove(tmpDir);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ─── generateMockAssets ──────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
describe("generateMockAssets", () => {
|
|
100
|
+
it("generates server.js and README.md by default", async () => {
|
|
101
|
+
const dsl = makeDsl();
|
|
102
|
+
const result = await generateMockAssets(dsl, tmpDir);
|
|
103
|
+
|
|
104
|
+
expect(result.files.length).toBe(2);
|
|
105
|
+
expect(result.files[0].path).toBe("mock/server.js");
|
|
106
|
+
expect(result.files[1].path).toBe("mock/README.md");
|
|
107
|
+
|
|
108
|
+
// server.js should exist on disk
|
|
109
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
110
|
+
expect(serverContent).toContain("express");
|
|
111
|
+
expect(serverContent).toContain("User CRUD");
|
|
112
|
+
expect(serverContent).toContain("/api/users");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("server.js includes auth middleware when endpoints have auth", async () => {
|
|
116
|
+
const dsl = makeDsl();
|
|
117
|
+
const result = await generateMockAssets(dsl, tmpDir);
|
|
118
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
119
|
+
|
|
120
|
+
expect(serverContent).toContain("requireAuth");
|
|
121
|
+
expect(serverContent).toContain("Authorization");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("server.js omits auth middleware when no endpoints need auth", async () => {
|
|
125
|
+
const dsl = makeDsl({
|
|
126
|
+
endpoints: [
|
|
127
|
+
{
|
|
128
|
+
id: "EP-001",
|
|
129
|
+
method: "GET",
|
|
130
|
+
path: "/api/health",
|
|
131
|
+
description: "Health check",
|
|
132
|
+
auth: false,
|
|
133
|
+
successStatus: 200,
|
|
134
|
+
successDescription: "OK",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
await generateMockAssets(dsl, tmpDir);
|
|
139
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
140
|
+
expect(serverContent).not.toContain("requireAuth");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("generates DELETE 204 endpoints with sendStatus", async () => {
|
|
144
|
+
const dsl = makeDsl();
|
|
145
|
+
await generateMockAssets(dsl, tmpDir);
|
|
146
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
147
|
+
expect(serverContent).toContain("sendStatus(204)");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("includes error simulation comment for endpoints with errors", async () => {
|
|
151
|
+
const dsl = makeDsl();
|
|
152
|
+
await generateMockAssets(dsl, tmpDir);
|
|
153
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
154
|
+
expect(serverContent).toContain("simulate_error=INVALID_INPUT");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("uses custom port", async () => {
|
|
158
|
+
const dsl = makeDsl();
|
|
159
|
+
await generateMockAssets(dsl, tmpDir, { port: 4000 });
|
|
160
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
161
|
+
expect(serverContent).toContain("4000");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("uses custom output directory", async () => {
|
|
165
|
+
const dsl = makeDsl();
|
|
166
|
+
await generateMockAssets(dsl, tmpDir, { outputDir: "mocks" });
|
|
167
|
+
expect(await fs.pathExists(path.join(tmpDir, "mocks/server.js"))).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("generates list endpoint fixtures with data array for GET list endpoints", async () => {
|
|
171
|
+
const dsl = makeDsl();
|
|
172
|
+
await generateMockAssets(dsl, tmpDir);
|
|
173
|
+
const serverContent = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
174
|
+
// EP-001 is "List all users" — should produce paginated fixture
|
|
175
|
+
expect(serverContent).toContain('"total"');
|
|
176
|
+
expect(serverContent).toContain('"page"');
|
|
177
|
+
expect(serverContent).toContain('"pageSize"');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("README.md contains endpoint table", async () => {
|
|
181
|
+
const dsl = makeDsl();
|
|
182
|
+
await generateMockAssets(dsl, tmpDir);
|
|
183
|
+
const readme = await fs.readFile(path.join(tmpDir, "mock/README.md"), "utf-8");
|
|
184
|
+
expect(readme).toContain("GET");
|
|
185
|
+
expect(readme).toContain("`/api/users`");
|
|
186
|
+
expect(readme).toContain("DELETE");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ─── MSW option ─────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
it("generates MSW handlers when msw option is true", async () => {
|
|
192
|
+
const dsl = makeDsl();
|
|
193
|
+
const result = await generateMockAssets(dsl, tmpDir, { msw: true });
|
|
194
|
+
|
|
195
|
+
const handlerFile = result.files.find((f) => f.path.includes("handlers.ts"));
|
|
196
|
+
expect(handlerFile).toBeTruthy();
|
|
197
|
+
|
|
198
|
+
const handlersContent = await fs.readFile(
|
|
199
|
+
path.join(tmpDir, "src/mocks/handlers.ts"),
|
|
200
|
+
"utf-8"
|
|
201
|
+
);
|
|
202
|
+
expect(handlersContent).toContain("import { http, HttpResponse }");
|
|
203
|
+
expect(handlersContent).toContain("http.get");
|
|
204
|
+
expect(handlersContent).toContain("http.post");
|
|
205
|
+
expect(handlersContent).toContain("http.delete");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("generates MSW browser setup file", async () => {
|
|
209
|
+
const dsl = makeDsl();
|
|
210
|
+
await generateMockAssets(dsl, tmpDir, { msw: true });
|
|
211
|
+
const browserContent = await fs.readFile(
|
|
212
|
+
path.join(tmpDir, "src/mocks/browser.ts"),
|
|
213
|
+
"utf-8"
|
|
214
|
+
);
|
|
215
|
+
expect(browserContent).toContain("setupWorker");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ─── Proxy option ───────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
it("generates proxy config when proxy option is true", async () => {
|
|
221
|
+
const dsl = makeDsl();
|
|
222
|
+
const result = await generateMockAssets(dsl, tmpDir, { proxy: true });
|
|
223
|
+
const proxyFile = result.files.find((f) => f.path.includes("proxy"));
|
|
224
|
+
expect(proxyFile).toBeTruthy();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("detects Vite framework for proxy config", async () => {
|
|
228
|
+
// Create a vite.config.ts to trigger vite detection
|
|
229
|
+
await fs.writeFile(path.join(tmpDir, "vite.config.ts"), "export default {}", "utf-8");
|
|
230
|
+
const dsl = makeDsl();
|
|
231
|
+
const result = await generateMockAssets(dsl, tmpDir, { proxy: true });
|
|
232
|
+
const proxyFile = result.files.find((f) => f.path.includes("proxy"));
|
|
233
|
+
expect(proxyFile?.path).toContain("vite");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("detects Next.js framework for proxy config", async () => {
|
|
237
|
+
await fs.writeFile(path.join(tmpDir, "next.config.js"), "module.exports = {}", "utf-8");
|
|
238
|
+
const dsl = makeDsl();
|
|
239
|
+
const result = await generateMockAssets(dsl, tmpDir, { proxy: true });
|
|
240
|
+
const proxyFile = result.files.find((f) => f.path.includes("proxy"));
|
|
241
|
+
expect(proxyFile?.path).toContain("next");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("detects CRA framework via react-scripts", async () => {
|
|
245
|
+
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
|
246
|
+
dependencies: { "react-scripts": "5.0.0" },
|
|
247
|
+
});
|
|
248
|
+
const dsl = makeDsl();
|
|
249
|
+
const result = await generateMockAssets(dsl, tmpDir, { proxy: true });
|
|
250
|
+
const proxyFile = result.files.find((f) => f.path.includes("proxy"));
|
|
251
|
+
expect(proxyFile?.path).toContain("cra");
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ─── Fixture value heuristics ────────────────────────────────────────────────
|
|
256
|
+
// We test these indirectly by checking generated server.js content
|
|
257
|
+
|
|
258
|
+
describe("fixture heuristics", () => {
|
|
259
|
+
it("generates email fixtures for email fields", async () => {
|
|
260
|
+
const dsl = makeDsl();
|
|
261
|
+
await generateMockAssets(dsl, tmpDir);
|
|
262
|
+
const content = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
263
|
+
expect(content).toContain("user@example.com");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("generates boolean fixtures for boolean fields", async () => {
|
|
267
|
+
const dsl = makeDsl();
|
|
268
|
+
await generateMockAssets(dsl, tmpDir);
|
|
269
|
+
const content = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
270
|
+
// isActive: Boolean → true
|
|
271
|
+
expect(content).toContain('"isActive": true');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("generates date fixtures for DateTime fields", async () => {
|
|
275
|
+
const dsl = makeDsl();
|
|
276
|
+
await generateMockAssets(dsl, tmpDir);
|
|
277
|
+
const content = await fs.readFile(path.join(tmpDir, "mock/server.js"), "utf-8");
|
|
278
|
+
expect(content).toContain("2024-01-15T10:30:00.000Z");
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// ─── findLatestDslFile ───────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
describe("findLatestDslFile", () => {
|
|
285
|
+
it("returns null when .ai-spec directory does not exist", async () => {
|
|
286
|
+
const result = await findLatestDslFile(tmpDir);
|
|
287
|
+
expect(result).toBeNull();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("returns null when no .dsl.json files exist", async () => {
|
|
291
|
+
await fs.ensureDir(path.join(tmpDir, ".ai-spec"));
|
|
292
|
+
await fs.writeFile(path.join(tmpDir, ".ai-spec/readme.md"), "hi");
|
|
293
|
+
const result = await findLatestDslFile(tmpDir);
|
|
294
|
+
expect(result).toBeNull();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("returns the most recently modified .dsl.json file", async () => {
|
|
298
|
+
const specDir = path.join(tmpDir, ".ai-spec");
|
|
299
|
+
await fs.ensureDir(specDir);
|
|
300
|
+
|
|
301
|
+
// Create two DSL files with different mtimes
|
|
302
|
+
const older = path.join(specDir, "old.dsl.json");
|
|
303
|
+
const newer = path.join(specDir, "new.dsl.json");
|
|
304
|
+
await fs.writeJson(older, { version: "1.0" });
|
|
305
|
+
|
|
306
|
+
// Small delay to ensure different mtime
|
|
307
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
308
|
+
await fs.writeJson(newer, { version: "1.0" });
|
|
309
|
+
|
|
310
|
+
const result = await findLatestDslFile(tmpDir);
|
|
311
|
+
expect(result).toBe(newer);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("scans nested directories", async () => {
|
|
315
|
+
const nestedDir = path.join(tmpDir, ".ai-spec", "v1");
|
|
316
|
+
await fs.ensureDir(nestedDir);
|
|
317
|
+
await fs.writeJson(path.join(nestedDir, "feature.dsl.json"), { version: "1.0" });
|
|
318
|
+
|
|
319
|
+
const result = await findLatestDslFile(tmpDir);
|
|
320
|
+
expect(result).toBe(path.join(nestedDir, "feature.dsl.json"));
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// ─── applyMockProxy / restoreMockProxy ───────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
describe("applyMockProxy / restoreMockProxy", () => {
|
|
327
|
+
it("applies Vite proxy: writes mock config + adds dev:mock script", async () => {
|
|
328
|
+
await fs.writeFile(path.join(tmpDir, "vite.config.ts"), "export default {}", "utf-8");
|
|
329
|
+
await fs.writeJson(path.join(tmpDir, "package.json"), { scripts: { dev: "vite" } });
|
|
330
|
+
|
|
331
|
+
const endpoints: ApiEndpoint[] = [
|
|
332
|
+
{
|
|
333
|
+
id: "EP-001", method: "GET", path: "/api/users", description: "List",
|
|
334
|
+
auth: false, successStatus: 200, successDescription: "OK",
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
const result = await applyMockProxy(tmpDir, 3001, endpoints);
|
|
338
|
+
|
|
339
|
+
expect(result.framework).toBe("vite");
|
|
340
|
+
expect(result.applied).toBe(true);
|
|
341
|
+
expect(result.devCommand).toBe("npm run dev:mock");
|
|
342
|
+
|
|
343
|
+
// Check that vite mock config was created
|
|
344
|
+
expect(await fs.pathExists(path.join(tmpDir, "vite.config.ai-spec-mock.ts"))).toBe(true);
|
|
345
|
+
|
|
346
|
+
// Check that package.json has dev:mock script
|
|
347
|
+
const pkg = await fs.readJson(path.join(tmpDir, "package.json"));
|
|
348
|
+
expect(pkg.scripts["dev:mock"]).toContain("vite.config.ai-spec-mock.ts");
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("applies CRA proxy: patches package.json proxy field", async () => {
|
|
352
|
+
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
|
353
|
+
dependencies: { "react-scripts": "5.0.0" },
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const result = await applyMockProxy(tmpDir, 3001);
|
|
357
|
+
|
|
358
|
+
expect(result.framework).toBe("cra");
|
|
359
|
+
expect(result.applied).toBe(true);
|
|
360
|
+
|
|
361
|
+
const pkg = await fs.readJson(path.join(tmpDir, "package.json"));
|
|
362
|
+
expect(pkg.proxy).toBe("http://localhost:3001");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("restoreMockProxy undoes Vite changes", async () => {
|
|
366
|
+
await fs.writeFile(path.join(tmpDir, "vite.config.ts"), "export default {}", "utf-8");
|
|
367
|
+
await fs.writeJson(path.join(tmpDir, "package.json"), { scripts: { dev: "vite" } });
|
|
368
|
+
|
|
369
|
+
await applyMockProxy(tmpDir, 3001);
|
|
370
|
+
const restoreResult = await restoreMockProxy(tmpDir);
|
|
371
|
+
|
|
372
|
+
expect(restoreResult.restored).toBe(true);
|
|
373
|
+
expect(await fs.pathExists(path.join(tmpDir, "vite.config.ai-spec-mock.ts"))).toBe(false);
|
|
374
|
+
|
|
375
|
+
const pkg = await fs.readJson(path.join(tmpDir, "package.json"));
|
|
376
|
+
expect(pkg.scripts["dev:mock"]).toBeUndefined();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("restoreMockProxy undoes CRA proxy change", async () => {
|
|
380
|
+
await fs.writeJson(path.join(tmpDir, "package.json"), {
|
|
381
|
+
dependencies: { "react-scripts": "5.0.0" },
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await applyMockProxy(tmpDir, 3001);
|
|
385
|
+
await restoreMockProxy(tmpDir);
|
|
386
|
+
|
|
387
|
+
const pkg = await fs.readJson(path.join(tmpDir, "package.json"));
|
|
388
|
+
expect(pkg.proxy).toBeUndefined();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("restoreMockProxy returns restored:false when no lock file", async () => {
|
|
392
|
+
const result = await restoreMockProxy(tmpDir);
|
|
393
|
+
expect(result.restored).toBe(false);
|
|
394
|
+
expect(result.note).toContain("No lock file");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("returns note for Next.js (no auto-patch)", async () => {
|
|
398
|
+
await fs.writeFile(path.join(tmpDir, "next.config.js"), "module.exports = {}", "utf-8");
|
|
399
|
+
const result = await applyMockProxy(tmpDir, 3001);
|
|
400
|
+
expect(result.framework).toBe("next");
|
|
401
|
+
expect(result.applied).toBe(false);
|
|
402
|
+
expect(result.note).toContain("next.config.js");
|
|
403
|
+
});
|
|
404
|
+
});
|