mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 15:46:30 +03:00
Carry native comp capture and completion integrity fixes into the reviewed component workflow. Bump the skill to 4.4.0 and engine/platform pins to 0.1.6; keep publication separate from this release candidate. AI assistance: implemented and validated with OpenAI Codex.
106 lines
4.1 KiB
Rust
106 lines
4.1 KiB
Rust
//! Native transport evidence: read the response that was rendered, never refetch.
|
|
use impeccable_browser::{cdp::Browser, discovery};
|
|
use std::collections::HashMap;
|
|
use std::io::{Read, Write};
|
|
use std::net::TcpListener;
|
|
use std::sync::{
|
|
Arc,
|
|
atomic::{AtomicUsize, Ordering},
|
|
};
|
|
use std::time::Duration;
|
|
|
|
#[test]
|
|
fn response_capture_reads_original_bytes_and_preserves_repeated_url_ambiguity() {
|
|
let env: HashMap<String, String> = std::env::vars().collect();
|
|
let Ok(exe) = discovery::find_browser(&env) else {
|
|
eprintln!("skip: no browser");
|
|
return;
|
|
};
|
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
let origin = format!("http://127.0.0.1:{}", listener.local_addr().unwrap().port());
|
|
let hits = Arc::new(AtomicUsize::new(0));
|
|
let count = hits.clone();
|
|
std::thread::spawn(move || {
|
|
for mut stream in listener.incoming().flatten() {
|
|
let mut request = [0u8; 4096];
|
|
let n = stream.read(&mut request).unwrap_or(0);
|
|
let request = String::from_utf8_lossy(&request[..n]);
|
|
let (kind, body) = if request.starts_with("GET /asset.bin ") {
|
|
let number = count.fetch_add(1, Ordering::SeqCst);
|
|
(
|
|
"application/octet-stream",
|
|
if number == 0 {
|
|
vec![0, 255, 13, 128, 42]
|
|
} else {
|
|
vec![99, 4, 0, 128]
|
|
},
|
|
)
|
|
} else {
|
|
(
|
|
"text/html",
|
|
b"<!doctype html><title>capture</title><body>fixture</body>".to_vec(),
|
|
)
|
|
};
|
|
let header = format!(
|
|
"HTTP/1.1 200 OK\r\nContent-Type: {kind}\r\nContent-Length: {}\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n",
|
|
body.len()
|
|
);
|
|
let _ = stream.write_all(header.as_bytes());
|
|
let _ = stream.write_all(&body);
|
|
}
|
|
});
|
|
let mut browser = Browser::launch(&exe, &[], false).expect("isolated browser");
|
|
let mut page = browser.new_page().unwrap();
|
|
assert!(
|
|
page.response_evidence(&[]).is_err(),
|
|
"capture must be explicitly enabled"
|
|
);
|
|
page.begin_response_capture().unwrap();
|
|
page.goto(&origin, "load", Duration::from_secs(15)).unwrap();
|
|
page.evaluate_value("fetch('/asset.bin').then(r=>r.arrayBuffer()).then(()=>true)")
|
|
.unwrap();
|
|
// Pump a browser round-trip after the fetch's completion event.
|
|
page.evaluate_value("true").unwrap();
|
|
let urls = vec![format!("{origin}/asset.bin")];
|
|
let first = page.response_evidence(&urls).unwrap();
|
|
assert_eq!(first.responses.len(), 1);
|
|
assert_eq!(
|
|
first.responses[0].body.as_deref(),
|
|
Some(&[0, 255, 13, 128, 42][..])
|
|
);
|
|
assert!(!first.responses[0].ambiguous_url);
|
|
assert_eq!(
|
|
hits.load(Ordering::SeqCst),
|
|
1,
|
|
"body evidence must not make another request"
|
|
);
|
|
page.evaluate_value("fetch('/asset.bin').then(r=>r.arrayBuffer()).then(()=>true)")
|
|
.unwrap();
|
|
page.evaluate_value("true").unwrap();
|
|
let repeated = page.response_evidence(&urls).unwrap();
|
|
assert_eq!(repeated.responses.len(), 2);
|
|
assert!(repeated.responses.iter().all(|r| r.ambiguous_url));
|
|
assert_eq!(
|
|
repeated.responses[0].body.as_deref(),
|
|
Some(&[0, 255, 13, 128, 42][..])
|
|
);
|
|
assert_eq!(
|
|
repeated.responses[1].body.as_deref(),
|
|
Some(&[99, 4, 0, 128][..])
|
|
);
|
|
assert_eq!(hits.load(Ordering::SeqCst), 2);
|
|
assert!(!repeated.changed_during_collection);
|
|
assert!(!repeated.truncated);
|
|
assert_eq!(repeated.responses[0].status, Some(200.0));
|
|
assert_eq!(repeated.responses[0].mime_type, "application/octet-stream");
|
|
// A new document must not borrow response evidence from its predecessor.
|
|
page.goto(&format!("{origin}/next"), "load", Duration::from_secs(15))
|
|
.unwrap();
|
|
let next = page.response_evidence(&urls).unwrap();
|
|
assert!(next.responses.is_empty());
|
|
assert_eq!(next.missing_urls, urls);
|
|
assert_eq!(hits.load(Ordering::SeqCst), 2);
|
|
page.close();
|
|
browser.close();
|
|
}
|