camofox-browser/tests/unit/tabLifecycleContract.test.js

170 lines
5.7 KiB
JavaScript

/**
* Tests for tab lifecycle contract hardening (JO-2452, JO-2456).
*
* Covers:
* - Navigate returns stale-tab error (no auto-create)
* - Session lastAccess refreshed on tab ops (prevents timeout of active sessions)
* - Popup/new-window pages become managed tabs (not orphaned)
* - scroll and links are serialized via withTabLock
*/
import { startServer, stopServer, getServerUrl } from '../helpers/startServer.js';
import { startTestSite, stopTestSite, getTestSiteUrl } from '../helpers/testSite.js';
import { createClient } from '../helpers/client.js';
describe('Tab Lifecycle Contract', () => {
let serverUrl;
let testSiteUrl;
beforeAll(async () => {
await startServer(0, { SESSION_TIMEOUT_MS: '30000' });
serverUrl = getServerUrl();
await startTestSite();
testSiteUrl = getTestSiteUrl();
}, 120000);
afterAll(async () => {
await stopTestSite();
await stopServer();
}, 30000);
describe('Stale tab errors (JO-2452)', () => {
test('navigate returns 404 for unknown tabId', async () => {
const client = createClient(serverUrl);
try {
await client.createTab(`${testSiteUrl}/pageA`);
try {
await client.navigate('nonexistent-tab-id', `${testSiteUrl}/pageB`);
fail('Should have thrown');
} catch (err) {
expect(err.status).toBe(404);
}
} finally {
await client.cleanup();
}
}, 60000);
test('snapshot returns 404 for unknown tabId', async () => {
const client = createClient(serverUrl);
try {
await client.createTab(`${testSiteUrl}/pageA`);
try {
await client.getSnapshot('nonexistent-tab-id');
fail('Should have thrown');
} catch (err) {
expect(err.status).toBe(404);
}
} finally {
await client.cleanup();
}
}, 60000);
test('click returns 404 for unknown tabId', async () => {
const client = createClient(serverUrl);
try {
await client.createTab(`${testSiteUrl}/pageA`);
try {
await client.click('nonexistent-tab-id', { ref: 'e1' });
fail('Should have thrown');
} catch (err) {
expect(err.status).toBe(404);
}
} finally {
await client.cleanup();
}
}, 60000);
test('scroll returns 404 for unknown tabId', async () => {
const client = createClient(serverUrl);
try {
await client.createTab(`${testSiteUrl}/pageA`);
try {
await client.scroll('nonexistent-tab-id', { direction: 'down', amount: 100 });
fail('Should have thrown');
} catch (err) {
expect(err.status).toBe(404);
}
} finally {
await client.cleanup();
}
}, 60000);
});
describe('Session lastAccess refresh (JO-2452)', () => {
test('tab operations refresh session lastAccess', async () => {
const client = createClient(serverUrl);
try {
const { tabId } = await client.createTab(`${testSiteUrl}/links`);
// Perform multiple tab operations — session should not expire
await new Promise(r => setTimeout(r, 2000));
await client.getSnapshot(tabId);
await new Promise(r => setTimeout(r, 2000));
await client.getLinks(tabId);
await new Promise(r => setTimeout(r, 2000));
await client.scroll(tabId, { direction: 'down', amount: 100 });
await new Promise(r => setTimeout(r, 2000));
// Session should still be alive after ops (each refreshed lastAccess)
const snapshot = await client.getSnapshot(tabId);
expect(snapshot.url).toContain('/links');
} finally {
await client.cleanup();
}
}, 60000);
});
describe('Popup/new-window policy (JO-2456)', () => {
test('target=_blank link creates a managed tab', async () => {
const client = createClient(serverUrl);
try {
const { tabId } = await client.createTab(`${testSiteUrl}/popup-source`);
// Click the target=_blank link
await client.click(tabId, { selector: '#blankLink' });
// Wait briefly for popup event to register
await new Promise(r => setTimeout(r, 1500));
// List tabs — should see more than 1 tab (the popup is now managed)
const res = await client.request('GET', `/tabs?userId=${client.userId}`);
expect(res.tabs.length).toBeGreaterThanOrEqual(2);
// Find the popup tab
const popupTab = res.tabs.find(t => t.url.includes('/popup-target'));
expect(popupTab).toBeDefined();
expect(popupTab.tabId).toBeDefined();
// The popup tab should be closeable via the API
await client.closeTab(popupTab.tabId);
const res2 = await client.request('GET', `/tabs?userId=${client.userId}`);
const stillThere = res2.tabs.find(t => t.tabId === popupTab.tabId);
expect(stillThere).toBeUndefined();
} finally {
await client.cleanup();
}
}, 60000);
test('window.open creates a managed tab', async () => {
const client = createClient(serverUrl);
try {
const { tabId } = await client.createTab(`${testSiteUrl}/popup-source`);
// Click the window.open button
await client.click(tabId, { selector: '#openBtn' });
// Wait briefly for popup event to register
await new Promise(r => setTimeout(r, 1500));
// List tabs — popup should be tracked
const res = await client.request('GET', `/tabs?userId=${client.userId}`);
const popupTab = res.tabs.find(t => t.url.includes('/popup-target'));
expect(popupTab).toBeDefined();
expect(popupTab.tabId).toBeDefined();
} finally {
await client.cleanup();
}
}, 60000);
});
});