242 lines
7 KiB
TypeScript
242 lines
7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import * as path from 'path';
|
|
|
|
import { validateConfigUpdatePayload } from '../../../src/main/ipc/configValidation';
|
|
|
|
describe('configValidation', () => {
|
|
it('accepts valid general updates', () => {
|
|
const result = validateConfigUpdatePayload('general', {
|
|
theme: 'system',
|
|
launchAtLogin: true,
|
|
});
|
|
|
|
expect(result.valid).toBe(true);
|
|
if (result.valid) {
|
|
expect(result.section).toBe('general');
|
|
expect(result.data).toEqual({
|
|
theme: 'system',
|
|
launchAtLogin: true,
|
|
});
|
|
}
|
|
});
|
|
|
|
it('accepts general.autoExpandAIGroups boolean toggle', () => {
|
|
const resultOn = validateConfigUpdatePayload('general', { autoExpandAIGroups: true });
|
|
expect(resultOn.valid).toBe(true);
|
|
if (resultOn.valid) {
|
|
expect(resultOn.data).toEqual({ autoExpandAIGroups: true });
|
|
}
|
|
|
|
const resultOff = validateConfigUpdatePayload('general', { autoExpandAIGroups: false });
|
|
expect(resultOff.valid).toBe(true);
|
|
if (resultOff.valid) {
|
|
expect(resultOff.data).toEqual({ autoExpandAIGroups: false });
|
|
}
|
|
});
|
|
|
|
it('rejects non-boolean general.autoExpandAIGroups', () => {
|
|
const result = validateConfigUpdatePayload('general', { autoExpandAIGroups: 'yes' });
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('boolean');
|
|
}
|
|
});
|
|
|
|
it('accepts absolute general.claudeRootPath updates', () => {
|
|
const result = validateConfigUpdatePayload('general', {
|
|
claudeRootPath: '/Users/test/.claude',
|
|
});
|
|
|
|
expect(result.valid).toBe(true);
|
|
if (result.valid) {
|
|
expect(result.section).toBe('general');
|
|
expect(result.data).toEqual({
|
|
claudeRootPath: path.resolve('/Users/test/.claude'),
|
|
});
|
|
}
|
|
});
|
|
|
|
it('rejects relative general.claudeRootPath updates', () => {
|
|
const result = validateConfigUpdatePayload('general', {
|
|
claudeRootPath: '.claude',
|
|
});
|
|
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('absolute path');
|
|
}
|
|
});
|
|
|
|
it('rejects invalid section names', () => {
|
|
const result = validateConfigUpdatePayload('invalid-section', { theme: 'dark' });
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('Section must be one of');
|
|
}
|
|
});
|
|
|
|
it('rejects unknown notification keys', () => {
|
|
const result = validateConfigUpdatePayload('notifications', { unknownField: true });
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('not supported');
|
|
}
|
|
});
|
|
|
|
it('accepts valid notifications.triggers payload', () => {
|
|
const result = validateConfigUpdatePayload('notifications', {
|
|
triggers: [
|
|
{
|
|
id: 'trigger-1',
|
|
name: 'test',
|
|
enabled: true,
|
|
contentType: 'tool_result',
|
|
mode: 'error_status',
|
|
requireError: true,
|
|
},
|
|
],
|
|
});
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('rejects invalid notifications.triggers payload', () => {
|
|
const result = validateConfigUpdatePayload('notifications', {
|
|
triggers: [{ id: 'missing-required-fields' }],
|
|
});
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('valid trigger');
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
'notifyOnLeadInbox',
|
|
'notifyOnUserInbox',
|
|
'notifyOnClarifications',
|
|
'notifyOnStatusChange',
|
|
'notifyOnTeamLaunched',
|
|
'autoResumeOnRateLimit',
|
|
'statusChangeOnlySolo',
|
|
] as const)('accepts boolean %s toggle', (key) => {
|
|
const resultOn = validateConfigUpdatePayload('notifications', { [key]: true });
|
|
expect(resultOn.valid).toBe(true);
|
|
if (resultOn.valid) {
|
|
expect(resultOn.data).toEqual({ [key]: true });
|
|
}
|
|
|
|
const resultOff = validateConfigUpdatePayload('notifications', { [key]: false });
|
|
expect(resultOff.valid).toBe(true);
|
|
if (resultOff.valid) {
|
|
expect(resultOff.data).toEqual({ [key]: false });
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
'notifyOnLeadInbox',
|
|
'notifyOnUserInbox',
|
|
'notifyOnClarifications',
|
|
'notifyOnStatusChange',
|
|
'notifyOnTeamLaunched',
|
|
'autoResumeOnRateLimit',
|
|
'statusChangeOnlySolo',
|
|
] as const)('rejects non-boolean %s', (key) => {
|
|
const result = validateConfigUpdatePayload('notifications', { [key]: 'yes' });
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('boolean');
|
|
}
|
|
});
|
|
|
|
it('accepts valid statusChangeStatuses string array', () => {
|
|
const result = validateConfigUpdatePayload('notifications', {
|
|
statusChangeStatuses: ['completed', 'in_progress'],
|
|
});
|
|
expect(result.valid).toBe(true);
|
|
if (result.valid) {
|
|
expect(result.data).toEqual({ statusChangeStatuses: ['completed', 'in_progress'] });
|
|
}
|
|
});
|
|
|
|
it('accepts empty statusChangeStatuses array', () => {
|
|
const result = validateConfigUpdatePayload('notifications', {
|
|
statusChangeStatuses: [],
|
|
});
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('rejects non-array statusChangeStatuses', () => {
|
|
const result = validateConfigUpdatePayload('notifications', {
|
|
statusChangeStatuses: true,
|
|
});
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('string[]');
|
|
}
|
|
});
|
|
|
|
it('rejects statusChangeStatuses with non-string items', () => {
|
|
const result = validateConfigUpdatePayload('notifications', {
|
|
statusChangeStatuses: [42],
|
|
});
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('string[]');
|
|
}
|
|
});
|
|
|
|
it('rejects out-of-range snoozeMinutes', () => {
|
|
const result = validateConfigUpdatePayload('notifications', { snoozeMinutes: 0 });
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('between 1 and');
|
|
}
|
|
});
|
|
|
|
it('accepts valid display updates', () => {
|
|
const result = validateConfigUpdatePayload('display', {
|
|
compactMode: true,
|
|
syntaxHighlighting: false,
|
|
});
|
|
|
|
expect(result.valid).toBe(true);
|
|
if (result.valid) {
|
|
expect(result.section).toBe('display');
|
|
expect(result.data).toEqual({
|
|
compactMode: true,
|
|
syntaxHighlighting: false,
|
|
});
|
|
}
|
|
});
|
|
|
|
it('accepts Codex provider connection beta updates', () => {
|
|
const result = validateConfigUpdatePayload('providerConnections', {
|
|
codex: {
|
|
apiKeyBetaEnabled: true,
|
|
authMode: 'api_key',
|
|
},
|
|
});
|
|
|
|
expect(result.valid).toBe(true);
|
|
if (result.valid) {
|
|
expect(result.data).toEqual({
|
|
codex: {
|
|
apiKeyBetaEnabled: true,
|
|
authMode: 'api_key',
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
it('rejects invalid Codex auth modes in providerConnections', () => {
|
|
const result = validateConfigUpdatePayload('providerConnections', {
|
|
codex: {
|
|
authMode: 'auto',
|
|
},
|
|
});
|
|
|
|
expect(result.valid).toBe(false);
|
|
if (!result.valid) {
|
|
expect(result.error).toContain('providerConnections.codex.authMode');
|
|
}
|
|
});
|
|
});
|