@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.
Files changed (32) hide show
  1. package/frigg-cli/__tests__/unit/commands/build.test.js +173 -405
  2. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
  3. package/frigg-cli/__tests__/unit/commands/install.test.js +359 -377
  4. package/frigg-cli/__tests__/unit/commands/ui.test.js +266 -512
  5. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
  6. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
  7. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +486 -0
  8. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  9. package/frigg-cli/__tests__/utils/test-setup.js +22 -21
  10. package/frigg-cli/db-setup-command/index.js +186 -0
  11. package/frigg-cli/generate-command/__tests__/generate-command.test.js +151 -162
  12. package/frigg-cli/generate-iam-command.js +7 -4
  13. package/frigg-cli/index.js +9 -1
  14. package/frigg-cli/install-command/index.js +1 -1
  15. package/frigg-cli/jest.config.js +124 -0
  16. package/frigg-cli/package.json +4 -1
  17. package/frigg-cli/start-command/index.js +95 -2
  18. package/frigg-cli/start-command/start-command.test.js +161 -19
  19. package/frigg-cli/utils/database-validator.js +158 -0
  20. package/frigg-cli/utils/error-messages.js +257 -0
  21. package/frigg-cli/utils/prisma-runner.js +280 -0
  22. package/infrastructure/CLAUDE.md +481 -0
  23. package/infrastructure/IAM-POLICY-TEMPLATES.md +30 -12
  24. package/infrastructure/create-frigg-infrastructure.js +0 -2
  25. package/infrastructure/iam-generator.js +18 -38
  26. package/infrastructure/iam-generator.test.js +40 -8
  27. package/package.json +6 -6
  28. package/test/index.js +2 -4
  29. package/test/mock-integration.js +4 -14
  30. package/frigg-cli/__tests__/jest.config.js +0 -102
  31. package/frigg-cli/__tests__/utils/command-tester.js +0 -170
  32. 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 commandTester;
8
- let mocks;
9
-
22
+ let consoleLogSpy;
23
+ let consoleErrorSpy;
24
+ let processExitSpy;
25
+ let originalCwd;
26
+
10
27
  beforeEach(() => {
11
- mocks = MockFactory.createMockEnvironment();
12
- commandTester = new CommandTester({
13
- name: 'build',
14
- description: 'Build the serverless application',
15
- action: buildCommand,
16
- options: [
17
- { flags: '-s, --stage <stage>', description: 'deployment stage', defaultValue: 'dev' },
18
- { flags: '-v, --verbose', description: 'enable verbose output' },
19
- { flags: '--app-path <path>', description: 'path to Frigg application directory' },
20
- { flags: '--config <path>', description: 'path to Frigg configuration file' },
21
- { flags: '--app <path>', description: 'alias for --app-path' }
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
- jest.clearAllMocks();
28
- commandTester.reset();
45
+ consoleLogSpy.mockRestore();
46
+ consoleErrorSpy.mockRestore();
47
+ processExitSpy.mockRestore();
29
48
  });
30
49
 
31
50
  describe('Success Cases', () => {
32
- it('should successfully build with default stage', async () => {
33
- // Arrange
34
- commandTester
35
- .mock('@friggframework/core', {
36
- findNearestBackendPackageJson: jest.fn().mockReturnValue('/mock/backend/package.json'),
37
- validateBackendPath: jest.fn().mockReturnValue(true)
38
- })
39
- .mock('./build-command/builder', {
40
- buildApplication: jest.fn().mockResolvedValue({
41
- success: true,
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
- .mock('./build-command/logger', mocks.logger);
62
+ );
63
+ });
47
64
 
48
- // Act
49
- const result = await commandTester.execute([]);
65
+ it('should spawn serverless with production stage', async () => {
66
+ await buildCommand({ stage: 'production' });
50
67
 
51
- // Assert
52
- expect(result.success).toBe(true);
53
- expect(result.exitCode).toBe(0);
68
+ expect(spawnSync).toHaveBeenCalledWith(
69
+ 'serverless',
70
+ expect.arrayContaining(['--stage', 'production']),
71
+ expect.any(Object)
72
+ );
54
73
  });
55
74
 
56
- it('should successfully build with production stage', async () => {
57
- // Arrange
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
- // Assert
76
- expect(result.success).toBe(true);
77
- expect(result.exitCode).toBe(0);
78
+ expect(spawnSync).toHaveBeenCalledWith(
79
+ 'serverless',
80
+ expect.arrayContaining(['--stage', 'staging']),
81
+ expect.any(Object)
82
+ );
78
83
  });
79
84
 
80
- it('should successfully build with verbose output', async () => {
81
- // Arrange
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
- // Act
98
- const result = await commandTester.execute(['--verbose']);
99
-
100
- // Assert
101
- expect(result.success).toBe(true);
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 successfully build with custom app path', async () => {
106
- // Arrange
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
- // Act
124
- const result = await commandTester.execute(['--app-path', customPath]);
98
+ const call = spawnSync.mock.calls[0];
99
+ const args = call[1];
125
100
 
126
- // Assert
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 handle backend-only build', async () => {
132
- // Arrange
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
- // Act
149
- const result = await commandTester.execute([]);
107
+ const call = spawnSync.mock.calls[0];
108
+ const options = call[2];
150
109
 
151
- // Assert
152
- expect(result.success).toBe(true);
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
- describe('Error Cases', () => {
158
- it('should handle invalid app path', async () => {
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
- // Act
170
- const result = await commandTester.execute([]);
117
+ const call = spawnSync.mock.calls[0];
118
+ const options = call[2];
171
119
 
172
- // Assert
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 handle build failure', async () => {
178
- // Arrange
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
- // Act
190
- const result = await commandTester.execute([]);
126
+ const call = spawnSync.mock.calls[0];
127
+ const options = call[2];
191
128
 
192
- // Assert
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 handle missing dependencies', async () => {
198
- // Arrange
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
- // Act
210
- const result = await commandTester.execute([]);
135
+ const call = spawnSync.mock.calls[0];
136
+ const options = call[2];
211
137
 
212
- // Assert
213
- expect(result.success).toBe(false);
214
- expect(result.exitCode).toBe(1);
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
- it('should handle insufficient disk space', async () => {
218
- // Arrange
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
- // Act
230
- const result = await commandTester.execute([]);
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
- // Assert
233
- expect(result.success).toBe(false);
234
- expect(result.exitCode).toBe(1);
235
- });
149
+ await buildCommand({ stage: 'dev' });
236
150
 
237
- it('should handle configuration loading error', async () => {
238
- // Arrange
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
- // Act
250
- const result = await commandTester.execute(['--stage', 'invalid-stage']);
154
+ // Verify parent env vars are passed through
155
+ expect(options.env.TEST_VAR).toBe('test-value');
251
156
 
252
- // Assert
253
- expect(result.success).toBe(false);
254
- expect(result.exitCode).toBe(1);
157
+ delete process.env.TEST_VAR;
255
158
  });
256
- });
257
159
 
258
- describe('Edge Cases', () => {
259
- it('should handle empty project directory', async () => {
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
- // Assert
274
- expect(result.success).toBe(false);
275
- expect(result.exitCode).toBe(1);
163
+ expect(spawnSync).toHaveBeenCalledWith(
164
+ 'serverless',
165
+ expect.arrayContaining(['--config', 'infrastructure.js']),
166
+ expect.any(Object)
167
+ );
276
168
  });
277
169
 
278
- it('should handle build timeout', async () => {
279
- // Arrange
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
- // Act
291
- const result = await commandTester.execute([]);
173
+ await buildCommand({ stage: 'dev' });
292
174
 
293
- // Assert
294
- expect(result.success).toBe(false);
295
- expect(result.exitCode).toBe(1);
175
+ expect(processExitSpy).not.toHaveBeenCalled();
296
176
  });
297
177
 
298
- it('should handle partial build success', async () => {
299
- // Arrange
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
- // Assert
320
- expect(result.success).toBe(false);
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 handle very large project build', async () => {
325
- // Arrange
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
- // Act
342
- const result = await commandTester.execute([]);
188
+ const [cmd, args, opts] = spawnSync.mock.calls[0];
343
189
 
344
- // Assert
345
- expect(result.success).toBe(true);
346
- expect(result.exitCode).toBe(0);
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
- describe('Option Validation', () => {
351
- it('should validate stage option values', async () => {
352
- // Arrange
353
- const validStages = ['dev', 'staging', 'production'];
354
-
355
- for (const stage of validStages) {
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 handle short stage option (-s)', async () => {
381
- // Arrange
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
- // Act
397
- const result = await commandTester.execute(['-s', 'production']);
212
+ const [, args] = spawnSync.mock.calls[0];
398
213
 
399
- // Assert
400
- expect(result.success).toBe(true);
401
- expect(result.exitCode).toBe(0);
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
- it('should handle short verbose option (-v)', async () => {
405
- // Arrange
406
- commandTester
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
- // Act
422
- const result = await commandTester.execute(['-v']);
229
+ await buildCommand({ stage: 'dev' });
423
230
 
424
- // Assert
425
- expect(result.success).toBe(true);
426
- expect(result.exitCode).toBe(0);
231
+ expect(processExitSpy).toHaveBeenCalledWith(1);
427
232
  });
428
233
 
429
- it('should handle combined short options', async () => {
430
- // Arrange
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
- // Act
447
- const result = await commandTester.execute(['-s', 'production', '-v']);
237
+ await buildCommand({ stage: 'dev' });
448
238
 
449
- // Assert
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
- describe('Performance Tests', () => {
456
- it('should complete build within reasonable time', async () => {
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
- // Act
475
- const result = await commandTester.execute([]);
476
- const endTime = Date.now();
245
+ await buildCommand({ stage: 'dev' });
477
246
 
478
- // Assert
479
- expect(result.success).toBe(true);
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
+ });