Files
hemx/examples/saas/tests/production_reference.rs
T
slhx agent 4e0d9ebaff feat(saas): expose redacted operational signals
req: operations/003

req: operations/005

req: operations/007

req: security/008

req: v1_release/003
2026-07-14 00:48:55 +02:00

261 lines
9.1 KiB
Rust

use hemx_test::TestProcess;
use std::fs;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const STARTUP_TIMEOUT: Duration = Duration::from_secs(12);
fn available_address() -> String {
let listener = TcpListener::bind("127.0.0.1:0").expect("reserve test port");
let address = listener.local_addr().expect("test address");
drop(listener);
address.to_string()
}
fn test_path(label: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock")
.as_nanos();
std::env::temp_dir().join(format!("hemx-saas-{label}-{}-{nonce}", std::process::id()))
}
fn start(address: &str, store: &Path) -> TestProcess {
let mut command = Command::new(env!("CARGO_BIN_EXE_hemx-saas-example"));
command
.env("HEMX_SAAS_ADDR", address)
.env("HEMX_SAAS_STORE", store);
TestProcess::start(command, "hemx-saas", address, STARTUP_TIMEOUT).expect("start SaaS app")
}
fn request(
address: &str,
method: &str,
path: &str,
headers: &[(&str, &str)],
body: &str,
) -> String {
let mut stream = TcpStream::connect(address).expect("connect to SaaS app");
write!(
stream,
"{method} {path} HTTP/1.1\r\nHost: {address}\r\nConnection: close\r\nContent-Length: {}\r\n",
body.len()
)
.expect("write request line");
for (name, value) in headers {
write!(stream, "{name}: {value}\r\n").expect("write request header");
}
write!(stream, "\r\n{body}").expect("finish request");
let mut response = String::new();
stream.read_to_string(&mut response).expect("read response");
response
}
fn create(address: &str, name: &str, bearer: &str, csrf: &str, origin: &str) -> String {
create_at_version(address, name, bearer, csrf, origin, None)
}
fn create_at_version(
address: &str,
name: &str,
bearer: &str,
csrf: &str,
origin: &str,
fingerprint: Option<&str>,
) -> String {
let mut headers = vec![
("Authorization", bearer),
("Origin", origin),
("Content-Type", "application/x-www-form-urlencoded"),
];
if let Some(fingerprint) = fingerprint {
headers.push(("x-hemx-fingerprint", fingerprint));
}
request(
address,
"POST",
"/projects",
&headers,
&format!("name={name}&csrf={csrf}"),
)
}
fn ready_fingerprint(response: &str) -> &str {
let marker = "\"fingerprint\":\"";
let start = response.find(marker).expect("readiness fingerprint") + marker.len();
let end = response[start..].find('"').expect("fingerprint end") + start;
&response[start..end]
}
#[test]
fn authenticated_project_mutation_is_atomic_and_survives_restart() {
// test req: auth/001 req: auth/002 req: auth/004 req: security/004 req: v1_release/003
let address = available_address();
let origin = format!("http://{address}");
let store = test_path("durable");
{
let _app = start(&address, &store);
let live = request(&address, "GET", "/health/live", &[], "");
assert!(live.starts_with("HTTP/1.1 200"), "{live}");
assert!(live.contains("{\"status\":\"live\"}"), "{live}");
let ready = request(&address, "GET", "/health/ready", &[], "");
assert!(ready.starts_with("HTTP/1.1 200"), "{ready}");
assert!(ready.contains("{\"status\":\"ready\","), "{ready}");
let denied_responses = [
create(
&address,
"DeniedAuth",
"Bearer secret-auth-material",
"demo-csrf",
&origin,
),
create(
&address,
"DeniedCsrf",
"Bearer demo-session",
"stale",
&origin,
),
create(
&address,
"DeniedOrigin",
"Bearer demo-session",
"demo-csrf",
"https://attacker.invalid",
),
];
for denied in &denied_responses {
assert!(denied.starts_with("HTTP/1.1 403"), "{denied}");
assert!(denied.contains("{\"code\":\"authorization-denied\"}"));
assert!(!denied.contains("Denied"));
assert!(!denied.contains("demo-csrf"));
assert!(!denied.contains("secret-auth-material"));
assert!(!denied.contains("attacker.invalid"));
}
let wrong_content_type = request(
&address,
"POST",
"/projects",
&[
("Authorization", "Bearer demo-session"),
("Origin", origin.as_str()),
("Content-Type", "text/plain"),
],
"name=WrongType&csrf=demo-csrf",
);
assert!(
wrong_content_type.starts_with("HTTP/1.1 415"),
"{wrong_content_type}"
);
let oversized = request(
&address,
"POST",
"/projects",
&[
("Authorization", "Bearer demo-session"),
("Origin", origin.as_str()),
("Content-Type", "application/x-www-form-urlencoded"),
],
&format!("name={}&csrf=demo-csrf", "x".repeat(9 * 1024)),
);
assert!(oversized.starts_with("HTTP/1.1 413"), "{oversized}");
let before = request(&address, "GET", "/", &[], "");
assert!(!before.contains("DeniedAuth"));
assert!(!before.contains("DeniedCsrf"));
assert!(!before.contains("DeniedOrigin"));
assert!(!before.contains("WrongType"));
let denied_metrics = request(&address, "GET", "/metrics", &[], "");
assert!(
denied_metrics.starts_with("HTTP/1.1 200"),
"{denied_metrics}"
);
assert!(
denied_metrics.contains("\"attempts\":3"),
"{denied_metrics}"
);
assert!(denied_metrics.contains("\"denied\":3"), "{denied_metrics}");
assert!(!denied_metrics.contains("Denied"));
assert!(!denied_metrics.contains("demo-csrf"));
assert!(!denied_metrics.contains("secret-auth-material"));
let stale = create_at_version(
&address,
"Stale%20Project",
"Bearer demo-session",
"demo-csrf",
&origin,
Some("0"),
);
assert!(stale.starts_with("HTTP/1.1 409"), "{stale}");
assert!(stale.contains("{\"code\":\"deployment-mismatch\"}"));
assert!(stale
.to_ascii_lowercase()
.contains("x-hemx-recovery: reload"));
assert!(!request(&address, "GET", "/", &[], "").contains("Stale Project"));
let fingerprint = ready_fingerprint(&ready);
let allowed = create_at_version(
&address,
"Durable%20Project",
"Bearer demo-session",
"demo-csrf",
&origin,
Some(fingerprint),
);
assert!(allowed.starts_with("HTTP/1.1 303"), "{allowed}");
assert!(request(&address, "GET", "/", &[], "").contains("Durable Project"));
let metrics = request(&address, "GET", "/metrics", &[], "");
assert!(metrics.contains("\"attempts\":5"), "{metrics}");
assert!(metrics.contains("\"succeeded\":1"), "{metrics}");
assert!(metrics.contains("\"mismatch\":1"), "{metrics}");
}
{
let _restarted = start(&address, &store);
let restored = request(&address, "GET", "/", &[], "");
assert!(restored.contains("Durable Project"), "{restored}");
assert!(restored.contains("1 project"), "{restored}");
}
let _ = fs::remove_file(store);
}
#[test]
fn failed_durable_commit_rolls_back_visible_state() {
// test req: failure/004 req: operations/002 req: v1_release/003
let address = available_address();
let origin = format!("http://{address}");
let store = test_path("rollback");
let _app = start(&address, &store);
fs::create_dir(&store).expect("block atomic rename destination");
let not_ready = request(&address, "GET", "/health/ready", &[], "");
assert!(not_ready.starts_with("HTTP/1.1 503"), "{not_ready}");
assert!(not_ready.contains("\"code\":\"storage-unavailable\""));
assert!(request(&address, "GET", "/health/live", &[], "").starts_with("HTTP/1.1 200"));
let rejected = create(
&address,
"Must%20Rollback",
"Bearer demo-session",
"demo-csrf",
&origin,
);
assert!(rejected.starts_with("HTTP/1.1 503"), "{rejected}");
assert!(rejected.contains("{\"code\":\"storage-unavailable\"}"));
assert!(!rejected.contains("Must Rollback"));
assert!(!rejected.contains("demo-csrf"));
assert!(!request(&address, "GET", "/", &[], "").contains("Must Rollback"));
assert!(!store.with_extension("tmp").exists());
let metrics = request(&address, "GET", "/metrics", &[], "");
assert!(metrics.contains("\"attempts\":1"), "{metrics}");
assert!(metrics.contains("\"failed\":1"), "{metrics}");
assert!(!metrics.contains("Must Rollback"));
let _ = fs::remove_dir(store);
}