kitstore-cli 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/.env.test +4 -0
- package/.eslintrc.js +29 -0
- package/dist/__tests__/commands/init.test.js +76 -0
- package/dist/__tests__/commands/install.test.js +422 -0
- package/dist/__tests__/commands/list.test.js +173 -0
- package/dist/__tests__/commands/login.test.js +281 -0
- package/dist/__tests__/commands/rule-check.test.js +72 -0
- package/dist/__tests__/commands/search.test.js +175 -0
- package/dist/__tests__/commands/upload.test.js +367 -0
- package/dist/__tests__/config.test.js +179 -0
- package/dist/__tests__/setup.js +8 -0
- package/dist/api/client.js +18 -0
- package/dist/api/generated/api.js +912 -0
- package/dist/api/generated/base.js +48 -0
- package/dist/api/generated/common.js +108 -0
- package/dist/api/generated/configuration.js +48 -0
- package/dist/api/generated/index.js +31 -0
- package/dist/commands/init.js +79 -0
- package/dist/commands/install.js +150 -0
- package/dist/commands/list.js +70 -0
- package/dist/commands/login.js +64 -0
- package/dist/commands/rule-check.js +81 -0
- package/dist/commands/search.js +59 -0
- package/dist/commands/upload.js +138 -0
- package/dist/config.js +84 -0
- package/dist/index.js +71 -0
- package/e2e/install.e2e.test.ts +237 -0
- package/e2e/integration.e2e.test.ts +346 -0
- package/e2e/login.e2e.test.ts +188 -0
- package/jest.config.js +24 -0
- package/openapitools.json +7 -0
- package/package.json +41 -0
- package/src/__tests__/commands/init.test.ts +52 -0
- package/src/__tests__/commands/install.test.ts +449 -0
- package/src/__tests__/commands/list.test.ts +164 -0
- package/src/__tests__/commands/login.test.ts +293 -0
- package/src/__tests__/commands/rule-check.test.ts +52 -0
- package/src/__tests__/commands/search.test.ts +168 -0
- package/src/__tests__/commands/upload.test.ts +404 -0
- package/src/__tests__/config.test.ts +181 -0
- package/src/__tests__/setup.ts +11 -0
- package/src/api/client.ts +20 -0
- package/src/api/generated/.openapi-generator/FILES +17 -0
- package/src/api/generated/.openapi-generator/VERSION +1 -0
- package/src/api/generated/.openapi-generator-ignore +23 -0
- package/src/api/generated/api.ts +1171 -0
- package/src/api/generated/base.ts +62 -0
- package/src/api/generated/common.ts +113 -0
- package/src/api/generated/configuration.ts +121 -0
- package/src/api/generated/docs/AuthApi.md +158 -0
- package/src/api/generated/docs/AuthResponseDto.md +22 -0
- package/src/api/generated/docs/AuthUserDto.md +24 -0
- package/src/api/generated/docs/HealthApi.md +183 -0
- package/src/api/generated/docs/LoginDto.md +22 -0
- package/src/api/generated/docs/RegisterDto.md +24 -0
- package/src/api/generated/docs/RuleAuthorDto.md +22 -0
- package/src/api/generated/docs/RuleResponseDto.md +36 -0
- package/src/api/generated/docs/RulesApi.md +289 -0
- package/src/api/generated/git_push.sh +57 -0
- package/src/api/generated/index.ts +18 -0
- package/src/commands/init.ts +46 -0
- package/src/commands/install.ts +129 -0
- package/src/commands/list.ts +71 -0
- package/src/commands/login.ts +65 -0
- package/src/commands/rule-check.ts +49 -0
- package/src/commands/search.ts +66 -0
- package/src/commands/upload.ts +117 -0
- package/src/config.ts +66 -0
- package/src/index.ts +79 -0
- package/test-cli-config.js +118 -0
- package/tsconfig.json +24 -0
package/.env.test
ADDED
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
parserOptions: {
|
|
4
|
+
project: 'tsconfig.json',
|
|
5
|
+
tsconfigRootDir: __dirname,
|
|
6
|
+
sourceType: 'module',
|
|
7
|
+
},
|
|
8
|
+
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
9
|
+
extends: [
|
|
10
|
+
'eslint:recommended',
|
|
11
|
+
'@typescript-eslint/recommended',
|
|
12
|
+
],
|
|
13
|
+
root: true,
|
|
14
|
+
env: {
|
|
15
|
+
node: true,
|
|
16
|
+
jest: true,
|
|
17
|
+
},
|
|
18
|
+
ignorePatterns: ['.eslintrc.js', 'dist'],
|
|
19
|
+
rules: {
|
|
20
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
21
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
22
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
23
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
24
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const init_1 = require("../../commands/init");
|
|
40
|
+
const fs = __importStar(require("fs-extra"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
43
|
+
jest.mock('fs-extra');
|
|
44
|
+
jest.mock('inquirer');
|
|
45
|
+
describe('Init Command', () => {
|
|
46
|
+
const cwd = process.cwd();
|
|
47
|
+
const cursorDir = path.join(cwd, '.cursor');
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
51
|
+
jest.spyOn(console, 'error').mockImplementation();
|
|
52
|
+
});
|
|
53
|
+
it('should initialize cursor kit directories', async () => {
|
|
54
|
+
fs.pathExists.mockResolvedValue(false);
|
|
55
|
+
fs.ensureDir.mockResolvedValue(undefined);
|
|
56
|
+
await (0, init_1.initCommand)();
|
|
57
|
+
expect(fs.ensureDir).toHaveBeenCalledWith(path.join(cursorDir, 'rules'));
|
|
58
|
+
expect(fs.ensureDir).toHaveBeenCalledWith(path.join(cursorDir, 'commands'));
|
|
59
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Successfully initialized Cursor Kit!'));
|
|
60
|
+
});
|
|
61
|
+
it('should ask for confirmation if .cursor exists', async () => {
|
|
62
|
+
fs.pathExists.mockResolvedValue(true);
|
|
63
|
+
inquirer_1.default.prompt.mockResolvedValue({ overwrite: true });
|
|
64
|
+
fs.ensureDir.mockResolvedValue(undefined);
|
|
65
|
+
await (0, init_1.initCommand)();
|
|
66
|
+
expect(inquirer_1.default.prompt).toHaveBeenCalled();
|
|
67
|
+
expect(fs.ensureDir).toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
it('should cancel if user chooses not to overwrite', async () => {
|
|
70
|
+
fs.pathExists.mockResolvedValue(true);
|
|
71
|
+
inquirer_1.default.prompt.mockResolvedValue({ overwrite: false });
|
|
72
|
+
await (0, init_1.initCommand)();
|
|
73
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Initialization cancelled.'));
|
|
74
|
+
expect(fs.ensureDir).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const install_1 = require("../../commands/install");
|
|
40
|
+
const config = __importStar(require("../../config"));
|
|
41
|
+
const apiClient = __importStar(require("../../api/client"));
|
|
42
|
+
const axios_1 = __importDefault(require("axios"));
|
|
43
|
+
const fs = __importStar(require("fs-extra"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
// Mock dependencies
|
|
46
|
+
jest.mock('../../config', () => ({
|
|
47
|
+
getConfig: jest.fn(),
|
|
48
|
+
getInstallPaths: jest.fn(),
|
|
49
|
+
}));
|
|
50
|
+
jest.mock('../../api/client', () => ({
|
|
51
|
+
createApi: jest.fn(),
|
|
52
|
+
}));
|
|
53
|
+
jest.mock('axios');
|
|
54
|
+
jest.mock('fs-extra', () => ({
|
|
55
|
+
pathExists: jest.fn(),
|
|
56
|
+
ensureDir: jest.fn(),
|
|
57
|
+
writeFile: jest.fn(),
|
|
58
|
+
createReadStream: jest.fn(),
|
|
59
|
+
}));
|
|
60
|
+
jest.mock('inquirer', () => ({
|
|
61
|
+
prompt: jest.fn(),
|
|
62
|
+
}));
|
|
63
|
+
// TC-UNIT-CLI-003: Install command (success)
|
|
64
|
+
describe('Install Command', () => {
|
|
65
|
+
const mockGetConfig = config.getConfig;
|
|
66
|
+
const mockGetInstallPaths = config.getInstallPaths;
|
|
67
|
+
const mockCreateApi = apiClient.createApi;
|
|
68
|
+
const mockAxiosGet = axios_1.default.get;
|
|
69
|
+
const mockFsEnsureDir = fs.ensureDir;
|
|
70
|
+
const mockFsPathExists = fs.pathExists;
|
|
71
|
+
const mockFsWriteFile = fs.writeFile;
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
jest.clearAllMocks();
|
|
74
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
75
|
+
jest.spyOn(console, 'error').mockImplementation();
|
|
76
|
+
// Re-establish process.exit mock for each test
|
|
77
|
+
jest.spyOn(process, 'exit').mockImplementation((code) => {
|
|
78
|
+
throw new Error(`Process exit with code ${code}`);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
afterEach(() => {
|
|
82
|
+
jest.restoreAllMocks();
|
|
83
|
+
});
|
|
84
|
+
it('should install rule successfully', async () => {
|
|
85
|
+
mockGetConfig.mockResolvedValue({
|
|
86
|
+
server: 'http://localhost:3000',
|
|
87
|
+
token: 'test-token',
|
|
88
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
89
|
+
lastLogin: new Date().toISOString(),
|
|
90
|
+
});
|
|
91
|
+
mockGetInstallPaths.mockReturnValue({
|
|
92
|
+
rules: '/path/to/rules',
|
|
93
|
+
commands: '/path/to/commands',
|
|
94
|
+
});
|
|
95
|
+
const mockRulesApi = {
|
|
96
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
97
|
+
data: [
|
|
98
|
+
{
|
|
99
|
+
id: 'rule-id-1',
|
|
100
|
+
name: 'Test Rule',
|
|
101
|
+
tag: 'test',
|
|
102
|
+
type: 'rule',
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}),
|
|
106
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
107
|
+
data: {
|
|
108
|
+
downloadUrl: 'https://signed-url.com/file',
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
mockCreateApi.mockResolvedValue({
|
|
113
|
+
authApi: {},
|
|
114
|
+
rulesApi: mockRulesApi,
|
|
115
|
+
});
|
|
116
|
+
mockAxiosGet.mockResolvedValue({
|
|
117
|
+
data: 'rule content',
|
|
118
|
+
});
|
|
119
|
+
mockFsPathExists.mockResolvedValue(false);
|
|
120
|
+
mockFsEnsureDir.mockResolvedValue(undefined);
|
|
121
|
+
mockFsWriteFile.mockResolvedValue(undefined);
|
|
122
|
+
// Mock inquirer when multiple rules exist
|
|
123
|
+
const mockInquirer = require('inquirer');
|
|
124
|
+
mockInquirer.prompt = jest.fn().mockResolvedValue({ rule: 'rule-id-1' });
|
|
125
|
+
await (0, install_1.installCommand)({
|
|
126
|
+
tag: 'test',
|
|
127
|
+
type: 'rule',
|
|
128
|
+
});
|
|
129
|
+
expect(mockRulesApi.rulesControllerFindAll).toHaveBeenCalled();
|
|
130
|
+
expect(mockRulesApi.rulesControllerGetDownloadUrl).toHaveBeenCalledWith('rule-id-1');
|
|
131
|
+
expect(mockAxiosGet).toHaveBeenCalledWith('https://signed-url.com/file', {
|
|
132
|
+
responseType: 'text',
|
|
133
|
+
});
|
|
134
|
+
expect(mockFsWriteFile).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
// TC-UNIT-CLI-026: Install with tag filtering
|
|
137
|
+
it('should filter rules by tag', async () => {
|
|
138
|
+
mockGetConfig.mockResolvedValue({
|
|
139
|
+
server: 'http://localhost:3000',
|
|
140
|
+
token: 'test-token',
|
|
141
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
142
|
+
lastLogin: new Date().toISOString(),
|
|
143
|
+
});
|
|
144
|
+
mockGetInstallPaths.mockReturnValue({
|
|
145
|
+
rules: '/path/to/rules',
|
|
146
|
+
commands: '/path/to/commands',
|
|
147
|
+
});
|
|
148
|
+
const mockRulesApi = {
|
|
149
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
150
|
+
data: [
|
|
151
|
+
{
|
|
152
|
+
id: 'rule-id-1',
|
|
153
|
+
name: 'Test Rule',
|
|
154
|
+
tag: 'test',
|
|
155
|
+
type: 'rule',
|
|
156
|
+
version: '1.0.0',
|
|
157
|
+
author: { username: 'testuser' },
|
|
158
|
+
description: 'A test rule',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
}),
|
|
162
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
163
|
+
data: { downloadUrl: 'https://signed-url.com/file' },
|
|
164
|
+
}),
|
|
165
|
+
};
|
|
166
|
+
mockCreateApi.mockResolvedValue({
|
|
167
|
+
authApi: {},
|
|
168
|
+
rulesApi: mockRulesApi,
|
|
169
|
+
});
|
|
170
|
+
mockAxiosGet.mockResolvedValue({ data: 'rule content' });
|
|
171
|
+
mockFsPathExists.mockResolvedValue(false);
|
|
172
|
+
mockFsEnsureDir.mockResolvedValue(undefined);
|
|
173
|
+
mockFsWriteFile.mockResolvedValue(undefined);
|
|
174
|
+
await (0, install_1.installCommand)({ tag: 'test' });
|
|
175
|
+
expect(mockRulesApi.rulesControllerFindAll).toHaveBeenCalledWith({ tag: 'test' });
|
|
176
|
+
});
|
|
177
|
+
// TC-UNIT-CLI-027: Install with type filtering
|
|
178
|
+
it('should filter rules by type', async () => {
|
|
179
|
+
mockGetConfig.mockResolvedValue({
|
|
180
|
+
server: 'http://localhost:3000',
|
|
181
|
+
token: 'test-token',
|
|
182
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
183
|
+
lastLogin: new Date().toISOString(),
|
|
184
|
+
});
|
|
185
|
+
mockGetInstallPaths.mockReturnValue({
|
|
186
|
+
rules: '/path/to/rules',
|
|
187
|
+
commands: '/path/to/commands',
|
|
188
|
+
});
|
|
189
|
+
const mockRulesApi = {
|
|
190
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
191
|
+
data: [
|
|
192
|
+
{
|
|
193
|
+
id: 'rule-id-1',
|
|
194
|
+
name: 'Test Rule',
|
|
195
|
+
tag: 'test',
|
|
196
|
+
type: 'rule',
|
|
197
|
+
version: '1.0.0',
|
|
198
|
+
author: { username: 'testuser' },
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
}),
|
|
202
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
203
|
+
data: { downloadUrl: 'https://signed-url.com/file' },
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
206
|
+
mockCreateApi.mockResolvedValue({
|
|
207
|
+
authApi: {},
|
|
208
|
+
rulesApi: mockRulesApi,
|
|
209
|
+
});
|
|
210
|
+
mockAxiosGet.mockResolvedValue({ data: 'rule content' });
|
|
211
|
+
mockFsPathExists.mockResolvedValue(false);
|
|
212
|
+
mockFsEnsureDir.mockResolvedValue(undefined);
|
|
213
|
+
mockFsWriteFile.mockResolvedValue(undefined);
|
|
214
|
+
await (0, install_1.installCommand)({ type: 'rule' });
|
|
215
|
+
expect(mockRulesApi.rulesControllerFindAll).toHaveBeenCalledWith({ type: 'rule' });
|
|
216
|
+
});
|
|
217
|
+
// TC-UNIT-CLI-028: Install with file download failure
|
|
218
|
+
it('should handle file download failure', async () => {
|
|
219
|
+
mockGetConfig.mockResolvedValue({
|
|
220
|
+
server: 'http://localhost:3000',
|
|
221
|
+
token: 'test-token',
|
|
222
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
223
|
+
lastLogin: new Date().toISOString(),
|
|
224
|
+
});
|
|
225
|
+
mockGetInstallPaths.mockReturnValue({
|
|
226
|
+
rules: '/path/to/rules',
|
|
227
|
+
commands: '/path/to/commands',
|
|
228
|
+
});
|
|
229
|
+
const mockRulesApi = {
|
|
230
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
231
|
+
data: [
|
|
232
|
+
{
|
|
233
|
+
id: 'rule-id-1',
|
|
234
|
+
name: 'Test Rule',
|
|
235
|
+
tag: 'test',
|
|
236
|
+
type: 'rule',
|
|
237
|
+
version: '1.0.0',
|
|
238
|
+
author: { username: 'testuser' },
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
}),
|
|
242
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
243
|
+
data: { downloadUrl: 'https://signed-url.com/file' },
|
|
244
|
+
}),
|
|
245
|
+
};
|
|
246
|
+
mockCreateApi.mockResolvedValue({
|
|
247
|
+
authApi: {},
|
|
248
|
+
rulesApi: mockRulesApi,
|
|
249
|
+
});
|
|
250
|
+
// Mock axios download failure
|
|
251
|
+
mockAxiosGet.mockRejectedValue(new Error('Download failed'));
|
|
252
|
+
mockFsPathExists.mockResolvedValue(false);
|
|
253
|
+
await expect((0, install_1.installCommand)({})).rejects.toThrow('Process exit with code 1');
|
|
254
|
+
expect(mockAxiosGet).toHaveBeenCalledWith('https://signed-url.com/file', {
|
|
255
|
+
responseType: 'text',
|
|
256
|
+
});
|
|
257
|
+
expect(console.error).toHaveBeenCalledWith('❌ Failed to download rule:', 'Download failed');
|
|
258
|
+
});
|
|
259
|
+
// TC-UNIT-CLI-029: Install with directory permission issues
|
|
260
|
+
it('should handle directory creation permission issues', async () => {
|
|
261
|
+
mockGetConfig.mockResolvedValue({
|
|
262
|
+
server: 'http://localhost:3000',
|
|
263
|
+
token: 'test-token',
|
|
264
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
265
|
+
lastLogin: new Date().toISOString(),
|
|
266
|
+
});
|
|
267
|
+
mockGetInstallPaths.mockReturnValue({
|
|
268
|
+
rules: '/path/to/rules',
|
|
269
|
+
commands: '/path/to/commands',
|
|
270
|
+
});
|
|
271
|
+
const mockRulesApi = {
|
|
272
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
273
|
+
data: [
|
|
274
|
+
{
|
|
275
|
+
id: 'rule-id-1',
|
|
276
|
+
name: 'Test Rule',
|
|
277
|
+
tag: 'test',
|
|
278
|
+
type: 'rule',
|
|
279
|
+
version: '1.0.0',
|
|
280
|
+
author: { username: 'testuser' },
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
}),
|
|
284
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
285
|
+
data: { downloadUrl: 'https://signed-url.com/file' },
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
mockCreateApi.mockResolvedValue({
|
|
289
|
+
authApi: {},
|
|
290
|
+
rulesApi: mockRulesApi,
|
|
291
|
+
});
|
|
292
|
+
mockAxiosGet.mockResolvedValue({ data: 'rule content' });
|
|
293
|
+
mockFsPathExists.mockResolvedValue(false);
|
|
294
|
+
mockFsEnsureDir.mockRejectedValue(new Error('Permission denied'));
|
|
295
|
+
mockFsWriteFile.mockResolvedValue(undefined);
|
|
296
|
+
await expect((0, install_1.installCommand)({})).rejects.toThrow('Process exit with code 1');
|
|
297
|
+
expect(mockFsEnsureDir).toHaveBeenCalledWith('/path/to/rules');
|
|
298
|
+
expect(console.error).toHaveBeenCalledWith('❌ Failed to create installation directory:', 'Permission denied');
|
|
299
|
+
});
|
|
300
|
+
// TC-UNIT-CLI-030: Install with force overwrite
|
|
301
|
+
it('should handle force overwrite option', async () => {
|
|
302
|
+
mockGetConfig.mockResolvedValue({
|
|
303
|
+
server: 'http://localhost:3000',
|
|
304
|
+
token: 'test-token',
|
|
305
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
306
|
+
lastLogin: new Date().toISOString(),
|
|
307
|
+
});
|
|
308
|
+
mockGetInstallPaths.mockReturnValue({
|
|
309
|
+
rules: '/path/to/rules',
|
|
310
|
+
commands: '/path/to/commands',
|
|
311
|
+
});
|
|
312
|
+
const mockRulesApi = {
|
|
313
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
314
|
+
data: [
|
|
315
|
+
{
|
|
316
|
+
id: 'rule-id-1',
|
|
317
|
+
name: 'Test Rule',
|
|
318
|
+
tag: 'test',
|
|
319
|
+
type: 'rule',
|
|
320
|
+
version: '1.0.0',
|
|
321
|
+
author: { username: 'testuser' },
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
}),
|
|
325
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
326
|
+
data: { downloadUrl: 'https://signed-url.com/file' },
|
|
327
|
+
}),
|
|
328
|
+
};
|
|
329
|
+
mockCreateApi.mockResolvedValue({
|
|
330
|
+
authApi: {},
|
|
331
|
+
rulesApi: mockRulesApi,
|
|
332
|
+
});
|
|
333
|
+
mockAxiosGet.mockResolvedValue({ data: 'rule content' });
|
|
334
|
+
mockFsPathExists.mockResolvedValue(true); // File already exists
|
|
335
|
+
mockFsEnsureDir.mockResolvedValue(undefined);
|
|
336
|
+
mockFsWriteFile.mockResolvedValue(undefined);
|
|
337
|
+
await (0, install_1.installCommand)({ force: true });
|
|
338
|
+
expect(mockFsWriteFile).toHaveBeenCalledWith(path.join('/path/to/rules', 'rule-id-1'), 'rule content');
|
|
339
|
+
expect(console.log).toHaveBeenCalledWith('✅ Successfully installed rule: Test Rule');
|
|
340
|
+
});
|
|
341
|
+
// TC-UNIT-CLI-031: Install with no rules found
|
|
342
|
+
it('should handle no rules found', async () => {
|
|
343
|
+
mockGetConfig.mockResolvedValue({
|
|
344
|
+
server: 'http://localhost:3000',
|
|
345
|
+
token: 'test-token',
|
|
346
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
347
|
+
lastLogin: new Date().toISOString(),
|
|
348
|
+
});
|
|
349
|
+
const mockRulesApi = {
|
|
350
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
351
|
+
data: [],
|
|
352
|
+
}),
|
|
353
|
+
};
|
|
354
|
+
mockCreateApi.mockResolvedValue({
|
|
355
|
+
authApi: {},
|
|
356
|
+
rulesApi: mockRulesApi,
|
|
357
|
+
});
|
|
358
|
+
await (0, install_1.installCommand)({});
|
|
359
|
+
expect(console.log).toHaveBeenCalledWith('📭 No rules found matching the criteria.');
|
|
360
|
+
});
|
|
361
|
+
// TC-UNIT-CLI-032: Install with multiple rules (interactive selection)
|
|
362
|
+
it('should handle multiple rules with interactive selection', async () => {
|
|
363
|
+
const mockInquirer = require('inquirer');
|
|
364
|
+
mockInquirer.prompt = jest.fn().mockResolvedValue({ rule: 'rule-id-2' });
|
|
365
|
+
mockGetConfig.mockResolvedValue({
|
|
366
|
+
server: 'http://localhost:3000',
|
|
367
|
+
token: 'test-token',
|
|
368
|
+
user: { id: 'user-id', username: 'testuser' },
|
|
369
|
+
lastLogin: new Date().toISOString(),
|
|
370
|
+
});
|
|
371
|
+
mockGetInstallPaths.mockReturnValue({
|
|
372
|
+
rules: '/path/to/rules',
|
|
373
|
+
commands: '/path/to/commands',
|
|
374
|
+
});
|
|
375
|
+
const mockRulesApi = {
|
|
376
|
+
rulesControllerFindAll: jest.fn().mockResolvedValue({
|
|
377
|
+
data: [
|
|
378
|
+
{
|
|
379
|
+
id: 'rule-id-1',
|
|
380
|
+
name: 'Test Rule 1',
|
|
381
|
+
tag: 'test',
|
|
382
|
+
type: 'rule',
|
|
383
|
+
version: '1.0.0',
|
|
384
|
+
author: { username: 'testuser' },
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
id: 'rule-id-2',
|
|
388
|
+
name: 'Test Rule 2',
|
|
389
|
+
tag: 'test',
|
|
390
|
+
type: 'rule',
|
|
391
|
+
version: '1.0.0',
|
|
392
|
+
author: { username: 'testuser' },
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
}),
|
|
396
|
+
rulesControllerGetDownloadUrl: jest.fn().mockResolvedValue({
|
|
397
|
+
data: { downloadUrl: 'https://signed-url.com/file' },
|
|
398
|
+
}),
|
|
399
|
+
};
|
|
400
|
+
mockCreateApi.mockResolvedValue({
|
|
401
|
+
authApi: {},
|
|
402
|
+
rulesApi: mockRulesApi,
|
|
403
|
+
});
|
|
404
|
+
mockAxiosGet.mockResolvedValue({ data: 'rule content' });
|
|
405
|
+
mockFsPathExists.mockResolvedValue(false);
|
|
406
|
+
mockFsEnsureDir.mockResolvedValue(undefined);
|
|
407
|
+
mockFsWriteFile.mockResolvedValue(undefined);
|
|
408
|
+
await (0, install_1.installCommand)({});
|
|
409
|
+
expect(mockInquirer.prompt).toHaveBeenCalledWith([
|
|
410
|
+
{
|
|
411
|
+
type: 'list',
|
|
412
|
+
name: 'rule',
|
|
413
|
+
message: 'Select a rule to install:',
|
|
414
|
+
choices: [
|
|
415
|
+
{ name: 'Test Rule 1 (rule-id-1)', value: 'rule-id-1' },
|
|
416
|
+
{ name: 'Test Rule 2 (rule-id-2)', value: 'rule-id-2' },
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
]);
|
|
420
|
+
expect(mockRulesApi.rulesControllerGetDownloadUrl).toHaveBeenCalledWith('rule-id-2');
|
|
421
|
+
});
|
|
422
|
+
});
|