Skip to content

test: cover judge.mjs - #16

Merged
ZhenghuaBao merged 1 commit into
mainfrom
test/judge-coverage
Aug 19, 2026
Merged

test: cover judge.mjs#16
ZhenghuaBao merged 1 commit into
mainfrom
test/judge-coverage

Conversation

@ZhenghuaBao

Copy link
Copy Markdown
Contributor

scripts/judge.mjs is the L2 precision gate and runs on every review (precision-filter defaults to "true"), but had no tests. This adds 27.

Scope

These deliberately do not test the prompt or the judge's taste — that is model quality, not a contract, and pinning it would just make the tests brittle. What they do test is every place this script decides to drop a finding, because each one is a silent way to lose a real defect.

What is pinned

Threshold parsing - --threshold gates every keep/drop, so a silently coerced value moves the gate without telling anyone. "0.8oops", "0x1", "1e-1" and friends exit 2 instead of becoming 0.8 / 0 the way parseFloat would. [0,1] bounds are enforced; the boundaries themselves are accepted.

Schema drift in the judge's response - this is where plain JS truthiness is actively dangerous:

Response value Naive JS Required
keep: "false" truthy -> kept dropped
confidence: true Number(true) = 1 -> clears any threshold dropped
confidence: [1] Number([1]) = 1 -> clears any threshold dropped
confidence: 2 clamped to 1 -> survives everything dropped
confidence: "0.95" accepted

Fail-open - a finding the judge never classified into any group is kept even at threshold 1, so a judge blind spot cannot become a silent drop.

Malformed groups - an out-of-range representative_id falls back to a valid member_id (the coverage pass has already marked those members handled, so the group vanishing would take real findings with it). A group with no resolvable representative is skipped loudly.

Transport - HTTP error and unparseable envelope exit 1 without writing a filtered result; a fenced ```json reply is unwrapped.

Request shape - model, temperature: 0 (a precision gate has to be deterministic), bearer auth.

Test hygiene

The LLM is a local mock, and HOME is redirected to an empty directory so the ~/.opencodereview/config.json fallback cannot pick up a real local harness config and make the tests lie. Async spawn rather than spawnSync, because the mock runs in the test process and the event loop has to stay live to answer the child.

🤖 Generated with Claude Code

judge.mjs is the L2 precision gate and runs on every review
(`precision-filter` defaults to "true"), but had no tests.

These deliberately do not test the prompt or the judge's taste -- that is
model quality, not a contract. They test every place this script decides to
DROP a finding, because each one is a silent way to lose a real defect.

27 tests:

- threshold parsing: "0.8oops", "0x1", "1e-1" and friends are rejected with
  exit 2 rather than silently truncated the way parseFloat would; [0,1]
  bounds enforced; boundaries accepted.
- schema drift in the judge's own response, which is where plain JS
  truthiness is actively dangerous: the STRING "false" must still drop, and
  a confidence of `true` or `[1]` must NOT coerce to 1 and sail past every
  threshold. Out-of-range confidence drops rather than clamping.
- fail-open: a finding the judge never classified is kept even at
  threshold 1, so a judge blind spot cannot become a silent drop.
- malformed groups: an out-of-range representative_id falls back to a valid
  member, and a group with no resolvable representative is skipped loudly
  instead of taking real findings down with it.
- transport: HTTP error and unparseable envelope exit 1 without writing a
  filtered result; a fenced ```json reply is unwrapped.
- request shape: model, temperature 0, bearer auth.

The LLM is a local mock and HOME is redirected to an empty dir so the
~/.opencodereview/config.json fallback cannot pick up a real local config.
Async spawn (not spawnSync) because the mock runs in the test process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ZhenghuaBao
ZhenghuaBao merged commit 3e40adb into main Aug 19, 2026
2 checks passed
@ZhenghuaBao
ZhenghuaBao deleted the test/judge-coverage branch August 19, 2026 03:20

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b028bb280b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/judge.test.mjs
Comment on lines +93 to +98
...process.env,
HOME: home,
USERPROFILE: home,
OCR_LLM_URL: "",
OCR_LLM_TOKEN: "",
JUDGE_MODEL: "",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear the inherited custom auth header

When the test runner's environment defines OCR_LLM_AUTH_HEADER (for example, a developer using the supported x-api-key configuration), spawnJudge inherits it while overriding only the URL, token, and model. The child therefore sends Bearer test-token under that custom header, and the request-shape test incorrectly fails because it asserts headers.authorization; this is reproducible with OCR_LLM_AUTH_HEADER=x-api-key node --test scripts/judge.test.mjs. Clear or explicitly set this variable in the helper so the suite remains hermetic.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant