Files
ccs/tests/unit/web-server/settings-routes-auth.test.js
T
kaitranntt ed6776aadc test(cliproxy): add comprehensive auth token test suite
- auth-token-manager.test.js: token generation, masking, inheritance (35 tests)
- tokens-command.test.js: CLI parsing and flag handling (24 tests)
- settings-routes-auth.test.js: API endpoint responses (22 tests)
- Total: 81 new tests covering all auth token functionality
2025-12-25 16:43:39 -05:00

285 lines
9.7 KiB
JavaScript

/**
* Settings Routes - Auth Tokens API Tests
*
* Tests for the auth tokens API endpoints:
* - GET /api/settings/auth/tokens (masked)
* - GET /api/settings/auth/tokens/raw (unmasked)
* - PUT /api/settings/auth/tokens (update)
* - POST /api/settings/auth/tokens/regenerate-secret
* - POST /api/settings/auth/tokens/reset
*/
const assert = require('assert');
describe('Settings Routes - Auth Tokens API', () => {
// =========================================================================
// maskToken Function Tests (same as in routes)
// =========================================================================
describe('maskToken', () => {
function maskToken(token) {
if (token.length <= 8) return '****';
return `${token.slice(0, 4)}...${token.slice(-4)}`;
}
it('masks long tokens correctly', () => {
assert.strictEqual(maskToken('ccs-internal-managed'), 'ccs-...aged');
});
it('fully masks short tokens', () => {
assert.strictEqual(maskToken('ccs'), '****');
});
it('handles exactly 8 character tokens', () => {
assert.strictEqual(maskToken('12345678'), '****');
});
it('handles 9 character tokens', () => {
assert.strictEqual(maskToken('123456789'), '1234...6789');
});
});
// =========================================================================
// Response Format Tests
// =========================================================================
describe('Response Format', () => {
/**
* Expected response format for GET /api/settings/auth/tokens
*/
function createMaskedResponse(apiKey, managementSecret, apiKeyIsCustom, secretIsCustom) {
function maskToken(token) {
if (token.length <= 8) return '****';
return `${token.slice(0, 4)}...${token.slice(-4)}`;
}
return {
apiKey: {
value: maskToken(apiKey),
isCustom: apiKeyIsCustom,
},
managementSecret: {
value: maskToken(managementSecret),
isCustom: secretIsCustom,
},
};
}
it('includes apiKey with value and isCustom', () => {
const response = createMaskedResponse('test-api-key-123', 'test-secret', true, false);
assert(response.apiKey, 'Should have apiKey');
assert.strictEqual(typeof response.apiKey.value, 'string');
assert.strictEqual(typeof response.apiKey.isCustom, 'boolean');
});
it('includes managementSecret with value and isCustom', () => {
const response = createMaskedResponse('key', 'test-secret-456', false, true);
assert(response.managementSecret, 'Should have managementSecret');
assert.strictEqual(typeof response.managementSecret.value, 'string');
assert.strictEqual(typeof response.managementSecret.isCustom, 'boolean');
});
it('masks values in response', () => {
const response = createMaskedResponse(
'very-long-api-key-12345',
'very-long-secret-67890',
true,
true
);
// Values should be masked (contain ...)
assert(response.apiKey.value.includes('...'), 'API key should be masked');
assert(response.managementSecret.value.includes('...'), 'Secret should be masked');
});
});
// =========================================================================
// PUT /api/settings/auth/tokens - Update Logic Tests
// =========================================================================
describe('PUT /api/settings/auth/tokens - Update Logic', () => {
/**
* Simulates the update logic
*/
function processUpdate(body, currentState) {
const { apiKey, managementSecret } = body;
const newState = { ...currentState };
if (apiKey !== undefined) {
// Empty string -> reset to default
newState.apiKey = apiKey || null;
}
if (managementSecret !== undefined) {
newState.managementSecret = managementSecret || null;
}
return newState;
}
it('updates apiKey when provided', () => {
const current = { apiKey: 'old-key', managementSecret: 'old-secret' };
const result = processUpdate({ apiKey: 'new-key' }, current);
assert.strictEqual(result.apiKey, 'new-key');
assert.strictEqual(result.managementSecret, 'old-secret');
});
it('updates managementSecret when provided', () => {
const current = { apiKey: 'old-key', managementSecret: 'old-secret' };
const result = processUpdate({ managementSecret: 'new-secret' }, current);
assert.strictEqual(result.apiKey, 'old-key');
assert.strictEqual(result.managementSecret, 'new-secret');
});
it('updates both when provided', () => {
const current = { apiKey: 'old-key', managementSecret: 'old-secret' };
const result = processUpdate({ apiKey: 'new-key', managementSecret: 'new-secret' }, current);
assert.strictEqual(result.apiKey, 'new-key');
assert.strictEqual(result.managementSecret, 'new-secret');
});
it('resets to default when empty string provided', () => {
const current = { apiKey: 'custom-key', managementSecret: 'custom-secret' };
const result = processUpdate({ apiKey: '', managementSecret: '' }, current);
assert.strictEqual(result.apiKey, null);
assert.strictEqual(result.managementSecret, null);
});
it('ignores undefined values (no change)', () => {
const current = { apiKey: 'keep-key', managementSecret: 'keep-secret' };
const result = processUpdate({}, current);
assert.strictEqual(result.apiKey, 'keep-key');
assert.strictEqual(result.managementSecret, 'keep-secret');
});
});
// =========================================================================
// POST /api/settings/auth/tokens/regenerate-secret - Tests
// =========================================================================
describe('POST /api/settings/auth/tokens/regenerate-secret', () => {
/**
* Simulates regenerate secret response
*/
function createRegenerateResponse(newSecret) {
function maskToken(token) {
if (token.length <= 8) return '****';
return `${token.slice(0, 4)}...${token.slice(-4)}`;
}
return {
success: true,
managementSecret: {
value: maskToken(newSecret),
isCustom: true,
},
message: 'Restart CLIProxy to apply changes',
};
}
it('returns success: true', () => {
const response = createRegenerateResponse('new-generated-secret-12345');
assert.strictEqual(response.success, true);
});
it('returns masked new secret', () => {
const response = createRegenerateResponse('abcdefghijklmnopqrstuvwxyz');
assert(response.managementSecret.value.includes('...'), 'Should be masked');
assert.strictEqual(response.managementSecret.isCustom, true);
});
it('includes restart message', () => {
const response = createRegenerateResponse('secret');
assert(response.message.includes('Restart'), 'Should include restart instruction');
});
});
// =========================================================================
// POST /api/settings/auth/tokens/reset - Tests
// =========================================================================
describe('POST /api/settings/auth/tokens/reset', () => {
/**
* Simulates reset response
*/
function createResetResponse(defaultApiKey, defaultSecret) {
function maskToken(token) {
if (token.length <= 8) return '****';
return `${token.slice(0, 4)}...${token.slice(-4)}`;
}
return {
success: true,
apiKey: {
value: maskToken(defaultApiKey),
isCustom: false,
},
managementSecret: {
value: maskToken(defaultSecret),
isCustom: false,
},
message: 'Tokens reset to defaults. Restart CLIProxy to apply.',
};
}
it('returns success: true', () => {
const response = createResetResponse('ccs-internal-managed', 'ccs');
assert.strictEqual(response.success, true);
});
it('returns isCustom: false for both tokens', () => {
const response = createResetResponse('ccs-internal-managed', 'ccs');
assert.strictEqual(response.apiKey.isCustom, false);
assert.strictEqual(response.managementSecret.isCustom, false);
});
it('includes reset message', () => {
const response = createResetResponse('key', 'secret');
assert(response.message.includes('reset'), 'Should include reset confirmation');
});
});
// =========================================================================
// Error Response Format Tests
// =========================================================================
describe('Error Response Format', () => {
/**
* Simulates error response
*/
function createErrorResponse(errorMessage) {
return {
error: errorMessage,
};
}
it('includes error message', () => {
const response = createErrorResponse('Something went wrong');
assert.strictEqual(response.error, 'Something went wrong');
});
it('does not include sensitive data in errors', () => {
const response = createErrorResponse('Failed to load config');
assert(!response.apiKey, 'Should not include apiKey');
assert(!response.managementSecret, 'Should not include secret');
});
});
// =========================================================================
// HTTP Status Code Tests
// =========================================================================
describe('HTTP Status Codes', () => {
const HTTP_OK = 200;
const HTTP_INTERNAL_ERROR = 500;
it('uses 200 for successful responses', () => {
assert.strictEqual(HTTP_OK, 200);
});
it('uses 500 for internal errors', () => {
assert.strictEqual(HTTP_INTERNAL_ERROR, 500);
});
});
});