push-guardian 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +15 -0
- package/.pushguardian-plugins.json +10 -0
- package/Dockerfile +41 -0
- package/Dockerfile.dev +20 -0
- package/README.md +386 -0
- package/TECHNO.md +139 -0
- package/babel.config.js +1 -0
- package/developper_utils.md +119 -0
- package/docker-compose.yml +41 -0
- package/docs/PLUGINS.md +223 -0
- package/docs/technical/architecture.md +298 -0
- package/docs/technical/performance-guide.md +390 -0
- package/docs/technical/plugin-persistence.md +169 -0
- package/docs/technical/plugins-guide.md +409 -0
- package/jest.config.js +22 -0
- package/package.json +53 -0
- package/plugins/example-plugin/index.js +55 -0
- package/plugins/example-plugin/plugin.json +8 -0
- package/scripts/coverage-report.js +75 -0
- package/src/cli/command/config.js +33 -0
- package/src/cli/command/install.js +137 -0
- package/src/cli/command/mirror.js +90 -0
- package/src/cli/command/performance.js +160 -0
- package/src/cli/command/plugin.js +171 -0
- package/src/cli/command/security.js +152 -0
- package/src/cli/command/shell.js +238 -0
- package/src/cli/command/validate.js +54 -0
- package/src/cli/index.js +23 -0
- package/src/cli/install/codeQualityTools.js +156 -0
- package/src/cli/install/hooks.js +89 -0
- package/src/cli/install/mirroring.js +299 -0
- package/src/core/codeQualityTools/configAnalyzer.js +216 -0
- package/src/core/codeQualityTools/configGenerator.js +381 -0
- package/src/core/codeQualityTools/configManager.js +65 -0
- package/src/core/codeQualityTools/fileDetector.js +62 -0
- package/src/core/codeQualityTools/languageTools.js +104 -0
- package/src/core/codeQualityTools/toolInstaller.js +53 -0
- package/src/core/configManager.js +43 -0
- package/src/core/errorCMD.js +9 -0
- package/src/core/interactiveMenu/interactiveMenu.js +73 -0
- package/src/core/mirroring/branchSynchronizer.js +59 -0
- package/src/core/mirroring/generate.js +114 -0
- package/src/core/mirroring/repoManager.js +112 -0
- package/src/core/mirroring/syncManager.js +176 -0
- package/src/core/module/env-loader.js +109 -0
- package/src/core/performance/metricsCollector.js +217 -0
- package/src/core/performance/performanceAnalyzer.js +182 -0
- package/src/core/plugins/basePlugin.js +89 -0
- package/src/core/plugins/pluginManager.js +123 -0
- package/src/core/plugins/pluginRegistry.js +215 -0
- package/src/core/validator.js +53 -0
- package/src/hooks/constrains/constrains.js +174 -0
- package/src/hooks/constrains/constraintEngine.js +140 -0
- package/src/utils/chalk-wrapper.js +26 -0
- package/src/utils/exec-wrapper.js +6 -0
- package/tests/fixtures/mock-eslint-config-array.js +8 -0
- package/tests/fixtures/mock-eslint-config-single.js +6 -0
- package/tests/fixtures/mockLoadedPlugin.js +11 -0
- package/tests/setup.js +28 -0
- package/tests/unit/basePlugin.test.js +355 -0
- package/tests/unit/branchSynchronizer.test.js +308 -0
- package/tests/unit/cli-commands.test.js +144 -0
- package/tests/unit/codeQualityConfigManager.test.js +233 -0
- package/tests/unit/codeQualityTools.test.js +36 -0
- package/tests/unit/command-install.test.js +247 -0
- package/tests/unit/command-mirror.test.js +179 -0
- package/tests/unit/command-performance.test.js +169 -0
- package/tests/unit/command-plugin.test.js +288 -0
- package/tests/unit/command-security.test.js +277 -0
- package/tests/unit/command-shell.test.js +325 -0
- package/tests/unit/configAnalyzer.test.js +593 -0
- package/tests/unit/configGenerator.test.js +808 -0
- package/tests/unit/configManager.test.js +195 -0
- package/tests/unit/constrains.test.js +463 -0
- package/tests/unit/constraint.test.js +554 -0
- package/tests/unit/env-loader.test.js +279 -0
- package/tests/unit/fileDetector.test.js +171 -0
- package/tests/unit/install-codeQualityTools.test.js +343 -0
- package/tests/unit/install-hooks.test.js +280 -0
- package/tests/unit/install-mirroring.test.js +731 -0
- package/tests/unit/install-modules.test.js +81 -0
- package/tests/unit/interactiveMenu.test.js +426 -0
- package/tests/unit/languageTools.test.js +244 -0
- package/tests/unit/metricsCollector.test.js +354 -0
- package/tests/unit/mirroring-generate.test.js +96 -0
- package/tests/unit/modules-exist.test.js +96 -0
- package/tests/unit/performanceAnalyzer.test.js +473 -0
- package/tests/unit/pluginManager.test.js +427 -0
- package/tests/unit/pluginRegistry.test.js +592 -0
- package/tests/unit/repoManager.test.js +469 -0
- package/tests/unit/reviewAppManager.test.js +5 -0
- package/tests/unit/security-command.test.js +43 -0
- package/tests/unit/syncManager.test.js +494 -0
- package/tests/unit/toolInstaller.test.js +240 -0
- package/tests/unit/utils.test.js +144 -0
- package/tests/unit/validator.test.js +215 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
jest.mock('../../src/core/configManager');
|
|
2
|
+
jest.mock('../../src/core/mirroring/syncManager');
|
|
3
|
+
jest.mock('../../src/core/mirroring/generate');
|
|
4
|
+
jest.mock('../../src/core/errorCMD');
|
|
5
|
+
jest.mock('../../src/core/module/env-loader');
|
|
6
|
+
jest.mock('@octokit/rest', () => ({
|
|
7
|
+
Octokit: jest.fn()
|
|
8
|
+
}));
|
|
9
|
+
jest.mock('gitlab', () => ({
|
|
10
|
+
Gitlab: jest.fn()
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('bitbucket', () => ({
|
|
13
|
+
Bitbucket: jest.fn()
|
|
14
|
+
}));
|
|
15
|
+
jest.mock('azure-devops-node-api', () => ({
|
|
16
|
+
WebApi: jest.fn()
|
|
17
|
+
}));
|
|
18
|
+
jest.mock('../../src/utils/chalk-wrapper', () => ({
|
|
19
|
+
getChalk: () => ({
|
|
20
|
+
red: (msg) => msg,
|
|
21
|
+
green: (msg) => msg,
|
|
22
|
+
blue: (msg) => msg,
|
|
23
|
+
cyan: (msg) => msg
|
|
24
|
+
})
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const mirrorCommand = require('../../src/cli/command/mirror');
|
|
28
|
+
const { loadConfig } = require('../../src/core/configManager');
|
|
29
|
+
const { SyncManager } = require('../../src/core/mirroring/syncManager');
|
|
30
|
+
const { generateWorkflow } = require('../../src/core/mirroring/generate');
|
|
31
|
+
const { getEnv, loadEnv } = require('../../src/core/module/env-loader');
|
|
32
|
+
const errorCMD = require('../../src/core/errorCMD');
|
|
33
|
+
|
|
34
|
+
describe('CLI Command - mirror', () => {
|
|
35
|
+
let mockSyncManager;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
jest.clearAllMocks();
|
|
39
|
+
console.log = jest.fn();
|
|
40
|
+
console.error = jest.fn();
|
|
41
|
+
jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
42
|
+
|
|
43
|
+
loadEnv.mockImplementation(() => {});
|
|
44
|
+
loadConfig.mockReturnValue({
|
|
45
|
+
mirroring: {
|
|
46
|
+
platforms: {
|
|
47
|
+
github: { enabled: true },
|
|
48
|
+
gitlab: { enabled: true }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
mockSyncManager = {
|
|
54
|
+
mirror: jest.fn().mockResolvedValue()
|
|
55
|
+
};
|
|
56
|
+
SyncManager.mockImplementation(() => mockSyncManager);
|
|
57
|
+
|
|
58
|
+
getEnv.mockReturnValue('');
|
|
59
|
+
generateWorkflow.mockImplementation(() => {});
|
|
60
|
+
errorCMD.mockImplementation(() => {});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
jest.restoreAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('doit avoir les bonnes métadonnées', () => {
|
|
68
|
+
expect(mirrorCommand.name).toBe('mirror');
|
|
69
|
+
expect(mirrorCommand.description).toContain('Référentiels miroirs');
|
|
70
|
+
expect(mirrorCommand.options).toHaveLength(10);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('doit générer workflow avec --generate', async () => {
|
|
74
|
+
await mirrorCommand.action({ generate: true });
|
|
75
|
+
|
|
76
|
+
expect(generateWorkflow).toHaveBeenCalled();
|
|
77
|
+
expect(SyncManager).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('doit quitter si config mirroring manquante', async () => {
|
|
81
|
+
loadConfig.mockReturnValue({});
|
|
82
|
+
|
|
83
|
+
await mirrorCommand.action({ source: 'github' });
|
|
84
|
+
|
|
85
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
86
|
+
expect.stringContaining('Configuration de mise en miroir manquante')
|
|
87
|
+
);
|
|
88
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('doit quitter si paramètres manquants', async () => {
|
|
92
|
+
await mirrorCommand.action({ source: 'github' });
|
|
93
|
+
|
|
94
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
95
|
+
expect.stringContaining('Plateformes, repos et propriétaires source/cible requis')
|
|
96
|
+
);
|
|
97
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('doit exécuter mirror avec options complètes', async () => {
|
|
101
|
+
await mirrorCommand.action({
|
|
102
|
+
source: 'github',
|
|
103
|
+
target: 'gitlab',
|
|
104
|
+
sourceRepo: 'my-repo',
|
|
105
|
+
targetRepo: 'my-mirror',
|
|
106
|
+
sourceOwner: 'user1',
|
|
107
|
+
targetOwner: 'user2',
|
|
108
|
+
syncBranches: true,
|
|
109
|
+
publicRepo: true
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(SyncManager).toHaveBeenCalledWith({
|
|
113
|
+
github: { enabled: true },
|
|
114
|
+
gitlab: { enabled: true }
|
|
115
|
+
});
|
|
116
|
+
expect(mockSyncManager.mirror).toHaveBeenCalledWith(
|
|
117
|
+
'github',
|
|
118
|
+
'gitlab',
|
|
119
|
+
'my-repo',
|
|
120
|
+
'my-mirror',
|
|
121
|
+
'user1',
|
|
122
|
+
'user2',
|
|
123
|
+
true,
|
|
124
|
+
true
|
|
125
|
+
);
|
|
126
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
127
|
+
expect.stringContaining('Mise en miroir terminée avec succès')
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('doit charger valeurs depuis env si non spécifiées', async () => {
|
|
132
|
+
getEnv.mockImplementation((key) => {
|
|
133
|
+
const envVars = {
|
|
134
|
+
'SOURCE_PLATFORM': 'github',
|
|
135
|
+
'TARGET_PLATFORM': 'gitlab',
|
|
136
|
+
'SOURCE_REPO': 'env-repo',
|
|
137
|
+
'TARGET_REPO': 'env-mirror',
|
|
138
|
+
'SOURCE_OWNER': 'env-user1',
|
|
139
|
+
'TARGET_OWNER': 'env-user2'
|
|
140
|
+
};
|
|
141
|
+
return envVars[key] || '';
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await mirrorCommand.action({});
|
|
145
|
+
|
|
146
|
+
expect(mockSyncManager.mirror).toHaveBeenCalledWith(
|
|
147
|
+
'github',
|
|
148
|
+
'gitlab',
|
|
149
|
+
'env-repo',
|
|
150
|
+
'env-mirror',
|
|
151
|
+
'env-user1',
|
|
152
|
+
'env-user2',
|
|
153
|
+
undefined,
|
|
154
|
+
undefined
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('doit appeler errorCMD en cas d\'erreur', async () => {
|
|
159
|
+
const testError = new Error('Mirror failed');
|
|
160
|
+
mockSyncManager.mirror.mockRejectedValue(testError);
|
|
161
|
+
|
|
162
|
+
await mirrorCommand.action({
|
|
163
|
+
source: 'github',
|
|
164
|
+
target: 'gitlab',
|
|
165
|
+
sourceRepo: 'repo',
|
|
166
|
+
targetRepo: 'mirror',
|
|
167
|
+
sourceOwner: 'user1',
|
|
168
|
+
targetOwner: 'user2'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(errorCMD).toHaveBeenCalledWith(testError);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('doit appeler loadEnv au démarrage', async () => {
|
|
175
|
+
await mirrorCommand.action({ generate: true });
|
|
176
|
+
|
|
177
|
+
expect(loadEnv).toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
jest.mock('../../src/core/performance/performanceAnalyzer');
|
|
2
|
+
jest.mock('../../src/core/performance/metricsCollector');
|
|
3
|
+
|
|
4
|
+
const performanceCommand = require('../../src/cli/command/performance');
|
|
5
|
+
const performanceAnalyzer = require('../../src/core/performance/performanceAnalyzer');
|
|
6
|
+
const metricsCollector = require('../../src/core/performance/metricsCollector');
|
|
7
|
+
|
|
8
|
+
describe('CLI Command - performance', () => {
|
|
9
|
+
let consoleLogSpy;
|
|
10
|
+
let processExitSpy;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
14
|
+
processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
consoleLogSpy.mockRestore();
|
|
20
|
+
processExitSpy.mockRestore();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('doit avoir les bonnes métadonnées', () => {
|
|
24
|
+
expect(performanceCommand.name).toBe('performance');
|
|
25
|
+
expect(performanceCommand.description).toBeDefined();
|
|
26
|
+
expect(performanceCommand.options).toBeDefined();
|
|
27
|
+
expect(performanceCommand.action).toBeInstanceOf(Function);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('doit avoir les bonnes options', () => {
|
|
31
|
+
expect(performanceCommand.options).toHaveLength(3);
|
|
32
|
+
expect(performanceCommand.options[0].flags).toContain('--analyze');
|
|
33
|
+
expect(performanceCommand.options[1].flags).toContain('--compare');
|
|
34
|
+
expect(performanceCommand.options[2].flags).toContain('--reset');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('action reset', () => {
|
|
38
|
+
test('doit réinitialiser les métriques avec --reset', async () => {
|
|
39
|
+
await performanceCommand.action({ reset: true });
|
|
40
|
+
|
|
41
|
+
expect(metricsCollector.reset).toHaveBeenCalled();
|
|
42
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('réinitialisées'));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('ne doit pas appeler analyze ou compare avec --reset', async () => {
|
|
46
|
+
await performanceCommand.action({ reset: true });
|
|
47
|
+
|
|
48
|
+
expect(performanceAnalyzer.analyze).not.toHaveBeenCalled();
|
|
49
|
+
expect(performanceAnalyzer.compare).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('action compare', () => {
|
|
54
|
+
test('doit comparer avec rapport précédent avec --compare', async () => {
|
|
55
|
+
const reportPath = '/path/to/report.json';
|
|
56
|
+
await performanceCommand.action({ compare: reportPath });
|
|
57
|
+
|
|
58
|
+
expect(performanceAnalyzer.compare).toHaveBeenCalledWith(reportPath);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('ne doit pas appeler analyze ou reset avec --compare', async () => {
|
|
62
|
+
await performanceCommand.action({ compare: '/path/to/report.json' });
|
|
63
|
+
|
|
64
|
+
expect(performanceAnalyzer.analyze).not.toHaveBeenCalled();
|
|
65
|
+
expect(metricsCollector.reset).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('action analyze', () => {
|
|
70
|
+
test('doit analyser les performances avec --analyze', async () => {
|
|
71
|
+
performanceAnalyzer.analyze.mockResolvedValue({});
|
|
72
|
+
await performanceCommand.action({ analyze: true });
|
|
73
|
+
|
|
74
|
+
expect(performanceAnalyzer.analyze).toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('ne doit pas appeler compare ou reset avec --analyze', async () => {
|
|
78
|
+
performanceAnalyzer.analyze.mockResolvedValue({});
|
|
79
|
+
await performanceCommand.action({ analyze: true });
|
|
80
|
+
|
|
81
|
+
expect(performanceAnalyzer.compare).not.toHaveBeenCalled();
|
|
82
|
+
expect(metricsCollector.reset).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('sans options', () => {
|
|
87
|
+
test('doit afficher message d\'aide si aucune option', async () => {
|
|
88
|
+
await performanceCommand.action({});
|
|
89
|
+
|
|
90
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
91
|
+
expect.stringContaining('--help')
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('ne doit appeler aucune action si aucune option', async () => {
|
|
96
|
+
await performanceCommand.action({});
|
|
97
|
+
|
|
98
|
+
expect(performanceAnalyzer.analyze).not.toHaveBeenCalled();
|
|
99
|
+
expect(performanceAnalyzer.compare).not.toHaveBeenCalled();
|
|
100
|
+
expect(metricsCollector.reset).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('gestion des erreurs', () => {
|
|
105
|
+
test('doit gérer erreur lors de analyze', async () => {
|
|
106
|
+
performanceAnalyzer.analyze.mockRejectedValue(new Error('Analyze failed'));
|
|
107
|
+
|
|
108
|
+
await performanceCommand.action({ analyze: true });
|
|
109
|
+
|
|
110
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
111
|
+
expect.stringContaining('Erreur'),
|
|
112
|
+
expect.stringContaining('Analyze failed')
|
|
113
|
+
);
|
|
114
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('doit gérer erreur lors de compare', async () => {
|
|
118
|
+
performanceAnalyzer.compare.mockImplementation(() => {
|
|
119
|
+
throw new Error('Compare failed');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await performanceCommand.action({ compare: '/path/to/report.json' });
|
|
123
|
+
|
|
124
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
125
|
+
expect.stringContaining('Erreur'),
|
|
126
|
+
expect.stringContaining('Compare failed')
|
|
127
|
+
);
|
|
128
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('doit gérer erreur lors de reset', async () => {
|
|
132
|
+
metricsCollector.reset.mockImplementation(() => {
|
|
133
|
+
throw new Error('Reset failed');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await performanceCommand.action({ reset: true });
|
|
137
|
+
|
|
138
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
139
|
+
expect.stringContaining('Erreur'),
|
|
140
|
+
expect.stringContaining('Reset failed')
|
|
141
|
+
);
|
|
142
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('priorité des options', () => {
|
|
147
|
+
test('reset doit avoir priorité sur compare', async () => {
|
|
148
|
+
await performanceCommand.action({ reset: true, compare: '/path/to/report.json' });
|
|
149
|
+
|
|
150
|
+
expect(metricsCollector.reset).toHaveBeenCalled();
|
|
151
|
+
expect(performanceAnalyzer.compare).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('reset doit avoir priorité sur analyze', async () => {
|
|
155
|
+
await performanceCommand.action({ reset: true, analyze: true });
|
|
156
|
+
|
|
157
|
+
expect(metricsCollector.reset).toHaveBeenCalled();
|
|
158
|
+
expect(performanceAnalyzer.analyze).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('compare doit avoir priorité sur analyze', async () => {
|
|
162
|
+
const reportPath = '/path/to/report.json';
|
|
163
|
+
await performanceCommand.action({ compare: reportPath, analyze: true });
|
|
164
|
+
|
|
165
|
+
expect(performanceAnalyzer.compare).toHaveBeenCalledWith(reportPath);
|
|
166
|
+
expect(performanceAnalyzer.analyze).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
jest.mock('../../src/core/plugins/pluginRegistry');
|
|
2
|
+
jest.mock('../../src/core/plugins/pluginManager');
|
|
3
|
+
|
|
4
|
+
const pluginCommand = require('../../src/cli/command/plugin');
|
|
5
|
+
const pluginRegistry = require('../../src/core/plugins/pluginRegistry');
|
|
6
|
+
const pluginManager = require('../../src/core/plugins/pluginManager');
|
|
7
|
+
|
|
8
|
+
describe('CLI Command - plugin', () => {
|
|
9
|
+
let consoleLogSpy;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
pluginRegistry.getPluginInfo.mockReturnValue(null);
|
|
15
|
+
pluginManager.listCommands.mockReturnValue([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
consoleLogSpy.mockRestore();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('doit avoir les bonnes métadonnées', () => {
|
|
23
|
+
expect(pluginCommand.name).toBe('plugin');
|
|
24
|
+
expect(pluginCommand.description).toBeDefined();
|
|
25
|
+
expect(pluginCommand.arguments).toBeDefined();
|
|
26
|
+
expect(pluginCommand.action).toBeInstanceOf(Function);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('doit avoir les bons arguments', () => {
|
|
30
|
+
expect(pluginCommand.arguments).toHaveLength(2);
|
|
31
|
+
expect(pluginCommand.arguments[0].name).toBe('<action>');
|
|
32
|
+
expect(pluginCommand.arguments[1].name).toBe('[args...]');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('action enable', () => {
|
|
36
|
+
test('doit activer un plugin avec nom valide', async () => {
|
|
37
|
+
await pluginCommand.action('enable', ['test-plugin'], {});
|
|
38
|
+
|
|
39
|
+
expect(pluginRegistry.enablePlugin).toHaveBeenCalledWith('test-plugin');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('doit afficher erreur si nom de plugin manquant', async () => {
|
|
43
|
+
await pluginCommand.action('enable', [], {});
|
|
44
|
+
|
|
45
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('requis'));
|
|
46
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Usage'));
|
|
47
|
+
expect(pluginRegistry.enablePlugin).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('doit afficher usage pour enable sans nom', async () => {
|
|
51
|
+
await pluginCommand.action('enable', [], {});
|
|
52
|
+
|
|
53
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
54
|
+
expect.stringContaining('push-guardian plugin enable <nom>')
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('action disable', () => {
|
|
60
|
+
test('doit désactiver un plugin avec nom valide', async () => {
|
|
61
|
+
await pluginCommand.action('disable', ['test-plugin'], {});
|
|
62
|
+
|
|
63
|
+
expect(pluginRegistry.disablePlugin).toHaveBeenCalledWith('test-plugin');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('doit afficher erreur si nom de plugin manquant', async () => {
|
|
67
|
+
await pluginCommand.action('disable', [], {});
|
|
68
|
+
|
|
69
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('requis'));
|
|
70
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Usage'));
|
|
71
|
+
expect(pluginRegistry.disablePlugin).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('doit afficher usage pour disable sans nom', async () => {
|
|
75
|
+
await pluginCommand.action('disable', [], {});
|
|
76
|
+
|
|
77
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
78
|
+
expect.stringContaining('push-guardian plugin disable <nom>')
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('action list', () => {
|
|
84
|
+
test('doit lister les plugins installés', async () => {
|
|
85
|
+
await pluginCommand.action('list', [], {});
|
|
86
|
+
|
|
87
|
+
expect(pluginManager.listPlugins).toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('doit fonctionner sans arguments supplémentaires', async () => {
|
|
91
|
+
await pluginCommand.action('list', [], {});
|
|
92
|
+
|
|
93
|
+
expect(pluginManager.listPlugins).toHaveBeenCalledWith();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('action commands', () => {
|
|
98
|
+
test('doit lister les commandes disponibles', async () => {
|
|
99
|
+
await pluginCommand.action('commands', [], {});
|
|
100
|
+
|
|
101
|
+
expect(pluginManager.displayCommands).toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('doit fonctionner sans arguments supplémentaires', async () => {
|
|
105
|
+
await pluginCommand.action('commands', [], {});
|
|
106
|
+
|
|
107
|
+
expect(pluginManager.displayCommands).toHaveBeenCalledWith();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('action run', () => {
|
|
112
|
+
test('doit exécuter une commande de plugin', async () => {
|
|
113
|
+
pluginManager.executeCommand.mockResolvedValue('result');
|
|
114
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
115
|
+
|
|
116
|
+
await pluginCommand.action('run', ['test-plugin', 'test-command'], {});
|
|
117
|
+
|
|
118
|
+
expect(pluginManager.executeCommand).toHaveBeenCalledWith(
|
|
119
|
+
'test-plugin',
|
|
120
|
+
'test-command',
|
|
121
|
+
[],
|
|
122
|
+
{}
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('doit passer les arguments à la commande', async () => {
|
|
127
|
+
pluginManager.executeCommand.mockResolvedValue(undefined);
|
|
128
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
129
|
+
|
|
130
|
+
await pluginCommand.action('run', ['test-plugin', 'test-command', 'arg1', 'arg2'], { opt: true });
|
|
131
|
+
|
|
132
|
+
expect(pluginManager.executeCommand).toHaveBeenCalledWith(
|
|
133
|
+
'test-plugin',
|
|
134
|
+
'test-command',
|
|
135
|
+
['arg1', 'arg2'],
|
|
136
|
+
{ opt: true }
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('doit afficher le résultat si défini', async () => {
|
|
141
|
+
pluginManager.executeCommand.mockResolvedValue('Success result');
|
|
142
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
143
|
+
|
|
144
|
+
await pluginCommand.action('run', ['test-plugin', 'test-command'], {});
|
|
145
|
+
|
|
146
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Success result');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('ne doit pas afficher undefined', async () => {
|
|
150
|
+
pluginManager.executeCommand.mockResolvedValue(undefined);
|
|
151
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
152
|
+
|
|
153
|
+
await pluginCommand.action('run', ['test-plugin', 'test-command'], {});
|
|
154
|
+
|
|
155
|
+
expect(consoleLogSpy).not.toHaveBeenCalledWith(undefined);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('doit afficher erreur si plugin manquant', async () => {
|
|
159
|
+
await pluginCommand.action('run', [], {});
|
|
160
|
+
|
|
161
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('requis'));
|
|
162
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
163
|
+
expect.stringContaining('push-guardian plugin run <commande>')
|
|
164
|
+
);
|
|
165
|
+
expect(pluginManager.executeCommand).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('doit résoudre automatiquement une commande unique', async () => {
|
|
169
|
+
pluginManager.listCommands.mockReturnValue([
|
|
170
|
+
{ plugin: 'example-plugin', command: 'hello', description: 'hello', args: [] }
|
|
171
|
+
]);
|
|
172
|
+
pluginManager.executeCommand.mockResolvedValue(undefined);
|
|
173
|
+
|
|
174
|
+
await pluginCommand.action('run', ['hello'], {});
|
|
175
|
+
|
|
176
|
+
expect(pluginManager.executeCommand).toHaveBeenCalledWith('example-plugin', 'hello', [], {});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('doit passer les args avec la syntaxe run <commande> [args...]', async () => {
|
|
180
|
+
pluginManager.listCommands.mockReturnValue([
|
|
181
|
+
{ plugin: 'example-plugin', command: 'hello', description: 'hello', args: [] }
|
|
182
|
+
]);
|
|
183
|
+
pluginManager.executeCommand.mockResolvedValue(undefined);
|
|
184
|
+
|
|
185
|
+
await pluginCommand.action('run', ['hello', 'a', 'b'], { flag: true });
|
|
186
|
+
|
|
187
|
+
expect(pluginManager.executeCommand).toHaveBeenCalledWith('example-plugin', 'hello', ['a', 'b'], {
|
|
188
|
+
flag: true
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('doit afficher erreur si commande introuvable en mode implicite', async () => {
|
|
193
|
+
pluginManager.listCommands.mockReturnValue([]);
|
|
194
|
+
|
|
195
|
+
await pluginCommand.action('run', ['unknown-cmd'], {});
|
|
196
|
+
|
|
197
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('introuvable'));
|
|
198
|
+
expect(pluginManager.executeCommand).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('doit afficher erreur si commande ambiguë en mode implicite', async () => {
|
|
202
|
+
pluginManager.listCommands.mockReturnValue([
|
|
203
|
+
{ plugin: 'plugin-a', command: 'hello', description: '', args: [] },
|
|
204
|
+
{ plugin: 'plugin-b', command: 'hello', description: '', args: [] }
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
await pluginCommand.action('run', ['hello'], {});
|
|
208
|
+
|
|
209
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('ambigu'));
|
|
210
|
+
expect(pluginManager.executeCommand).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('doit gérer erreur d\'exécution', async () => {
|
|
214
|
+
pluginManager.executeCommand.mockRejectedValue(new Error('Execution failed'));
|
|
215
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
216
|
+
|
|
217
|
+
await pluginCommand.action('run', ['test-plugin', 'test-command'], {});
|
|
218
|
+
|
|
219
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
220
|
+
expect.stringContaining('Erreur'),
|
|
221
|
+
expect.stringContaining('Execution failed')
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('action inconnue', () => {
|
|
227
|
+
test('doit afficher erreur pour action inconnue', async () => {
|
|
228
|
+
await pluginCommand.action('unknown', [], {});
|
|
229
|
+
|
|
230
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
231
|
+
expect.stringContaining('Action inconnue: unknown')
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('doit afficher aide pour action inconnue', async () => {
|
|
236
|
+
await pluginCommand.action('invalid-action', [], {});
|
|
237
|
+
|
|
238
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
239
|
+
expect.stringContaining('Gestion des plugins')
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('sans action', () => {
|
|
245
|
+
test('doit afficher aide si aucune action', async () => {
|
|
246
|
+
await pluginCommand.action(undefined, [], {});
|
|
247
|
+
|
|
248
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
249
|
+
expect.stringContaining('Gestion des plugins')
|
|
250
|
+
);
|
|
251
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('enable'));
|
|
252
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('disable'));
|
|
253
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('list'));
|
|
254
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('commands'));
|
|
255
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('run'));
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('intégration avec args array', () => {
|
|
260
|
+
test('doit gérer correctement args vide', async () => {
|
|
261
|
+
pluginManager.executeCommand.mockResolvedValue(undefined);
|
|
262
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
263
|
+
|
|
264
|
+
await pluginCommand.action('run', ['plugin', 'cmd'], {});
|
|
265
|
+
|
|
266
|
+
expect(pluginManager.executeCommand).toHaveBeenCalledWith(
|
|
267
|
+
'plugin',
|
|
268
|
+
'cmd',
|
|
269
|
+
[],
|
|
270
|
+
{}
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('doit gérer plusieurs args', async () => {
|
|
275
|
+
pluginManager.executeCommand.mockResolvedValue(undefined);
|
|
276
|
+
pluginRegistry.getPluginInfo.mockReturnValue({ enabled: true });
|
|
277
|
+
|
|
278
|
+
await pluginCommand.action('run', ['plugin', 'cmd', 'a', 'b', 'c'], {});
|
|
279
|
+
|
|
280
|
+
expect(pluginManager.executeCommand).toHaveBeenCalledWith(
|
|
281
|
+
'plugin',
|
|
282
|
+
'cmd',
|
|
283
|
+
['a', 'b', 'c'],
|
|
284
|
+
{}
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
});
|