@friggframework/devtools 2.0.0-next.41 → 2.0.0-next.42
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/frigg-cli/__tests__/unit/commands/build.test.js +173 -405
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +359 -377
- package/frigg-cli/__tests__/unit/commands/ui.test.js +266 -512
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +486 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-setup.js +22 -21
- package/frigg-cli/db-setup-command/index.js +186 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +151 -162
- package/frigg-cli/generate-iam-command.js +7 -4
- package/frigg-cli/index.js +9 -1
- package/frigg-cli/install-command/index.js +1 -1
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +4 -1
- package/frigg-cli/start-command/index.js +95 -2
- package/frigg-cli/start-command/start-command.test.js +161 -19
- package/frigg-cli/utils/database-validator.js +158 -0
- package/frigg-cli/utils/error-messages.js +257 -0
- package/frigg-cli/utils/prisma-runner.js +280 -0
- package/infrastructure/CLAUDE.md +481 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +30 -12
- package/infrastructure/create-frigg-infrastructure.js +0 -2
- package/infrastructure/iam-generator.js +18 -38
- package/infrastructure/iam-generator.test.js +40 -8
- package/package.json +6 -6
- package/test/index.js +2 -4
- package/test/mock-integration.js +4 -14
- package/frigg-cli/__tests__/jest.config.js +0 -102
- package/frigg-cli/__tests__/utils/command-tester.js +0 -170
- package/test/auther-definition-tester.js +0 -125
|
@@ -1,483 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test suite for build command
|
|
3
|
+
*
|
|
4
|
+
* Tests the serverless package build functionality including:
|
|
5
|
+
* - Command execution with spawnSync
|
|
6
|
+
* - Stage option handling
|
|
7
|
+
* - Verbose flag support
|
|
8
|
+
* - Environment variable setup
|
|
9
|
+
* - Error handling and process exit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Mock dependencies BEFORE requiring modules
|
|
13
|
+
jest.mock('child_process', () => ({
|
|
14
|
+
spawnSync: jest.fn()
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Require after mocks
|
|
18
|
+
const { spawnSync } = require('child_process');
|
|
1
19
|
const { buildCommand } = require('../../../build-command');
|
|
2
|
-
const { CommandTester } = require('../../utils/command-tester');
|
|
3
|
-
const { MockFactory } = require('../../utils/mock-factory');
|
|
4
|
-
const { TestFixtures } = require('../../utils/test-fixtures');
|
|
5
20
|
|
|
6
21
|
describe('CLI Command: build', () => {
|
|
7
|
-
let
|
|
8
|
-
let
|
|
9
|
-
|
|
22
|
+
let consoleLogSpy;
|
|
23
|
+
let consoleErrorSpy;
|
|
24
|
+
let processExitSpy;
|
|
25
|
+
let originalCwd;
|
|
26
|
+
|
|
10
27
|
beforeEach(() => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
|
|
30
|
+
// Mock console methods
|
|
31
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
32
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
33
|
+
|
|
34
|
+
// Mock process.exit to prevent actual exit
|
|
35
|
+
processExitSpy = jest.spyOn(process, 'exit').mockImplementation();
|
|
36
|
+
|
|
37
|
+
// Mock successful serverless execution by default
|
|
38
|
+
spawnSync.mockReturnValue({ status: 0 });
|
|
39
|
+
|
|
40
|
+
// Store original cwd for restoration
|
|
41
|
+
originalCwd = process.cwd();
|
|
24
42
|
});
|
|
25
43
|
|
|
26
44
|
afterEach(() => {
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
consoleLogSpy.mockRestore();
|
|
46
|
+
consoleErrorSpy.mockRestore();
|
|
47
|
+
processExitSpy.mockRestore();
|
|
29
48
|
});
|
|
30
49
|
|
|
31
50
|
describe('Success Cases', () => {
|
|
32
|
-
it('should
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
43
|
-
duration: 30000
|
|
44
|
-
})
|
|
51
|
+
it('should spawn serverless with default stage', async () => {
|
|
52
|
+
await buildCommand({ stage: 'dev' });
|
|
53
|
+
|
|
54
|
+
expect(spawnSync).toHaveBeenCalledWith(
|
|
55
|
+
'serverless',
|
|
56
|
+
['package', '--config', 'infrastructure.js', '--stage', 'dev'],
|
|
57
|
+
expect.objectContaining({
|
|
58
|
+
cwd: expect.any(String),
|
|
59
|
+
stdio: 'inherit',
|
|
60
|
+
shell: true
|
|
45
61
|
})
|
|
46
|
-
|
|
62
|
+
);
|
|
63
|
+
});
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
it('should spawn serverless with production stage', async () => {
|
|
66
|
+
await buildCommand({ stage: 'production' });
|
|
50
67
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
expect(spawnSync).toHaveBeenCalledWith(
|
|
69
|
+
'serverless',
|
|
70
|
+
expect.arrayContaining(['--stage', 'production']),
|
|
71
|
+
expect.any(Object)
|
|
72
|
+
);
|
|
54
73
|
});
|
|
55
74
|
|
|
56
|
-
it('should
|
|
57
|
-
|
|
58
|
-
commandTester
|
|
59
|
-
.mock('@friggframework/core', {
|
|
60
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
61
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
62
|
-
})
|
|
63
|
-
.mock('./build-command/builder', {
|
|
64
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
65
|
-
success: true,
|
|
66
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
67
|
-
duration: 45000
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
.mock('./build-command/logger', mocks.logger);
|
|
71
|
-
|
|
72
|
-
// Act
|
|
73
|
-
const result = await commandTester.execute(['--stage', 'production']);
|
|
75
|
+
it('should spawn serverless with staging stage', async () => {
|
|
76
|
+
await buildCommand({ stage: 'staging' });
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
expect(spawnSync).toHaveBeenCalledWith(
|
|
79
|
+
'serverless',
|
|
80
|
+
expect.arrayContaining(['--stage', 'staging']),
|
|
81
|
+
expect.any(Object)
|
|
82
|
+
);
|
|
78
83
|
});
|
|
79
84
|
|
|
80
|
-
it('should
|
|
81
|
-
|
|
82
|
-
commandTester
|
|
83
|
-
.mock('@friggframework/core', {
|
|
84
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
85
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
86
|
-
})
|
|
87
|
-
.mock('./build-command/builder', {
|
|
88
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
89
|
-
success: true,
|
|
90
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
91
|
-
duration: 30000,
|
|
92
|
-
verbose: true
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
.mock('./build-command/logger', mocks.logger);
|
|
85
|
+
it('should append verbose flag when verbose option is true', async () => {
|
|
86
|
+
await buildCommand({ stage: 'dev', verbose: true });
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
expect(result.exitCode).toBe(0);
|
|
88
|
+
expect(spawnSync).toHaveBeenCalledWith(
|
|
89
|
+
'serverless',
|
|
90
|
+
expect.arrayContaining(['--verbose']),
|
|
91
|
+
expect.any(Object)
|
|
92
|
+
);
|
|
103
93
|
});
|
|
104
94
|
|
|
105
|
-
it('should
|
|
106
|
-
|
|
107
|
-
const customPath = '/custom/app/path';
|
|
108
|
-
|
|
109
|
-
commandTester
|
|
110
|
-
.mock('@friggframework/core', {
|
|
111
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/custom/backend/package.json'),
|
|
112
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
113
|
-
})
|
|
114
|
-
.mock('./build-command/builder', {
|
|
115
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
116
|
-
success: true,
|
|
117
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
118
|
-
duration: 30000
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
.mock('./build-command/logger', mocks.logger);
|
|
95
|
+
it('should NOT append verbose flag when verbose option is false', async () => {
|
|
96
|
+
await buildCommand({ stage: 'dev', verbose: false });
|
|
122
97
|
|
|
123
|
-
|
|
124
|
-
const
|
|
98
|
+
const call = spawnSync.mock.calls[0];
|
|
99
|
+
const args = call[1];
|
|
125
100
|
|
|
126
|
-
|
|
127
|
-
expect(result.success).toBe(true);
|
|
128
|
-
expect(result.exitCode).toBe(0);
|
|
101
|
+
expect(args).not.toContain('--verbose');
|
|
129
102
|
});
|
|
130
103
|
|
|
131
|
-
it('should
|
|
132
|
-
|
|
133
|
-
commandTester
|
|
134
|
-
.mock('@friggframework/core', {
|
|
135
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
136
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
137
|
-
})
|
|
138
|
-
.mock('./build-command/builder', {
|
|
139
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
140
|
-
success: true,
|
|
141
|
-
artifacts: ['backend.zip'],
|
|
142
|
-
duration: 20000,
|
|
143
|
-
frontendSkipped: true
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
.mock('./build-command/logger', mocks.logger);
|
|
104
|
+
it('should use process.cwd() as working directory', async () => {
|
|
105
|
+
await buildCommand({ stage: 'dev' });
|
|
147
106
|
|
|
148
|
-
|
|
149
|
-
const
|
|
107
|
+
const call = spawnSync.mock.calls[0];
|
|
108
|
+
const options = call[2];
|
|
150
109
|
|
|
151
|
-
//
|
|
152
|
-
expect(
|
|
153
|
-
expect(result.exitCode).toBe(0);
|
|
110
|
+
// Verify ACTUAL cwd value, not generic check
|
|
111
|
+
expect(options.cwd).toBe(process.cwd());
|
|
154
112
|
});
|
|
155
|
-
});
|
|
156
113
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Arrange
|
|
160
|
-
commandTester
|
|
161
|
-
.mock('@friggframework/core', {
|
|
162
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue(null),
|
|
163
|
-
validateBackendPath: jest.fn().mockImplementation(() => {
|
|
164
|
-
throw new Error('Invalid backend path');
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
.mock('./build-command/logger', mocks.logger);
|
|
114
|
+
it('should use stdio inherit for output streaming', async () => {
|
|
115
|
+
await buildCommand({ stage: 'dev' });
|
|
168
116
|
|
|
169
|
-
|
|
170
|
-
const
|
|
117
|
+
const call = spawnSync.mock.calls[0];
|
|
118
|
+
const options = call[2];
|
|
171
119
|
|
|
172
|
-
|
|
173
|
-
expect(result.success).toBe(false);
|
|
174
|
-
expect(result.exitCode).toBe(1);
|
|
120
|
+
expect(options.stdio).toBe('inherit');
|
|
175
121
|
});
|
|
176
122
|
|
|
177
|
-
it('should
|
|
178
|
-
|
|
179
|
-
commandTester
|
|
180
|
-
.mock('@friggframework/core', {
|
|
181
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
182
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
183
|
-
})
|
|
184
|
-
.mock('./build-command/builder', {
|
|
185
|
-
buildApplication: jest.fn().mockRejectedValue(new Error('Build failed: compilation error'))
|
|
186
|
-
})
|
|
187
|
-
.mock('./build-command/logger', mocks.logger);
|
|
123
|
+
it('should use shell mode for execution', async () => {
|
|
124
|
+
await buildCommand({ stage: 'dev' });
|
|
188
125
|
|
|
189
|
-
|
|
190
|
-
const
|
|
126
|
+
const call = spawnSync.mock.calls[0];
|
|
127
|
+
const options = call[2];
|
|
191
128
|
|
|
192
|
-
|
|
193
|
-
expect(result.success).toBe(false);
|
|
194
|
-
expect(result.exitCode).toBe(1);
|
|
129
|
+
expect(options.shell).toBe(true);
|
|
195
130
|
});
|
|
196
131
|
|
|
197
|
-
it('should
|
|
198
|
-
|
|
199
|
-
commandTester
|
|
200
|
-
.mock('@friggframework/core', {
|
|
201
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
202
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
203
|
-
})
|
|
204
|
-
.mock('./build-command/builder', {
|
|
205
|
-
buildApplication: jest.fn().mockRejectedValue(new Error('Missing dependencies: webpack'))
|
|
206
|
-
})
|
|
207
|
-
.mock('./build-command/logger', mocks.logger);
|
|
132
|
+
it('should set NODE_PATH environment variable with actual resolved path', async () => {
|
|
133
|
+
await buildCommand({ stage: 'dev' });
|
|
208
134
|
|
|
209
|
-
|
|
210
|
-
const
|
|
135
|
+
const call = spawnSync.mock.calls[0];
|
|
136
|
+
const options = call[2];
|
|
211
137
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
138
|
+
// Verify ACTUAL resolved path, not just "contains node_modules"
|
|
139
|
+
const path = require('path');
|
|
140
|
+
const expectedNodePath = path.resolve(process.cwd(), 'node_modules');
|
|
216
141
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
commandTester
|
|
220
|
-
.mock('@friggframework/core', {
|
|
221
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
222
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
223
|
-
})
|
|
224
|
-
.mock('./build-command/builder', {
|
|
225
|
-
buildApplication: jest.fn().mockRejectedValue(new Error('ENOSPC: no space left on device'))
|
|
226
|
-
})
|
|
227
|
-
.mock('./build-command/logger', mocks.logger);
|
|
142
|
+
expect(options.env.NODE_PATH).toBe(expectedNodePath);
|
|
143
|
+
});
|
|
228
144
|
|
|
229
|
-
|
|
230
|
-
|
|
145
|
+
it('should pass through all process.env variables', async () => {
|
|
146
|
+
// Set a test env var
|
|
147
|
+
process.env.TEST_VAR = 'test-value';
|
|
231
148
|
|
|
232
|
-
|
|
233
|
-
expect(result.success).toBe(false);
|
|
234
|
-
expect(result.exitCode).toBe(1);
|
|
235
|
-
});
|
|
149
|
+
await buildCommand({ stage: 'dev' });
|
|
236
150
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
commandTester
|
|
240
|
-
.mock('@friggframework/core', {
|
|
241
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
242
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
243
|
-
})
|
|
244
|
-
.mock('./build-command/builder', {
|
|
245
|
-
buildApplication: jest.fn().mockRejectedValue(new Error('Invalid configuration: missing stage'))
|
|
246
|
-
})
|
|
247
|
-
.mock('./build-command/logger', mocks.logger);
|
|
151
|
+
const call = spawnSync.mock.calls[0];
|
|
152
|
+
const options = call[2];
|
|
248
153
|
|
|
249
|
-
//
|
|
250
|
-
|
|
154
|
+
// Verify parent env vars are passed through
|
|
155
|
+
expect(options.env.TEST_VAR).toBe('test-value');
|
|
251
156
|
|
|
252
|
-
|
|
253
|
-
expect(result.success).toBe(false);
|
|
254
|
-
expect(result.exitCode).toBe(1);
|
|
157
|
+
delete process.env.TEST_VAR;
|
|
255
158
|
});
|
|
256
|
-
});
|
|
257
159
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Arrange
|
|
261
|
-
commandTester
|
|
262
|
-
.mock('@friggframework/core', {
|
|
263
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue(null),
|
|
264
|
-
validateBackendPath: jest.fn().mockImplementation(() => {
|
|
265
|
-
throw new Error('No backend directory found');
|
|
266
|
-
})
|
|
267
|
-
})
|
|
268
|
-
.mock('./build-command/logger', mocks.logger);
|
|
269
|
-
|
|
270
|
-
// Act
|
|
271
|
-
const result = await commandTester.execute([]);
|
|
160
|
+
it('should use infrastructure.js as config file', async () => {
|
|
161
|
+
await buildCommand({ stage: 'dev' });
|
|
272
162
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
163
|
+
expect(spawnSync).toHaveBeenCalledWith(
|
|
164
|
+
'serverless',
|
|
165
|
+
expect.arrayContaining(['--config', 'infrastructure.js']),
|
|
166
|
+
expect.any(Object)
|
|
167
|
+
);
|
|
276
168
|
});
|
|
277
169
|
|
|
278
|
-
it('should
|
|
279
|
-
|
|
280
|
-
commandTester
|
|
281
|
-
.mock('@friggframework/core', {
|
|
282
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
283
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
284
|
-
})
|
|
285
|
-
.mock('./build-command/builder', {
|
|
286
|
-
buildApplication: jest.fn().mockRejectedValue(new Error('Build timeout after 300 seconds'))
|
|
287
|
-
})
|
|
288
|
-
.mock('./build-command/logger', mocks.logger);
|
|
170
|
+
it('should NOT call process.exit when build succeeds', async () => {
|
|
171
|
+
spawnSync.mockReturnValue({ status: 0 });
|
|
289
172
|
|
|
290
|
-
|
|
291
|
-
const result = await commandTester.execute([]);
|
|
173
|
+
await buildCommand({ stage: 'dev' });
|
|
292
174
|
|
|
293
|
-
|
|
294
|
-
expect(result.success).toBe(false);
|
|
295
|
-
expect(result.exitCode).toBe(1);
|
|
175
|
+
expect(processExitSpy).not.toHaveBeenCalled();
|
|
296
176
|
});
|
|
297
177
|
|
|
298
|
-
it('should
|
|
299
|
-
|
|
300
|
-
commandTester
|
|
301
|
-
.mock('@friggframework/core', {
|
|
302
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
303
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
304
|
-
})
|
|
305
|
-
.mock('./build-command/builder', {
|
|
306
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
307
|
-
success: false,
|
|
308
|
-
artifacts: ['backend.zip'],
|
|
309
|
-
duration: 30000,
|
|
310
|
-
errors: ['Frontend build failed'],
|
|
311
|
-
warnings: ['Some dependencies are deprecated']
|
|
312
|
-
})
|
|
313
|
-
})
|
|
314
|
-
.mock('./build-command/logger', mocks.logger);
|
|
315
|
-
|
|
316
|
-
// Act
|
|
317
|
-
const result = await commandTester.execute([]);
|
|
178
|
+
it('should log build start messages', async () => {
|
|
179
|
+
await buildCommand({ stage: 'dev' });
|
|
318
180
|
|
|
319
|
-
|
|
320
|
-
expect(
|
|
321
|
-
expect(result.exitCode).toBe(1);
|
|
181
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('Building the serverless application...');
|
|
182
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('📦 Packaging serverless application...');
|
|
322
183
|
});
|
|
323
184
|
|
|
324
|
-
it('should
|
|
325
|
-
|
|
326
|
-
commandTester
|
|
327
|
-
.mock('@friggframework/core', {
|
|
328
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
329
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
330
|
-
})
|
|
331
|
-
.mock('./build-command/builder', {
|
|
332
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
333
|
-
success: true,
|
|
334
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
335
|
-
duration: 300000, // 5 minutes
|
|
336
|
-
size: '250MB'
|
|
337
|
-
})
|
|
338
|
-
})
|
|
339
|
-
.mock('./build-command/logger', mocks.logger);
|
|
185
|
+
it('should construct complete valid serverless command', async () => {
|
|
186
|
+
await buildCommand({ stage: 'production', verbose: true });
|
|
340
187
|
|
|
341
|
-
|
|
342
|
-
const result = await commandTester.execute([]);
|
|
188
|
+
const [cmd, args, opts] = spawnSync.mock.calls[0];
|
|
343
189
|
|
|
344
|
-
//
|
|
345
|
-
expect(
|
|
346
|
-
expect(
|
|
347
|
-
|
|
348
|
-
|
|
190
|
+
// Verify complete command structure
|
|
191
|
+
expect(cmd).toBe('serverless');
|
|
192
|
+
expect(args).toEqual([
|
|
193
|
+
'package',
|
|
194
|
+
'--config',
|
|
195
|
+
'infrastructure.js',
|
|
196
|
+
'--stage',
|
|
197
|
+
'production',
|
|
198
|
+
'--verbose'
|
|
199
|
+
]);
|
|
349
200
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
commandTester
|
|
357
|
-
.mock('@friggframework/core', {
|
|
358
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
359
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
360
|
-
})
|
|
361
|
-
.mock('./build-command/builder', {
|
|
362
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
363
|
-
success: true,
|
|
364
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
365
|
-
duration: 30000,
|
|
366
|
-
stage
|
|
367
|
-
})
|
|
368
|
-
})
|
|
369
|
-
.mock('./build-command/logger', mocks.logger);
|
|
370
|
-
|
|
371
|
-
// Act
|
|
372
|
-
const result = await commandTester.execute(['--stage', stage]);
|
|
373
|
-
|
|
374
|
-
// Assert
|
|
375
|
-
expect(result.success).toBe(true);
|
|
376
|
-
expect(result.exitCode).toBe(0);
|
|
377
|
-
}
|
|
201
|
+
// Verify all required options present
|
|
202
|
+
expect(opts.cwd).toBeDefined();
|
|
203
|
+
expect(opts.stdio).toBe('inherit');
|
|
204
|
+
expect(opts.shell).toBe(true);
|
|
205
|
+
expect(opts.env).toBeDefined();
|
|
206
|
+
expect(opts.env.NODE_PATH).toBeDefined();
|
|
378
207
|
});
|
|
379
208
|
|
|
380
|
-
it('should
|
|
381
|
-
|
|
382
|
-
commandTester
|
|
383
|
-
.mock('@friggframework/core', {
|
|
384
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
385
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
386
|
-
})
|
|
387
|
-
.mock('./build-command/builder', {
|
|
388
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
389
|
-
success: true,
|
|
390
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
391
|
-
duration: 30000
|
|
392
|
-
})
|
|
393
|
-
})
|
|
394
|
-
.mock('./build-command/logger', mocks.logger);
|
|
209
|
+
it('should build command without verbose when verbose=false', async () => {
|
|
210
|
+
await buildCommand({ stage: 'dev', verbose: false });
|
|
395
211
|
|
|
396
|
-
|
|
397
|
-
const result = await commandTester.execute(['-s', 'production']);
|
|
212
|
+
const [, args] = spawnSync.mock.calls[0];
|
|
398
213
|
|
|
399
|
-
//
|
|
400
|
-
expect(
|
|
401
|
-
|
|
214
|
+
// Verify exact args without verbose
|
|
215
|
+
expect(args).toEqual([
|
|
216
|
+
'package',
|
|
217
|
+
'--config',
|
|
218
|
+
'infrastructure.js',
|
|
219
|
+
'--stage',
|
|
220
|
+
'dev'
|
|
221
|
+
]);
|
|
402
222
|
});
|
|
223
|
+
});
|
|
403
224
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
.mock('@friggframework/core', {
|
|
408
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
409
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
410
|
-
})
|
|
411
|
-
.mock('./build-command/builder', {
|
|
412
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
413
|
-
success: true,
|
|
414
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
415
|
-
duration: 30000,
|
|
416
|
-
verbose: true
|
|
417
|
-
})
|
|
418
|
-
})
|
|
419
|
-
.mock('./build-command/logger', mocks.logger);
|
|
225
|
+
describe('Error Handling', () => {
|
|
226
|
+
it('should exit with code 1 when serverless fails', async () => {
|
|
227
|
+
spawnSync.mockReturnValue({ status: 1 });
|
|
420
228
|
|
|
421
|
-
|
|
422
|
-
const result = await commandTester.execute(['-v']);
|
|
229
|
+
await buildCommand({ stage: 'dev' });
|
|
423
230
|
|
|
424
|
-
|
|
425
|
-
expect(result.success).toBe(true);
|
|
426
|
-
expect(result.exitCode).toBe(0);
|
|
231
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
427
232
|
});
|
|
428
233
|
|
|
429
|
-
it('should
|
|
430
|
-
|
|
431
|
-
commandTester
|
|
432
|
-
.mock('@friggframework/core', {
|
|
433
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
434
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
435
|
-
})
|
|
436
|
-
.mock('./build-command/builder', {
|
|
437
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
438
|
-
success: true,
|
|
439
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
440
|
-
duration: 30000,
|
|
441
|
-
verbose: true
|
|
442
|
-
})
|
|
443
|
-
})
|
|
444
|
-
.mock('./build-command/logger', mocks.logger);
|
|
234
|
+
it('should log error message when build fails', async () => {
|
|
235
|
+
spawnSync.mockReturnValue({ status: 2 });
|
|
445
236
|
|
|
446
|
-
|
|
447
|
-
const result = await commandTester.execute(['-s', 'production', '-v']);
|
|
237
|
+
await buildCommand({ stage: 'dev' });
|
|
448
238
|
|
|
449
|
-
|
|
450
|
-
expect(result.success).toBe(true);
|
|
451
|
-
expect(result.exitCode).toBe(0);
|
|
239
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Serverless build failed with code 2');
|
|
452
240
|
});
|
|
453
|
-
});
|
|
454
241
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
// Arrange
|
|
458
|
-
const startTime = Date.now();
|
|
459
|
-
|
|
460
|
-
commandTester
|
|
461
|
-
.mock('@friggframework/core', {
|
|
462
|
-
findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
|
|
463
|
-
validateBackendPath: jest.fn().mockReturnValue(true)
|
|
464
|
-
})
|
|
465
|
-
.mock('./build-command/builder', {
|
|
466
|
-
buildApplication: jest.fn().mockResolvedValue({
|
|
467
|
-
success: true,
|
|
468
|
-
artifacts: ['backend.zip', 'frontend.zip'],
|
|
469
|
-
duration: 30000
|
|
470
|
-
})
|
|
471
|
-
})
|
|
472
|
-
.mock('./build-command/logger', mocks.logger);
|
|
242
|
+
it('should exit with code 1 for any non-zero status', async () => {
|
|
243
|
+
spawnSync.mockReturnValue({ status: 127 });
|
|
473
244
|
|
|
474
|
-
|
|
475
|
-
const result = await commandTester.execute([]);
|
|
476
|
-
const endTime = Date.now();
|
|
245
|
+
await buildCommand({ stage: 'dev' });
|
|
477
246
|
|
|
478
|
-
|
|
479
|
-
expect(
|
|
480
|
-
expect(endTime - startTime).toBeLessThan(5000); // Should complete within 5 seconds
|
|
247
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
248
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Serverless build failed with code 127');
|
|
481
249
|
});
|
|
482
250
|
});
|
|
483
|
-
});
|
|
251
|
+
});
|