@capawesome/cli
Version:
The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.
123 lines (122 loc) • 6.21 kB
JavaScript
import { DEFAULT_API_BASE_URL, DEFAULT_CONSOLE_BASE_URL } from '../config/consts.js';
import configService from '../services/config.js';
import sessionCodesService from '../services/session-code.js';
import sessionsService from '../services/sessions.js';
import { prompt } from '../utils/prompt.js';
import userConfig from '../utils/user-config.js';
import consola from 'consola';
import nock from 'nock';
import open from 'open';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import loginCommand from './login.js';
// Mock dependencies
vi.mock('@/utils/user-config.js');
vi.mock('@/services/session-code.js');
vi.mock('@/services/sessions.js');
vi.mock('@/services/config.js');
vi.mock('open');
vi.mock('consola');
vi.mock('@/utils/prompt.js');
vi.mock('@/utils/environment.js', () => ({
isInteractive: () => true,
}));
vi.mock('@/utils/environment.js', () => ({
isInteractive: () => true,
}));
describe('login', () => {
const mockUserConfig = vi.mocked(userConfig);
const mockSessionCodesService = vi.mocked(sessionCodesService);
const mockSessionsService = vi.mocked(sessionsService);
const mockConfigService = vi.mocked(configService);
const mockOpen = vi.mocked(open);
const mockConsola = vi.mocked(consola);
const mockPrompt = vi.mocked(prompt);
beforeEach(() => {
vi.clearAllMocks();
mockUserConfig.write.mockImplementation(() => { });
mockUserConfig.read.mockReturnValue({});
// Mock config service to return consistent URLs
mockConfigService.getValueForKey.mockImplementation((key) => {
if (key === 'CONSOLE_BASE_URL')
return Promise.resolve(DEFAULT_CONSOLE_BASE_URL);
if (key === 'API_BASE_URL')
return Promise.resolve(DEFAULT_API_BASE_URL);
return Promise.resolve('');
});
vi.spyOn(process, 'exit').mockImplementation((code) => {
throw new Error(`Process exited with code ${code}`);
});
});
afterEach(() => {
nock.cleanAll();
vi.restoreAllMocks();
});
it('should use the provided token for authentication', async () => {
const testToken = 'valid-token-123';
const options = { token: testToken };
// Mock userConfig.read to return our test token after it's written
mockUserConfig.read.mockReturnValue({ token: testToken });
// Set up nock to intercept the /v1/users/me request
const scope = nock(DEFAULT_API_BASE_URL)
.get('/v1/users/me')
.matchHeader('Authorization', `Bearer ${testToken}`)
.reply(200, { id: 'user-123', email: 'test@example.com' });
await loginCommand.action(options, undefined);
expect(mockUserConfig.write).toHaveBeenCalledWith({ token: testToken });
expect(scope.isDone()).toBe(true);
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
});
it('should open the browser', async () => {
const options = {};
mockPrompt
.mockResolvedValueOnce('browser') // authentication method
.mockResolvedValueOnce(true); // should proceed
mockSessionCodesService.create.mockResolvedValue({
id: 'device-code-123',
code: 'ABCD1234',
});
mockSessionsService.create.mockResolvedValue({ id: 'session-123' });
// Mock userConfig.read to return the session token
mockUserConfig.read.mockReturnValue({ token: 'session-123' });
// Set up nock to intercept the /v1/users/me request
const scope = nock(DEFAULT_API_BASE_URL)
.get('/v1/users/me')
.matchHeader('Authorization', `Bearer session-123`)
.reply(200, { id: 'user-123', email: 'test@example.com' });
await loginCommand.action(options, undefined);
expect(mockPrompt).toHaveBeenCalledWith('How would you like to authenticate Capawesome CLI?', {
type: 'select',
options: [
{ label: 'Login with a web browser', value: 'browser' },
{ label: 'Paste an authentication token', value: 'token' },
],
});
expect(mockSessionCodesService.create).toHaveBeenCalled();
expect(mockConsola.box).toHaveBeenCalledWith('Copy your one-time code: ABCD-1234');
expect(mockOpen).toHaveBeenCalledWith(`${DEFAULT_CONSOLE_BASE_URL}/login/device`);
expect(scope.isDone()).toBe(true);
expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
});
it('should throw an error because the provided token is empty', async () => {
const options = { token: '' };
// This test should exit early without making API calls since empty token is caught in login logic
await expect(loginCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
expect(mockConsola.error).toHaveBeenCalledWith(`Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
});
it('should throw an error because the provided token is invalid', async () => {
const invalidToken = 'invalid-token';
const options = { token: invalidToken };
// Mock userConfig.read to return our invalid token after it's written
mockUserConfig.read.mockReturnValue({ token: invalidToken });
// Set up nock to intercept the /v1/users/me request and return 401
const scope = nock(DEFAULT_API_BASE_URL)
.get('/v1/users/me')
.matchHeader('Authorization', `Bearer ${invalidToken}`)
.reply(401, { message: 'Unauthorized' });
await expect(loginCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
expect(mockUserConfig.write).toHaveBeenCalledWith({ token: invalidToken });
expect(mockUserConfig.write).toHaveBeenCalledWith({}); // Clears token on error
expect(scope.isDone()).toBe(true);
expect(mockConsola.error).toHaveBeenCalledWith(`Invalid token. Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
});
});