test: add global test setup with screen limits and reach 1337 tests

- Add test/setup.ts with max 10 concurrent screen sessions limiter
- Add orphaned Claude/screen process cleanup before/after tests
- Add semaphore-based screen slot acquisition for concurrency control
- Update vitest.config.ts with setupFiles and fileParallelism: false
- Add 16 new test files for comprehensive coverage
- Update README badge to show 1337 total tests
- Update CLAUDE.md with test setup documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-22 11:30:10 +01:00
co-authored by Claude Opus 4.5
parent 675e5f2616
commit 34fb33fbc6
22 changed files with 11761 additions and 3 deletions
+375 -2
View File
@@ -1,8 +1,8 @@
/**
* @fileoverview Tests for types module utility functions
*
* Tests the helper functions for creating response objects
* and initial state structures.
* Tests the helper functions for creating response objects,
* initial state structures, type guards, and error handling utilities.
*/
import { describe, it, expect } from 'vitest';
@@ -13,6 +13,10 @@ import {
createInitialRalphSessionState,
createInitialState,
ErrorMessages,
ApiErrorCode,
DEFAULT_CONFIG,
isError,
getErrorMessage,
} from '../src/types.js';
describe('types utility functions', () => {
@@ -50,6 +54,35 @@ describe('types utility functions', () => {
expect(response.error).toBeDefined();
}
});
it('should prefer details over default message when provided', () => {
const response = createErrorResponse('NOT_FOUND', 'Custom not found message');
expect(response.error).toBe('Custom not found message');
});
it('should handle whitespace-only details', () => {
const response = createErrorResponse('INVALID_INPUT', ' ');
expect(response.error).toBe(' ');
});
it('should handle very long error details', () => {
const longDetails = 'x'.repeat(10000);
const response = createErrorResponse('INTERNAL_ERROR', longDetails);
expect(response.error).toBe(longDetails);
expect(response.error?.length).toBe(10000);
});
it('should handle special characters in details', () => {
const specialDetails = 'Error: <script>alert("xss")</script> & more "quotes"';
const response = createErrorResponse('OPERATION_FAILED', specialDetails);
expect(response.error).toBe(specialDetails);
});
it('should handle unicode in details', () => {
const unicodeDetails = 'Error: 日本語 🚀 émojis';
const response = createErrorResponse('INVALID_INPUT', unicodeDetails);
expect(response.error).toBe(unicodeDetails);
});
});
describe('createSuccessResponse', () => {
@@ -67,6 +100,69 @@ describe('types utility functions', () => {
expect(response.success).toBe(true);
expect(response.data).toEqual(data);
});
it('should handle null data', () => {
const response = createSuccessResponse(null);
expect(response.success).toBe(true);
expect(response.data).toBeNull();
});
it('should handle undefined explicitly', () => {
const response = createSuccessResponse(undefined);
expect(response.success).toBe(true);
expect(response.data).toBeUndefined();
});
it('should handle array data', () => {
const data = [1, 2, 3, { nested: true }];
const response = createSuccessResponse(data);
expect(response.data).toEqual(data);
});
it('should handle nested object data', () => {
const data = {
level1: {
level2: {
level3: {
value: 'deep',
},
},
},
};
const response = createSuccessResponse(data);
expect(response.data).toEqual(data);
});
it('should handle string data', () => {
const response = createSuccessResponse('simple string');
expect(response.data).toBe('simple string');
});
it('should handle number data', () => {
const response = createSuccessResponse(42);
expect(response.data).toBe(42);
});
it('should handle boolean data', () => {
const response = createSuccessResponse(true);
expect(response.data).toBe(true);
});
it('should handle Date data', () => {
const date = new Date('2024-01-15');
const response = createSuccessResponse(date);
expect(response.data).toEqual(date);
});
it('should handle empty object data', () => {
const response = createSuccessResponse({});
expect(response.data).toEqual({});
});
it('should handle empty array data', () => {
const response = createSuccessResponse([]);
expect(response.data).toEqual([]);
});
});
describe('createInitialRalphTrackerState', () => {
@@ -82,6 +178,27 @@ describe('types utility functions', () => {
expect(state.lastActivity).toBeLessThanOrEqual(Date.now());
expect(state.elapsedHours).toBeNull();
});
it('should create fresh instances each time', () => {
const state1 = createInitialRalphTrackerState();
const state2 = createInitialRalphTrackerState();
expect(state1).not.toBe(state2);
expect(state1).toEqual(state2);
});
it('should have correct types for all fields', () => {
const state = createInitialRalphTrackerState();
expect(typeof state.enabled).toBe('boolean');
expect(typeof state.active).toBe('boolean');
expect(typeof state.cycleCount).toBe('number');
expect(state.maxIterations === null || typeof state.maxIterations === 'number').toBe(true);
expect(state.completionPhrase === null || typeof state.completionPhrase === 'string').toBe(true);
expect(state.startedAt === null || typeof state.startedAt === 'number').toBe(true);
expect(typeof state.lastActivity).toBe('number');
expect(state.elapsedHours === null || typeof state.elapsedHours === 'number').toBe(true);
});
});
describe('createInitialRalphSessionState', () => {
@@ -95,6 +212,33 @@ describe('types utility functions', () => {
expect(state.todos).toEqual([]);
expect(state.lastUpdated).toBeLessThanOrEqual(Date.now());
});
it('should handle empty session ID', () => {
const state = createInitialRalphSessionState('');
expect(state.sessionId).toBe('');
});
it('should handle UUID-style session ID', () => {
const uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
const state = createInitialRalphSessionState(uuid);
expect(state.sessionId).toBe(uuid);
});
it('should create independent loop state', () => {
const state1 = createInitialRalphSessionState('session-1');
const state2 = createInitialRalphSessionState('session-2');
state1.loop.active = true;
expect(state2.loop.active).toBe(false);
});
it('should create independent todos array', () => {
const state1 = createInitialRalphSessionState('session-1');
const state2 = createInitialRalphSessionState('session-2');
state1.todos.push({ id: 'todo-1', content: 'Test', status: 'pending', detectedAt: Date.now() });
expect(state2.todos).toHaveLength(0);
});
});
describe('createInitialState', () => {
@@ -127,5 +271,234 @@ describe('types utility functions', () => {
expect(state.ralphLoop.tasksCompleted).toBe(0);
expect(state.ralphLoop.tasksGenerated).toBe(0);
});
it('should create fresh instances each time', () => {
const state1 = createInitialState();
const state2 = createInitialState();
expect(state1).not.toBe(state2);
state1.sessions['test'] = {} as any;
expect(state2.sessions['test']).toBeUndefined();
});
it('should have all Ralph Loop fields', () => {
const state = createInitialState();
expect(state.ralphLoop).toHaveProperty('status');
expect(state.ralphLoop).toHaveProperty('startedAt');
expect(state.ralphLoop).toHaveProperty('minDurationMs');
expect(state.ralphLoop).toHaveProperty('tasksCompleted');
expect(state.ralphLoop).toHaveProperty('tasksGenerated');
expect(state.ralphLoop).toHaveProperty('lastCheckAt');
});
it('should have all config fields', () => {
const state = createInitialState();
expect(state.config).toHaveProperty('pollIntervalMs');
expect(state.config).toHaveProperty('defaultTimeoutMs');
expect(state.config).toHaveProperty('maxConcurrentSessions');
expect(state.config).toHaveProperty('stateFilePath');
expect(state.config).toHaveProperty('respawn');
expect(state.config).toHaveProperty('lastUsedCase');
});
it('should have all respawn config fields', () => {
const state = createInitialState();
expect(state.config.respawn).toHaveProperty('idleTimeoutMs');
expect(state.config.respawn).toHaveProperty('updatePrompt');
expect(state.config.respawn).toHaveProperty('interStepDelayMs');
expect(state.config.respawn).toHaveProperty('enabled');
expect(state.config.respawn).toHaveProperty('sendClear');
expect(state.config.respawn).toHaveProperty('sendInit');
});
});
describe('ErrorMessages', () => {
it('should have messages for all error codes', () => {
const codes: ApiErrorCode[] = [
ApiErrorCode.NOT_FOUND,
ApiErrorCode.INVALID_INPUT,
ApiErrorCode.SESSION_BUSY,
ApiErrorCode.OPERATION_FAILED,
ApiErrorCode.ALREADY_EXISTS,
ApiErrorCode.INTERNAL_ERROR,
];
for (const code of codes) {
expect(ErrorMessages[code]).toBeDefined();
expect(typeof ErrorMessages[code]).toBe('string');
expect(ErrorMessages[code].length).toBeGreaterThan(0);
}
});
it('should have unique messages', () => {
const messages = Object.values(ErrorMessages);
const uniqueMessages = new Set(messages);
expect(uniqueMessages.size).toBe(messages.length);
});
});
describe('DEFAULT_CONFIG', () => {
it('should have sensible default poll interval', () => {
expect(DEFAULT_CONFIG.pollIntervalMs).toBeGreaterThanOrEqual(100);
expect(DEFAULT_CONFIG.pollIntervalMs).toBeLessThanOrEqual(10000);
});
it('should have sensible default timeout', () => {
expect(DEFAULT_CONFIG.defaultTimeoutMs).toBeGreaterThanOrEqual(60000);
});
it('should have sensible max concurrent sessions', () => {
expect(DEFAULT_CONFIG.maxConcurrentSessions).toBeGreaterThan(0);
expect(DEFAULT_CONFIG.maxConcurrentSessions).toBeLessThanOrEqual(100);
});
it('should have respawn disabled by default', () => {
expect(DEFAULT_CONFIG.respawn.enabled).toBe(false);
});
it('should have valid respawn config', () => {
expect(DEFAULT_CONFIG.respawn.idleTimeoutMs).toBeGreaterThan(0);
expect(DEFAULT_CONFIG.respawn.interStepDelayMs).toBeGreaterThan(0);
expect(typeof DEFAULT_CONFIG.respawn.updatePrompt).toBe('string');
expect(typeof DEFAULT_CONFIG.respawn.sendClear).toBe('boolean');
expect(typeof DEFAULT_CONFIG.respawn.sendInit).toBe('boolean');
});
it('should have null lastUsedCase initially', () => {
expect(DEFAULT_CONFIG.lastUsedCase).toBeNull();
});
});
describe('isError', () => {
it('should return true for Error instances', () => {
expect(isError(new Error('test'))).toBe(true);
expect(isError(new TypeError('test'))).toBe(true);
expect(isError(new RangeError('test'))).toBe(true);
expect(isError(new SyntaxError('test'))).toBe(true);
});
it('should return false for non-Error values', () => {
expect(isError('error string')).toBe(false);
expect(isError(123)).toBe(false);
expect(isError(null)).toBe(false);
expect(isError(undefined)).toBe(false);
expect(isError({})).toBe(false);
expect(isError({ message: 'fake error' })).toBe(false);
});
it('should return false for arrays', () => {
expect(isError([])).toBe(false);
expect(isError([new Error('test')])).toBe(false);
});
it('should return false for functions', () => {
expect(isError(() => {})).toBe(false);
expect(isError(Error)).toBe(false);
});
});
describe('getErrorMessage', () => {
it('should extract message from Error objects', () => {
expect(getErrorMessage(new Error('test message'))).toBe('test message');
expect(getErrorMessage(new TypeError('type error'))).toBe('type error');
});
it('should return string errors directly', () => {
expect(getErrorMessage('string error')).toBe('string error');
});
it('should extract message from objects with message property', () => {
expect(getErrorMessage({ message: 'object message' })).toBe('object message');
});
it('should return default message for null/undefined', () => {
expect(getErrorMessage(null)).toBe('An unknown error occurred');
expect(getErrorMessage(undefined)).toBe('An unknown error occurred');
});
it('should return default message for numbers', () => {
expect(getErrorMessage(42)).toBe('An unknown error occurred');
expect(getErrorMessage(NaN)).toBe('An unknown error occurred');
});
it('should return default message for booleans', () => {
expect(getErrorMessage(true)).toBe('An unknown error occurred');
expect(getErrorMessage(false)).toBe('An unknown error occurred');
});
it('should return default message for objects without message', () => {
expect(getErrorMessage({})).toBe('An unknown error occurred');
expect(getErrorMessage({ error: 'test' })).toBe('An unknown error occurred');
});
it('should handle empty string message', () => {
expect(getErrorMessage(new Error(''))).toBe('');
expect(getErrorMessage('')).toBe('');
expect(getErrorMessage({ message: '' })).toBe('');
});
it('should handle non-string message property', () => {
expect(getErrorMessage({ message: 123 })).toBe('123');
expect(getErrorMessage({ message: null })).toBe('null');
expect(getErrorMessage({ message: { nested: true } })).toBe('[object Object]');
});
it('should handle arrays', () => {
expect(getErrorMessage([])).toBe('An unknown error occurred');
expect(getErrorMessage(['error'])).toBe('An unknown error occurred');
});
it('should handle symbols', () => {
expect(getErrorMessage(Symbol('test'))).toBe('An unknown error occurred');
});
});
describe('Type Definitions', () => {
describe('SessionStatus', () => {
it('should support all status values', () => {
const statuses: Array<'idle' | 'busy' | 'stopped' | 'error'> = ['idle', 'busy', 'stopped', 'error'];
expect(statuses).toHaveLength(4);
});
});
describe('TaskStatus', () => {
it('should support all status values', () => {
const statuses: Array<'pending' | 'running' | 'completed' | 'failed'> = ['pending', 'running', 'completed', 'failed'];
expect(statuses).toHaveLength(4);
});
});
describe('RalphLoopStatus', () => {
it('should support all status values', () => {
const statuses: Array<'stopped' | 'running' | 'paused'> = ['stopped', 'running', 'paused'];
expect(statuses).toHaveLength(3);
});
});
describe('RalphTodoStatus', () => {
it('should support all status values', () => {
const statuses: Array<'pending' | 'in_progress' | 'completed'> = ['pending', 'in_progress', 'completed'];
expect(statuses).toHaveLength(3);
});
});
});
describe('ApiErrorCode Enum', () => {
it('should have correct values', () => {
expect(ApiErrorCode.NOT_FOUND).toBe('NOT_FOUND');
expect(ApiErrorCode.INVALID_INPUT).toBe('INVALID_INPUT');
expect(ApiErrorCode.SESSION_BUSY).toBe('SESSION_BUSY');
expect(ApiErrorCode.OPERATION_FAILED).toBe('OPERATION_FAILED');
expect(ApiErrorCode.ALREADY_EXISTS).toBe('ALREADY_EXISTS');
expect(ApiErrorCode.INTERNAL_ERROR).toBe('INTERNAL_ERROR');
});
it('should have 6 error codes', () => {
const codes = Object.values(ApiErrorCode);
expect(codes).toHaveLength(6);
});
});
});