//! `http_fetch`: the fixed `curl` arguments, and what `toolkit` makes of `curl`'s answer, against //! fake `curl` scripts. Do not edit. mod support; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; use std::sync::{Mutex, MutexGuard}; use proto::tools::HttpFetchArgs; use support::TempDir; use toolkit::Exit; use toolkit::fetch::{curl_args, fetch_with}; const URL: &str = "https://example.com/a?b=c"; /// Every test that starts a process takes its turn. Otherwise another test's fork can hold /// the script open for writing at the moment it is run, and running it fails with "text file /// busy" (ETXTBSY), which has nothing to do with the code under test. static SERIAL: Mutex<()> = Mutex::new(()); fn serial() -> MutexGuard<'static, ()> { SERIAL.lock().unwrap_or_else(|p| p.into_inner()) } fn fake_curl(dir: &TempDir, body: &str) -> PathBuf { let path = dir.path().join("curl"); std::fs::write(&path, format!("#!/bin/sh\n{body}\n")).unwrap(); std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap(); path } fn args() -> HttpFetchArgs { HttpFetchArgs { url: URL.to_string(), } } #[test] fn the_argument_list_is_fixed_and_ends_with_the_url() { let expected: Vec<&str> = vec![ "--silent", "--show-error", "--proto", "=https", "--proto-redir", "=https", "--location", "--max-redirs", "5", "--max-time", "50", "--max-filesize", "8388608", "--cacert", "/etc/ssl/certs/ca-certificates.crt", "--proxy", "socks5h://localhost/run/egress/egress.sock", "--write-out", "\n[http %{response_code}]", "--url", URL, ]; assert_eq!(curl_args(URL), expected); } #[test] fn curl_is_given_exactly_those_arguments() { let _serial = serial(); let dir = TempDir::new("fetch-args"); let curl = fake_curl(&dir, r#"for a in "$@"; do printf '%s|' "$a"; done"#); let got = fetch_with(&curl, &args()); assert_eq!(got.exit, Exit::Done); let expected: String = curl_args(URL).iter().map(|a| format!("{a}|")).collect(); assert_eq!(got.stdout, expected); } #[test] fn a_successful_fetch_is_the_body_and_the_status_line() { let _serial = serial(); let dir = TempDir::new("fetch-ok"); let curl = fake_curl(&dir, r#"printf 'hello\n\n[http 404]'"#); let got = fetch_with(&curl, &args()); assert_eq!( (got.exit, got.stdout.as_str()), (Exit::Done, "hello\n\n[http 404]") ); } #[test] fn a_failed_fetch_is_exit_1_with_curls_first_error_line() { let _serial = serial(); let dir = TempDir::new("fetch-fail"); let curl = fake_curl( &dir, "printf 'partial'; printf '\\ncurl: (97) cannot complete SOCKS5 connection to evil.test. (2)\\nmore\\n' >&2; exit 97", ); let got = fetch_with(&curl, &args()); assert_eq!(got.exit, Exit::ToolError); assert_eq!( got.stdout, format!( "http_fetch: {URL}: curl: (97) cannot complete SOCKS5 connection to evil.test. (2)" ) ); } #[test] fn a_failure_without_a_message_names_the_exit() { let _serial = serial(); let dir = TempDir::new("fetch-quiet"); let curl = fake_curl(&dir, "exit 28"); let got = fetch_with(&curl, &args()); assert_eq!( (got.exit, got.stdout.as_str()), ( Exit::ToolError, "http_fetch: https://example.com/a?b=c: curl exited 28" ) ); } #[test] fn a_curl_that_cannot_start_is_exit_1() { let _serial = serial(); let got = fetch_with(&PathBuf::from("/no/such/curl"), &args()); assert_eq!(got.exit, Exit::ToolError); assert!( got.stdout .starts_with("http_fetch: cannot start /no/such/curl: "), "{}", got.stdout ); } #[test] fn much_output_on_both_streams_does_not_stop_curl() { let _serial = serial(); let dir = TempDir::new("fetch-both"); let curl = fake_curl( &dir, "head -c 300000 /dev/zero | tr '\\0' e >&2; head -c 300000 /dev/zero | tr '\\0' o; exit 0", ); let got = fetch_with(&curl, &args()); assert_eq!((got.exit, got.stdout.len()), (Exit::Done, 300000)); }