test: sidebar agent test suite (layers 1-2)

Layer 1 (unit): 18 tests for URL sanitization in sidebar-utils.ts — http/https
pass, chrome:// rejected, javascript: rejected, control chars stripped, truncation.

Layer 2 (integration): 13 tests for server HTTP endpoints — auth, sidebar-command
queue writes, activeTabUrl override/fallback, event relay to chat buffer, message
queuing, queue overflow (429), chat clear, agent kill.

Source changes for testability:
- Extract sanitizeExtensionUrl() to browse/src/sidebar-utils.ts
- Add BROWSE_HEADLESS_SKIP env var to skip browser launch in HTTP-only tests
- Add SIDEBAR_QUEUE_PATH env var to both server.ts and sidebar-agent.ts
- Add SIDEBAR_AGENT_TIMEOUT env var to sidebar-agent.ts
- Sync package.json version to match VERSION (0.12.2.0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-26 18:46:16 -06:00
parent 84b6f354be
commit ac80abdc34
5 changed files with 443 additions and 5 deletions

View File

@@ -13,7 +13,7 @@ import { spawn } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
const QUEUE = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent-queue.jsonl');
const QUEUE = process.env.SIDEBAR_QUEUE_PATH || path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent-queue.jsonl');
const SERVER_PORT = parseInt(process.env.BROWSE_SERVER_PORT || '34567', 10);
const SERVER_URL = `http://127.0.0.1:${SERVER_PORT}`;
const POLL_MS = 500; // Fast polling — server already did the user-facing response
@@ -205,14 +205,15 @@ async function askClaude(queueEntry: any): Promise<void> {
});
});
// Timeout after 300 seconds (5 min — multi-page tasks need time)
// Timeout (default 300s / 5 min — multi-page tasks need time)
const timeoutMs = parseInt(process.env.SIDEBAR_AGENT_TIMEOUT || '300000', 10);
setTimeout(() => {
try { proc.kill(); } catch {}
sendEvent({ type: 'agent_error', error: 'Timed out after 300s' }).then(() => {
sendEvent({ type: 'agent_error', error: `Timed out after ${timeoutMs / 1000}s` }).then(() => {
isProcessing = false;
resolve();
});
}, 300000);
}, timeoutMs);
});
}

View File

@@ -0,0 +1,21 @@
/**
* Shared sidebar utilities — extracted for testability.
*/
/**
* Sanitize a URL from the Chrome extension before embedding in a prompt.
* Only accepts http/https, strips control characters, truncates to 2048 chars.
* Returns null if the URL is invalid or uses a non-http scheme.
*/
export function sanitizeExtensionUrl(url: string | null | undefined): string | null {
if (!url) return null;
try {
const u = new URL(url);
if (u.protocol === 'http:' || u.protocol === 'https:') {
return u.href.replace(/[\x00-\x1f\x7f]/g, '').slice(0, 2048);
}
return null;
} catch {
return null;
}
}