@rigour-labs/core 3.0.3 → 3.0.5
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/dist/gates/deprecated-apis.d.ts +55 -0
- package/dist/gates/deprecated-apis.js +724 -0
- package/dist/gates/deprecated-apis.test.d.ts +1 -0
- package/dist/gates/deprecated-apis.test.js +288 -0
- package/dist/gates/phantom-apis.d.ts +77 -0
- package/dist/gates/phantom-apis.js +675 -0
- package/dist/gates/phantom-apis.test.d.ts +1 -0
- package/dist/gates/phantom-apis.test.js +320 -0
- package/dist/gates/runner.js +13 -0
- package/dist/gates/test-quality.d.ts +67 -0
- package/dist/gates/test-quality.js +512 -0
- package/dist/gates/test-quality.test.d.ts +1 -0
- package/dist/gates/test-quality.test.js +312 -0
- package/dist/templates/index.js +31 -1
- package/dist/types/index.d.ts +348 -0
- package/dist/types/index.js +33 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
const mockFindFiles = vi.hoisted(() => vi.fn());
|
|
3
|
+
const mockReadFile = vi.hoisted(() => vi.fn());
|
|
4
|
+
const mockPathExists = vi.hoisted(() => vi.fn());
|
|
5
|
+
vi.mock('../utils/scanner.js', () => ({
|
|
6
|
+
FileScanner: { findFiles: mockFindFiles },
|
|
7
|
+
}));
|
|
8
|
+
vi.mock('fs-extra', () => ({
|
|
9
|
+
default: {
|
|
10
|
+
readFile: mockReadFile,
|
|
11
|
+
pathExists: mockPathExists,
|
|
12
|
+
pathExistsSync: vi.fn().mockReturnValue(false),
|
|
13
|
+
readFileSync: vi.fn().mockReturnValue(''),
|
|
14
|
+
readJson: vi.fn().mockResolvedValue(null),
|
|
15
|
+
readdirSync: vi.fn().mockReturnValue([]),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
import { PhantomApisGate } from './phantom-apis.js';
|
|
19
|
+
describe('PhantomApisGate — Node.js', () => {
|
|
20
|
+
let gate;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
gate = new PhantomApisGate();
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it('should flag non-existent fs methods', async () => {
|
|
26
|
+
mockFindFiles.mockResolvedValue(['src/utils.ts']);
|
|
27
|
+
mockReadFile.mockResolvedValue(`
|
|
28
|
+
import fs from 'fs';
|
|
29
|
+
const data = fs.readFileAsync('test.txt');
|
|
30
|
+
const result = fs.writeFilePromise('out.txt', data);
|
|
31
|
+
`);
|
|
32
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
33
|
+
expect(failures).toHaveLength(1);
|
|
34
|
+
expect(failures[0].details).toContain('readFileAsync');
|
|
35
|
+
expect(failures[0].details).toContain('writeFilePromise');
|
|
36
|
+
});
|
|
37
|
+
it('should NOT flag real fs methods', async () => {
|
|
38
|
+
mockFindFiles.mockResolvedValue(['src/utils.ts']);
|
|
39
|
+
mockReadFile.mockResolvedValue(`
|
|
40
|
+
import fs from 'fs';
|
|
41
|
+
const data = fs.readFileSync('test.txt', 'utf-8');
|
|
42
|
+
fs.writeFileSync('out.txt', data);
|
|
43
|
+
fs.existsSync('/tmp');
|
|
44
|
+
const stream = fs.createReadStream('big.txt');
|
|
45
|
+
`);
|
|
46
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
47
|
+
expect(failures).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
it('should flag non-existent path methods', async () => {
|
|
50
|
+
mockFindFiles.mockResolvedValue(['src/paths.ts']);
|
|
51
|
+
mockReadFile.mockResolvedValue(`
|
|
52
|
+
import path from 'path';
|
|
53
|
+
const combined = path.combine('a', 'b');
|
|
54
|
+
const exists = path.exists('/tmp');
|
|
55
|
+
`);
|
|
56
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
57
|
+
expect(failures).toHaveLength(1);
|
|
58
|
+
expect(failures[0].details).toContain('combine');
|
|
59
|
+
expect(failures[0].details).toContain('exists');
|
|
60
|
+
});
|
|
61
|
+
it('should NOT flag real path methods', async () => {
|
|
62
|
+
mockFindFiles.mockResolvedValue(['src/paths.ts']);
|
|
63
|
+
mockReadFile.mockResolvedValue(`
|
|
64
|
+
import path from 'path';
|
|
65
|
+
const p = path.join('a', 'b');
|
|
66
|
+
const ext = path.extname('file.txt');
|
|
67
|
+
const abs = path.resolve('.');
|
|
68
|
+
`);
|
|
69
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
70
|
+
expect(failures).toHaveLength(0);
|
|
71
|
+
});
|
|
72
|
+
it('should flag non-existent crypto methods', async () => {
|
|
73
|
+
mockFindFiles.mockResolvedValue(['src/crypto.ts']);
|
|
74
|
+
mockReadFile.mockResolvedValue(`
|
|
75
|
+
import crypto from 'crypto';
|
|
76
|
+
const hash = crypto.generateHash('sha256', 'data');
|
|
77
|
+
const key = crypto.createKey('aes-256');
|
|
78
|
+
`);
|
|
79
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
80
|
+
expect(failures).toHaveLength(1);
|
|
81
|
+
expect(failures[0].details).toContain('generateHash');
|
|
82
|
+
expect(failures[0].details).toContain('createKey');
|
|
83
|
+
});
|
|
84
|
+
it('should NOT flag real crypto methods', async () => {
|
|
85
|
+
mockFindFiles.mockResolvedValue(['src/crypto.ts']);
|
|
86
|
+
mockReadFile.mockResolvedValue(`
|
|
87
|
+
import crypto from 'crypto';
|
|
88
|
+
const hash = crypto.createHash('sha256');
|
|
89
|
+
const uuid = crypto.randomUUID();
|
|
90
|
+
`);
|
|
91
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
92
|
+
expect(failures).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
it('should handle require() imports', async () => {
|
|
95
|
+
mockFindFiles.mockResolvedValue(['src/legacy.js']);
|
|
96
|
+
mockReadFile.mockResolvedValue(`
|
|
97
|
+
const fs = require('fs');
|
|
98
|
+
fs.readFileAsync('data.txt');
|
|
99
|
+
`);
|
|
100
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
101
|
+
expect(failures).toHaveLength(1);
|
|
102
|
+
expect(failures[0].details).toContain('readFileAsync');
|
|
103
|
+
});
|
|
104
|
+
it('should handle node: protocol imports', async () => {
|
|
105
|
+
mockFindFiles.mockResolvedValue(['src/modern.ts']);
|
|
106
|
+
mockReadFile.mockResolvedValue(`
|
|
107
|
+
import os from 'node:os';
|
|
108
|
+
const info = os.cpuInfo();
|
|
109
|
+
`);
|
|
110
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
111
|
+
expect(failures).toHaveLength(1);
|
|
112
|
+
expect(failures[0].details).toContain('cpuInfo');
|
|
113
|
+
});
|
|
114
|
+
it('should suggest closest real method', async () => {
|
|
115
|
+
mockFindFiles.mockResolvedValue(['src/typo.ts']);
|
|
116
|
+
mockReadFile.mockResolvedValue(`
|
|
117
|
+
import fs from 'fs';
|
|
118
|
+
fs.readFileSyn('data.txt');
|
|
119
|
+
`);
|
|
120
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
121
|
+
expect(failures).toHaveLength(1);
|
|
122
|
+
expect(failures[0].details).toContain('readFileSync');
|
|
123
|
+
});
|
|
124
|
+
it('should not scan when disabled', async () => {
|
|
125
|
+
const disabled = new PhantomApisGate({ enabled: false });
|
|
126
|
+
const failures = await disabled.run({ cwd: '/project' });
|
|
127
|
+
expect(failures).toHaveLength(0);
|
|
128
|
+
expect(mockFindFiles).not.toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('PhantomApisGate — Python', () => {
|
|
132
|
+
let gate;
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
gate = new PhantomApisGate();
|
|
135
|
+
vi.clearAllMocks();
|
|
136
|
+
});
|
|
137
|
+
it('should flag non-existent os methods', async () => {
|
|
138
|
+
mockFindFiles.mockResolvedValue(['utils.py']);
|
|
139
|
+
mockReadFile.mockResolvedValue(`
|
|
140
|
+
import os
|
|
141
|
+
current = os.getCurrentDirectory()
|
|
142
|
+
files = os.listFiles('.')
|
|
143
|
+
`);
|
|
144
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
145
|
+
expect(failures).toHaveLength(1);
|
|
146
|
+
expect(failures[0].details).toContain('getCurrentDirectory');
|
|
147
|
+
expect(failures[0].details).toContain('listFiles');
|
|
148
|
+
});
|
|
149
|
+
it('should NOT flag real os methods', async () => {
|
|
150
|
+
mockFindFiles.mockResolvedValue(['utils.py']);
|
|
151
|
+
mockReadFile.mockResolvedValue(`
|
|
152
|
+
import os
|
|
153
|
+
current = os.getcwd()
|
|
154
|
+
files = os.listdir('.')
|
|
155
|
+
exists = os.path
|
|
156
|
+
`);
|
|
157
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
158
|
+
expect(failures).toHaveLength(0);
|
|
159
|
+
});
|
|
160
|
+
it('should flag non-existent json methods', async () => {
|
|
161
|
+
mockFindFiles.mockResolvedValue(['parser.py']);
|
|
162
|
+
mockReadFile.mockResolvedValue(`
|
|
163
|
+
import json
|
|
164
|
+
data = json.parse('{"key": "value"}')
|
|
165
|
+
result = json.stringify(data)
|
|
166
|
+
`);
|
|
167
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
168
|
+
expect(failures).toHaveLength(1);
|
|
169
|
+
expect(failures[0].details).toContain('parse');
|
|
170
|
+
expect(failures[0].details).toContain('stringify');
|
|
171
|
+
});
|
|
172
|
+
it('should NOT flag real json methods', async () => {
|
|
173
|
+
mockFindFiles.mockResolvedValue(['parser.py']);
|
|
174
|
+
mockReadFile.mockResolvedValue(`
|
|
175
|
+
import json
|
|
176
|
+
data = json.loads('{"key": "value"}')
|
|
177
|
+
output = json.dumps(data)
|
|
178
|
+
`);
|
|
179
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
180
|
+
expect(failures).toHaveLength(0);
|
|
181
|
+
});
|
|
182
|
+
it('should handle aliased imports', async () => {
|
|
183
|
+
mockFindFiles.mockResolvedValue(['utils.py']);
|
|
184
|
+
mockReadFile.mockResolvedValue(`
|
|
185
|
+
import os as operating_system
|
|
186
|
+
result = operating_system.getCurrentDirectory()
|
|
187
|
+
`);
|
|
188
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
189
|
+
expect(failures).toHaveLength(1);
|
|
190
|
+
expect(failures[0].details).toContain('getCurrentDirectory');
|
|
191
|
+
});
|
|
192
|
+
it('should flag non-existent subprocess methods', async () => {
|
|
193
|
+
mockFindFiles.mockResolvedValue(['runner.py']);
|
|
194
|
+
mockReadFile.mockResolvedValue(`
|
|
195
|
+
import subprocess
|
|
196
|
+
result = subprocess.execute(['ls', '-la'])
|
|
197
|
+
`);
|
|
198
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
199
|
+
expect(failures).toHaveLength(1);
|
|
200
|
+
expect(failures[0].details).toContain('execute');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe('PhantomApisGate — Go', () => {
|
|
204
|
+
let gate;
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
gate = new PhantomApisGate();
|
|
207
|
+
vi.clearAllMocks();
|
|
208
|
+
});
|
|
209
|
+
it('should flag Python-style Go method names', async () => {
|
|
210
|
+
mockFindFiles.mockResolvedValue(['main.go']);
|
|
211
|
+
mockReadFile.mockResolvedValue(`
|
|
212
|
+
package main
|
|
213
|
+
import "strings"
|
|
214
|
+
func main() {
|
|
215
|
+
result := strings.includes("hello", "ell")
|
|
216
|
+
}
|
|
217
|
+
`);
|
|
218
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
219
|
+
expect(failures).toHaveLength(1);
|
|
220
|
+
expect(failures[0].details).toContain('includes');
|
|
221
|
+
expect(failures[0].details).toContain('strings.Contains');
|
|
222
|
+
});
|
|
223
|
+
it('should flag JS-style JSON methods in Go', async () => {
|
|
224
|
+
mockFindFiles.mockResolvedValue(['parser.go']);
|
|
225
|
+
mockReadFile.mockResolvedValue(`
|
|
226
|
+
package main
|
|
227
|
+
import "encoding/json"
|
|
228
|
+
func parse() {
|
|
229
|
+
data := json.parse(raw)
|
|
230
|
+
out := json.stringify(data)
|
|
231
|
+
}
|
|
232
|
+
`);
|
|
233
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
234
|
+
expect(failures).toHaveLength(1);
|
|
235
|
+
expect(failures[0].details).toContain('parse');
|
|
236
|
+
});
|
|
237
|
+
it('should flag os.Exists in Go', async () => {
|
|
238
|
+
mockFindFiles.mockResolvedValue(['files.go']);
|
|
239
|
+
mockReadFile.mockResolvedValue(`
|
|
240
|
+
package main
|
|
241
|
+
import "os"
|
|
242
|
+
func check() {
|
|
243
|
+
exists := os.Exists("/tmp/file")
|
|
244
|
+
}
|
|
245
|
+
`);
|
|
246
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
247
|
+
expect(failures).toHaveLength(1);
|
|
248
|
+
expect(failures[0].details).toContain('Exists');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe('PhantomApisGate — C#', () => {
|
|
252
|
+
let gate;
|
|
253
|
+
beforeEach(() => {
|
|
254
|
+
gate = new PhantomApisGate();
|
|
255
|
+
vi.clearAllMocks();
|
|
256
|
+
});
|
|
257
|
+
it('should flag Java-style method casing in C#', async () => {
|
|
258
|
+
mockFindFiles.mockResolvedValue(['Program.cs']);
|
|
259
|
+
mockReadFile.mockResolvedValue(`
|
|
260
|
+
using System;
|
|
261
|
+
class Program {
|
|
262
|
+
void Main() {
|
|
263
|
+
string s = "hello";
|
|
264
|
+
int len = s.length;
|
|
265
|
+
bool eq = s.equals("world");
|
|
266
|
+
string str = s.toString();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
`);
|
|
270
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
271
|
+
expect(failures.length).toBeGreaterThanOrEqual(1);
|
|
272
|
+
expect(failures[0].details).toMatch(/length|equals|toString/);
|
|
273
|
+
});
|
|
274
|
+
it('should flag Java collections in C#', async () => {
|
|
275
|
+
mockFindFiles.mockResolvedValue(['Service.cs']);
|
|
276
|
+
mockReadFile.mockResolvedValue(`
|
|
277
|
+
using System.Collections.Generic;
|
|
278
|
+
List<string> items = new ArrayList<string>();
|
|
279
|
+
HashMap<string, int> map = new HashMap<string, int>();
|
|
280
|
+
System.out.println("hello");
|
|
281
|
+
`);
|
|
282
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
283
|
+
expect(failures.length).toBeGreaterThanOrEqual(1);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
describe('PhantomApisGate — Java', () => {
|
|
287
|
+
let gate;
|
|
288
|
+
beforeEach(() => {
|
|
289
|
+
gate = new PhantomApisGate();
|
|
290
|
+
vi.clearAllMocks();
|
|
291
|
+
});
|
|
292
|
+
it('should flag JS/Python-style method names in Java', async () => {
|
|
293
|
+
mockFindFiles.mockResolvedValue(['Main.java']);
|
|
294
|
+
mockReadFile.mockResolvedValue(`
|
|
295
|
+
import java.util.List;
|
|
296
|
+
class Main {
|
|
297
|
+
void test() {
|
|
298
|
+
List<String> items = new ArrayList<>();
|
|
299
|
+
items.push("hello");
|
|
300
|
+
items.includes("test");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
`);
|
|
304
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
305
|
+
expect(failures.length).toBeGreaterThanOrEqual(1);
|
|
306
|
+
expect(failures[0].details).toMatch(/push|includes/);
|
|
307
|
+
});
|
|
308
|
+
it('should flag new List() in Java', async () => {
|
|
309
|
+
mockFindFiles.mockResolvedValue(['Service.java']);
|
|
310
|
+
mockReadFile.mockResolvedValue(`
|
|
311
|
+
import java.util.*;
|
|
312
|
+
class Service {
|
|
313
|
+
List<String> items = new List<String>();
|
|
314
|
+
Map<String, Integer> map = new Map<String, Integer>();
|
|
315
|
+
}
|
|
316
|
+
`);
|
|
317
|
+
const failures = await gate.run({ cwd: '/project' });
|
|
318
|
+
expect(failures.length).toBeGreaterThanOrEqual(1);
|
|
319
|
+
});
|
|
320
|
+
});
|
package/dist/gates/runner.js
CHANGED
|
@@ -18,6 +18,9 @@ import { HallucinatedImportsGate } from './hallucinated-imports.js';
|
|
|
18
18
|
import { InconsistentErrorHandlingGate } from './inconsistent-error-handling.js';
|
|
19
19
|
import { ContextWindowArtifactsGate } from './context-window-artifacts.js';
|
|
20
20
|
import { PromiseSafetyGate } from './promise-safety.js';
|
|
21
|
+
import { PhantomApisGate } from './phantom-apis.js';
|
|
22
|
+
import { DeprecatedApisGate } from './deprecated-apis.js';
|
|
23
|
+
import { TestQualityGate } from './test-quality.js';
|
|
21
24
|
import { execa } from 'execa';
|
|
22
25
|
import { Logger } from '../utils/logger.js';
|
|
23
26
|
export class GateRunner {
|
|
@@ -78,6 +81,16 @@ export class GateRunner {
|
|
|
78
81
|
if (this.config.gates.promise_safety?.enabled !== false) {
|
|
79
82
|
this.gates.push(new PromiseSafetyGate(this.config.gates.promise_safety));
|
|
80
83
|
}
|
|
84
|
+
// v3.1+ Extended Hallucination Detection
|
|
85
|
+
if (this.config.gates.phantom_apis?.enabled !== false) {
|
|
86
|
+
this.gates.push(new PhantomApisGate(this.config.gates.phantom_apis));
|
|
87
|
+
}
|
|
88
|
+
if (this.config.gates.deprecated_apis?.enabled !== false) {
|
|
89
|
+
this.gates.push(new DeprecatedApisGate(this.config.gates.deprecated_apis));
|
|
90
|
+
}
|
|
91
|
+
if (this.config.gates.test_quality?.enabled !== false) {
|
|
92
|
+
this.gates.push(new TestQualityGate(this.config.gates.test_quality));
|
|
93
|
+
}
|
|
81
94
|
// Environment Alignment Gate (Should be prioritized)
|
|
82
95
|
if (this.config.gates.environment?.enabled) {
|
|
83
96
|
this.gates.unshift(new EnvironmentGate(this.config.gates));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Test Quality Gate
|
|
3
|
+
*
|
|
4
|
+
* Detects AI-generated test anti-patterns that create false confidence.
|
|
5
|
+
* AI models generate tests that look comprehensive but actually test
|
|
6
|
+
* the AI's own assumptions rather than the developer's intent.
|
|
7
|
+
*
|
|
8
|
+
* Detected anti-patterns:
|
|
9
|
+
* 1. Empty test bodies — tests with no assertions
|
|
10
|
+
* 2. Tautological assertions — expect(true).toBe(true), assert True
|
|
11
|
+
* 3. Mock-everything — tests that mock every dependency (test nothing real)
|
|
12
|
+
* 4. Missing error path tests — only happy path tested
|
|
13
|
+
* 5. Shallow snapshot abuse — snapshot tests with no semantic assertions
|
|
14
|
+
* 6. Assertion-free async — async tests that never await/assert
|
|
15
|
+
*
|
|
16
|
+
* Supported test frameworks:
|
|
17
|
+
* JS/TS — Jest, Vitest, Mocha, Jasmine, Node test runner
|
|
18
|
+
* Python — pytest, unittest
|
|
19
|
+
* Go — testing package (t.Run, table-driven tests)
|
|
20
|
+
* Java — JUnit 4/5, TestNG
|
|
21
|
+
* Kotlin — JUnit 5, kotlin.test
|
|
22
|
+
*
|
|
23
|
+
* @since v3.0.0
|
|
24
|
+
* @since v3.0.3 — Go, Java, Kotlin support added
|
|
25
|
+
*/
|
|
26
|
+
import { Gate, GateContext } from './base.js';
|
|
27
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
28
|
+
export interface TestQualityIssue {
|
|
29
|
+
file: string;
|
|
30
|
+
line: number;
|
|
31
|
+
pattern: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
}
|
|
34
|
+
export interface TestQualityConfig {
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
check_empty_tests?: boolean;
|
|
37
|
+
check_tautological?: boolean;
|
|
38
|
+
check_mock_heavy?: boolean;
|
|
39
|
+
check_snapshot_abuse?: boolean;
|
|
40
|
+
check_assertion_free_async?: boolean;
|
|
41
|
+
max_mocks_per_test?: number;
|
|
42
|
+
ignore_patterns?: string[];
|
|
43
|
+
}
|
|
44
|
+
export declare class TestQualityGate extends Gate {
|
|
45
|
+
private config;
|
|
46
|
+
constructor(config?: TestQualityConfig);
|
|
47
|
+
protected get provenance(): Provenance;
|
|
48
|
+
run(context: GateContext): Promise<Failure[]>;
|
|
49
|
+
private checkJSTestQuality;
|
|
50
|
+
private analyzeJSTestBlock;
|
|
51
|
+
private checkPythonTestQuality;
|
|
52
|
+
private analyzePythonTestBlock;
|
|
53
|
+
/**
|
|
54
|
+
* Go test quality checks.
|
|
55
|
+
* Go tests use func TestXxx(t *testing.T) pattern.
|
|
56
|
+
* Assertions via t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow.
|
|
57
|
+
* Also checks for t.Run subtests and table-driven patterns.
|
|
58
|
+
*/
|
|
59
|
+
private checkGoTestQuality;
|
|
60
|
+
/**
|
|
61
|
+
* Java/Kotlin test quality checks.
|
|
62
|
+
* JUnit 4: @Test + Assert.assertEquals, assertTrue, etc.
|
|
63
|
+
* JUnit 5: @Test + Assertions.assertEquals, assertThrows, etc.
|
|
64
|
+
* Kotlin: @Test + kotlin.test assertEquals, etc.
|
|
65
|
+
*/
|
|
66
|
+
private checkJavaKotlinTestQuality;
|
|
67
|
+
}
|