@codexa/cli 9.0.7 → 9.0.9
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/commands/decide.ts +120 -3
- package/commands/discover.ts +18 -9
- package/commands/integration.test.ts +754 -0
- package/commands/knowledge.test.ts +2 -6
- package/commands/knowledge.ts +20 -4
- package/commands/patterns.ts +8 -644
- package/commands/product.ts +41 -104
- package/commands/spec-resolver.test.ts +2 -13
- package/commands/standards.ts +33 -3
- package/commands/task.ts +21 -4
- package/commands/utils.test.ts +25 -87
- package/commands/utils.ts +20 -82
- package/context/assembly.ts +11 -12
- package/context/domains.test.ts +300 -0
- package/context/domains.ts +157 -0
- package/context/generator.ts +14 -13
- package/context/index.ts +6 -1
- package/context/references.test.ts +159 -0
- package/context/references.ts +159 -0
- package/context/sections.ts +18 -1
- package/db/schema.ts +40 -5
- package/db/test-helpers.ts +33 -0
- package/gates/standards-validator.test.ts +447 -0
- package/gates/standards-validator.ts +164 -125
- package/gates/typecheck-validator.ts +296 -92
- package/gates/validator.ts +93 -8
- package/package.json +1 -1
- package/protocol/process-return.ts +39 -4
- package/workflow.ts +54 -84
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, mock, spyOn } from "bun:test";
|
|
2
|
+
import { getDb } from "../db/connection";
|
|
3
|
+
import { initSchema, runMigrations } from "../db/schema";
|
|
4
|
+
import { writeFileSync, mkdirSync, rmSync, existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
// Mock grepai functions before importing validator
|
|
8
|
+
import * as patterns from "../commands/patterns";
|
|
9
|
+
|
|
10
|
+
import { validateAgainstStandards, printValidationResult } from "./standards-validator";
|
|
11
|
+
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════
|
|
13
|
+
// HELPERS
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
const TMP_DIR = join(process.cwd(), ".codexa", "test-tmp-standards");
|
|
17
|
+
|
|
18
|
+
function ensureTmpDir() {
|
|
19
|
+
if (!existsSync(TMP_DIR)) {
|
|
20
|
+
mkdirSync(TMP_DIR, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createTmpFile(name: string, content: string): string {
|
|
25
|
+
ensureTmpDir();
|
|
26
|
+
const path = join(TMP_DIR, name);
|
|
27
|
+
writeFileSync(path, content);
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function insertStandard(opts: {
|
|
32
|
+
category: string;
|
|
33
|
+
scope: string;
|
|
34
|
+
rule: string;
|
|
35
|
+
enforcement?: string;
|
|
36
|
+
anti_examples?: string;
|
|
37
|
+
semantic_query?: string | null;
|
|
38
|
+
expect?: string;
|
|
39
|
+
}) {
|
|
40
|
+
const db = getDb();
|
|
41
|
+
db.run(
|
|
42
|
+
`INSERT INTO standards (category, scope, rule, anti_examples, enforcement, semantic_query, expect, source, created_at)
|
|
43
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'test', datetime('now'))`,
|
|
44
|
+
[
|
|
45
|
+
opts.category,
|
|
46
|
+
opts.scope,
|
|
47
|
+
opts.rule,
|
|
48
|
+
opts.anti_examples || null,
|
|
49
|
+
opts.enforcement || "required",
|
|
50
|
+
opts.semantic_query || null,
|
|
51
|
+
opts.expect || "no_match",
|
|
52
|
+
]
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════
|
|
57
|
+
// TESTS
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
describe("standards-validator", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
initSchema();
|
|
63
|
+
runMigrations();
|
|
64
|
+
const db = getDb();
|
|
65
|
+
db.run("DELETE FROM standards");
|
|
66
|
+
|
|
67
|
+
// Clean tmp files
|
|
68
|
+
if (existsSync(TMP_DIR)) {
|
|
69
|
+
rmSync(TMP_DIR, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ─────────────────────────────────────────────────────────
|
|
74
|
+
// BASE CASES
|
|
75
|
+
// ─────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
it("returns passed when no standards exist", () => {
|
|
78
|
+
const file = createTmpFile("test.ts", "export const x = 1;");
|
|
79
|
+
const result = validateAgainstStandards([file], "all");
|
|
80
|
+
expect(result.passed).toBe(true);
|
|
81
|
+
expect(result.violations).toHaveLength(0);
|
|
82
|
+
expect(result.warnings).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns passed when files list is empty", () => {
|
|
86
|
+
insertStandard({ category: "code", scope: "all", rule: "No console.log" });
|
|
87
|
+
const result = validateAgainstStandards([], "all");
|
|
88
|
+
expect(result.passed).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────────────────────────────────────
|
|
92
|
+
// TEXTUAL VALIDATION (anti_examples)
|
|
93
|
+
// ─────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
it("detects anti_examples violation in file content", () => {
|
|
96
|
+
const file = createTmpFile("api.ts", 'console.log("debug"); export function handle() {}');
|
|
97
|
+
insertStandard({
|
|
98
|
+
category: "code",
|
|
99
|
+
scope: "all",
|
|
100
|
+
rule: "Nao usar console.log",
|
|
101
|
+
anti_examples: JSON.stringify(["console.log"]),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = validateAgainstStandards([file], "all");
|
|
105
|
+
expect(result.passed).toBe(false);
|
|
106
|
+
expect(result.violations).toHaveLength(1);
|
|
107
|
+
expect(result.violations[0].detail).toContain("console.log");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("passes when anti_examples not found in file", () => {
|
|
111
|
+
const file = createTmpFile("api.ts", "export function handle() { return 'ok'; }");
|
|
112
|
+
insertStandard({
|
|
113
|
+
category: "code",
|
|
114
|
+
scope: "all",
|
|
115
|
+
rule: "Nao usar console.log",
|
|
116
|
+
anti_examples: JSON.stringify(["console.log"]),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const result = validateAgainstStandards([file], "all");
|
|
120
|
+
expect(result.passed).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("treats recommended enforcement as warning not violation", () => {
|
|
124
|
+
const file = createTmpFile("api.ts", 'console.log("test");');
|
|
125
|
+
insertStandard({
|
|
126
|
+
category: "code",
|
|
127
|
+
scope: "all",
|
|
128
|
+
rule: "Evitar console.log",
|
|
129
|
+
anti_examples: JSON.stringify(["console.log"]),
|
|
130
|
+
enforcement: "recommended",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const result = validateAgainstStandards([file], "all");
|
|
134
|
+
expect(result.passed).toBe(true); // recommended does NOT block
|
|
135
|
+
expect(result.warnings).toHaveLength(1);
|
|
136
|
+
expect(result.violations).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("filters standards by scope", () => {
|
|
140
|
+
const file = createTmpFile("api.ts", 'console.log("test");');
|
|
141
|
+
|
|
142
|
+
// Standard for frontend scope only
|
|
143
|
+
insertStandard({
|
|
144
|
+
category: "code",
|
|
145
|
+
scope: "frontend",
|
|
146
|
+
rule: "Frontend: no console.log",
|
|
147
|
+
anti_examples: JSON.stringify(["console.log"]),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Validate with backend domain — should NOT match frontend standard
|
|
151
|
+
const result = validateAgainstStandards([file], "backend");
|
|
152
|
+
expect(result.passed).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("matches scope 'all' for any domain", () => {
|
|
156
|
+
const file = createTmpFile("api.ts", 'console.log("test");');
|
|
157
|
+
insertStandard({
|
|
158
|
+
category: "code",
|
|
159
|
+
scope: "all",
|
|
160
|
+
rule: "No console.log",
|
|
161
|
+
anti_examples: JSON.stringify(["console.log"]),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const result = validateAgainstStandards([file], "backend");
|
|
165
|
+
expect(result.passed).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("skips nonexistent files gracefully", () => {
|
|
169
|
+
insertStandard({
|
|
170
|
+
category: "code",
|
|
171
|
+
scope: "all",
|
|
172
|
+
rule: "No eval",
|
|
173
|
+
anti_examples: JSON.stringify(["eval("]),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = validateAgainstStandards(["/nonexistent/file.ts"], "all");
|
|
177
|
+
expect(result.passed).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("handles invalid anti_examples JSON gracefully", () => {
|
|
181
|
+
const file = createTmpFile("api.ts", "export const x = 1;");
|
|
182
|
+
const db = getDb();
|
|
183
|
+
db.run(
|
|
184
|
+
`INSERT INTO standards (category, scope, rule, anti_examples, enforcement, source, created_at)
|
|
185
|
+
VALUES ('code', 'all', 'Bad JSON', 'not-valid-json', 'required', 'test', datetime('now'))`
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const result = validateAgainstStandards([file], "all");
|
|
189
|
+
expect(result.passed).toBe(true); // Invalid JSON should not crash
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ─────────────────────────────────────────────────────────
|
|
193
|
+
// SEMANTIC VALIDATION (grepai)
|
|
194
|
+
// ─────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
it("detects no_match violation when grepai finds matching file", () => {
|
|
197
|
+
const file = createTmpFile("handler.ts", "export function handle() {}");
|
|
198
|
+
const resolvedPath = join(TMP_DIR, "handler.ts").replace(/\\/g, "/");
|
|
199
|
+
|
|
200
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
201
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
202
|
+
{ path: resolvedPath, score: 0.85 },
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
insertStandard({
|
|
206
|
+
category: "code",
|
|
207
|
+
scope: "all",
|
|
208
|
+
rule: "No .then() chains",
|
|
209
|
+
semantic_query: "promise .then() chain instead of async await",
|
|
210
|
+
expect: "no_match",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const result = validateAgainstStandards([file], "all");
|
|
214
|
+
expect(result.passed).toBe(false);
|
|
215
|
+
expect(result.violations).toHaveLength(1);
|
|
216
|
+
expect(result.violations[0].detail).toContain("Violacao semantica");
|
|
217
|
+
expect(result.violations[0].detail).toContain("0.85");
|
|
218
|
+
|
|
219
|
+
availSpy.mockRestore();
|
|
220
|
+
searchSpy.mockRestore();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("passes no_match when grepai finds no matches", () => {
|
|
224
|
+
const file = createTmpFile("clean.ts", "export const x = 1;");
|
|
225
|
+
|
|
226
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
227
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([]);
|
|
228
|
+
|
|
229
|
+
insertStandard({
|
|
230
|
+
category: "code",
|
|
231
|
+
scope: "all",
|
|
232
|
+
rule: "No .then() chains",
|
|
233
|
+
semantic_query: "promise .then() chain",
|
|
234
|
+
expect: "no_match",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const result = validateAgainstStandards([file], "all");
|
|
238
|
+
expect(result.passed).toBe(true);
|
|
239
|
+
|
|
240
|
+
availSpy.mockRestore();
|
|
241
|
+
searchSpy.mockRestore();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("detects must_match violation when grepai finds no match", () => {
|
|
245
|
+
const file = createTmpFile("service.ts", "export function process() {}");
|
|
246
|
+
|
|
247
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
248
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([]);
|
|
249
|
+
|
|
250
|
+
insertStandard({
|
|
251
|
+
category: "practice",
|
|
252
|
+
scope: "all",
|
|
253
|
+
rule: "Must have error handling",
|
|
254
|
+
semantic_query: "try catch error handling",
|
|
255
|
+
expect: "must_match",
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = validateAgainstStandards([file], "all");
|
|
259
|
+
expect(result.passed).toBe(false);
|
|
260
|
+
expect(result.violations).toHaveLength(1);
|
|
261
|
+
expect(result.violations[0].detail).toContain("Padrao obrigatorio nao encontrado");
|
|
262
|
+
|
|
263
|
+
availSpy.mockRestore();
|
|
264
|
+
searchSpy.mockRestore();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("passes must_match when grepai finds matching file", () => {
|
|
268
|
+
const file = createTmpFile("service.ts", "try { } catch (e) { }");
|
|
269
|
+
const resolvedPath = join(TMP_DIR, "service.ts").replace(/\\/g, "/");
|
|
270
|
+
|
|
271
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
272
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
273
|
+
{ path: resolvedPath, score: 0.9 },
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
insertStandard({
|
|
277
|
+
category: "practice",
|
|
278
|
+
scope: "all",
|
|
279
|
+
rule: "Must have error handling",
|
|
280
|
+
semantic_query: "try catch error handling",
|
|
281
|
+
expect: "must_match",
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const result = validateAgainstStandards([file], "all");
|
|
285
|
+
expect(result.passed).toBe(true);
|
|
286
|
+
|
|
287
|
+
availSpy.mockRestore();
|
|
288
|
+
searchSpy.mockRestore();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("ignores grepai results below score threshold", () => {
|
|
292
|
+
const file = createTmpFile("maybe.ts", "export const x = 1;");
|
|
293
|
+
const resolvedPath = join(TMP_DIR, "maybe.ts").replace(/\\/g, "/");
|
|
294
|
+
|
|
295
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
296
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
297
|
+
{ path: resolvedPath, score: 0.5 }, // Below 0.7 threshold
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
insertStandard({
|
|
301
|
+
category: "code",
|
|
302
|
+
scope: "all",
|
|
303
|
+
rule: "No console.log",
|
|
304
|
+
semantic_query: "console.log statement",
|
|
305
|
+
expect: "no_match",
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const result = validateAgainstStandards([file], "all");
|
|
309
|
+
expect(result.passed).toBe(true); // Score too low = no match
|
|
310
|
+
|
|
311
|
+
availSpy.mockRestore();
|
|
312
|
+
searchSpy.mockRestore();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("ignores grepai results for files not in validation list", () => {
|
|
316
|
+
const file = createTmpFile("target.ts", "export const x = 1;");
|
|
317
|
+
|
|
318
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
319
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
320
|
+
{ path: "/some/other/file.ts", score: 0.95 }, // High score but wrong file
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
insertStandard({
|
|
324
|
+
category: "code",
|
|
325
|
+
scope: "all",
|
|
326
|
+
rule: "No eval",
|
|
327
|
+
semantic_query: "eval() usage",
|
|
328
|
+
expect: "no_match",
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const result = validateAgainstStandards([file], "all");
|
|
332
|
+
expect(result.passed).toBe(true); // Match is for a different file
|
|
333
|
+
|
|
334
|
+
availSpy.mockRestore();
|
|
335
|
+
searchSpy.mockRestore();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// ─────────────────────────────────────────────────────────
|
|
339
|
+
// FALLBACK (grepai unavailable)
|
|
340
|
+
// ─────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
it("falls back to anti_examples when grepai not available", () => {
|
|
343
|
+
const file = createTmpFile("api.ts", 'eval("alert(1)");');
|
|
344
|
+
|
|
345
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(false);
|
|
346
|
+
|
|
347
|
+
insertStandard({
|
|
348
|
+
category: "code",
|
|
349
|
+
scope: "all",
|
|
350
|
+
rule: "No eval",
|
|
351
|
+
anti_examples: JSON.stringify(["eval("]),
|
|
352
|
+
semantic_query: "eval usage for dynamic code execution",
|
|
353
|
+
expect: "no_match",
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const result = validateAgainstStandards([file], "all");
|
|
357
|
+
expect(result.passed).toBe(false); // Falls back to anti_examples check
|
|
358
|
+
expect(result.violations).toHaveLength(1);
|
|
359
|
+
|
|
360
|
+
availSpy.mockRestore();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("skips must_match standards when grepai not available", () => {
|
|
364
|
+
const file = createTmpFile("service.ts", "export function process() {}");
|
|
365
|
+
|
|
366
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(false);
|
|
367
|
+
|
|
368
|
+
insertStandard({
|
|
369
|
+
category: "practice",
|
|
370
|
+
scope: "all",
|
|
371
|
+
rule: "Must have error handling",
|
|
372
|
+
semantic_query: "try catch error handling",
|
|
373
|
+
expect: "must_match",
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const result = validateAgainstStandards([file], "all");
|
|
377
|
+
expect(result.passed).toBe(true); // Cannot validate must_match without grepai
|
|
378
|
+
|
|
379
|
+
availSpy.mockRestore();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// ─────────────────────────────────────────────────────────
|
|
383
|
+
// CAP & MIXED
|
|
384
|
+
// ─────────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
it("caps semantic standards at MAX_SEMANTIC_QUERIES (10)", () => {
|
|
387
|
+
const file = createTmpFile("test.ts", "export const x = 1;");
|
|
388
|
+
|
|
389
|
+
let callCount = 0;
|
|
390
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
391
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockImplementation(() => {
|
|
392
|
+
callCount++;
|
|
393
|
+
return [];
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Insert 15 semantic standards
|
|
397
|
+
for (let i = 0; i < 15; i++) {
|
|
398
|
+
insertStandard({
|
|
399
|
+
category: "code",
|
|
400
|
+
scope: "all",
|
|
401
|
+
rule: `Rule ${i}`,
|
|
402
|
+
semantic_query: `query ${i}`,
|
|
403
|
+
expect: "no_match",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
validateAgainstStandards([file], "all");
|
|
408
|
+
expect(callCount).toBe(10); // Capped at 10
|
|
409
|
+
|
|
410
|
+
availSpy.mockRestore();
|
|
411
|
+
searchSpy.mockRestore();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("validates both semantic and textual standards in same run", () => {
|
|
415
|
+
const file = createTmpFile("mixed.ts", 'eval("code"); export const x = 1;');
|
|
416
|
+
const resolvedPath = join(TMP_DIR, "mixed.ts").replace(/\\/g, "/");
|
|
417
|
+
|
|
418
|
+
const availSpy = spyOn(patterns, "isGrepaiAvailable").mockReturnValue(true);
|
|
419
|
+
const searchSpy = spyOn(patterns, "searchWithGrepai").mockReturnValue([
|
|
420
|
+
{ path: resolvedPath, score: 0.9 },
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
// Semantic standard
|
|
424
|
+
insertStandard({
|
|
425
|
+
category: "code",
|
|
426
|
+
scope: "all",
|
|
427
|
+
rule: "No .then() chains",
|
|
428
|
+
semantic_query: "promise .then() chain",
|
|
429
|
+
expect: "no_match",
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Textual standard (no semantic_query)
|
|
433
|
+
insertStandard({
|
|
434
|
+
category: "code",
|
|
435
|
+
scope: "all",
|
|
436
|
+
rule: "No eval",
|
|
437
|
+
anti_examples: JSON.stringify(["eval("]),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const result = validateAgainstStandards([file], "all");
|
|
441
|
+
expect(result.passed).toBe(false);
|
|
442
|
+
expect(result.violations).toHaveLength(2); // One semantic + one textual
|
|
443
|
+
|
|
444
|
+
availSpy.mockRestore();
|
|
445
|
+
searchSpy.mockRestore();
|
|
446
|
+
});
|
|
447
|
+
});
|