clckernel 1.2.6
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/AGENTS.md +124 -0
- package/README.es.md +166 -0
- package/README.md +167 -0
- package/bin/cli.js +12 -0
- package/docs/Journal/001-adr-clckernel-governance.md +18 -0
- package/package.json +44 -0
- package/src/adapters/astro.js +67 -0
- package/src/adapters/base.js +109 -0
- package/src/adapters/django.js +70 -0
- package/src/adapters/fastapi.js +69 -0
- package/src/adapters/go.js +50 -0
- package/src/adapters/laravel.js +52 -0
- package/src/adapters/nextjs.js +72 -0
- package/src/adapters/rails.js +54 -0
- package/src/adapters/rust.js +51 -0
- package/src/catalog.js +217 -0
- package/src/config.js +250 -0
- package/src/detector.js +85 -0
- package/src/doctor.js +125 -0
- package/src/generator.js +473 -0
- package/src/index.js +52 -0
- package/src/technologies/detector.js +243 -0
- package/src/technologies/guards/_shared.js +66 -0
- package/src/ui.js +101 -0
- package/src/yaml.js +173 -0
- package/test/adapters/astro.test.js +102 -0
- package/test/adapters/base.test.js +86 -0
- package/test/adapters/django.test.js +95 -0
- package/test/adapters/fastapi.test.js +88 -0
- package/test/adapters/go.test.js +81 -0
- package/test/adapters/laravel.test.js +106 -0
- package/test/adapters/nextjs.test.js +113 -0
- package/test/adapters/rails.test.js +108 -0
- package/test/adapters/rust.test.js +83 -0
- package/test/catalog.test.js +170 -0
- package/test/config.test.js +512 -0
- package/test/detector.test.js +345 -0
- package/test/doctor.test.js +302 -0
- package/test/generator.test.js +419 -0
- package/test/guards_new.test.js +95 -0
- package/test/helpers.js +36 -0
- package/test/integration/cli-flow.test.js +270 -0
- package/test/technologies/celery_guard.test.js +185 -0
- package/test/technologies/detector.test.js +342 -0
- package/test/technologies/docker_guard.test.js +187 -0
- package/test/technologies/integration.test.js +123 -0
- package/test/technologies/postgres_guard.test.js +87 -0
- package/test/technologies/redis_guard.test.js +128 -0
- package/tools/audit.js +219 -0
- package/tools/audit.py +273 -0
- package/tools/celery_guard.py +209 -0
- package/tools/check_a11y.js +109 -0
- package/tools/check_api_contracts.js +139 -0
- package/tools/check_architecture.js +139 -0
- package/tools/check_architecture.py +264 -0
- package/tools/check_custom.js +163 -0
- package/tools/check_custom.py +395 -0
- package/tools/check_db_efficiency.py +190 -0
- package/tools/check_migrations.py +223 -0
- package/tools/check_performance.js +142 -0
- package/tools/check_responsive.js +131 -0
- package/tools/check_scope.py +180 -0
- package/tools/check_seo.js +139 -0
- package/tools/check_storybook.js +108 -0
- package/tools/check_ui_reuse.js +135 -0
- package/tools/docker_guard.py +210 -0
- package/tools/postgres_guard.py +192 -0
- package/tools/redis_guard.py +197 -0
- package/tools/scan_secrets.js +109 -0
- package/tools/scan_secrets.py +153 -0
- package/tools/verify_tdd.py +212 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('BaseAdapter', () => {
|
|
6
|
+
const BaseAdapter = require('../../src/adapters/base');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('stores name, projectType, and testRunner', () => {
|
|
10
|
+
const adapter = new BaseAdapter('Test', 'frontend', 'Jest');
|
|
11
|
+
assert.strictEqual(adapter.name, 'Test');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'frontend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'Jest');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns false by default', () => {
|
|
19
|
+
const adapter = new BaseAdapter('Test', 'frontend', 'Jest');
|
|
20
|
+
assert.strictEqual(adapter.detect('/tmp'), false);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('getSafeguards()', () => {
|
|
25
|
+
it('returns empty array by default', () => {
|
|
26
|
+
const adapter = new BaseAdapter('Test', 'frontend', 'Jest');
|
|
27
|
+
assert.deepStrictEqual(adapter.getSafeguards(), []);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('getTools()', () => {
|
|
32
|
+
it('returns empty array by default', () => {
|
|
33
|
+
const adapter = new BaseAdapter('Test', 'frontend', 'Jest');
|
|
34
|
+
assert.deepStrictEqual(adapter.getTools(), []);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('provision()', () => {
|
|
39
|
+
it('creates docs/, sdds/, tools/ directories', () => {
|
|
40
|
+
const adapter = new BaseAdapter('Test', 'frontend', 'Jest');
|
|
41
|
+
const dir = createTmpProject({});
|
|
42
|
+
try {
|
|
43
|
+
adapter.provision(dir, {});
|
|
44
|
+
|
|
45
|
+
assert.ok(require('fs').existsSync(require('path').join(dir, 'docs', 'Journal')));
|
|
46
|
+
assert.ok(require('fs').existsSync(require('path').join(dir, 'docs', 'Architecture')));
|
|
47
|
+
assert.ok(require('fs').existsSync(require('path').join(dir, 'docs', 'Development')));
|
|
48
|
+
assert.ok(require('fs').existsSync(require('path').join(dir, 'sdds')));
|
|
49
|
+
assert.ok(require('fs').existsSync(require('path').join(dir, 'tools')));
|
|
50
|
+
} finally {
|
|
51
|
+
teardown(dir);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('writes .gitignore with sdds/* entry', () => {
|
|
56
|
+
const adapter = new BaseAdapter('Test', 'frontend', 'Jest');
|
|
57
|
+
const dir = createTmpProject({});
|
|
58
|
+
try {
|
|
59
|
+
adapter.provision(dir, {});
|
|
60
|
+
|
|
61
|
+
const gitignore = require('fs').readFileSync(
|
|
62
|
+
require('path').join(dir, '.gitignore'), 'utf-8'
|
|
63
|
+
);
|
|
64
|
+
assert.ok(gitignore.includes('sdds/*'));
|
|
65
|
+
} finally {
|
|
66
|
+
teardown(dir);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('writes AGENTS.md with adapter name and safeguards', () => {
|
|
71
|
+
const adapter = new BaseAdapter('TestStack', 'backend', 'Pytest');
|
|
72
|
+
const dir = createTmpProject({});
|
|
73
|
+
try {
|
|
74
|
+
adapter.provision(dir, {});
|
|
75
|
+
|
|
76
|
+
const agents = require('fs').readFileSync(
|
|
77
|
+
require('path').join(dir, 'AGENTS.md'), 'utf-8'
|
|
78
|
+
);
|
|
79
|
+
assert.ok(agents.includes('TestStack'));
|
|
80
|
+
assert.ok(agents.includes('BACKEND'));
|
|
81
|
+
} finally {
|
|
82
|
+
teardown(dir);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('DjangoAdapter', () => {
|
|
6
|
+
const DjangoAdapter = require('../../src/adapters/django');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new DjangoAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'Django Framework');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'backend');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('detect()', () => {
|
|
17
|
+
it('returns true when manage.py exists', () => {
|
|
18
|
+
const dir = createTmpProject({ 'manage.py': '#!/usr/bin/env python' });
|
|
19
|
+
try {
|
|
20
|
+
const adapter = new DjangoAdapter();
|
|
21
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
22
|
+
} finally {
|
|
23
|
+
teardown(dir);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns true when requirements.txt contains django', () => {
|
|
28
|
+
const dir = createTmpProject({ 'requirements.txt': 'django>=4.0\npsycopg2' });
|
|
29
|
+
try {
|
|
30
|
+
const adapter = new DjangoAdapter();
|
|
31
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
32
|
+
} finally {
|
|
33
|
+
teardown(dir);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns true when pyproject.toml contains django', () => {
|
|
38
|
+
const dir = createTmpProject({
|
|
39
|
+
'pyproject.toml': '[project]\nname = "myapp"\ndependencies = ["django>=4.0"]',
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
const adapter = new DjangoAdapter();
|
|
43
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
44
|
+
} finally {
|
|
45
|
+
teardown(dir);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns false when no django indicators present', () => {
|
|
50
|
+
const dir = createTmpProject({
|
|
51
|
+
'requirements.txt': 'flask>=2.0',
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
const adapter = new DjangoAdapter();
|
|
55
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
56
|
+
} finally {
|
|
57
|
+
teardown(dir);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns false when directory is empty', () => {
|
|
62
|
+
const dir = createTmpProject({});
|
|
63
|
+
try {
|
|
64
|
+
const adapter = new DjangoAdapter();
|
|
65
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
66
|
+
} finally {
|
|
67
|
+
teardown(dir);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('getTools()', () => {
|
|
73
|
+
it('returns array of tool objects with required fields', () => {
|
|
74
|
+
const adapter = new DjangoAdapter();
|
|
75
|
+
const tools = adapter.getTools();
|
|
76
|
+
assert.ok(Array.isArray(tools));
|
|
77
|
+
assert.ok(tools.length > 0);
|
|
78
|
+
for (const tool of tools) {
|
|
79
|
+
assert.ok(tool.name);
|
|
80
|
+
assert.ok(tool.language);
|
|
81
|
+
assert.ok(typeof tool.command === 'string');
|
|
82
|
+
assert.ok(tool.description);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('getSafeguards()', () => {
|
|
88
|
+
it('returns non-empty array of strings', () => {
|
|
89
|
+
const adapter = new DjangoAdapter();
|
|
90
|
+
const guards = adapter.getSafeguards();
|
|
91
|
+
assert.ok(Array.isArray(guards));
|
|
92
|
+
assert.ok(guards.length >= 5);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('FastApiAdapter', () => {
|
|
6
|
+
const FastApiAdapter = require('../../src/adapters/fastapi');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new FastApiAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'FastAPI Framework');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'backend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'Pytest');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns true when pyproject.toml contains fastapi', () => {
|
|
19
|
+
const dir = createTmpProject({
|
|
20
|
+
'pyproject.toml': '[project]\nname = "api"\ndependencies = ["fastapi>=0.100"]',
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const adapter = new FastApiAdapter();
|
|
24
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
25
|
+
} finally {
|
|
26
|
+
teardown(dir);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns true when requirements.txt contains fastapi', () => {
|
|
31
|
+
const dir = createTmpProject({
|
|
32
|
+
'requirements.txt': 'fastapi>=0.100\nuvicorn',
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const adapter = new FastApiAdapter();
|
|
36
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
37
|
+
} finally {
|
|
38
|
+
teardown(dir);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns false when no fastapi indicators present', () => {
|
|
43
|
+
const dir = createTmpProject({
|
|
44
|
+
'requirements.txt': 'flask>=2.0',
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
const adapter = new FastApiAdapter();
|
|
48
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
49
|
+
} finally {
|
|
50
|
+
teardown(dir);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns false when directory is empty', () => {
|
|
55
|
+
const dir = createTmpProject({});
|
|
56
|
+
try {
|
|
57
|
+
const adapter = new FastApiAdapter();
|
|
58
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
59
|
+
} finally {
|
|
60
|
+
teardown(dir);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('getTools()', () => {
|
|
66
|
+
it('returns array of tool objects with required fields', () => {
|
|
67
|
+
const adapter = new FastApiAdapter();
|
|
68
|
+
const tools = adapter.getTools();
|
|
69
|
+
assert.ok(Array.isArray(tools));
|
|
70
|
+
assert.ok(tools.length > 0);
|
|
71
|
+
for (const tool of tools) {
|
|
72
|
+
assert.ok(tool.name);
|
|
73
|
+
assert.ok(tool.language);
|
|
74
|
+
assert.ok(typeof tool.command === 'string');
|
|
75
|
+
assert.ok(tool.description);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getSafeguards()', () => {
|
|
81
|
+
it('returns non-empty array of strings', () => {
|
|
82
|
+
const adapter = new FastApiAdapter();
|
|
83
|
+
const guards = adapter.getSafeguards();
|
|
84
|
+
assert.ok(Array.isArray(guards));
|
|
85
|
+
assert.ok(guards.length >= 5);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('GoAdapter', () => {
|
|
6
|
+
const GoAdapter = require('../../src/adapters/go');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new GoAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'Go (Golang)');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'backend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'go test');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns true when go.mod exists', () => {
|
|
19
|
+
const dir = createTmpProject({ 'go.mod': 'module example.com/myapp\ngo 1.21' });
|
|
20
|
+
try {
|
|
21
|
+
const adapter = new GoAdapter();
|
|
22
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
23
|
+
} finally {
|
|
24
|
+
teardown(dir);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns false when go.mod does not exist', () => {
|
|
29
|
+
const dir = createTmpProject({});
|
|
30
|
+
try {
|
|
31
|
+
const adapter = new GoAdapter();
|
|
32
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
33
|
+
} finally {
|
|
34
|
+
teardown(dir);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('getTools()', () => {
|
|
40
|
+
it('returns array of tool objects with required fields', () => {
|
|
41
|
+
const adapter = new GoAdapter();
|
|
42
|
+
const tools = adapter.getTools();
|
|
43
|
+
assert.ok(Array.isArray(tools));
|
|
44
|
+
assert.ok(tools.length > 0);
|
|
45
|
+
for (const tool of tools) {
|
|
46
|
+
assert.ok(tool.name);
|
|
47
|
+
assert.ok(tool.language);
|
|
48
|
+
assert.ok(typeof tool.command === 'string');
|
|
49
|
+
assert.ok(tool.description);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('all external tools have language=external and empty path', () => {
|
|
54
|
+
const adapter = new GoAdapter();
|
|
55
|
+
const tools = adapter.getTools();
|
|
56
|
+
const externalTools = tools.filter((t) => t.language === 'external');
|
|
57
|
+
for (const tool of externalTools) {
|
|
58
|
+
assert.strictEqual(tool.language, 'external');
|
|
59
|
+
assert.strictEqual(tool.path, '');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('includes custom rules guard', () => {
|
|
64
|
+
const adapter = new GoAdapter();
|
|
65
|
+
const tools = adapter.getTools();
|
|
66
|
+
const customTool = tools.find((t) => t.name === 'check_custom');
|
|
67
|
+
assert.ok(customTool, 'check_custom tool should exist');
|
|
68
|
+
assert.strictEqual(customTool.language, 'js');
|
|
69
|
+
assert.strictEqual(customTool.path, 'check_custom.js');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('getSafeguards()', () => {
|
|
74
|
+
it('returns non-empty array of strings', () => {
|
|
75
|
+
const adapter = new GoAdapter();
|
|
76
|
+
const guards = adapter.getSafeguards();
|
|
77
|
+
assert.ok(Array.isArray(guards));
|
|
78
|
+
assert.ok(guards.length >= 5);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('LaravelAdapter', () => {
|
|
6
|
+
const LaravelAdapter = require('../../src/adapters/laravel');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new LaravelAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'PHP / Laravel');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'backend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'Pest / PHPUnit');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns true when both composer.json and artisan exist', () => {
|
|
19
|
+
const dir = createTmpProject({
|
|
20
|
+
'composer.json': JSON.stringify({ require: { 'laravel/framework': '^10.0' } }),
|
|
21
|
+
'artisan': '#!/usr/bin/env php',
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
const adapter = new LaravelAdapter();
|
|
25
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
26
|
+
} finally {
|
|
27
|
+
teardown(dir);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns false when only composer.json exists', () => {
|
|
32
|
+
const dir = createTmpProject({
|
|
33
|
+
'composer.json': JSON.stringify({ require: {} }),
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
const adapter = new LaravelAdapter();
|
|
37
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
38
|
+
} finally {
|
|
39
|
+
teardown(dir);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns false when only artisan exists', () => {
|
|
44
|
+
const dir = createTmpProject({ 'artisan': '#!/usr/bin/env php' });
|
|
45
|
+
try {
|
|
46
|
+
const adapter = new LaravelAdapter();
|
|
47
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
48
|
+
} finally {
|
|
49
|
+
teardown(dir);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns false when directory is empty', () => {
|
|
54
|
+
const dir = createTmpProject({});
|
|
55
|
+
try {
|
|
56
|
+
const adapter = new LaravelAdapter();
|
|
57
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
58
|
+
} finally {
|
|
59
|
+
teardown(dir);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getTools()', () => {
|
|
65
|
+
it('returns array of tool objects with required fields', () => {
|
|
66
|
+
const adapter = new LaravelAdapter();
|
|
67
|
+
const tools = adapter.getTools();
|
|
68
|
+
assert.ok(Array.isArray(tools));
|
|
69
|
+
assert.ok(tools.length > 0);
|
|
70
|
+
for (const tool of tools) {
|
|
71
|
+
assert.ok(tool.name);
|
|
72
|
+
assert.ok(tool.language);
|
|
73
|
+
assert.ok(typeof tool.command === 'string');
|
|
74
|
+
assert.ok(tool.description);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('all external tools have language=external and empty path', () => {
|
|
79
|
+
const adapter = new LaravelAdapter();
|
|
80
|
+
const tools = adapter.getTools();
|
|
81
|
+
const externalTools = tools.filter((t) => t.language === 'external');
|
|
82
|
+
for (const tool of externalTools) {
|
|
83
|
+
assert.strictEqual(tool.language, 'external');
|
|
84
|
+
assert.strictEqual(tool.path, '');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('includes custom rules guard', () => {
|
|
89
|
+
const adapter = new LaravelAdapter();
|
|
90
|
+
const tools = adapter.getTools();
|
|
91
|
+
const customTool = tools.find((t) => t.name === 'check_custom');
|
|
92
|
+
assert.ok(customTool, 'check_custom tool should exist');
|
|
93
|
+
assert.strictEqual(customTool.language, 'python');
|
|
94
|
+
assert.strictEqual(customTool.path, 'check_custom.py');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('getSafeguards()', () => {
|
|
99
|
+
it('returns non-empty array of strings', () => {
|
|
100
|
+
const adapter = new LaravelAdapter();
|
|
101
|
+
const guards = adapter.getSafeguards();
|
|
102
|
+
assert.ok(Array.isArray(guards));
|
|
103
|
+
assert.ok(guards.length >= 5);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('NextjsAdapter', () => {
|
|
6
|
+
const NextjsAdapter = require('../../src/adapters/nextjs');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new NextjsAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'Next.js (App Router)');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'frontend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'Vitest / Jest');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns true when package.json has next dependency', () => {
|
|
19
|
+
const dir = createTmpProject({
|
|
20
|
+
'package.json': JSON.stringify({ dependencies: { next: '14.0.0' } }),
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const adapter = new NextjsAdapter();
|
|
24
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
25
|
+
} finally {
|
|
26
|
+
teardown(dir);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns true when next is in devDependencies', () => {
|
|
31
|
+
const dir = createTmpProject({
|
|
32
|
+
'package.json': JSON.stringify({ devDependencies: { next: '14.0.0' } }),
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const adapter = new NextjsAdapter();
|
|
36
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
37
|
+
} finally {
|
|
38
|
+
teardown(dir);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns false when package.json has no next dependency', () => {
|
|
43
|
+
const dir = createTmpProject({
|
|
44
|
+
'package.json': JSON.stringify({ dependencies: { react: '18.0.0' } }),
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
const adapter = new NextjsAdapter();
|
|
48
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
49
|
+
} finally {
|
|
50
|
+
teardown(dir);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns false when no package.json exists', () => {
|
|
55
|
+
const dir = createTmpProject({});
|
|
56
|
+
try {
|
|
57
|
+
const adapter = new NextjsAdapter();
|
|
58
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
59
|
+
} finally {
|
|
60
|
+
teardown(dir);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns false when package.json is malformed JSON', () => {
|
|
65
|
+
const dir = createTmpProject({
|
|
66
|
+
'package.json': '{invalid json',
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
const adapter = new NextjsAdapter();
|
|
70
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
71
|
+
} finally {
|
|
72
|
+
teardown(dir);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('getTools()', () => {
|
|
78
|
+
it('returns array of tool objects with required fields', () => {
|
|
79
|
+
const adapter = new NextjsAdapter();
|
|
80
|
+
const tools = adapter.getTools();
|
|
81
|
+
assert.ok(Array.isArray(tools));
|
|
82
|
+
assert.ok(tools.length > 0);
|
|
83
|
+
for (const tool of tools) {
|
|
84
|
+
assert.ok(tool.name, 'tool must have name');
|
|
85
|
+
assert.ok(tool.language, 'tool must have language');
|
|
86
|
+
assert.ok(typeof tool.command === 'string', 'tool must have command string');
|
|
87
|
+
assert.ok(tool.description, 'tool must have description');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('external tools have language=external and empty path', () => {
|
|
92
|
+
const adapter = new NextjsAdapter();
|
|
93
|
+
const tools = adapter.getTools();
|
|
94
|
+
const external = tools.filter((t) => t.language === 'external');
|
|
95
|
+
assert.ok(external.length > 0, 'should have external tools');
|
|
96
|
+
for (const tool of external) {
|
|
97
|
+
assert.strictEqual(tool.path, '');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('getSafeguards()', () => {
|
|
103
|
+
it('returns non-empty array of strings', () => {
|
|
104
|
+
const adapter = new NextjsAdapter();
|
|
105
|
+
const guards = adapter.getSafeguards();
|
|
106
|
+
assert.ok(Array.isArray(guards));
|
|
107
|
+
assert.ok(guards.length >= 5, 'should have at least 5 safeguards');
|
|
108
|
+
for (const g of guards) {
|
|
109
|
+
assert.strictEqual(typeof g, 'string');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { createTmpProject, teardown } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
describe('RailsAdapter', () => {
|
|
6
|
+
const RailsAdapter = require('../../src/adapters/rails');
|
|
7
|
+
|
|
8
|
+
describe('constructor', () => {
|
|
9
|
+
it('creates adapter with correct name and projectType', () => {
|
|
10
|
+
const adapter = new RailsAdapter();
|
|
11
|
+
assert.strictEqual(adapter.name, 'Ruby on Rails');
|
|
12
|
+
assert.strictEqual(adapter.projectType, 'backend');
|
|
13
|
+
assert.strictEqual(adapter.testRunner, 'RSpec / Minitest');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detect()', () => {
|
|
18
|
+
it('returns true when Gemfile and config/routes.rb exist', () => {
|
|
19
|
+
const dir = createTmpProject({
|
|
20
|
+
'Gemfile': "source 'https://rubygems.org'\ngem 'rails', '~> 7.0'",
|
|
21
|
+
'config/routes.rb': 'Rails.application.routes.draw do end',
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
const adapter = new RailsAdapter();
|
|
25
|
+
assert.strictEqual(adapter.detect(dir), true);
|
|
26
|
+
} finally {
|
|
27
|
+
teardown(dir);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns false when only Gemfile exists (no routes.rb)', () => {
|
|
32
|
+
const dir = createTmpProject({
|
|
33
|
+
'Gemfile': "source 'https://rubygems.org'",
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
const adapter = new RailsAdapter();
|
|
37
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
38
|
+
} finally {
|
|
39
|
+
teardown(dir);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns false when only routes.rb exists (no Gemfile)', () => {
|
|
44
|
+
const dir = createTmpProject({
|
|
45
|
+
'config/routes.rb': 'Rails.application.routes.draw do end',
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const adapter = new RailsAdapter();
|
|
49
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
50
|
+
} finally {
|
|
51
|
+
teardown(dir);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns false when directory is empty', () => {
|
|
56
|
+
const dir = createTmpProject({});
|
|
57
|
+
try {
|
|
58
|
+
const adapter = new RailsAdapter();
|
|
59
|
+
assert.strictEqual(adapter.detect(dir), false);
|
|
60
|
+
} finally {
|
|
61
|
+
teardown(dir);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('getTools()', () => {
|
|
67
|
+
it('returns array of tool objects with required fields', () => {
|
|
68
|
+
const adapter = new RailsAdapter();
|
|
69
|
+
const tools = adapter.getTools();
|
|
70
|
+
assert.ok(Array.isArray(tools));
|
|
71
|
+
assert.ok(tools.length > 0);
|
|
72
|
+
for (const tool of tools) {
|
|
73
|
+
assert.ok(tool.name);
|
|
74
|
+
assert.ok(tool.language);
|
|
75
|
+
assert.ok(typeof tool.command === 'string');
|
|
76
|
+
assert.ok(tool.description);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('all external tools have language=external and empty path', () => {
|
|
81
|
+
const adapter = new RailsAdapter();
|
|
82
|
+
const tools = adapter.getTools();
|
|
83
|
+
const externalTools = tools.filter((t) => t.language === 'external');
|
|
84
|
+
for (const tool of externalTools) {
|
|
85
|
+
assert.strictEqual(tool.language, 'external');
|
|
86
|
+
assert.strictEqual(tool.path, '');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('includes custom rules guard', () => {
|
|
91
|
+
const adapter = new RailsAdapter();
|
|
92
|
+
const tools = adapter.getTools();
|
|
93
|
+
const customTool = tools.find((t) => t.name === 'check_custom');
|
|
94
|
+
assert.ok(customTool, 'check_custom tool should exist');
|
|
95
|
+
assert.strictEqual(customTool.language, 'js');
|
|
96
|
+
assert.strictEqual(customTool.path, 'check_custom.js');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('getSafeguards()', () => {
|
|
101
|
+
it('returns non-empty array of strings', () => {
|
|
102
|
+
const adapter = new RailsAdapter();
|
|
103
|
+
const guards = adapter.getSafeguards();
|
|
104
|
+
assert.ok(Array.isArray(guards));
|
|
105
|
+
assert.ok(guards.length >= 5);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|