Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion skills/brainstorming/scripts/server.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ function handleMessage(text) {
}
touchActivity();
console.log(JSON.stringify({ source: 'user-event', ...event }));
if (event.choice) {
// event may be null (JSON.parse('null')); property access on null throws
// and would escape the socket 'data' handler as an uncaughtException.
if (event && event.choice) {
const eventsFile = path.join(STATE_DIR, 'events');
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
}
Expand Down
47 changes: 47 additions & 0 deletions tests/brainstorm-server/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,53 @@ async function runTests() {
ws.close();
});

await test('handles JSON null from client without crashing', async () => {
// JSON.parse('null') yields null; reading .choice on null throws and,
// before the handleMessage guard, killed the server process.
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
await new Promise(resolve => ws.on('open', resolve));

ws.send('null');
await sleep(300);

const res = await fetch(`http://localhost:${TEST_PORT}/`);
assert.strictEqual(res.status, 200, 'Server should still be running after JSON null');
ws.close();
Comment on lines +304 to +309
});

await test('handles JSON primitive values from client without crashing', async () => {
// Numbers, strings, booleans are valid JSON top-level values. They
// should be ignored (no .choice, so no events file write) without
// throwing.
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
await new Promise(resolve => ws.on('open', resolve));

ws.send('42');
ws.send('"hello"');
ws.send('true');
await sleep(300);

const res = await fetch(`http://localhost:${TEST_PORT}/`);
assert.strictEqual(res.status, 200, 'Server should still be running after primitive values');
ws.close();
});

await test('does NOT write events file for JSON null', async () => {
// Regression for the null guard: ensure null doesn't slip past the
// truthiness check and write a stray events file.
const eventsFile = path.join(STATE_DIR, 'events');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);

const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
await new Promise(resolve => ws.on('open', resolve));

ws.send('null');
await sleep(300);

assert(!fs.existsSync(eventsFile), 'state/events should not exist for null payload');
ws.close();
});

// ========== File Watching ==========
console.log('\n--- File Watching ---');

Expand Down