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
6 changes: 0 additions & 6 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,4 @@ server.on("error", (err) => {
process.on("uncaughtException", (err) => {
console.error("Uncaught Exception:", err);
process.exit(1);
});

// Handle unhandled promise rejections
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
process.exit(1);
});
27 changes: 27 additions & 0 deletions backend/tests/unhandledRejection.policy.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";

// ---------------------------------------------------------------------------
// unhandledRejection policy fix (issue #1441): two contradictory global
// listeners existed — the first logs and continues, the second called
// process.exit(1), so any unhandled rejection killed the whole server.
// Exactly one log-and-continue policy must remain.
// ---------------------------------------------------------------------------

const serverSource = readFileSync(resolve(__dirname, "../server.js"), "utf8");

describe("server.js unhandledRejection policy", () => {
it("registers exactly one unhandledRejection listener", () => {
const matches = serverSource.match(/process\.on\(\s*["']unhandledRejection["']/g) || [];
expect(matches.length).toBe(1);
});

it("does not exit the process from the unhandledRejection handler", () => {
const listenerBlock = serverSource.slice(
serverSource.indexOf('"unhandledRejection"'),
serverSource.indexOf("unhandledRejection") + 300
);
expect(listenerBlock).not.toMatch(/process\.exit/);
Comment on lines +16 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- backend/tests/unhandledRejection.policy.unit.test.js ---'
cat -n backend/tests/unhandledRejection.policy.unit.test.js

printf '%s\n' '--- backend/server.js relevant lines ---'
nl -ba backend/server.js | sed -n '1,80p'

printf '%s\n' '--- references to the policy and test setup ---'
rg -n --glob '!node_modules' 'unhandledRejection|console\.error|process\.exit|serverSource' backend package.json 2>/dev/null | head -200

Repository: Canopus-Labs/PrepPilot

Length of output: 10123


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- backend/server.js lines 1-25 and 175-205 ---'
sed -n '1,25p;175,205p' backend/server.js

printf '%s\n' '--- project module and test configuration ---'
for f in package.json backend/package.json vitest.config.js backend/vitest.config.js; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat "$f"
  fi
done

printf '%s\n' '--- server.js imports and startup calls ---'
rg -n '^(import|const .*require)|listen\(|connect\(|start|app\.' backend/server.js | head -120

Repository: Canopus-Labs/PrepPilot

Length of output: 5718


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

test_source = Path("backend/tests/unhandledRejection.policy.unit.test.js").read_text(encoding="utf-8-sig")
server_source = Path("backend/server.js").read_text(encoding="utf-8")

pattern = re.compile(r'''process\.on\(\s*["']unhandledRejection["']''')

def assertions(source):
    matches = pattern.findall(source)
    start = source.find('"unhandledRejection"')
    end = source.find("unhandledRejection") + 300
    block = source[start:end]
    return len(matches), start, end, "process.exit" in block

variants = {
    "current": server_source,
    "single-quoted event": server_source.replace(
        'process.on("unhandledRejection"', "process.on('unhandledRejection'"
    ),
    "uses process.once": server_source.replace(
        'process.on("unhandledRejection"', 'process.once("unhandledRejection"'
    ),
    "exit after window": server_source.replace(
        '  console.error("Unhandled Promise Rejection:", err);',
        '  console.error("Unhandled Promise Rejection:", err);\\n' + " " * 400 + "process.exit(1);",
    ),
    "missing console.error": server_source.replace(
        '  console.error("Unhandled Promise Rejection:", err);\\n', ""
    ),
}

print("test source uses runtime listener APIs:", bool(re.search(
    r'process\.(listeners|once|addListener)|import\s+.*server', test_source
)))
for name, source in variants.items():
    count, start, end, has_exit = assertions(source)
    print(f"{name}: matches={count}, start={start}, end={end}, detects_exit={has_exit}")

server_lines = server_source.splitlines()
print("server starts listening:", any("app.listen(" in line for line in server_lines))
print("policy logs:", any("Unhandled Promise Rejection:" in line for line in server_lines))
PY

Repository: Canopus-Labs/PrepPilot

Length of output: 594


Test the registered policy at runtime, not with source-text matching.

The current tests can pass when the handler uses process.once, does not register, omits console.error, or calls process.exit outside the 300-character slice. Importing backend/server.js starts the server, so move the registration to a side-effect-free module, inspect process.listeners("unhandledRejection"), invoke the listener, and spy on console.error and process.exit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/unhandledRejection.policy.unit.test.js` around lines 16 - 25,
The tests in unhandledRejection.policy.unit.test.js must validate runtime
behavior instead of matching backend/server.js source text. Move the
unhandledRejection registration into a side-effect-free module, import it in the
test, inspect process.listeners("unhandledRejection") to verify one registered
listener, invoke that listener, and spy on console.error and process.exit to
assert the required logging and non-exit policy without starting the server.

});
});