|
| 1 | +# Fetch Fixture Test Hardening Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Replace the scheduler-sensitive HTTP fixture regression test with deterministic staged input and correct the prior plan's expected file scope. |
| 6 | + |
| 7 | +**Architecture:** Keep `read_request` responsible for the `TcpStream` timeout and delegate header consumption to a helper generic over `Read`. Exercise that helper with a staged reader that signals and blocks exactly when more bytes are requested after the request line. |
| 8 | + |
| 9 | +**Tech Stack:** Rust 2024, standard library I/O and channels, Cargo integration tests, pnpm repository scripts. |
| 10 | + |
| 11 | +## Global Constraints |
| 12 | + |
| 13 | +- Preserve the existing 32 KiB request-header bound and two-second socket timeout. |
| 14 | +- Add no dependencies. |
| 15 | +- Keep production HTTP fixture behavior unchanged. |
| 16 | +- Use signed conventional commits without attribution. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +### Task 1: Deterministic complete-header regression test |
| 21 | + |
| 22 | +**Files:** |
| 23 | +- Modify: `crates/cli/tests/cli.rs:1-10,955-999` |
| 24 | + |
| 25 | +**Interfaces:** |
| 26 | +- Consumes: `std::io::Read`, `std::sync::mpsc`, and the existing `MAX_REQUEST_HEADERS_SIZE` constant. |
| 27 | +- Produces: `read_request_from<R: Read>(reader: &mut R) -> String` and a staged-reader regression test. |
| 28 | + |
| 29 | +- [ ] **Step 1: Extract the reader-generic helper without changing behavior** |
| 30 | + |
| 31 | +Keep socket configuration in `read_request` and move the existing complete-header loop and path extraction into: |
| 32 | + |
| 33 | +```rust |
| 34 | +fn read_request(stream: &mut TcpStream) -> String { |
| 35 | + stream.set_read_timeout(Some(Duration::from_secs(2))).unwrap(); |
| 36 | + read_request_from(stream) |
| 37 | +} |
| 38 | + |
| 39 | +fn read_request_from<R: Read>(reader: &mut R) -> String { |
| 40 | + let mut request = Vec::with_capacity(512); |
| 41 | + while !request.ends_with(b"\r\n\r\n") { |
| 42 | + let mut byte = [0_u8; 1]; |
| 43 | + reader.read_exact(&mut byte).unwrap(); |
| 44 | + request.push(byte[0]); |
| 45 | + assert!( |
| 46 | + request.len() <= MAX_REQUEST_HEADERS_SIZE, |
| 47 | + "HTTP request headers are unexpectedly large" |
| 48 | + ); |
| 49 | + } |
| 50 | + |
| 51 | + let request_line = request.split(|byte| *byte == b'\n').next().unwrap_or_default(); |
| 52 | + let request_line = String::from_utf8_lossy(request_line); |
| 53 | + request_line.split_whitespace().nth(1).unwrap_or_default().to_string() |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +- [ ] **Step 2: Add a staged `Read` implementation and replace the timeout-based test** |
| 58 | + |
| 59 | +Use `Cursor<Vec<u8>>` for the two stages. On the first read after the request line is exhausted, signal the test and wait for explicit release before exposing the remaining headers: |
| 60 | + |
| 61 | +```rust |
| 62 | +struct StagedRequest { |
| 63 | + request_line: Cursor<Vec<u8>>, |
| 64 | + headers: Cursor<Vec<u8>>, |
| 65 | + waiting_sender: Option<mpsc::Sender<()>>, |
| 66 | + release_receiver: Receiver<()>, |
| 67 | +} |
| 68 | + |
| 69 | +impl Read for StagedRequest { |
| 70 | + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> { |
| 71 | + let read = self.request_line.read(buffer)?; |
| 72 | + if read > 0 { |
| 73 | + return Ok(read); |
| 74 | + } |
| 75 | + |
| 76 | + if let Some(sender) = self.waiting_sender.take() { |
| 77 | + sender.send(()).map_err(std::io::Error::other)?; |
| 78 | + self.release_receiver.recv().map_err(std::io::Error::other)?; |
| 79 | + } |
| 80 | + |
| 81 | + self.headers.read(buffer) |
| 82 | + } |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +Spawn `read_request_from` on a worker thread. Wait for the staged reader's signal, assert `path_receiver.try_recv()` returns `mpsc::TryRecvError::Empty`, release the remaining headers, and assert the parsed path is `/complete`. |
| 87 | + |
| 88 | +- [ ] **Step 3: Prove the regression test detects request-line-only behavior** |
| 89 | + |
| 90 | +Temporarily change the helper loop terminator to stop after the first newline. Run: |
| 91 | + |
| 92 | +```bash |
| 93 | +cargo test -p srcmap-cli --test cli read_request_waits_for_complete_headers -- --exact |
| 94 | +``` |
| 95 | + |
| 96 | +Expected: FAIL because the worker returns before the staged reader requests the remaining headers. Restore complete-header reading immediately afterward. |
| 97 | + |
| 98 | +- [ ] **Step 4: Verify the focused behavior** |
| 99 | + |
| 100 | +Run: |
| 101 | + |
| 102 | +```bash |
| 103 | +cargo test -p srcmap-cli --test cli read_request_waits_for_complete_headers -- --exact |
| 104 | +cargo test -p srcmap-cli --test cli fetch |
| 105 | +``` |
| 106 | + |
| 107 | +Expected: both commands exit successfully. |
| 108 | + |
| 109 | +### Task 2: Documentation scope and repository verification |
| 110 | + |
| 111 | +**Files:** |
| 112 | +- Modify: `docs/superpowers/plans/2026-07-14-js-dependencies-fetch-fixture.md:217` |
| 113 | + |
| 114 | +**Interfaces:** |
| 115 | +- Consumes: the merged file list from PR #75. |
| 116 | +- Produces: an accurate expected-scope statement naming the intentional worktree ignore entry. |
| 117 | + |
| 118 | +- [ ] **Step 1: Correct the expected final scope** |
| 119 | + |
| 120 | +Replace the stale sentence with: |
| 121 | + |
| 122 | +```markdown |
| 123 | +Expected: only the design, plan, fixture test helper, dependency manifests, lockfiles, and the intentional `.worktrees/` ignore entry are changed; commits have valid signatures. |
| 124 | +``` |
| 125 | + |
| 126 | +- [ ] **Step 2: Run full verification with bounded logs** |
| 127 | + |
| 128 | +Run: |
| 129 | + |
| 130 | +```bash |
| 131 | +pnpm check > /tmp/srcmap-fetch-hardening-check.log 2>&1 |
| 132 | +pnpm test > /tmp/srcmap-fetch-hardening-test.log 2>&1 |
| 133 | +git diff --check origin/main...HEAD |
| 134 | +``` |
| 135 | + |
| 136 | +Expected: all commands exit successfully. |
| 137 | + |
| 138 | +- [ ] **Step 3: Review and commit the intended scope** |
| 139 | + |
| 140 | +Review `git status --short`, `git diff`, and staged files. Commit the test and documentation changes with: |
| 141 | + |
| 142 | +```bash |
| 143 | +git commit -S -m "test: harden fetch fixture synchronization" |
| 144 | +``` |
| 145 | + |
| 146 | +- [ ] **Step 4: Push and create the pull request** |
| 147 | + |
| 148 | +Push `codex/harden-fetch-fixture-test`, create a ready pull request targeting `main`, require all checks, and verify the exact merge commit after merging. |
0 commit comments