@codexa/cli 9.0.1 → 9.0.3
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/architect.test.ts +531 -0
- package/commands/architect.ts +68 -11
- package/commands/clear.ts +0 -1
- package/commands/decide.ts +28 -28
- package/commands/discover.ts +128 -3
- package/commands/knowledge.ts +2 -27
- package/commands/patterns.test.ts +169 -0
- package/commands/plan.test.ts +73 -0
- package/commands/plan.ts +4 -2
- package/commands/sync.ts +90 -0
- package/commands/task.ts +43 -159
- package/commands/utils.ts +251 -249
- package/db/schema.test.ts +333 -0
- package/db/schema.ts +160 -130
- package/gates/validator.test.ts +617 -0
- package/gates/validator.ts +42 -10
- package/package.json +3 -1
- package/protocol/process-return.ts +25 -93
- package/protocol/subagent-protocol.test.ts +936 -0
- package/protocol/subagent-protocol.ts +25 -1
- package/workflow.ts +102 -21
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive tests for validateByExtension function from validator.ts
|
|
3
|
+
*
|
|
4
|
+
* Tests file content validation by extension:
|
|
5
|
+
* - TypeScript/JavaScript: requires code structure keywords (export, import, function, etc.)
|
|
6
|
+
* - TSX/JSX: requires code structure AND (< or export for components)
|
|
7
|
+
* - CSS/SCSS/SASS: requires { and :
|
|
8
|
+
* - JSON: must parse successfully
|
|
9
|
+
* - SQL: requires SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, or DROP (case insensitive)
|
|
10
|
+
* - Python: requires def, class, import, or from
|
|
11
|
+
* - Go: requires package declaration
|
|
12
|
+
* - Markdown: requires >= 50 characters after trim
|
|
13
|
+
* - Unknown extensions: always valid (no validation)
|
|
14
|
+
*
|
|
15
|
+
* Total: 91 tests covering validation rules, edge cases, and real-world samples
|
|
16
|
+
*/
|
|
17
|
+
import { describe, it, expect } from "bun:test";
|
|
18
|
+
import { validateByExtension, enforceGate, validateGate, type FileValidationResult } from "./validator";
|
|
19
|
+
import { GateError } from "../errors";
|
|
20
|
+
|
|
21
|
+
describe("validateByExtension", () => {
|
|
22
|
+
describe("TypeScript/JavaScript files", () => {
|
|
23
|
+
it("should accept .ts file with export function", () => {
|
|
24
|
+
const result = validateByExtension(".ts", "export function foo() { return 1; }");
|
|
25
|
+
expect(result.valid).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should accept .ts file with import statement", () => {
|
|
29
|
+
const result = validateByExtension(".ts", 'import { bar } from "baz";');
|
|
30
|
+
expect(result.valid).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should accept .ts file with class declaration", () => {
|
|
34
|
+
const result = validateByExtension(".ts", "class Foo { constructor() {} }");
|
|
35
|
+
expect(result.valid).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should reject .ts file with only comments", () => {
|
|
39
|
+
const result = validateByExtension(".ts", "// hello world\n// nothing here");
|
|
40
|
+
expect(result.valid).toBe(false);
|
|
41
|
+
expect(result.reason).toContain("sem declaracoes validas");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should reject .tsx file with only JSX (no code structure)", () => {
|
|
45
|
+
// TSX/JSX require BOTH code structure (export/function/etc) AND JSX
|
|
46
|
+
const result = validateByExtension(".tsx", "<div>hello</div>");
|
|
47
|
+
expect(result.valid).toBe(false);
|
|
48
|
+
expect(result.reason).toContain("sem declaracoes validas");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should accept .tsx file with export default", () => {
|
|
52
|
+
const result = validateByExtension(".tsx", "export default function App() { return null; }");
|
|
53
|
+
expect(result.valid).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should reject .tsx file without JSX or export", () => {
|
|
57
|
+
// First check is code structure - fails before JSX check
|
|
58
|
+
const result = validateByExtension(".tsx", "// just a comment");
|
|
59
|
+
expect(result.valid).toBe(false);
|
|
60
|
+
expect(result.reason).toContain("sem declaracoes validas");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should accept .js file with const declaration", () => {
|
|
64
|
+
const result = validateByExtension(".js", "const x = 1;");
|
|
65
|
+
expect(result.valid).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should accept .jsx file with JSX component and export", () => {
|
|
69
|
+
// JSX files need code structure keywords, not just JSX
|
|
70
|
+
const result = validateByExtension(".jsx", "export const Component = () => <div />;");
|
|
71
|
+
expect(result.valid).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should accept .ts file with interface", () => {
|
|
75
|
+
const result = validateByExtension(".ts", "interface User { id: number; }");
|
|
76
|
+
expect(result.valid).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should accept .ts file with type alias", () => {
|
|
80
|
+
const result = validateByExtension(".ts", "type UserId = string;");
|
|
81
|
+
expect(result.valid).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should accept .js file with let declaration", () => {
|
|
85
|
+
const result = validateByExtension(".js", "let count = 0;");
|
|
86
|
+
expect(result.valid).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should accept .js file with var declaration", () => {
|
|
90
|
+
const result = validateByExtension(".js", "var name = 'test';");
|
|
91
|
+
expect(result.valid).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("CSS/SCSS/SASS files", () => {
|
|
96
|
+
it("should accept .css file with valid rule", () => {
|
|
97
|
+
const result = validateByExtension(".css", ".btn { color: red; }");
|
|
98
|
+
expect(result.valid).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should reject .css file with only text", () => {
|
|
102
|
+
const result = validateByExtension(".css", "just text no rules");
|
|
103
|
+
expect(result.valid).toBe(false);
|
|
104
|
+
expect(result.reason).toContain("CSS sem regras validas");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should reject .css file with only opening brace", () => {
|
|
108
|
+
const result = validateByExtension(".css", "{ something }");
|
|
109
|
+
expect(result.valid).toBe(false);
|
|
110
|
+
expect(result.reason).toContain("CSS sem regras validas");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should reject .css file with only colon", () => {
|
|
114
|
+
const result = validateByExtension(".css", "color: red");
|
|
115
|
+
expect(result.valid).toBe(false);
|
|
116
|
+
expect(result.reason).toContain("CSS sem regras validas");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should accept .scss file with nested rules", () => {
|
|
120
|
+
const result = validateByExtension(".scss", ".parent { .child { color: blue; } }");
|
|
121
|
+
expect(result.valid).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should accept .sass file with indented syntax and colon", () => {
|
|
125
|
+
// SASS still needs braces and colons for validation
|
|
126
|
+
const result = validateByExtension(".sass", ".container { color: green; }");
|
|
127
|
+
expect(result.valid).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("JSON files", () => {
|
|
132
|
+
it("should accept .json file with valid JSON object", () => {
|
|
133
|
+
const result = validateByExtension(".json", '{"key": "value"}');
|
|
134
|
+
expect(result.valid).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should accept .json file with valid JSON array", () => {
|
|
138
|
+
const result = validateByExtension(".json", '[1, 2, 3]');
|
|
139
|
+
expect(result.valid).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should reject .json file with invalid JSON", () => {
|
|
143
|
+
const result = validateByExtension(".json", "{invalid json}");
|
|
144
|
+
expect(result.valid).toBe(false);
|
|
145
|
+
expect(result.reason).toContain("JSON invalido");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should reject .json file with trailing comma", () => {
|
|
149
|
+
const result = validateByExtension(".json", '{"key": "value",}');
|
|
150
|
+
expect(result.valid).toBe(false);
|
|
151
|
+
expect(result.reason).toContain("JSON invalido");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("SQL files", () => {
|
|
156
|
+
it("should accept .sql file with SELECT statement", () => {
|
|
157
|
+
const result = validateByExtension(".sql", "SELECT * FROM users;");
|
|
158
|
+
expect(result.valid).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should accept .sql file with CREATE TABLE", () => {
|
|
162
|
+
const result = validateByExtension(".sql", "CREATE TABLE foo (id INT);");
|
|
163
|
+
expect(result.valid).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should accept .sql file with lowercase select", () => {
|
|
167
|
+
const result = validateByExtension(".sql", "select id from posts;");
|
|
168
|
+
expect(result.valid).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should accept .sql file with INSERT statement", () => {
|
|
172
|
+
const result = validateByExtension(".sql", "INSERT INTO users (name) VALUES ('Alice');");
|
|
173
|
+
expect(result.valid).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should accept .sql file with UPDATE statement", () => {
|
|
177
|
+
const result = validateByExtension(".sql", "UPDATE users SET active = 1;");
|
|
178
|
+
expect(result.valid).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should accept .sql file with DELETE statement", () => {
|
|
182
|
+
const result = validateByExtension(".sql", "DELETE FROM logs WHERE date < '2020-01-01';");
|
|
183
|
+
expect(result.valid).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should accept .sql file with ALTER statement", () => {
|
|
187
|
+
const result = validateByExtension(".sql", "ALTER TABLE users ADD COLUMN email TEXT;");
|
|
188
|
+
expect(result.valid).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should accept .sql file with DROP statement", () => {
|
|
192
|
+
const result = validateByExtension(".sql", "DROP TABLE temp_data;");
|
|
193
|
+
expect(result.valid).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should reject .sql file with only comment", () => {
|
|
197
|
+
const result = validateByExtension(".sql", "-- just a comment");
|
|
198
|
+
expect(result.valid).toBe(false);
|
|
199
|
+
expect(result.reason).toContain("SQL sem statements validos");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("Python files", () => {
|
|
204
|
+
it("should accept .py file with function definition", () => {
|
|
205
|
+
const result = validateByExtension(".py", "def foo():\n pass");
|
|
206
|
+
expect(result.valid).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should accept .py file with class definition", () => {
|
|
210
|
+
const result = validateByExtension(".py", "class Bar:\n pass");
|
|
211
|
+
expect(result.valid).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should accept .py file with import statement", () => {
|
|
215
|
+
const result = validateByExtension(".py", "import os");
|
|
216
|
+
expect(result.valid).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should accept .py file with from import", () => {
|
|
220
|
+
const result = validateByExtension(".py", "from datetime import datetime");
|
|
221
|
+
expect(result.valid).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should reject .py file with only variable assignment", () => {
|
|
225
|
+
const result = validateByExtension(".py", "x = 1");
|
|
226
|
+
expect(result.valid).toBe(false);
|
|
227
|
+
expect(result.reason).toContain("Python sem definicoes");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should reject .py file with only comment", () => {
|
|
231
|
+
const result = validateByExtension(".py", "# just a comment");
|
|
232
|
+
expect(result.valid).toBe(false);
|
|
233
|
+
expect(result.reason).toContain("Python sem definicoes");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("Go files", () => {
|
|
238
|
+
it("should accept .go file with package declaration", () => {
|
|
239
|
+
const result = validateByExtension(".go", "package main\n\nfunc main() {}");
|
|
240
|
+
expect(result.valid).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should accept .go file with package declaration only", () => {
|
|
244
|
+
const result = validateByExtension(".go", "package utils");
|
|
245
|
+
expect(result.valid).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("should reject .go file without package declaration", () => {
|
|
249
|
+
const result = validateByExtension(".go", "func main() {}");
|
|
250
|
+
expect(result.valid).toBe(false);
|
|
251
|
+
expect(result.reason).toContain("Go sem declaracao package");
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("Markdown files", () => {
|
|
256
|
+
it("should accept .md file with 50+ characters", () => {
|
|
257
|
+
const content = "# Title\n\nThis is a markdown document with enough content to be valid.";
|
|
258
|
+
const result = validateByExtension(".md", content);
|
|
259
|
+
expect(result.valid).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should reject .md file with less than 50 characters", () => {
|
|
263
|
+
const result = validateByExtension(".md", "# Short");
|
|
264
|
+
expect(result.valid).toBe(false);
|
|
265
|
+
expect(result.reason).toContain("Markdown muito curto");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should accept .md file with exactly 50 characters", () => {
|
|
269
|
+
const content = "1234567890123456789012345678901234567890123456789";
|
|
270
|
+
const result = validateByExtension(".md", content);
|
|
271
|
+
expect(result.valid).toBe(false); // 49 chars after trim
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should accept .md file with 51 characters", () => {
|
|
275
|
+
const content = "12345678901234567890123456789012345678901234567890X";
|
|
276
|
+
const result = validateByExtension(".md", content);
|
|
277
|
+
expect(result.valid).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("Unknown extensions", () => {
|
|
282
|
+
it("should accept .yaml file with any content", () => {
|
|
283
|
+
const result = validateByExtension(".yaml", "key: value");
|
|
284
|
+
expect(result.valid).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should accept .txt file with any content", () => {
|
|
288
|
+
const result = validateByExtension(".txt", "plain text");
|
|
289
|
+
expect(result.valid).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should accept .html file with any content", () => {
|
|
293
|
+
const result = validateByExtension(".html", "<html></html>");
|
|
294
|
+
expect(result.valid).toBe(true);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("should accept .xml file with any content", () => {
|
|
298
|
+
const result = validateByExtension(".xml", "<root></root>");
|
|
299
|
+
expect(result.valid).toBe(true);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should accept .env file with any content", () => {
|
|
303
|
+
const result = validateByExtension(".env", "NODE_ENV=production");
|
|
304
|
+
expect(result.valid).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should accept unknown extension with empty content", () => {
|
|
308
|
+
const result = validateByExtension(".xyz", "");
|
|
309
|
+
expect(result.valid).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("Edge cases", () => {
|
|
314
|
+
it("should handle whitespace-only content correctly for .ts", () => {
|
|
315
|
+
const result = validateByExtension(".ts", " \n\n ");
|
|
316
|
+
expect(result.valid).toBe(false);
|
|
317
|
+
expect(result.reason).toContain("sem declaracoes validas");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should handle multiline .ts with comments and code", () => {
|
|
321
|
+
const content = `
|
|
322
|
+
// This is a comment
|
|
323
|
+
export const value = 42;
|
|
324
|
+
// Another comment
|
|
325
|
+
`;
|
|
326
|
+
const result = validateByExtension(".ts", content);
|
|
327
|
+
expect(result.valid).toBe(true);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should handle .tsx with JSX spread", () => {
|
|
331
|
+
const result = validateByExtension(".tsx", "const App = () => <div {...props} />;");
|
|
332
|
+
expect(result.valid).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should handle .json with nested objects", () => {
|
|
336
|
+
const content = '{"user": {"name": "Alice", "age": 30}}';
|
|
337
|
+
const result = validateByExtension(".json", content);
|
|
338
|
+
expect(result.valid).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should handle .sql with mixed case", () => {
|
|
342
|
+
const result = validateByExtension(".sql", "SeLeCt * FrOm UsErS;");
|
|
343
|
+
expect(result.valid).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("should handle .css with media queries", () => {
|
|
347
|
+
const content = "@media (min-width: 768px) { .container { width: 100%; } }";
|
|
348
|
+
const result = validateByExtension(".css", content);
|
|
349
|
+
expect(result.valid).toBe(true);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should handle .py with async def", () => {
|
|
353
|
+
const result = validateByExtension(".py", "async def fetch(): pass");
|
|
354
|
+
expect(result.valid).toBe(true);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("should handle extension with no leading dot", () => {
|
|
358
|
+
// Function doesn't validate extension format, treats "ts" as unknown extension
|
|
359
|
+
const result = validateByExtension("ts", "export const x = 1;");
|
|
360
|
+
expect(result.valid).toBe(true); // Passes as unknown extension (no validation)
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should handle uppercase extension", () => {
|
|
364
|
+
const result = validateByExtension(".TS", "export const x = 1;");
|
|
365
|
+
expect(result.valid).toBe(true); // Should work if toLowerCase() is applied
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("Additional edge cases", () => {
|
|
370
|
+
it("should handle .tsx with JSX but missing code structure", () => {
|
|
371
|
+
const result = validateByExtension(".tsx", "const x = <div>test</div>;");
|
|
372
|
+
expect(result.valid).toBe(true); // Has 'const' keyword
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("should handle .tsx with export and no JSX", () => {
|
|
376
|
+
const result = validateByExtension(".tsx", "export const API_URL = 'https://api.example.com';");
|
|
377
|
+
expect(result.valid).toBe(true); // Has both export and code structure
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("should handle .jsx with only JSX syntax (no valid structure)", () => {
|
|
381
|
+
const result = validateByExtension(".jsx", "<div><span>Hello</span></div>");
|
|
382
|
+
expect(result.valid).toBe(false);
|
|
383
|
+
expect(result.reason).toContain("sem declaracoes validas");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("should handle .css with only selector (no properties)", () => {
|
|
387
|
+
const result = validateByExtension(".css", ".btn { }");
|
|
388
|
+
expect(result.valid).toBe(false); // Has { but no :
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("should handle .css with CSS variables", () => {
|
|
392
|
+
const result = validateByExtension(".css", ":root { --primary-color: blue; }");
|
|
393
|
+
expect(result.valid).toBe(true);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("should handle .json with null value", () => {
|
|
397
|
+
const result = validateByExtension(".json", '{"value": null}');
|
|
398
|
+
expect(result.valid).toBe(true);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("should handle .json with boolean values", () => {
|
|
402
|
+
const result = validateByExtension(".json", '{"enabled": true, "debug": false}');
|
|
403
|
+
expect(result.valid).toBe(true);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("should handle .json with number value", () => {
|
|
407
|
+
const result = validateByExtension(".json", '123');
|
|
408
|
+
expect(result.valid).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("should handle .json with string value", () => {
|
|
412
|
+
const result = validateByExtension(".json", '"hello world"');
|
|
413
|
+
expect(result.valid).toBe(true);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("should handle .sql with comment before statement", () => {
|
|
417
|
+
const result = validateByExtension(".sql", "-- Get all users\nSELECT * FROM users;");
|
|
418
|
+
expect(result.valid).toBe(true);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("should handle .sql with multiple statements", () => {
|
|
422
|
+
const content = "DROP TABLE IF EXISTS users;\nCREATE TABLE users (id INT);";
|
|
423
|
+
const result = validateByExtension(".sql", content);
|
|
424
|
+
expect(result.valid).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should handle .py with decorator", () => {
|
|
428
|
+
const result = validateByExtension(".py", "@property\ndef name(self):\n pass");
|
|
429
|
+
expect(result.valid).toBe(true);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("should handle .py with lambda", () => {
|
|
433
|
+
const result = validateByExtension(".py", "square = lambda x: x * x");
|
|
434
|
+
expect(result.valid).toBe(false); // No def, class, import, or from
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("should handle .py with multiline import", () => {
|
|
438
|
+
const content = "from collections import (\n Counter,\n OrderedDict\n)";
|
|
439
|
+
const result = validateByExtension(".py", content);
|
|
440
|
+
expect(result.valid).toBe(true);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("should handle .go with multiple imports", () => {
|
|
444
|
+
const content = 'package main\n\nimport (\n "fmt"\n "os"\n)';
|
|
445
|
+
const result = validateByExtension(".go", content);
|
|
446
|
+
expect(result.valid).toBe(true);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("should handle .go with package comment", () => {
|
|
450
|
+
const content = "// Package utils provides utilities\npackage utils";
|
|
451
|
+
const result = validateByExtension(".go", content);
|
|
452
|
+
expect(result.valid).toBe(true);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("should handle .md with exactly 50 characters after trim", () => {
|
|
456
|
+
const content = "01234567890123456789012345678901234567890123456789"; // 50 chars
|
|
457
|
+
const result = validateByExtension(".md", content);
|
|
458
|
+
expect(result.valid).toBe(true);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("should handle .md with 49 characters after trim", () => {
|
|
462
|
+
const content = "0123456789012345678901234567890123456789012345678"; // 49 chars
|
|
463
|
+
const result = validateByExtension(".md", content);
|
|
464
|
+
expect(result.valid).toBe(false);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should handle .md with whitespace padding", () => {
|
|
468
|
+
const content = " " + "x".repeat(50) + " "; // 50 chars after trim
|
|
469
|
+
const result = validateByExtension(".md", content);
|
|
470
|
+
expect(result.valid).toBe(true);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("should handle .ts with multiple exports on one line", () => {
|
|
474
|
+
const result = validateByExtension(".ts", "export { foo, bar, baz };");
|
|
475
|
+
expect(result.valid).toBe(true);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("should handle .ts with export default", () => {
|
|
479
|
+
const result = validateByExtension(".ts", "export default class App {}");
|
|
480
|
+
expect(result.valid).toBe(true);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("should handle .ts with re-export", () => {
|
|
484
|
+
const result = validateByExtension(".ts", 'export * from "./types";');
|
|
485
|
+
expect(result.valid).toBe(true);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("should handle .js with dynamic import", () => {
|
|
489
|
+
const result = validateByExtension(".js", 'const module = await import("./utils");');
|
|
490
|
+
expect(result.valid).toBe(true);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("should handle .css with @import", () => {
|
|
494
|
+
const result = validateByExtension(".css", '@import url("reset.css"); .btn { color: red; }');
|
|
495
|
+
expect(result.valid).toBe(true);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("should handle .css with @keyframes", () => {
|
|
499
|
+
const content = "@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }";
|
|
500
|
+
const result = validateByExtension(".css", content);
|
|
501
|
+
expect(result.valid).toBe(true);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("should handle empty extension", () => {
|
|
505
|
+
const result = validateByExtension("", "some content");
|
|
506
|
+
expect(result.valid).toBe(true); // Unknown extension
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe("Real-world code samples", () => {
|
|
511
|
+
it("should accept realistic React component", () => {
|
|
512
|
+
const content = `
|
|
513
|
+
import React from 'react';
|
|
514
|
+
|
|
515
|
+
export default function Button({ label, onClick }) {
|
|
516
|
+
return (
|
|
517
|
+
<button onClick={onClick} className="btn">
|
|
518
|
+
{label}
|
|
519
|
+
</button>
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
`;
|
|
523
|
+
const result = validateByExtension(".tsx", content);
|
|
524
|
+
expect(result.valid).toBe(true);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it("should accept realistic TypeScript utility", () => {
|
|
528
|
+
const content = `
|
|
529
|
+
export function formatDate(date: Date): string {
|
|
530
|
+
return date.toISOString().split('T')[0];
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export const DEFAULT_LOCALE = 'en-US';
|
|
534
|
+
`;
|
|
535
|
+
const result = validateByExtension(".ts", content);
|
|
536
|
+
expect(result.valid).toBe(true);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("should accept realistic CSS module", () => {
|
|
540
|
+
const content = `
|
|
541
|
+
.container {
|
|
542
|
+
display: flex;
|
|
543
|
+
flex-direction: column;
|
|
544
|
+
padding: 1rem;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.container .item {
|
|
548
|
+
margin-bottom: 0.5rem;
|
|
549
|
+
}
|
|
550
|
+
`;
|
|
551
|
+
const result = validateByExtension(".css", content);
|
|
552
|
+
expect(result.valid).toBe(true);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it("should accept realistic SQL migration", () => {
|
|
556
|
+
const content = `
|
|
557
|
+
-- Migration: Add users table
|
|
558
|
+
CREATE TABLE users (
|
|
559
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
560
|
+
email TEXT NOT NULL UNIQUE,
|
|
561
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
565
|
+
`;
|
|
566
|
+
const result = validateByExtension(".sql", content);
|
|
567
|
+
expect(result.valid).toBe(true);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("should accept realistic Python class", () => {
|
|
571
|
+
const content = `
|
|
572
|
+
class User:
|
|
573
|
+
def __init__(self, name, email):
|
|
574
|
+
self.name = name
|
|
575
|
+
self.email = email
|
|
576
|
+
|
|
577
|
+
def get_display_name(self):
|
|
578
|
+
return self.name.title()
|
|
579
|
+
`;
|
|
580
|
+
const result = validateByExtension(".py", content);
|
|
581
|
+
expect(result.valid).toBe(true);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// ═══════════════════════════════════════════════════════════════
|
|
587
|
+
// enforceGate() tests — verifica que lança GateError em vez de process.exit
|
|
588
|
+
// ═══════════════════════════════════════════════════════════════
|
|
589
|
+
|
|
590
|
+
describe("enforceGate", () => {
|
|
591
|
+
it("should throw GateError when gate fails", () => {
|
|
592
|
+
// "task-done" with no taskId makes "task-is-running" fail immediately
|
|
593
|
+
// without needing DB access
|
|
594
|
+
try {
|
|
595
|
+
enforceGate("task-done", {});
|
|
596
|
+
expect(true).toBe(false); // Should not reach here
|
|
597
|
+
} catch (e) {
|
|
598
|
+
expect(e instanceof GateError).toBe(true);
|
|
599
|
+
const ge = e as GateError;
|
|
600
|
+
expect(ge.message).toContain("BLOQUEADO:");
|
|
601
|
+
expect(ge.message).toContain("Resolva:");
|
|
602
|
+
expect(ge.exitCode).toBe(1);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it("should not throw for unknown command (no gates defined)", () => {
|
|
607
|
+
// Unknown commands have no gate checks, so they pass
|
|
608
|
+
expect(() => enforceGate("nonexistent-command")).not.toThrow();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it("should return a GateResult from validateGate", () => {
|
|
612
|
+
const result = validateGate("task-done", {});
|
|
613
|
+
expect(result.passed).toBe(false);
|
|
614
|
+
expect(result.reason).toBeDefined();
|
|
615
|
+
expect(result.resolution).toBeDefined();
|
|
616
|
+
});
|
|
617
|
+
});
|