@capawesome/cli
Version:
The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.
145 lines (144 loc) • 6.78 kB
JavaScript
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
import authorizationService from '../../../services/authorization-service.js';
import { prompt } from '../../../utils/prompt.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 updateBundleCommand from './update.js';
// Mock dependencies
vi.mock('@/utils/user-config.js');
vi.mock('@/utils/prompt.js');
vi.mock('@/services/authorization-service.js');
vi.mock('consola');
vi.mock('@/utils/environment.js', () => ({
isInteractive: () => true,
}));
describe('apps-bundles-update', () => {
const mockUserConfig = vi.mocked(userConfig);
const mockPrompt = vi.mocked(prompt);
const mockConsola = vi.mocked(consola);
const mockAuthorizationService = vi.mocked(authorizationService);
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 bundleId = 'bundle-456';
const options = { appId, bundleId };
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
await expect(updateBundleCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
});
it('should update bundle with provided options', async () => {
const appId = 'app-123';
const bundleId = 'bundle-456';
const rollout = 0.5;
const androidMax = '1000';
const iosMin = '2.0.0';
const testToken = 'test-token';
const options = { appId, bundleId, rollout, androidMax, iosMin };
const scope = nock(DEFAULT_API_BASE_URL)
.patch(`/v1/apps/${appId}/bundles/${bundleId}`, {
appId,
appBundleId: bundleId,
maxAndroidAppVersionCode: androidMax,
maxIosAppVersionCode: undefined,
minAndroidAppVersionCode: undefined,
minIosAppVersionCode: iosMin,
rolloutPercentage: rollout,
})
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, { id: bundleId });
await updateBundleCommand.action(options, undefined);
expect(scope.isDone()).toBe(true);
expect(mockConsola.success).toHaveBeenCalledWith('Bundle updated successfully.');
});
it('should prompt for app selection when appId not provided', async () => {
const orgId = 'org-1';
const appId = 'app-1';
const bundleId = 'bundle-456';
const testToken = 'test-token';
const organization = { id: orgId, name: 'Org 1' };
const app = { id: appId, name: 'App 1' };
const options = { bundleId, rollout: 1 };
const orgsScope = nock(DEFAULT_API_BASE_URL)
.get('/v1/organizations')
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, [organization]);
const appsScope = nock(DEFAULT_API_BASE_URL)
.get('/v1/apps')
.query({ organizationId: orgId })
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, [app]);
const updateScope = nock(DEFAULT_API_BASE_URL)
.patch(`/v1/apps/${appId}/bundles/${bundleId}`)
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, { id: bundleId });
mockPrompt
.mockResolvedValueOnce(orgId) // organization selection
.mockResolvedValueOnce(appId); // app selection
await updateBundleCommand.action(options, undefined);
expect(orgsScope.isDone()).toBe(true);
expect(appsScope.isDone()).toBe(true);
expect(updateScope.isDone()).toBe(true);
expect(mockConsola.success).toHaveBeenCalledWith('Bundle updated successfully.');
});
it('should prompt for bundleId when not provided', async () => {
const appId = 'app-123';
const bundleId = 'bundle-456';
const testToken = 'test-token';
const options = { appId };
const scope = nock(DEFAULT_API_BASE_URL)
.patch(`/v1/apps/${appId}/bundles/${bundleId}`)
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, { id: bundleId });
mockPrompt.mockResolvedValueOnce(bundleId); // bundle ID input
await updateBundleCommand.action(options, undefined);
expect(scope.isDone()).toBe(true);
expect(mockPrompt).toHaveBeenCalledWith('Enter the bundle ID:', { type: 'text' });
expect(mockConsola.success).toHaveBeenCalledWith('Bundle updated successfully.');
});
it('should handle API error during update', async () => {
const appId = 'app-123';
const bundleId = 'bundle-456';
const testToken = 'test-token';
const options = { appId, bundleId };
const scope = nock(DEFAULT_API_BASE_URL)
.patch(`/v1/apps/${appId}/bundles/${bundleId}`)
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(404, { message: 'Bundle not found' });
await expect(updateBundleCommand.action(options, undefined)).rejects.toThrow();
expect(scope.isDone()).toBe(true);
});
it('should handle error when no organizations exist', async () => {
const testToken = 'test-token';
const options = {};
const scope = nock(DEFAULT_API_BASE_URL)
.get('/v1/organizations')
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, []);
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => {
throw new Error(`process.exit called with code ${code}`);
});
try {
await updateBundleCommand.action(options, undefined);
}
catch (error) {
expect(error.message).toBe('process.exit called with code 1');
}
expect(scope.isDone()).toBe(true);
expect(mockConsola.error).toHaveBeenCalledWith('You must create an organization before updating a bundle.');
expect(exitSpy).toHaveBeenCalledWith(1);
});
});