UNPKG

@capawesome/cli

Version:

The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.

329 lines (328 loc) 15.5 kB
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js'; import authorizationService from '../../../services/authorization-service.js'; import { isReadable } from '../../../utils/file.js'; import userConfig from '../../../utils/user-config.js'; import consola from 'consola'; import nock from 'nock'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import registerCommand from './register.js'; // Mock dependencies vi.mock('@/utils/user-config.js'); vi.mock('@/utils/prompt.js'); vi.mock('@/services/authorization-service.js'); vi.mock('@/utils/file.js'); vi.mock('@/utils/zip.js'); vi.mock('@/utils/buffer.js'); vi.mock('@/utils/private-key.js'); vi.mock('@/utils/hash.js'); vi.mock('@/utils/signature.js'); vi.mock('consola'); describe('apps-liveupdates-register', () => { const mockUserConfig = vi.mocked(userConfig); const mockAuthorizationService = vi.mocked(authorizationService); const mockIsReadable = vi.mocked(isReadable); const mockConsola = vi.mocked(consola); beforeEach(() => { vi.clearAllMocks(); mockUserConfig.read.mockReturnValue({ token: 'test-token' }); mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true); mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token'); vi.spyOn(process, 'exit').mockImplementation((code) => { throw new Error(`Process exited with code ${code}`); }); }); afterEach(() => { nock.cleanAll(); vi.restoreAllMocks(); }); it('should require authentication', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const options = { appId, url: bundleUrl, rolloutPercentage: 1 }; mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false); await expect(registerCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1'); expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Set the `CAPAWESOME_TOKEN` environment variable or use the `--token` option.'); }); it('should register bundle with self-hosted URL', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundleId = 'bundle-456'; const testToken = 'test-token'; const options = { appId, url: bundleUrl, rolloutPercentage: 1, yes: true, }; const appScope = nock(DEFAULT_API_BASE_URL) .get(`/v1/apps/${appId}`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(200, { id: appId, name: 'Test App' }); const bundleScope = nock(DEFAULT_API_BASE_URL) .post(`/v1/apps/${appId}/bundles`, { appId, url: bundleUrl, artifactType: 'zip', rolloutPercentage: 0.01, }) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(201, { id: bundleId, appBuildId: 'build-789' }); await registerCommand.action(options, undefined); expect(appScope.isDone()).toBe(true); expect(bundleScope.isDone()).toBe(true); expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`); expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.'); }); it('should pass gitRef to API when provided', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundleId = 'bundle-456'; const testToken = 'test-token'; const gitRef = 'v1.0.0'; const options = { appId, url: bundleUrl, rolloutPercentage: 1, gitRef, yes: true, }; const appScope = nock(DEFAULT_API_BASE_URL) .get(`/v1/apps/${appId}`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(200, { id: appId, name: 'Test App' }); const bundleScope = nock(DEFAULT_API_BASE_URL) .post(`/v1/apps/${appId}/bundles`, { appId, url: bundleUrl, artifactType: 'zip', gitRef, rolloutPercentage: 0.01, }) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(201, { id: bundleId, appBuildId: 'build-789' }); await registerCommand.action(options, undefined); expect(appScope.isDone()).toBe(true); expect(bundleScope.isDone()).toBe(true); expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`); expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.'); }); it('should register bundle with checksum when path is provided', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundlePath = './bundle.zip'; const testHash = 'test-hash'; const bundleId = 'bundle-456'; const testToken = 'test-token'; const testBuffer = Buffer.from('test'); const options = { appId, url: bundleUrl, path: bundlePath, rolloutPercentage: 1, yes: true, }; mockIsReadable.mockResolvedValue(true); // Mock utility functions const mockZip = await import('../../../utils/zip.js'); const mockBuffer = await import('../../../utils/buffer.js'); const mockHash = await import('../../../utils/hash.js'); vi.mocked(mockZip.default.isZipped).mockReturnValue(true); vi.mocked(mockBuffer.createBufferFromPath).mockResolvedValue(testBuffer); vi.mocked(mockHash.createHash).mockResolvedValue(testHash); const appScope = nock(DEFAULT_API_BASE_URL) .get(`/v1/apps/${appId}`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(200, { id: appId, name: 'Test App' }); const bundleScope = nock(DEFAULT_API_BASE_URL) .post(`/v1/apps/${appId}/bundles`, { appId, url: bundleUrl, checksum: testHash, artifactType: 'zip', rolloutPercentage: 0.01, }) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(201, { id: bundleId, appBuildId: 'build-789' }); await registerCommand.action(options, undefined); expect(appScope.isDone()).toBe(true); expect(bundleScope.isDone()).toBe(true); expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`); expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.'); }); it('should register bundle with signature when private key is provided', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundlePath = './bundle.zip'; const privateKeyPath = 'private-key.pem'; const testHash = 'test-hash'; const testSignature = 'test-signature'; const bundleId = 'bundle-456'; const testToken = 'test-token'; const testBuffer = Buffer.from('test'); const options = { appId, url: bundleUrl, path: bundlePath, privateKey: privateKeyPath, rolloutPercentage: 1, yes: true, }; mockIsReadable.mockImplementation((path) => { if (path === privateKeyPath) return Promise.resolve(true); if (path === bundlePath) return Promise.resolve(true); return Promise.resolve(false); }); // Mock utility functions const mockZip = await import('../../../utils/zip.js'); const mockBuffer = await import('../../../utils/buffer.js'); const mockPrivateKey = await import('../../../utils/private-key.js'); const mockHash = await import('../../../utils/hash.js'); const mockSignature = await import('../../../utils/signature.js'); vi.mocked(mockZip.default.isZipped).mockReturnValue(true); vi.mocked(mockBuffer.createBufferFromPath).mockResolvedValue(testBuffer); vi.mocked(mockBuffer.isPrivateKeyContent).mockReturnValue(false); vi.mocked(mockPrivateKey.formatPrivateKey).mockReturnValue('formatted-private-key'); vi.mocked(mockBuffer.createBufferFromString).mockReturnValue(testBuffer); vi.mocked(mockHash.createHash).mockResolvedValue(testHash); vi.mocked(mockSignature.createSignature).mockResolvedValue(testSignature); const appScope = nock(DEFAULT_API_BASE_URL) .get(`/v1/apps/${appId}`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(200, { id: appId, name: 'Test App' }); const bundleScope = nock(DEFAULT_API_BASE_URL) .post(`/v1/apps/${appId}/bundles`, { appId, url: bundleUrl, checksum: testHash, signature: testSignature, artifactType: 'zip', rolloutPercentage: 0.01, }) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(201, { id: bundleId, appBuildId: 'build-789' }); await registerCommand.action(options, undefined); expect(appScope.isDone()).toBe(true); expect(bundleScope.isDone()).toBe(true); expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`); expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.'); }); it('should handle private key with plain text content', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundlePath = './bundle.zip'; const privateKeyContent = '-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCgxvzJrMCbmtjb\n-----END PRIVATE KEY-----'; const testHash = 'test-hash'; const testSignature = 'test-signature'; const bundleId = 'bundle-456'; const testToken = 'test-token'; const testBuffer = Buffer.from('test'); const options = { appId, url: bundleUrl, path: bundlePath, privateKey: privateKeyContent, rolloutPercentage: 1, yes: true, }; mockIsReadable.mockResolvedValue(true); // Mock utility functions const mockZip = await import('../../../utils/zip.js'); const mockBuffer = await import('../../../utils/buffer.js'); const mockPrivateKey = await import('../../../utils/private-key.js'); const mockHash = await import('../../../utils/hash.js'); const mockSignature = await import('../../../utils/signature.js'); vi.mocked(mockZip.default.isZipped).mockReturnValue(true); vi.mocked(mockBuffer.createBufferFromPath).mockResolvedValue(testBuffer); vi.mocked(mockBuffer.createBufferFromString).mockReturnValue(testBuffer); vi.mocked(mockBuffer.isPrivateKeyContent).mockReturnValue(true); vi.mocked(mockPrivateKey.formatPrivateKey).mockReturnValue('formatted-private-key'); vi.mocked(mockHash.createHash).mockResolvedValue(testHash); vi.mocked(mockSignature.createSignature).mockResolvedValue(testSignature); const appScope = nock(DEFAULT_API_BASE_URL) .get(`/v1/apps/${appId}`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(200, { id: appId, name: 'Test App' }); const bundleScope = nock(DEFAULT_API_BASE_URL) .post(`/v1/apps/${appId}/bundles`, { appId, url: bundleUrl, checksum: testHash, signature: testSignature, artifactType: 'zip', rolloutPercentage: 0.01, }) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(201, { id: bundleId, appBuildId: 'build-789' }); await registerCommand.action(options, undefined); expect(appScope.isDone()).toBe(true); expect(bundleScope.isDone()).toBe(true); expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`); expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.'); }); it('should handle private key file not found', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundlePath = './bundle.zip'; const privateKeyPath = 'nonexistent-key.pem'; const options = { appId, url: bundleUrl, path: bundlePath, privateKey: privateKeyPath, rolloutPercentage: 1, }; mockIsReadable.mockImplementation((path) => { if (path === privateKeyPath) return Promise.resolve(false); return Promise.resolve(true); }); // Mock utility functions const mockZip = await import('../../../utils/zip.js'); const mockBuffer = await import('../../../utils/buffer.js'); vi.mocked(mockZip.default.isZipped).mockReturnValue(true); vi.mocked(mockBuffer.isPrivateKeyContent).mockReturnValue(false); await expect(registerCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1'); expect(mockConsola.error).toHaveBeenCalledWith(`The private key file does not exist or is not accessible: ${privateKeyPath}`); }); it('should validate path must be a zip file', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const bundlePath = './dist'; const options = { appId, url: bundleUrl, path: bundlePath, rolloutPercentage: 1, }; mockIsReadable.mockResolvedValue(true); // Mock zip utility to return false const mockZip = await import('../../../utils/zip.js'); vi.mocked(mockZip.default.isZipped).mockReturnValue(false); await expect(registerCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1'); expect(mockConsola.error).toHaveBeenCalledWith('The path must be a zip file when providing a URL.'); }); it('should handle API error during registration', async () => { const appId = 'app-123'; const bundleUrl = 'https://example.com/bundle.zip'; const testToken = 'test-token'; const options = { appId, url: bundleUrl, rolloutPercentage: 1, yes: true, }; const appScope = nock(DEFAULT_API_BASE_URL) .get(`/v1/apps/${appId}`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(200, { id: appId, name: 'Test App' }); const bundleScope = nock(DEFAULT_API_BASE_URL) .post(`/v1/apps/${appId}/bundles`) .matchHeader('Authorization', `Bearer ${testToken}`) .reply(400, { message: 'Invalid bundle data' }); await expect(registerCommand.action(options, undefined)).rejects.toThrow(); expect(appScope.isDone()).toBe(true); expect(bundleScope.isDone()).toBe(true); }); });