#![cfg(feature = "bin")]
mod common;
use common::{ChildGuard, http_get, pick_port, wait_for_listener};
use serial_test::serial;
use std::process::{Child, Command, Stdio};
use std::time::Duration;
const BURN: &str = env!("CARGO_BIN_EXE_burn");
fn http_post(port: u16, path: &str, body: &str) -> String {
common::http_post(port, path, body, "application/json")
}
fn spawn_burn_inline(source: &str) -> Child {
Command::new(BURN)
.env("BURN_QUIET", "1")
.env("BURN_SHARDS", "2")
.arg("-e")
.arg(source)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn burn")
}
#[test]
#[serial]
fn burn_serves_hello_from_burn() {
let port = pick_port();
let source = format!(
r#"
const http = require("node:http");
http.createServer((_req, res) => {{
res.end("hello from burn\n");
}}).listen({port}, () => console.log("listening"));
"#
);
let _child = ChildGuard::new(spawn_burn_inline(&source));
assert!(
wait_for_listener(port, Duration::from_secs(60)),
"burn listener didn't come up on :{port}"
);
let resp = http_get(port, "/");
assert!(resp.starts_with("HTTP/1.1 200"), "resp:\n{resp}");
assert!(resp.contains("hello from burn"), "resp:\n{resp}");
}
#[test]
#[serial]
fn burn_server_echoes_method_and_path() {
let port = pick_port();
let source = format!(
r#"
const http = require("http");
http.createServer((req, res) => {{
res.setHeader("x-method", req.method);
res.setHeader("x-url", req.url);
res.writeHead(201);
res.end("echo\n");
}}).listen({port});
"#
);
let _child = ChildGuard::new(spawn_burn_inline(&source));
assert!(wait_for_listener(port, Duration::from_secs(60)));
let resp = http_get(port, "/test?q=1");
assert!(resp.starts_with("HTTP/1.1 201"), "resp:\n{resp}");
assert!(resp.contains("x-method: GET"), "resp:\n{resp}");
assert!(resp.contains("x-url: /test?q=1"), "resp:\n{resp}");
assert!(resp.contains("echo"), "resp:\n{resp}");
}
#[test]
#[serial]
fn incoming_message_emits_buffer_chunks() {
let port = pick_port();
let source = format!(
r#"
const http = require('http');
http.createServer((req, res) => {{
const chunks = [];
req.on('data', (chunk) => {{
chunks.push({{
type: typeof chunk,
isBuffer: Buffer.isBuffer(chunk),
ctor: chunk && chunk.constructor && chunk.constructor.name,
len: chunk && chunk.length,
}});
}});
req.on('end', () => {{
// Concat must succeed - proves chunks are Buffers in
// the body-parser sense.
let total;
try {{
const bufs = [];
// Re-emit to test concat - we already consumed above,
// so feed the asserted shape into a synthetic concat.
total = chunks.length;
}} catch (_) {{
total = -1;
}}
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({{ chunks, total }}));
}});
}}).listen({port}, () => console.log("listening"));
"#
);
let _child = ChildGuard::new(spawn_burn_inline(&source));
assert!(
wait_for_listener(port, Duration::from_secs(60)),
"listener didn't bind on :{port}"
);
let resp = http_post(port, "/", r#"{"hello":"world"}"#);
assert!(resp.starts_with("HTTP/1.1 200"), "resp:\n{resp}");
assert!(
resp.contains("\"isBuffer\":true"),
"expected isBuffer:true in response, got:\n{resp}"
);
assert!(
resp.contains("\"len\":17"),
"expected len:17 (length of {{\"hello\":\"world\"}}) in response, got:\n{resp}"
);
assert!(
!resp.contains("\"type\":\"string\""),
"chunk should not be string post-fix, got:\n{resp}"
);
}
#[test]
#[serial]
fn body_parser_pattern_buffer_concat_succeeds() {
let port = pick_port();
let source = format!(
r#"
const http = require('http');
http.createServer((req, res) => {{
const chunks = [];
req.on('data', (c) => chunks.push(c));
req.on('end', () => {{
try {{
const buf = Buffer.concat(chunks);
const parsed = JSON.parse(buf.toString('utf8'));
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({{ ok: true, echo: parsed }}));
}} catch (e) {{
res.statusCode = 500;
res.end('concat error: ' + e.message);
}}
}});
}}).listen({port}, () => console.log("listening"));
"#
);
let _child = ChildGuard::new(spawn_burn_inline(&source));
assert!(wait_for_listener(port, Duration::from_secs(60)));
let resp = http_post(port, "/", r#"{"a":1,"b":[2,3]}"#);
assert!(resp.starts_with("HTTP/1.1 200"), "resp:\n{resp}");
assert!(resp.contains("\"ok\":true"), "resp:\n{resp}");
assert!(resp.contains("\"a\":1"), "resp:\n{resp}");
}
#[test]
#[serial]
fn burn_plain_script_exits_cleanly() {
let child = Command::new(BURN)
.env("BURN_QUIET", "1")
.env("BURN_SHARDS", "2")
.arg("-e")
.arg(r#"console.log("no listen")"#)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("run burn");
assert!(
child.status.success(),
"exit {}, stderr: {}",
child.status,
String::from_utf8_lossy(&child.stderr)
);
assert!(String::from_utf8_lossy(&child.stdout).contains("no listen"));
}