Skip to content

Commit 9967c5c

Browse files
test: stabilize fetch fixtures and update JS tooling
* docs: design dependency and fixture updates * docs: plan dependency and fixture updates * chore: ignore local worktrees * test: consume complete fixture requests * chore: update JavaScript tooling
1 parent 16e6092 commit 9967c5c

6 files changed

Lines changed: 544 additions & 211 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
target/
22
node_modules/
3+
.worktrees/
34
*.node
45
.DS_Store
56

crates/cli/tests/cli.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -951,22 +951,53 @@ fn fetch_invalid_url_json() {
951951
assert_eq!(v["code"], "INVALID_INPUT");
952952
}
953953

954+
const MAX_REQUEST_HEADERS_SIZE: usize = 32 * 1024;
955+
954956
fn read_request(stream: &mut TcpStream) -> String {
955957
stream.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
956-
let mut request_line = Vec::with_capacity(128);
957-
loop {
958+
let mut request = Vec::with_capacity(512);
959+
while !request.ends_with(b"\r\n\r\n") {
958960
let mut byte = [0_u8; 1];
959961
stream.read_exact(&mut byte).unwrap();
960-
request_line.push(byte[0]);
961-
if byte[0] == b'\n' {
962-
break;
963-
}
964-
assert!(request_line.len() < 8_192, "HTTP request line is unexpectedly long");
962+
request.push(byte[0]);
963+
assert!(
964+
request.len() <= MAX_REQUEST_HEADERS_SIZE,
965+
"HTTP request headers are unexpectedly large"
966+
);
965967
}
966-
let request_line = String::from_utf8_lossy(&request_line);
968+
969+
let request_line = request.split(|byte| *byte == b'\n').next().unwrap_or_default();
970+
let request_line = String::from_utf8_lossy(request_line);
967971
request_line.split_whitespace().nth(1).unwrap_or_default().to_string()
968972
}
969973

974+
#[test]
975+
fn read_request_waits_for_complete_headers() {
976+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
977+
let address = listener.local_addr().unwrap();
978+
let (accepted_sender, accepted_receiver) = mpsc::channel();
979+
let (path_sender, path_receiver) = mpsc::channel();
980+
let server = thread::spawn(move || {
981+
let (mut stream, _) = listener.accept().unwrap();
982+
accepted_sender.send(()).unwrap();
983+
path_sender.send(read_request(&mut stream)).unwrap();
984+
});
985+
986+
let mut client = TcpStream::connect(address).unwrap();
987+
accepted_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
988+
client.write_all(b"GET /complete HTTP/1.1\r\n").unwrap();
989+
client.flush().unwrap();
990+
991+
assert!(
992+
path_receiver.recv_timeout(Duration::from_millis(100)).is_err(),
993+
"request completed before the header terminator"
994+
);
995+
996+
client.write_all(b"Host: localhost\r\nConnection: close\r\n\r\n").unwrap();
997+
assert_eq!(path_receiver.recv_timeout(Duration::from_secs(2)).unwrap(), "/complete");
998+
server.join().unwrap();
999+
}
1000+
9701001
fn response(status: &str, headers: &[(&str, &str)], body: &str) -> String {
9711002
let mut response = format!("HTTP/1.1 {status}\r\nContent-Length: {}\r\n", body.len());
9721003
for (name, value) in headers {
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# JavaScript Dependencies and Fetch Fixture Stability 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:** Update the root JavaScript tooling and eliminate intermittent Windows fetch-test socket resets without changing production behavior.
6+
7+
**Architecture:** Keep the dependency refresh isolated to `package.json` and `pnpm-lock.yaml`. Correct the test-only HTTP fixture by reading a complete header block before responding, with a deterministic test that proves the helper does not return after only a request line.
8+
9+
**Tech Stack:** Rust 2024, standard-library TCP sockets and channels, Cargo tests, pnpm, fallow, oxfmt, oxlint, GitHub Actions, CodSpeed.
10+
11+
## Global Constraints
12+
13+
- Keep production fetch behavior unchanged.
14+
- Add no HTTP mock-server dependency.
15+
- Do not increase timeouts to hide the socket reset.
16+
- Preserve `memchr` 2.8.2 because 2.8.3 failed the performance gate.
17+
- Use signed conventional commits.
18+
19+
## File Structure
20+
21+
- Modify `crates/cli/tests/cli.rs`: deterministic fixture regression test and complete-header request reader.
22+
- Modify `package.json`: exact root tooling versions.
23+
- Modify `pnpm-lock.yaml`: regenerated dependency graph.
24+
- Create `docs/superpowers/plans/2026-07-14-js-dependencies-fetch-fixture.md`: execution record.
25+
26+
---
27+
28+
### Task 1: Consume Complete HTTP Fixture Requests
29+
30+
**Files:**
31+
- Modify and test: `crates/cli/tests/cli.rs:950-1015`
32+
33+
**Interfaces:**
34+
- Consumes: `TcpListener`, `TcpStream`, `mpsc`, `Duration`, and the existing `read_request(&mut TcpStream) -> String` helper.
35+
- Produces: the same `read_request(&mut TcpStream) -> String` interface, now returning only after `\r\n\r\n` has been consumed.
36+
37+
- [ ] **Step 1: Add the deterministic failing test after `read_request`**
38+
39+
```rust
40+
#[test]
41+
fn read_request_waits_for_complete_headers() {
42+
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
43+
let address = listener.local_addr().unwrap();
44+
let (accepted_sender, accepted_receiver) = mpsc::channel();
45+
let (path_sender, path_receiver) = mpsc::channel();
46+
let server = thread::spawn(move || {
47+
let (mut stream, _) = listener.accept().unwrap();
48+
accepted_sender.send(()).unwrap();
49+
path_sender.send(read_request(&mut stream)).unwrap();
50+
});
51+
52+
let mut client = TcpStream::connect(address).unwrap();
53+
accepted_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
54+
client.write_all(b"GET /complete HTTP/1.1\r\n").unwrap();
55+
client.flush().unwrap();
56+
57+
assert!(
58+
path_receiver.recv_timeout(Duration::from_millis(100)).is_err(),
59+
"request completed before the header terminator"
60+
);
61+
62+
client.write_all(b"Host: localhost\r\nConnection: close\r\n\r\n").unwrap();
63+
assert_eq!(path_receiver.recv_timeout(Duration::from_secs(2)).unwrap(), "/complete");
64+
server.join().unwrap();
65+
}
66+
```
67+
68+
- [ ] **Step 2: Run the focused test and verify red**
69+
70+
Run:
71+
72+
```bash
73+
cargo test -p srcmap-cli --test cli read_request_waits_for_complete_headers -- --exact
74+
```
75+
76+
Expected: FAIL with `request completed before the header terminator`.
77+
78+
- [ ] **Step 3: Replace request-line-only reading with bounded complete-header reading**
79+
80+
```rust
81+
const MAX_REQUEST_HEADERS_SIZE: usize = 32 * 1024;
82+
83+
fn read_request(stream: &mut TcpStream) -> String {
84+
stream.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
85+
let mut request = Vec::with_capacity(512);
86+
while !request.ends_with(b"\r\n\r\n") {
87+
let mut byte = [0_u8; 1];
88+
stream.read_exact(&mut byte).unwrap();
89+
request.push(byte[0]);
90+
assert!(
91+
request.len() <= MAX_REQUEST_HEADERS_SIZE,
92+
"HTTP request headers are unexpectedly large"
93+
);
94+
}
95+
96+
let request_line = request.split(|byte| *byte == b'\n').next().unwrap_or_default();
97+
let request_line = String::from_utf8_lossy(request_line);
98+
request_line.split_whitespace().nth(1).unwrap_or_default().to_string()
99+
}
100+
```
101+
102+
- [ ] **Step 4: Run the focused test and fetch integration tests**
103+
104+
Run:
105+
106+
```bash
107+
cargo test -p srcmap-cli --test cli read_request_waits_for_complete_headers -- --exact
108+
cargo test -p srcmap-cli --test cli fetch_
109+
```
110+
111+
Expected: PASS.
112+
113+
- [ ] **Step 5: Run a local stress loop**
114+
115+
Run:
116+
117+
```bash
118+
for run in {1..25}; do cargo test -q -p srcmap-cli --test cli fetch_ || exit 1; done
119+
```
120+
121+
Expected: every iteration exits successfully.
122+
123+
- [ ] **Step 6: Commit the fixture fix**
124+
125+
```bash
126+
git add crates/cli/tests/cli.rs
127+
git commit -S -m "test: consume complete fixture requests"
128+
```
129+
130+
### Task 2: Update Root JavaScript Tooling
131+
132+
**Files:**
133+
- Modify: `package.json:40-44`
134+
- Modify: `pnpm-lock.yaml`
135+
136+
**Interfaces:**
137+
- Consumes: existing root scripts for fallow, oxfmt, and oxlint.
138+
- Produces: exact versions `fallow` 3.5.0, `oxfmt` 0.59.0, and `oxlint` 1.74.0 with a frozen-install-compatible lockfile.
139+
140+
- [ ] **Step 1: Update exact manifest versions**
141+
142+
```json
143+
"devDependencies": {
144+
"fallow": "3.5.0",
145+
"oxfmt": "0.59.0",
146+
"oxlint": "1.74.0"
147+
}
148+
```
149+
150+
- [ ] **Step 2: Regenerate the lockfile without running package scripts**
151+
152+
Run:
153+
154+
```bash
155+
pnpm install --lockfile-only --ignore-scripts
156+
```
157+
158+
Expected: `package.json` and `pnpm-lock.yaml` resolve the three requested versions.
159+
160+
- [ ] **Step 3: Verify the updated tools**
161+
162+
Run:
163+
164+
```bash
165+
pnpm run fmt:js:check
166+
pnpm run lint:js
167+
pnpm outdated --format list
168+
```
169+
170+
Expected: formatting and linting pass, with no newer direct JavaScript dependency reported.
171+
172+
- [ ] **Step 4: Review dependency scope and preserve the Rust lock selection**
173+
174+
Run:
175+
176+
```bash
177+
git diff -- package.json pnpm-lock.yaml
178+
rg -n 'name = "memchr"|version = "2\.8\.2"' Cargo.lock
179+
```
180+
181+
Expected: only the requested JavaScript tooling graph changes and `memchr` remains at 2.8.2.
182+
183+
- [ ] **Step 5: Commit the dependency update**
184+
185+
```bash
186+
git add package.json pnpm-lock.yaml
187+
git commit -S -m "chore: update JavaScript tooling"
188+
```
189+
190+
### Task 3: Full Verification and Publication
191+
192+
**Files:**
193+
- Verify all files changed since `origin/main`.
194+
195+
**Interfaces:**
196+
- Consumes: completed Tasks 1 and 2.
197+
- Produces: a clean pull request with passing local and remote validation, merged to `main`.
198+
199+
- [ ] **Step 1: Run full local verification with bounded logs**
200+
201+
```bash
202+
pnpm check > /tmp/srcmap-js-fixture-check.log 2>&1
203+
pnpm test > /tmp/srcmap-js-fixture-test.log 2>&1
204+
```
205+
206+
Expected: both commands exit successfully.
207+
208+
- [ ] **Step 2: Review the complete diff and repository state**
209+
210+
```bash
211+
git diff --check origin/main...HEAD
212+
git diff --stat origin/main...HEAD
213+
git status --short --branch
214+
git log --show-signature --format=fuller origin/main..HEAD
215+
```
216+
217+
Expected: only the design, plan, fixture test helper, dependency manifest, and lockfile are changed; commits have valid signatures.
218+
219+
- [ ] **Step 3: Push and create a ready pull request**
220+
221+
```bash
222+
git push -u origin codex/update-js-stabilize-fetch-tests
223+
gh pr create --base main --head codex/update-js-stabilize-fetch-tests --title "test: stabilize fetch fixtures and update JS tooling" --body-file /tmp/srcmap-js-fixture-pr.md
224+
```
225+
226+
Expected: a ready pull request is created.
227+
228+
- [ ] **Step 4: Require all PR checks**
229+
230+
Run `gh pr checks --watch` and inspect any failure before retrying or fixing it.
231+
232+
Expected: Windows CI, security, coverage, and CodSpeed pass.
233+
234+
- [ ] **Step 5: Squash merge and verify the exact merge commit**
235+
236+
```bash
237+
gh pr merge --squash --delete-branch --subject "test: stabilize fetch fixtures and update JS tooling"
238+
```
239+
240+
Expected: the pull request is merged, local `main` matches `origin/main`, and every workflow for the merge commit completes successfully.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# JavaScript Dependencies and Fetch Fixture Stability
2+
3+
## Context
4+
5+
The root JavaScript tooling dependencies have newer releases available:
6+
7+
- `fallow` 3.5.0
8+
- `oxfmt` 0.59.0
9+
- `oxlint` 1.74.0
10+
11+
Separately, Windows CI intermittently times out while fetch integration tests wait for a second local HTTP request. The local fixture currently reads only the request line before writing a response and closing the socket. Unread request headers can cause Windows to reset the connection, so the client treats the first request as failed and never sends the expected second request.
12+
13+
## Goals
14+
15+
- Update the three root JavaScript tooling dependencies to their current exact versions.
16+
- Make the local HTTP fixture consume a complete request header block before responding.
17+
- Prove the fixture behavior with a deterministic regression test.
18+
- Keep production fetch behavior unchanged.
19+
- Preserve the intentional `memchr` 2.8.2 lockfile selection because 2.8.3 failed the performance gate.
20+
21+
## Non-goals
22+
23+
- Refactor the production fetch implementation.
24+
- Add an HTTP mock-server dependency.
25+
- Increase timeouts to hide the socket reset.
26+
- Change unrelated test infrastructure.
27+
28+
## Design
29+
30+
### Dependency update
31+
32+
Update the exact versions in the root `package.json`, regenerate `pnpm-lock.yaml`, and run the existing formatting, linting, analysis, and test commands. No package scripts or configuration should change unless a new tool version exposes a real incompatibility.
33+
34+
### HTTP fixture
35+
36+
Change `read_request` in `crates/cli/tests/cli.rs` to read bytes until the HTTP header terminator `\r\n\r\n`. Retain the first request line for path parsing and reject an unexpectedly large header block with a named size limit.
37+
38+
This ensures the server has consumed the client's request data before it writes a response and drops the connection. The fixture remains dependency-free and continues to support only the GET requests needed by these tests.
39+
40+
### Regression test
41+
42+
Add a fixture-level test that opens a loopback connection and sends the request in two stages:
43+
44+
1. Send only the request line and verify `read_request` has not completed.
45+
2. Send headers and the terminating blank line.
46+
3. Verify `read_request` completes and returns the expected path.
47+
48+
The test must fail against the current request-line-only implementation before the helper is changed. It must pass after the complete-header implementation is added.
49+
50+
## Verification
51+
52+
- Run the focused regression test and record the expected red then green result.
53+
- Repeatedly run the fetch integration tests to exercise the fixture.
54+
- Run `pnpm check` and `pnpm test`.
55+
- Confirm the lockfile has no unexpected dependency changes.
56+
- Push a pull request and require Windows CI, security checks, coverage, and CodSpeed to pass.
57+
- After merge, verify every workflow for the exact merge commit on `main`.
58+
59+
## Rollout
60+
61+
Land the dependency update and fixture fix together because the test-only fix removes the CI flake that could otherwise obscure validation of the dependency update. If a tooling update causes a separate failure, isolate that compatibility change in its own commit before merging.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
"coverage:rust:html": "cargo llvm-cov --html"
3939
},
4040
"devDependencies": {
41-
"fallow": "3.4.2",
42-
"oxfmt": "0.58.0",
43-
"oxlint": "1.73.0"
41+
"fallow": "3.5.0",
42+
"oxfmt": "0.59.0",
43+
"oxlint": "1.74.0"
4444
},
4545
"packageManager": "pnpm@10.33.0"
4646
}

0 commit comments

Comments
 (0)