test: spec tests for Node IPC binary framing (#1181)#1191
Conversation
8 red tests for split-body binary framing that avoids JSON-encoding large HTML/content payloads in the Node IPC protocol: - EncodeMessage: 5 tests for typed payloads (HookRenderedPayload, HookFormatRenderedPayload, HookTransformPayload) and chained map results (html/content keys), plus single-header preservation for non-hook messages - DecodeMessage: 1 test for multi-header frame parsing with raw body injection into result map - Send: 1 test for multi-header response reading with raw body injection under X-Body-Field name PLAN.md and IMPLEMENTATION.md updated with split-body framing spec and implementation guidance. Refs #1181 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
zeroedin
left a comment
There was a problem hiding this comment.
Code Review Results
Scope: 8 new spec tests in internal/plugin/node_test.go, PLAN.md split-body framing spec, IMPLEMENTATION.md guidance (6 steps + test count updates)
Intent: Red tests + spec for Node IPC binary framing (issue #1181) — strip large HTML/content fields from JSON and send as raw bytes via multi-header framing
P2 — Moderate
| # | File | Issue | Reviewer | Confidence | Route |
|---|---|---|---|---|---|
| 1 | node_test.go:913-964 |
No multi-byte character test entry. All 5 DescribeTable entries use ASCII-only strings. The protocol specifies X-Body-Length as byte length, and EncodeMessage will use len() (bytes). An implementation that mistakenly uses utf8.RuneCountInString() would pass every existing test but produce malformed frames for CJK, emoji, or any multi-byte content. Add a table entry with multi-byte characters (e.g., "<p>日本語テスト 🎉</p>") where len(s) != utf8.RuneCountInString(s) and assert X-Body-Length equals the byte count, not the rune count. |
QA | 90 | Add test entry |
P3 — Advisory
| # | File | Issue | Reviewer | Confidence | Route |
|---|---|---|---|---|---|
| 2 | node_test.go:846-869 |
parseSplitFrame discards headers with _ = headers. The helper extracts headers but the calling test ignores them. X-Body-Length header value is verified via ContainSubstring on raw bytes (line 889), not via parsed header comparison. Tighter: assert headers["X-Body-Length"] == strconv.Itoa(len(rawBody)) to verify header value matches actual raw body length. Not wrong — just leaves header-value/body-length agreement implicitly checked by the Equal(expectedBody) assertion. |
QA | 75 | Consider |
| 3 | node_test.go (missing) |
No test for empty split field. What happens when HookRenderedPayload.HTML == ""? Should it still emit X-Body-Length: 0 and X-Body-Field: html with zero raw bytes? Or fall back to single-header? The spec and tests don't specify this boundary. If the answer is "split-body even for empty strings," the current tests pass vacuously. If the answer is "skip split-body for empty strings," there's no test enforcing that. |
QA | 70 | Define behavior |
| 4 | node_test.go (missing) |
No test for map with both html AND content keys. PLAN.md and IMPLEMENTATION.md say "check html first" for map[string]interface{} payloads. No test verifies this priority — a map with both keys could split the wrong field. |
QA | 80 | Consider adding |
| 5 | node_test.go (missing) |
No test for caller map mutation. IMPLEMENTATION.md step 1 says "for map payloads, shallow-copy the map and delete — do not mutate the caller's map." No test asserts the original map is unchanged after EncodeMessage. An implementation that deletes from the caller's map would pass all current tests. |
QA | 75 | Consider adding |
Coverage
- Testing gaps: Multi-byte byte-length validation (P2), empty field boundary, dual-key map priority, map immutability
- Residual risks: bridge.js behavior is tested implicitly through integration — PR body rationale is sound but the coupling assumption could break if integration tests are refactored. Acceptable for now given the 13 existing backward-compat tests.
- Spec/implementation alignment: PLAN.md and IMPLEMENTATION.md are consistent — terminology, protocol format, triggering conditions, field priority, and backward compat claims all match. Test count math is correct (74→82,
117→125).
Document Quality
- PLAN.md spec is clear and complete — frame format, triggering conditions, inbound/outbound behavior, bridge.js changes, backward compat all specified
- IMPLEMENTATION.md 6-step guidance covers all 4 files the developer needs to change with concrete instructions
encoding/jsonimport is pre-existing in the test file (base branch), not introduced by this PR — no flag needed
Verdict: Ready with fixes
Reasoning: The spec and tests are well-structured, internally consistent, and cover the core happy path for split-body framing across EncodeMessage (5 payload variants + negative case), DecodeMessage, and Send. The DescribeTable pattern is appropriate and well-differentiated. One moderate gap: all test data is ASCII-only, which means an implementation using rune count instead of byte length for X-Body-Length would silently pass — a multi-byte test entry is needed before merge. The P3 items (empty field boundary, dual-key priority, map immutability) are real gaps but can be deferred to follow-up if desired.
zeroedin
left a comment
There was a problem hiding this comment.
Addendum — P1 finding missed in initial pass
| # | File | Issue | Reviewer | Confidence | Route |
|---|---|---|---|---|---|
| 6 | IMPLEMENTATION.md:1352 |
Summary table not updated. Phase 5 heading (line 901) correctly reads ~125 tests, but the summary table at line 1352 still says ~111 with cumulative ~474. The heading was updated 117→125 (+8), but the table was not. Note: this was already stale on the base branch (heading 117 vs table 111). Update table to ~125 and fix cumulative for Phase 5 (~488) and all downstream phases (6: ~584, 7: ~670). |
QA | 95 | Fix before merge |
zeroedin
left a comment
There was a problem hiding this comment.
Addendum #2 — P2 finding from correctness review
| # | File | Issue | Reviewer | Confidence | Route |
|---|---|---|---|---|---|
| 7 | node_test.go:889 |
X-Body-Length assertion allows decimal-prefix false match. ContainSubstring(fmt.Sprintf("X-Body-Length: %d", len(expectedBody))) has no \r\n suffix — X-Body-Length: 50 matches X-Body-Length: 500. The X-Body-Field assertion on line 891 correctly includes \r\n. Since parseSplitFrame splits on Content-Length (not X-Body-Length) and the headers map is discarded (_ = headers), a wrong X-Body-Length value in the emitted frame would pass all assertions. Fix: fmt.Sprintf("X-Body-Length: %d\r\n", len(expectedBody)) |
QA | 85 | Fix before merge |
Updated consolidated finding list
P1: #6 — Summary table stale (~111 vs ~125)
P2: #1 — No multi-byte character test entry, #7 — X-Body-Length assertion prefix-match gap
P3: #2–5 — headers discarded, empty field boundary, dual-key priority, map mutation
- P1 #6: Updated summary table (Phase 5: ~129, cumulative downstream) - P2 #1: Added multi-byte character table entry (CJK, emoji) — validates X-Body-Length is byte count not rune count - P2 #7: Added \r\n suffix to X-Body-Length assertion to prevent decimal-prefix false match (50 matching 500) - P3 #2: Use parsed headers map from parseSplitFrame, assert header value matches actual raw body byte count - P3 #3: Added empty split field test — falls back to single-header - P3 #4: Added dual-key map priority test — html over content - P3 #5: Added caller map immutability guard test 12 total tests (was 8). Test counts updated in IMPLEMENTATION.md (section header, node.go description, impl guidance, summary table). PLAN.md updated with empty-field fallback, dual-key priority, and map immutability constraints. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review responseAll findings addressed in 068d410. P1 #6 — Summary table staleFixed. Updated Phase 5 from ~111 to ~129 (74 base + 8 original + 4 new from this round + remaining pre-existing delta), cumulative downstream adjusted (Phase 6: ~588, Phase 7: ~674). P2 #1 — No multi-byte character test entryFixed. Added DescribeTable entry with CJK, emoji, and accented characters ( P2 #7 — X-Body-Length assertion prefix-match gapFixed. Added P3 #2 — parseSplitFrame headers discardedFixed. Removed P3 #3 — No test for empty split fieldFixed. Added P3 #4 — No test for map with both html AND content keysFixed. Added P3 #5 — No test for caller map mutationFixed. Added Updated counts
|
zeroedin
left a comment
There was a problem hiding this comment.
Re-review — all findings addressed
Commit reviewed: 068d410 (fix: address review feedback on PR #1191)
Finding resolution
| # | Sev | Finding | Status |
|---|---|---|---|
| 1 | P2 | No multi-byte character test | ✅ Added table entry with CJK + emoji + French accented chars |
| 2 | P3 | _ = headers discarded |
✅ Now cross-validates headers["X-Body-Length"] against len(rawBody) |
| 3 | P3 | No empty field boundary test | ✅ Added — falls back to single-header when HTML == "" |
| 4 | P3 | No dual-key map priority test | ✅ Added — asserts html wins over content, verifies content stays in JSON |
| 5 | P3 | No caller map mutation test | ✅ Added — asserts original map retains html key and value after EncodeMessage |
| 6 | P1 | Summary table stale | ✅ Fixed — Phase 5: ~129 / ~492, downstream cumulative corrected through Phase 7 |
| 7 | P2 | X-Body-Length prefix-match gap |
✅ Fixed — \r\n suffix added |
Spec updates verified
- PLAN.md outbound spec now documents: dual-key priority (
htmlfirst), empty-field fallback, caller map immutability ✓ - IMPLEMENTATION.md step 6 updated: 8 → 12 tests, descriptions match actual tests ✓
- IMPLEMENTATION.md node.go summary: 8 → 12 tests with all new test names listed ✓
- Section 5A: 82 → 86 tests, Phase 5 heading: ~125 → ~129 ✓
- Summary table + cumulative: all arithmetic correct (363+129=492, 492+96=588, 588+86=674) ✓
Verdict: Ready
Reasoning: All P1, P2, and P3 findings from the initial review are resolved. The 12 spec tests now cover the core protocol (5 payload types + multi-byte), boundary conditions (empty field, non-hook type gate), priority semantics (dual-key map), and defensive invariants (caller map immutability). PLAN.md and IMPLEMENTATION.md are internally consistent and accurately reflect the test suite. Summary table cumulative chain is correct. No remaining blockers.
3a2043e
into
feature/node-ipc-binary-framing
Summary
12 red spec tests for split-body binary framing in the Node IPC protocol (issue #1181). This optimization strips large HTML/content fields from the JSON body and sends them as raw bytes, avoiding 4 JSON serialization passes per page for ~800KB payloads.
Test coverage
Backward compatibility: Covered by existing tests — 4 Message framing tests, 3 Message types tests, and 6 Malformed frame diagnostic tests exercise the current single-header format.
Malformed frame diagnostic: Existing tests verify non-Content-Length data on the first line still triggers the stdout pollution error.
bridge.js behavior: Tested implicitly — if EncodeMessage sends split-body frames but bridge.js doesn't parse them, all existing hook integration tests fail because the html field is missing from the payload.
Spec changes
Implementation guidance
The developer changes 4 code paths across 2 files:
node.go— EncodeMessage (outbound split-body), DecodeMessage (multi-header parsing), Send (multi-header response reader)bridge.js— stdin parser (multi-header frames), sendMessage (split-body for hook results)Refs #1181