Merge pull request #108 from aakhter/cod-30-test-ci-stability

test: COD-30 stabilize focused and perf browser tests
This commit is contained in:
Ark0N
2026-06-08 17:54:33 +02:00
committed by GitHub
5 changed files with 184 additions and 157 deletions
+19 -35
View File
@@ -5,16 +5,22 @@ import { PORTS, KEYBOARD, SELECTORS, BODY_CLASSES, WAIT } from './helpers/consta
import { createTestServer, stopTestServer } from './helpers/server.js'; import { createTestServer, stopTestServer } from './helpers/server.js';
import { createDevicePage, getBrowser, closeAllBrowsers } from './helpers/browser.js'; import { createDevicePage, getBrowser, closeAllBrowsers } from './helpers/browser.js';
import { import {
showKeyboard, hideKeyboard, showKeyboard,
showKeyboardViaCDP, hideKeyboardViaCDP, hideKeyboard,
showKeyboardViaMock, hideKeyboardViaMock, showKeyboardViaCDP,
showKeyboardViaDOM, hideKeyboardViaDOM, hideKeyboardViaCDP,
showKeyboardViaMock,
hideKeyboardViaMock,
showKeyboardViaDOM,
hideKeyboardViaDOM,
setupViewportMock, setupViewportMock,
} from './helpers/keyboard-sim.js'; } from './helpers/keyboard-sim.js';
import { getCDP, setVisualViewportHeight } from './helpers/cdp.js'; import { getCDP, setVisualViewportHeight } from './helpers/cdp.js';
import { import {
assertHasClass, assertNotHasClass, assertHasClass,
assertVisible, assertHidden, assertNotHasClass,
assertVisible,
assertHidden,
getCSSProperty, getCSSProperty,
} from './helpers/assertions.js'; } from './helpers/assertions.js';
import { REPRESENTATIVE_DEVICES } from './devices.js'; import { REPRESENTATIVE_DEVICES } from './devices.js';
@@ -167,9 +173,7 @@ describe('Virtual Keyboard', () => {
const success = await showKeyboardViaMock(page, KEYBOARD.TYPICAL_IOS_HEIGHT); const success = await showKeyboardViaMock(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
expect(success).toBe(true); expect(success).toBe(true);
const hasClass = await page.evaluate(() => const hasClass = await page.evaluate(() => document.body.classList.contains('keyboard-visible'));
document.body.classList.contains('keyboard-visible'),
);
expect(hasClass).toBe(true); expect(hasClass).toBe(true);
} finally { } finally {
await context.close(); await context.close();
@@ -280,12 +284,13 @@ describe('Virtual Keyboard', () => {
expect(mainPadding).toBe(''); expect(mainPadding).toBe('');
}); });
it('accessory bar has 7 action buttons', async () => { it('accessory bar has the simple-mode action buttons', async () => {
const count = await page.evaluate(() => { const actions = await page.evaluate(() => {
const buttons = document.querySelectorAll('.keyboard-accessory-bar [data-action]'); return Array.from(document.querySelectorAll('.keyboard-accessory-bar [data-action]')).map(
return buttons.length; (button) => (button as HTMLElement).dataset.action
);
}); });
expect(count).toBe(7); expect(actions).toEqual(['scroll-up', 'scroll-down', 'init', 'clear', 'paste', 'dismiss']);
}); });
it('double-tap confirm on /clear button', async () => { it('double-tap confirm on /clear button', async () => {
@@ -321,27 +326,6 @@ describe('Virtual Keyboard', () => {
expect(text).toBe('Tap again'); expect(text).toBe('Tap again');
}); });
it('double-tap confirm on /compact button', async () => {
await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION);
await page.evaluate(`
if (typeof app !== 'undefined') app.activeSessionId = 'test-session';
`);
await page.evaluate(() => {
const btn = document.querySelector('[data-action="compact"]') as HTMLElement;
btn?.click();
});
await page.waitForTimeout(100);
const confirming = await page.evaluate(() => {
const btn = document.querySelector('[data-action="compact"]');
return btn?.classList.contains('confirming') ?? false;
});
expect(confirming).toBe(true);
});
it('double-tap expires after 2s', async () => { it('double-tap expires after 2s', async () => {
await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT); await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT);
await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION); await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION);
+13 -7
View File
@@ -1256,7 +1256,7 @@ describe('Operation Lightspeed', () => {
await Promise.all(ids.map((id) => deleteSession(baseUrl, id))); await Promise.all(ids.map((id) => deleteSession(baseUrl, id)));
}); });
it('should correctly filter SSE under concurrent session lifecycle', async () => { it('should broadcast lifecycle events while filtering concurrent session terminal streams', async () => {
// Create 2 sessions // Create 2 sessions
const target = await createSession(baseUrl); const target = await createSession(baseUrl);
const other = await createSession(baseUrl); const other = await createSession(baseUrl);
@@ -1309,15 +1309,21 @@ describe('Operation Lightspeed', () => {
const events = parseSSEEvents(receivedData); const events = parseSSEEvents(receivedData);
// Should see target's rename but not other's events // session:updated is a lifecycle event broadcast to all clients; the
const targetUpdated = events.find((e) => e.event === 'session:updated' && (e.data as any).id === target); // subscription filter applies only to high-volume terminal streams.
const updatedEvents = events.filter((e) => e.event === 'session:updated');
const targetUpdated = updatedEvents.find((e) => (e.data as any).id === target);
expect(targetUpdated).toBeDefined(); expect(targetUpdated).toBeDefined();
// Should NOT see other's events const otherLifecycleEvents = events.filter(
const otherEvents = events.filter( (e) => e.event !== 'init' && e.event !== 'session:terminal' && (e.data as any)?.id === other
(e) => ((e.data as any)?.id === other || (e.data as any)?.sessionId === other) && e.event !== 'init'
); );
expect(otherEvents.length).toBe(0); expect(otherLifecycleEvents.length).toBeGreaterThan(0);
const otherTerminalEvents = events.filter(
(e) => e.event === 'session:terminal' && (e.data as any)?.sessionId === other
);
expect(otherTerminalEvents.length).toBe(0);
await deleteSession(baseUrl, target); await deleteSession(baseUrl, target);
}); });
+130 -98
View File
@@ -19,24 +19,24 @@ const BASE_URL = `http://localhost:${PORT}`;
// Thresholds (ms) // Thresholds (ms)
const THRESHOLDS = { const THRESHOLDS = {
PAGE_LOAD: 3000, // Full page load including JS init PAGE_LOAD: 3000, // Full page load including JS init
DOMContentLoaded: 1500, // HTML parsed DOMContentLoaded: 1500, // Browser nav timing through deferred script execution
SSE_CONNECT: 2000, // SSE EventSource open SSE_CONNECT: 2000, // SSE EventSource open
TAB_CREATE_API: 200, // POST /api/sessions response TAB_CREATE_API: 200, // POST /api/sessions response
TAB_RENDER: 300, // Tab element appears in DOM TAB_RENDER: 300, // Tab element appears in DOM
TAB_SWITCH: 400, // Tab click to active class applied (includes tmux session creation) TAB_SWITCH: 400, // Tab click to active class applied (includes tmux session creation)
TERMINAL_INIT: 500, // xterm.js instance created for tab TERMINAL_INIT: 500, // xterm.js instance created for tab
INPUT_ROUNDTRIP: 500, // Keystroke sent via API → acknowledged INPUT_ROUNDTRIP: 500, // Keystroke sent via API → acknowledged
SETTINGS_OPEN: 300, // Settings modal visible SETTINGS_OPEN: 300, // Settings modal visible
SETTINGS_CLOSE: 200, // Settings modal hidden SETTINGS_CLOSE: 200, // Settings modal hidden
SESSION_OPTIONS_OPEN: 300, // Session options modal visible SESSION_OPTIONS_OPEN: 300, // Session options modal visible
SESSION_OPTIONS_TAB: 200, // Modal tab switch SESSION_OPTIONS_TAB: 200, // Modal tab switch
SUBAGENT_WINDOW_OPEN: 400, // Subagent window rendered SUBAGENT_WINDOW_OPEN: 400, // Subagent window rendered
SUBAGENT_WINDOW_CLOSE: 200, SUBAGENT_WINDOW_CLOSE: 200,
BULK_TAB_CREATE: 3000, // Create 10 sessions BULK_TAB_CREATE: 3000, // Create 10 sessions
BULK_TAB_SWITCH_AVG: 300, // Average per-tab switch across 10 tabs (includes buffer loads) BULK_TAB_SWITCH_AVG: 300, // Average per-tab switch across 10 tabs (includes buffer loads)
MEMORY_HEAP_MB: 200, // Max JS heap after heavy load MEMORY_HEAP_MB: 200, // Max JS heap after heavy load
BUFFER_LOAD_16KB: 500, // Load a 16KB terminal buffer BUFFER_LOAD_16KB: 500, // Load a 16KB terminal buffer
}; };
let server: WebServer; let server: WebServer;
@@ -88,6 +88,25 @@ async function measure(fn: () => Promise<void>): Promise<number> {
return performance.now() - start; return performance.now() - start;
} }
type BrowserNavigationTiming = {
domInteractive: number;
domContentLoadedEventEnd: number;
loadEventEnd: number;
};
/** Get the browser's own navigation timing, excluding Playwright harness overhead. */
async function getBrowserNavigationTiming(page: Page): Promise<BrowserNavigationTiming> {
return page.evaluate(() => {
const entry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;
if (!entry) throw new Error('Navigation timing entry not available');
return {
domInteractive: entry.domInteractive,
domContentLoadedEventEnd: entry.domContentLoadedEventEnd,
loadEventEnd: entry.loadEventEnd,
};
});
}
/** Get JS heap size in MB (Chromium only) */ /** Get JS heap size in MB (Chromium only) */
async function getHeapMB(page: Page): Promise<number> { async function getHeapMB(page: Page): Promise<number> {
const metrics = await page.evaluate(() => { const metrics = await page.evaluate(() => {
@@ -123,12 +142,15 @@ describe('Page load performance', () => {
it('DOMContentLoaded fires within threshold', async () => { it('DOMContentLoaded fires within threshold', async () => {
({ context, page } = await freshPage()); ({ context, page } = await freshPage());
const timing = await measure(async () => { const wallTiming = await measure(async () => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' }); await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
}); });
const navigationTiming = await getBrowserNavigationTiming(page);
console.log(`[page load] DOMContentLoaded: ${timing.toFixed(0)}ms`); console.log(
expect(timing).toBeLessThan(THRESHOLDS.DOMContentLoaded); `[page load] DOMContentLoaded: ${navigationTiming.domContentLoadedEventEnd.toFixed(0)}ms (wall ${wallTiming.toFixed(0)}ms)`
);
expect(navigationTiming.domContentLoadedEventEnd).toBeLessThan(THRESHOLDS.DOMContentLoaded);
}); });
it('full app initialization completes within threshold', async () => { it('full app initialization completes within threshold', async () => {
@@ -157,7 +179,7 @@ describe('Page load performance', () => {
const dot = document.getElementById('connectionDot'); const dot = document.getElementById('connectionDot');
return dot?.classList.contains('connected') || indicator.style.display === 'none'; return dot?.classList.contains('connected') || indicator.style.display === 'none';
}, },
{ timeout: 5000 }, { timeout: 5000 }
); );
}); });
@@ -225,7 +247,7 @@ describe('Session tab creation', () => {
await page.waitForFunction( await page.waitForFunction(
(expected: number) => document.querySelectorAll('.session-tab').length > expected, (expected: number) => document.querySelectorAll('.session-tab').length > expected,
tabCountBefore, tabCountBefore,
{ timeout: 3000 }, { timeout: 3000 }
); );
}); });
@@ -250,7 +272,7 @@ describe('Session tab creation', () => {
if (!container) return false; if (!container) return false;
return container.querySelector('.xterm-screen') !== null; return container.querySelector('.xterm-screen') !== null;
}, },
{ timeout: 3000 }, { timeout: 3000 }
); );
}); });
@@ -277,7 +299,7 @@ describe('Tab switching performance', () => {
await page.waitForFunction( await page.waitForFunction(
(count: number) => document.querySelectorAll('.session-tab').length >= count, (count: number) => document.querySelectorAll('.session-tab').length >= count,
sessionIds.length, sessionIds.length,
{ timeout: 5000 }, { timeout: 5000 }
); );
// Select first tab // Select first tab
await page.locator(`.session-tab[data-id="${sessionIds[0]}"]`).click(); await page.locator(`.session-tab[data-id="${sessionIds[0]}"]`).click();
@@ -303,7 +325,7 @@ describe('Tab switching performance', () => {
await page.waitForFunction( await page.waitForFunction(
(id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'), (id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'),
targetId, targetId,
{ timeout: 2000 }, { timeout: 2000 }
); );
}); });
timings.push(timing); timings.push(timing);
@@ -325,7 +347,7 @@ describe('Tab switching performance', () => {
await page.waitForFunction( await page.waitForFunction(
(id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'), (id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'),
targetId, targetId,
{ timeout: 2000 }, { timeout: 2000 }
); );
}); });
timings.push(timing); timings.push(timing);
@@ -362,11 +384,9 @@ describe('Bulk tab operations', () => {
sessionIds.push(id); sessionIds.push(id);
} }
// Wait for all tabs to render // Wait for all tabs to render
await page.waitForFunction( await page.waitForFunction((count: number) => document.querySelectorAll('.session-tab').length >= count, 10, {
(count: number) => document.querySelectorAll('.session-tab').length >= count, timeout: 5000,
10, });
{ timeout: 5000 },
);
}); });
console.log(`[bulk create] 10 sessions: ${timing.toFixed(0)}ms`); console.log(`[bulk create] 10 sessions: ${timing.toFixed(0)}ms`);
@@ -383,7 +403,7 @@ describe('Bulk tab operations', () => {
await page.waitForFunction( await page.waitForFunction(
(id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'), (id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'),
targetId, targetId,
{ timeout: 2000 }, { timeout: 2000 }
); );
}); });
timings.push(timing); timings.push(timing);
@@ -497,7 +517,10 @@ describe('Settings modal performance', () => {
it('closes within threshold', async () => { it('closes within threshold', async () => {
// Make sure it's open // Make sure it's open
const isOpen = await page.locator('#appSettingsModal.active').isVisible().catch(() => false); const isOpen = await page
.locator('#appSettingsModal.active')
.isVisible()
.catch(() => false);
if (!isOpen) { if (!isOpen) {
await page.locator('.btn-settings').click(); await page.locator('.btn-settings').click();
await page.waitForSelector('#appSettingsModal.active', { timeout: 2000 }); await page.waitForSelector('#appSettingsModal.active', { timeout: 2000 });
@@ -506,10 +529,9 @@ describe('Settings modal performance', () => {
const timing = await measure(async () => { const timing = await measure(async () => {
// Press Escape to close // Press Escape to close
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
await page.waitForFunction( await page.waitForFunction(() => !document.querySelector('#appSettingsModal')?.classList.contains('active'), {
() => !document.querySelector('#appSettingsModal')?.classList.contains('active'), timeout: 2000,
{ timeout: 2000 }, });
);
}); });
console.log(`[settings] close: ${timing.toFixed(0)}ms`); console.log(`[settings] close: ${timing.toFixed(0)}ms`);
@@ -528,10 +550,9 @@ describe('Settings modal performance', () => {
// Close // Close
const closeTime = await measure(async () => { const closeTime = await measure(async () => {
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
await page.waitForFunction( await page.waitForFunction(() => !document.querySelector('#appSettingsModal')?.classList.contains('active'), {
() => !document.querySelector('#appSettingsModal')?.classList.contains('active'), timeout: 2000,
{ timeout: 2000 }, });
);
}); });
timings.push(openTime + closeTime); timings.push(openTime + closeTime);
} }
@@ -575,7 +596,10 @@ describe('Session options modal performance', () => {
it('tab switching within modal is instant', async () => { it('tab switching within modal is instant', async () => {
// Ensure modal is open // Ensure modal is open
const isOpen = await page.locator('#sessionOptionsModal.active').isVisible().catch(() => false); const isOpen = await page
.locator('#sessionOptionsModal.active')
.isVisible()
.catch(() => false);
if (!isOpen) { if (!isOpen) {
await page.locator(`.session-tab[data-id="${sessionId}"] .tab-gear`).click(); await page.locator(`.session-tab[data-id="${sessionId}"] .tab-gear`).click();
await page.waitForSelector('#sessionOptionsModal.active', { timeout: 2000 }); await page.waitForSelector('#sessionOptionsModal.active', { timeout: 2000 });
@@ -590,7 +614,7 @@ describe('Session options modal performance', () => {
await page.waitForFunction( await page.waitForFunction(
(t: string) => document.querySelector(`[data-tab="${t}"]`)?.classList.contains('active'), (t: string) => document.querySelector(`[data-tab="${t}"]`)?.classList.contains('active'),
tab, tab,
{ timeout: 1000 }, { timeout: 1000 }
); );
}); });
timings.push(timing); timings.push(timing);
@@ -634,27 +658,32 @@ describe('Subagent window simulation', () => {
}, sessionId); }, sessionId);
const timing = await measure(async () => { const timing = await measure(async () => {
await page.evaluate(({ agentId, cSessionId }: { agentId: string; cSessionId: string }) => { await page.evaluate(
const app = (window as unknown as { ({ agentId, cSessionId }: { agentId: string; cSessionId: string }) => {
app: { const app = (
subagents: Map<string, Record<string, unknown>>; window as unknown as {
openSubagentWindow: (id: string) => void; app: {
} subagents: Map<string, Record<string, unknown>>;
}).app; openSubagentWindow: (id: string) => void;
// Inject fake agent data };
app.subagents.set(agentId, { }
agentId, ).app;
sessionId: cSessionId, // Inject fake agent data
status: 'active', app.subagents.set(agentId, {
description: 'Performance test agent', agentId,
startedAt: Date.now(), sessionId: cSessionId,
lastActivityAt: Date.now(), status: 'active',
toolCallCount: 0, description: 'Performance test agent',
entryCount: 0, startedAt: Date.now(),
fileSize: 0, lastActivityAt: Date.now(),
}); toolCallCount: 0,
app.openSubagentWindow(agentId); entryCount: 0,
}, { agentId: 'perf-agent-1', cSessionId: claudeSessionId }); fileSize: 0,
});
app.openSubagentWindow(agentId);
},
{ agentId: 'perf-agent-1', cSessionId: claudeSessionId }
);
await page.waitForSelector('.subagent-window', { timeout: 3000 }); await page.waitForSelector('.subagent-window', { timeout: 3000 });
}); });
@@ -679,7 +708,7 @@ describe('Subagent window simulation', () => {
const el = document.getElementById('subagent-window-perf-agent-1'); const el = document.getElementById('subagent-window-perf-agent-1');
return el && el.style.display === 'none'; return el && el.style.display === 'none';
}, },
{ timeout: 2000 }, { timeout: 2000 }
); );
}); });
@@ -698,33 +727,35 @@ describe('Subagent window simulation', () => {
const timing = await measure(async () => { const timing = await measure(async () => {
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
await page.evaluate(({ idx, cSessionId }: { idx: number; cSessionId: string }) => { await page.evaluate(
const agentId = `perf-multi-agent-${idx}`; ({ idx, cSessionId }: { idx: number; cSessionId: string }) => {
const app = (window as unknown as { const agentId = `perf-multi-agent-${idx}`;
app: { const app = (
subagents: Map<string, Record<string, unknown>>; window as unknown as {
openSubagentWindow: (id: string) => void; app: {
} subagents: Map<string, Record<string, unknown>>;
}).app; openSubagentWindow: (id: string) => void;
app.subagents.set(agentId, { };
agentId, }
sessionId: cSessionId, ).app;
status: 'active', app.subagents.set(agentId, {
description: `Perf agent ${idx}`, agentId,
startedAt: Date.now(), sessionId: cSessionId,
lastActivityAt: Date.now(), status: 'active',
toolCallCount: 0, description: `Perf agent ${idx}`,
entryCount: 0, startedAt: Date.now(),
fileSize: 0, lastActivityAt: Date.now(),
}); toolCallCount: 0,
app.openSubagentWindow(agentId); entryCount: 0,
}, { idx: i, cSessionId: claudeSessionId }); fileSize: 0,
});
app.openSubagentWindow(agentId);
},
{ idx: i, cSessionId: claudeSessionId }
);
} }
// Wait for all 5 // Wait for all 5
await page.waitForFunction( await page.waitForFunction(() => document.querySelectorAll('.subagent-window').length >= 5, { timeout: 5000 });
() => document.querySelectorAll('.subagent-window').length >= 5,
{ timeout: 5000 },
);
}); });
const windowCount = await page.locator('.subagent-window').count(); const windowCount = await page.locator('.subagent-window').count();
@@ -772,7 +803,7 @@ describe('SSE event throughput', () => {
await page.waitForFunction( await page.waitForFunction(
(count: number) => document.querySelectorAll('.session-tab').length >= count, (count: number) => document.querySelectorAll('.session-tab').length >= count,
sessionIds.length, sessionIds.length,
{ timeout: 5000 }, { timeout: 5000 }
); );
const elapsed = performance.now() - start; const elapsed = performance.now() - start;
@@ -797,7 +828,7 @@ describe('SSE event throughput', () => {
await page.waitForFunction( await page.waitForFunction(
(count: number) => document.querySelectorAll('.session-tab').length >= count, (count: number) => document.querySelectorAll('.session-tab').length >= count,
sessionIds.length, sessionIds.length,
{ timeout: 5000 }, { timeout: 5000 }
); );
const start = performance.now(); const start = performance.now();
@@ -813,7 +844,7 @@ describe('SSE event throughput', () => {
} }
return true; return true;
}, },
{ timeout: 5000 }, { timeout: 5000 }
); );
const elapsed = performance.now() - start; const elapsed = performance.now() - start;
@@ -880,11 +911,9 @@ describe('Memory usage under load', () => {
const id = await createSession(page, `perf-mem-${i}`); const id = await createSession(page, `perf-mem-${i}`);
sessionIds.push(id); sessionIds.push(id);
} }
await page.waitForFunction( await page.waitForFunction((count: number) => document.querySelectorAll('.session-tab').length >= count, 10, {
(count: number) => document.querySelectorAll('.session-tab').length >= count, timeout: 5000,
10, });
{ timeout: 5000 },
);
// Switch through all tabs // Switch through all tabs
for (const id of sessionIds) { for (const id of sessionIds) {
@@ -895,10 +924,13 @@ describe('Memory usage under load', () => {
const heapAfter = await getHeapMB(page); const heapAfter = await getHeapMB(page);
const heapGrowth = heapAfter - heapBefore; const heapGrowth = heapAfter - heapBefore;
console.log(`[memory] before: ${heapBefore.toFixed(1)}MB, after: ${heapAfter.toFixed(1)}MB, growth: ${heapGrowth.toFixed(1)}MB`); console.log(
`[memory] before: ${heapBefore.toFixed(1)}MB, after: ${heapAfter.toFixed(1)}MB, growth: ${heapGrowth.toFixed(1)}MB`
);
// Heap should stay under absolute limit // Heap should stay under absolute limit
if (heapAfter > 0) { // memory API may not be available if (heapAfter > 0) {
// memory API may not be available
expect(heapAfter).toBeLessThan(THRESHOLDS.MEMORY_HEAP_MB); expect(heapAfter).toBeLessThan(THRESHOLDS.MEMORY_HEAP_MB);
} }
}); });
+17 -16
View File
@@ -106,7 +106,7 @@ describe('Ralph Integration Tests', () => {
const res = await fetch(`${baseUrl}/api/sessions/non-existent-id`); const res = await fetch(`${baseUrl}/api/sessions/non-existent-id`);
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -136,7 +136,8 @@ describe('Ralph Integration Tests', () => {
// Verify session is gone // Verify session is gone
const getRes = await fetch(`${baseUrl}/api/sessions/${sessionId}`); const getRes = await fetch(`${baseUrl}/api/sessions/${sessionId}`);
const getData = await getRes.json(); const getData = await getRes.json();
expect(getData.error).toBe('Session not found'); expect(getRes.status).toBe(404);
expect(getData.error).toContain('not found');
}); });
it('should create shell session', async () => { it('should create shell session', async () => {
@@ -191,7 +192,7 @@ describe('Ralph Integration Tests', () => {
const res = await fetch(`${baseUrl}/api/sessions/fake-session/ralph-state`); const res = await fetch(`${baseUrl}/api/sessions/fake-session/ralph-state`);
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.error).toContain('not found'); expect(data.error).toContain('not found');
}); });
@@ -359,7 +360,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(400);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('INVALID_INPUT'); expect(data.errorCode).toBe('INVALID_INPUT');
}); });
@@ -372,7 +373,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -395,7 +396,7 @@ describe('Ralph Integration Tests', () => {
createdSessions.push(createData.sessionId); createdSessions.push(createData.sessionId);
// Wait for session to be ready // Wait for session to be ready
await new Promise(r => setTimeout(r, 200)); await new Promise((r) => setTimeout(r, 200));
const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, { const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, {
method: 'POST', method: 'POST',
@@ -422,7 +423,7 @@ describe('Ralph Integration Tests', () => {
createdSessions.push(createData.sessionId); createdSessions.push(createData.sessionId);
// Wait for session to be ready // Wait for session to be ready
await new Promise(r => setTimeout(r, 200)); await new Promise((r) => setTimeout(r, 200));
const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, { const res = await fetch(`${baseUrl}/api/sessions/${createData.sessionId}/resize`, {
method: 'POST', method: 'POST',
@@ -431,7 +432,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(400);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('INVALID_INPUT'); expect(data.errorCode).toBe('INVALID_INPUT');
}); });
@@ -472,7 +473,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -497,7 +498,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(400);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('INVALID_INPUT'); expect(data.errorCode).toBe('INVALID_INPUT');
}); });
@@ -536,7 +537,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -561,7 +562,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(400);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('INVALID_INPUT'); expect(data.errorCode).toBe('INVALID_INPUT');
}); });
@@ -626,7 +627,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -907,7 +908,7 @@ describe('Ralph Integration Tests', () => {
}); });
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -991,7 +992,7 @@ describe('Ralph Integration Tests', () => {
const res = await fetch(`${baseUrl}/api/sessions/fake-session/output`); const res = await fetch(`${baseUrl}/api/sessions/fake-session/output`);
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
@@ -1021,7 +1022,7 @@ describe('Ralph Integration Tests', () => {
const res = await fetch(`${baseUrl}/api/sessions/fake-session/terminal`); const res = await fetch(`${baseUrl}/api/sessions/fake-session/terminal`);
const data = await res.json(); const data = await res.json();
expect(res.status).toBe(200); expect(res.status).toBe(404);
expect(data.success).toBe(false); expect(data.success).toBe(false);
expect(data.errorCode).toBe('NOT_FOUND'); expect(data.errorCode).toBe('NOT_FOUND');
}); });
+5 -1
View File
@@ -6,11 +6,15 @@
* This means tests CANNOT kill, create, or interact with real tmux * This means tests CANNOT kill, create, or interact with real tmux
* sessions regardless of what the test code does. * sessions regardless of what the test code does.
* *
* This setup file only handles mock/timer cleanup between tests. * This setup file strips shell-level auth configuration that can leak from a
* running Codeman instance, then handles mock/timer cleanup between tests.
*/ */
import { afterEach, vi } from 'vitest'; import { afterEach, vi } from 'vitest';
delete process.env.CODEMAN_PASSWORD;
delete process.env.CODEMAN_USERNAME;
afterEach(() => { afterEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
vi.useRealTimers(); vi.useRealTimers();