diff --git a/test/mobile/keyboard.test.ts b/test/mobile/keyboard.test.ts index 48c701f0..92491627 100644 --- a/test/mobile/keyboard.test.ts +++ b/test/mobile/keyboard.test.ts @@ -5,16 +5,22 @@ import { PORTS, KEYBOARD, SELECTORS, BODY_CLASSES, WAIT } from './helpers/consta import { createTestServer, stopTestServer } from './helpers/server.js'; import { createDevicePage, getBrowser, closeAllBrowsers } from './helpers/browser.js'; import { - showKeyboard, hideKeyboard, - showKeyboardViaCDP, hideKeyboardViaCDP, - showKeyboardViaMock, hideKeyboardViaMock, - showKeyboardViaDOM, hideKeyboardViaDOM, + showKeyboard, + hideKeyboard, + showKeyboardViaCDP, + hideKeyboardViaCDP, + showKeyboardViaMock, + hideKeyboardViaMock, + showKeyboardViaDOM, + hideKeyboardViaDOM, setupViewportMock, } from './helpers/keyboard-sim.js'; import { getCDP, setVisualViewportHeight } from './helpers/cdp.js'; import { - assertHasClass, assertNotHasClass, - assertVisible, assertHidden, + assertHasClass, + assertNotHasClass, + assertVisible, + assertHidden, getCSSProperty, } from './helpers/assertions.js'; import { REPRESENTATIVE_DEVICES } from './devices.js'; @@ -167,9 +173,7 @@ describe('Virtual Keyboard', () => { const success = await showKeyboardViaMock(page, KEYBOARD.TYPICAL_IOS_HEIGHT); expect(success).toBe(true); - const hasClass = await page.evaluate(() => - document.body.classList.contains('keyboard-visible'), - ); + const hasClass = await page.evaluate(() => document.body.classList.contains('keyboard-visible')); expect(hasClass).toBe(true); } finally { await context.close(); @@ -280,12 +284,13 @@ describe('Virtual Keyboard', () => { expect(mainPadding).toBe(''); }); - it('accessory bar has 7 action buttons', async () => { - const count = await page.evaluate(() => { - const buttons = document.querySelectorAll('.keyboard-accessory-bar [data-action]'); - return buttons.length; + it('accessory bar has the simple-mode action buttons', async () => { + const actions = await page.evaluate(() => { + return Array.from(document.querySelectorAll('.keyboard-accessory-bar [data-action]')).map( + (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 () => { @@ -321,27 +326,6 @@ describe('Virtual Keyboard', () => { 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 () => { await showKeyboard(page, KEYBOARD.TYPICAL_IOS_HEIGHT); await page.waitForTimeout(WAIT.KEYBOARD_ANIMATION); diff --git a/test/operation-lightspeed.test.ts b/test/operation-lightspeed.test.ts index 2e3dbfec..c1c3a351 100644 --- a/test/operation-lightspeed.test.ts +++ b/test/operation-lightspeed.test.ts @@ -1256,7 +1256,7 @@ describe('Operation Lightspeed', () => { 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 const target = await createSession(baseUrl); const other = await createSession(baseUrl); @@ -1309,15 +1309,21 @@ describe('Operation Lightspeed', () => { const events = parseSSEEvents(receivedData); - // Should see target's rename but not other's events - const targetUpdated = events.find((e) => e.event === 'session:updated' && (e.data as any).id === target); + // session:updated is a lifecycle event broadcast to all clients; the + // 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(); - // Should NOT see other's events - const otherEvents = events.filter( - (e) => ((e.data as any)?.id === other || (e.data as any)?.sessionId === other) && e.event !== 'init' + const otherLifecycleEvents = events.filter( + (e) => e.event !== 'init' && e.event !== 'session:terminal' && (e.data as any)?.id === other + ); + expect(otherLifecycleEvents.length).toBeGreaterThan(0); + + const otherTerminalEvents = events.filter( + (e) => e.event === 'session:terminal' && (e.data as any)?.sessionId === other ); - expect(otherEvents.length).toBe(0); + expect(otherTerminalEvents.length).toBe(0); await deleteSession(baseUrl, target); }); diff --git a/test/perf-browser.test.ts b/test/perf-browser.test.ts index c699cf9f..39f13a6c 100644 --- a/test/perf-browser.test.ts +++ b/test/perf-browser.test.ts @@ -19,24 +19,24 @@ const BASE_URL = `http://localhost:${PORT}`; // Thresholds (ms) const THRESHOLDS = { - PAGE_LOAD: 3000, // Full page load including JS init - DOMContentLoaded: 1500, // HTML parsed - SSE_CONNECT: 2000, // SSE EventSource open - TAB_CREATE_API: 200, // POST /api/sessions response - TAB_RENDER: 300, // Tab element appears in DOM - TAB_SWITCH: 400, // Tab click to active class applied (includes tmux session creation) - TERMINAL_INIT: 500, // xterm.js instance created for tab - INPUT_ROUNDTRIP: 500, // Keystroke sent via API → acknowledged - SETTINGS_OPEN: 300, // Settings modal visible - SETTINGS_CLOSE: 200, // Settings modal hidden + PAGE_LOAD: 3000, // Full page load including JS init + DOMContentLoaded: 1500, // Browser nav timing through deferred script execution + SSE_CONNECT: 2000, // SSE EventSource open + TAB_CREATE_API: 200, // POST /api/sessions response + TAB_RENDER: 300, // Tab element appears in DOM + TAB_SWITCH: 400, // Tab click to active class applied (includes tmux session creation) + TERMINAL_INIT: 500, // xterm.js instance created for tab + INPUT_ROUNDTRIP: 500, // Keystroke sent via API → acknowledged + SETTINGS_OPEN: 300, // Settings modal visible + SETTINGS_CLOSE: 200, // Settings modal hidden 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_CLOSE: 200, - BULK_TAB_CREATE: 3000, // Create 10 sessions - 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 - BUFFER_LOAD_16KB: 500, // Load a 16KB terminal buffer + BULK_TAB_CREATE: 3000, // Create 10 sessions + 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 + BUFFER_LOAD_16KB: 500, // Load a 16KB terminal buffer }; let server: WebServer; @@ -88,6 +88,25 @@ async function measure(fn: () => Promise): Promise { 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 { + 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) */ async function getHeapMB(page: Page): Promise { const metrics = await page.evaluate(() => { @@ -123,12 +142,15 @@ describe('Page load performance', () => { it('DOMContentLoaded fires within threshold', async () => { ({ context, page } = await freshPage()); - const timing = await measure(async () => { + const wallTiming = await measure(async () => { await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' }); }); + const navigationTiming = await getBrowserNavigationTiming(page); - console.log(`[page load] DOMContentLoaded: ${timing.toFixed(0)}ms`); - expect(timing).toBeLessThan(THRESHOLDS.DOMContentLoaded); + console.log( + `[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 () => { @@ -157,7 +179,7 @@ describe('Page load performance', () => { const dot = document.getElementById('connectionDot'); return dot?.classList.contains('connected') || indicator.style.display === 'none'; }, - { timeout: 5000 }, + { timeout: 5000 } ); }); @@ -225,7 +247,7 @@ describe('Session tab creation', () => { await page.waitForFunction( (expected: number) => document.querySelectorAll('.session-tab').length > expected, tabCountBefore, - { timeout: 3000 }, + { timeout: 3000 } ); }); @@ -250,7 +272,7 @@ describe('Session tab creation', () => { if (!container) return false; return container.querySelector('.xterm-screen') !== null; }, - { timeout: 3000 }, + { timeout: 3000 } ); }); @@ -277,7 +299,7 @@ describe('Tab switching performance', () => { await page.waitForFunction( (count: number) => document.querySelectorAll('.session-tab').length >= count, sessionIds.length, - { timeout: 5000 }, + { timeout: 5000 } ); // Select first tab await page.locator(`.session-tab[data-id="${sessionIds[0]}"]`).click(); @@ -303,7 +325,7 @@ describe('Tab switching performance', () => { await page.waitForFunction( (id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'), targetId, - { timeout: 2000 }, + { timeout: 2000 } ); }); timings.push(timing); @@ -325,7 +347,7 @@ describe('Tab switching performance', () => { await page.waitForFunction( (id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'), targetId, - { timeout: 2000 }, + { timeout: 2000 } ); }); timings.push(timing); @@ -362,11 +384,9 @@ describe('Bulk tab operations', () => { sessionIds.push(id); } // Wait for all tabs to render - await page.waitForFunction( - (count: number) => document.querySelectorAll('.session-tab').length >= count, - 10, - { timeout: 5000 }, - ); + await page.waitForFunction((count: number) => document.querySelectorAll('.session-tab').length >= count, 10, { + timeout: 5000, + }); }); console.log(`[bulk create] 10 sessions: ${timing.toFixed(0)}ms`); @@ -383,7 +403,7 @@ describe('Bulk tab operations', () => { await page.waitForFunction( (id: string) => document.querySelector(`.session-tab[data-id="${id}"]`)?.classList.contains('active'), targetId, - { timeout: 2000 }, + { timeout: 2000 } ); }); timings.push(timing); @@ -497,7 +517,10 @@ describe('Settings modal performance', () => { it('closes within threshold', async () => { // 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) { await page.locator('.btn-settings').click(); await page.waitForSelector('#appSettingsModal.active', { timeout: 2000 }); @@ -506,10 +529,9 @@ describe('Settings modal performance', () => { const timing = await measure(async () => { // Press Escape to close await page.keyboard.press('Escape'); - await page.waitForFunction( - () => !document.querySelector('#appSettingsModal')?.classList.contains('active'), - { timeout: 2000 }, - ); + await page.waitForFunction(() => !document.querySelector('#appSettingsModal')?.classList.contains('active'), { + timeout: 2000, + }); }); console.log(`[settings] close: ${timing.toFixed(0)}ms`); @@ -528,10 +550,9 @@ describe('Settings modal performance', () => { // Close const closeTime = await measure(async () => { await page.keyboard.press('Escape'); - await page.waitForFunction( - () => !document.querySelector('#appSettingsModal')?.classList.contains('active'), - { timeout: 2000 }, - ); + await page.waitForFunction(() => !document.querySelector('#appSettingsModal')?.classList.contains('active'), { + timeout: 2000, + }); }); timings.push(openTime + closeTime); } @@ -575,7 +596,10 @@ describe('Session options modal performance', () => { it('tab switching within modal is instant', async () => { // 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) { await page.locator(`.session-tab[data-id="${sessionId}"] .tab-gear`).click(); await page.waitForSelector('#sessionOptionsModal.active', { timeout: 2000 }); @@ -590,7 +614,7 @@ describe('Session options modal performance', () => { await page.waitForFunction( (t: string) => document.querySelector(`[data-tab="${t}"]`)?.classList.contains('active'), tab, - { timeout: 1000 }, + { timeout: 1000 } ); }); timings.push(timing); @@ -634,27 +658,32 @@ describe('Subagent window simulation', () => { }, sessionId); const timing = await measure(async () => { - await page.evaluate(({ agentId, cSessionId }: { agentId: string; cSessionId: string }) => { - const app = (window as unknown as { - app: { - subagents: Map>; - openSubagentWindow: (id: string) => void; - } - }).app; - // Inject fake agent data - app.subagents.set(agentId, { - agentId, - sessionId: cSessionId, - status: 'active', - description: 'Performance test agent', - startedAt: Date.now(), - lastActivityAt: Date.now(), - toolCallCount: 0, - entryCount: 0, - fileSize: 0, - }); - app.openSubagentWindow(agentId); - }, { agentId: 'perf-agent-1', cSessionId: claudeSessionId }); + await page.evaluate( + ({ agentId, cSessionId }: { agentId: string; cSessionId: string }) => { + const app = ( + window as unknown as { + app: { + subagents: Map>; + openSubagentWindow: (id: string) => void; + }; + } + ).app; + // Inject fake agent data + app.subagents.set(agentId, { + agentId, + sessionId: cSessionId, + status: 'active', + description: 'Performance test agent', + startedAt: Date.now(), + lastActivityAt: Date.now(), + toolCallCount: 0, + entryCount: 0, + fileSize: 0, + }); + app.openSubagentWindow(agentId); + }, + { agentId: 'perf-agent-1', cSessionId: claudeSessionId } + ); await page.waitForSelector('.subagent-window', { timeout: 3000 }); }); @@ -679,7 +708,7 @@ describe('Subagent window simulation', () => { const el = document.getElementById('subagent-window-perf-agent-1'); return el && el.style.display === 'none'; }, - { timeout: 2000 }, + { timeout: 2000 } ); }); @@ -698,33 +727,35 @@ describe('Subagent window simulation', () => { const timing = await measure(async () => { for (let i = 0; i < 5; i++) { - await page.evaluate(({ idx, cSessionId }: { idx: number; cSessionId: string }) => { - const agentId = `perf-multi-agent-${idx}`; - const app = (window as unknown as { - app: { - subagents: Map>; - openSubagentWindow: (id: string) => void; - } - }).app; - app.subagents.set(agentId, { - agentId, - sessionId: cSessionId, - status: 'active', - description: `Perf agent ${idx}`, - startedAt: Date.now(), - lastActivityAt: Date.now(), - toolCallCount: 0, - entryCount: 0, - fileSize: 0, - }); - app.openSubagentWindow(agentId); - }, { idx: i, cSessionId: claudeSessionId }); + await page.evaluate( + ({ idx, cSessionId }: { idx: number; cSessionId: string }) => { + const agentId = `perf-multi-agent-${idx}`; + const app = ( + window as unknown as { + app: { + subagents: Map>; + openSubagentWindow: (id: string) => void; + }; + } + ).app; + app.subagents.set(agentId, { + agentId, + sessionId: cSessionId, + status: 'active', + description: `Perf agent ${idx}`, + startedAt: Date.now(), + lastActivityAt: Date.now(), + toolCallCount: 0, + entryCount: 0, + fileSize: 0, + }); + app.openSubagentWindow(agentId); + }, + { idx: i, cSessionId: claudeSessionId } + ); } // Wait for all 5 - await page.waitForFunction( - () => document.querySelectorAll('.subagent-window').length >= 5, - { timeout: 5000 }, - ); + await page.waitForFunction(() => document.querySelectorAll('.subagent-window').length >= 5, { timeout: 5000 }); }); const windowCount = await page.locator('.subagent-window').count(); @@ -772,7 +803,7 @@ describe('SSE event throughput', () => { await page.waitForFunction( (count: number) => document.querySelectorAll('.session-tab').length >= count, sessionIds.length, - { timeout: 5000 }, + { timeout: 5000 } ); const elapsed = performance.now() - start; @@ -797,7 +828,7 @@ describe('SSE event throughput', () => { await page.waitForFunction( (count: number) => document.querySelectorAll('.session-tab').length >= count, sessionIds.length, - { timeout: 5000 }, + { timeout: 5000 } ); const start = performance.now(); @@ -813,7 +844,7 @@ describe('SSE event throughput', () => { } return true; }, - { timeout: 5000 }, + { timeout: 5000 } ); const elapsed = performance.now() - start; @@ -880,11 +911,9 @@ describe('Memory usage under load', () => { const id = await createSession(page, `perf-mem-${i}`); sessionIds.push(id); } - await page.waitForFunction( - (count: number) => document.querySelectorAll('.session-tab').length >= count, - 10, - { timeout: 5000 }, - ); + await page.waitForFunction((count: number) => document.querySelectorAll('.session-tab').length >= count, 10, { + timeout: 5000, + }); // Switch through all tabs for (const id of sessionIds) { @@ -895,10 +924,13 @@ describe('Memory usage under load', () => { const heapAfter = await getHeapMB(page); 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 - 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); } }); diff --git a/test/ralph-integration.test.ts b/test/ralph-integration.test.ts index d84b9a6c..27521066 100644 --- a/test/ralph-integration.test.ts +++ b/test/ralph-integration.test.ts @@ -106,7 +106,7 @@ describe('Ralph Integration Tests', () => { const res = await fetch(`${baseUrl}/api/sessions/non-existent-id`); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.errorCode).toBe('NOT_FOUND'); }); @@ -136,7 +136,8 @@ describe('Ralph Integration Tests', () => { // Verify session is gone const getRes = await fetch(`${baseUrl}/api/sessions/${sessionId}`); 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 () => { @@ -191,7 +192,7 @@ describe('Ralph Integration Tests', () => { const res = await fetch(`${baseUrl}/api/sessions/fake-session/ralph-state`); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.error).toContain('not found'); }); @@ -359,7 +360,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(400); expect(data.success).toBe(false); expect(data.errorCode).toBe('INVALID_INPUT'); }); @@ -372,7 +373,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.errorCode).toBe('NOT_FOUND'); }); @@ -395,7 +396,7 @@ describe('Ralph Integration Tests', () => { createdSessions.push(createData.sessionId); // 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`, { method: 'POST', @@ -422,7 +423,7 @@ describe('Ralph Integration Tests', () => { createdSessions.push(createData.sessionId); // 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`, { method: 'POST', @@ -431,7 +432,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(400); expect(data.success).toBe(false); expect(data.errorCode).toBe('INVALID_INPUT'); }); @@ -472,7 +473,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.errorCode).toBe('NOT_FOUND'); }); @@ -497,7 +498,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(400); expect(data.success).toBe(false); expect(data.errorCode).toBe('INVALID_INPUT'); }); @@ -536,7 +537,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.errorCode).toBe('NOT_FOUND'); }); @@ -561,7 +562,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(400); expect(data.success).toBe(false); expect(data.errorCode).toBe('INVALID_INPUT'); }); @@ -626,7 +627,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.errorCode).toBe('NOT_FOUND'); }); @@ -907,7 +908,7 @@ describe('Ralph Integration Tests', () => { }); const data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); 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 data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); 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 data = await res.json(); - expect(res.status).toBe(200); + expect(res.status).toBe(404); expect(data.success).toBe(false); expect(data.errorCode).toBe('NOT_FOUND'); }); diff --git a/test/setup.ts b/test/setup.ts index eb853324..f55acbc1 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -6,11 +6,15 @@ * This means tests CANNOT kill, create, or interact with real tmux * 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'; +delete process.env.CODEMAN_PASSWORD; +delete process.env.CODEMAN_USERNAME; + afterEach(() => { vi.clearAllMocks(); vi.useRealTimers();