mirror of
https://github.com/ducaale/xh.git
synced 2025-05-28 18:21:11 +00:00
1804 lines
44 KiB
Rust
1804 lines
44 KiB
Rust
#![cfg(feature = "integration-tests")]
|
|
use std::{
|
|
fs::File,
|
|
fs::{create_dir_all, read_to_string, OpenOptions},
|
|
io::{Seek, SeekFrom, Write},
|
|
process::Command,
|
|
time::Duration,
|
|
};
|
|
|
|
use assert_cmd::prelude::*;
|
|
use httpmock::{HttpMockRequest, Method::*, MockServer};
|
|
use indoc::{formatdoc, indoc};
|
|
use predicate::str::contains;
|
|
use predicates::prelude::*;
|
|
use serde_json::json;
|
|
use tempfile::{tempdir, tempfile};
|
|
|
|
pub fn random_string() -> String {
|
|
use rand::Rng;
|
|
|
|
rand::thread_rng()
|
|
.sample_iter(&rand::distributions::Alphanumeric)
|
|
.take(10)
|
|
.map(char::from)
|
|
.collect()
|
|
}
|
|
|
|
fn get_base_command() -> Command {
|
|
let mut cmd = Command::cargo_bin("xh").expect("binary should be present");
|
|
cmd.env("HOME", "");
|
|
#[cfg(target_os = "windows")]
|
|
cmd.env("XH_TEST_MODE_WIN_HOME_DIR", "");
|
|
cmd
|
|
}
|
|
|
|
/// Sensible default command to test with. use [`get_base_command`] if this
|
|
/// setup doesn't apply.
|
|
fn get_command() -> Command {
|
|
let mut cmd = get_base_command();
|
|
cmd.env("XH_TEST_MODE", "1");
|
|
cmd.env("XH_TEST_MODE_TERM", "1");
|
|
cmd
|
|
}
|
|
|
|
/// Do not pretend the output goes to a terminal.
|
|
fn redirecting_command() -> Command {
|
|
let mut cmd = get_base_command();
|
|
cmd.env("XH_TEST_MODE", "1");
|
|
cmd
|
|
}
|
|
|
|
/// Color output (with ANSI colors) by default.
|
|
fn color_command() -> Command {
|
|
let mut cmd = get_command();
|
|
cmd.env("XH_TEST_MODE_COLOR", "1");
|
|
cmd
|
|
}
|
|
|
|
#[test]
|
|
fn basic_json_post() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, then| {
|
|
when.method(POST)
|
|
.header("Content-Type", "application/json")
|
|
.json_body(json!({"name": "ali"}));
|
|
then.header("Content-Type", "application/json")
|
|
.json_body(json!({"got": "name", "status": "ok"}));
|
|
});
|
|
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg("--pretty=format")
|
|
.arg("post")
|
|
.arg(server.base_url())
|
|
.arg("name=ali")
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
{
|
|
"got": "name",
|
|
"status": "ok"
|
|
}
|
|
|
|
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn basic_get() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, then| {
|
|
when.method(GET);
|
|
then.body("foobar\n");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg("get")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout("foobar\n\n");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn basic_head() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.method(HEAD);
|
|
});
|
|
|
|
get_command().arg("head").arg(server.base_url()).assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn basic_options() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, then| {
|
|
when.method(OPTIONS);
|
|
then.header("Allow", "GET, HEAD, OPTIONS");
|
|
});
|
|
|
|
get_command()
|
|
.arg("-h")
|
|
.arg("options")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(contains("HTTP/1.1 200 OK"))
|
|
.stdout(contains("allow:"));
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn multiline_value() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.method(POST).body("foo=bar%0Abaz");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--form")
|
|
.arg("post")
|
|
.arg(server.base_url())
|
|
.arg("foo=bar\nbaz")
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn header() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("X-Foo", "Bar");
|
|
});
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("x-foo:Bar")
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn query_param() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.query_param("foo", "bar");
|
|
});
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("foo==bar")
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn json_param() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.json_body(json!({"foo": [1, 2, 3]}));
|
|
});
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("foo:=[1,2,3]")
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn verbose() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, then| {
|
|
when.header("Connection", "keep-alive")
|
|
.header("Content-Type", "application/json")
|
|
.header("Content-Length", "9")
|
|
.header("User-Agent", "xh/0.0.0 (test mode)")
|
|
.json_body(json!({"x": "y"}));
|
|
then.body("a body")
|
|
.header("date", "N/A")
|
|
.header("X-Foo", "Bar");
|
|
});
|
|
get_command()
|
|
.arg("--verbose")
|
|
.arg(server.base_url())
|
|
.arg("x=y")
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
POST / HTTP/1.1
|
|
accept: application/json, */*;q=0.5
|
|
accept-encoding: gzip, br
|
|
connection: keep-alive
|
|
content-length: 9
|
|
content-type: application/json
|
|
host: http.mock
|
|
user-agent: xh/0.0.0 (test mode)
|
|
|
|
{
|
|
"x": "y"
|
|
}
|
|
|
|
|
|
|
|
HTTP/1.1 200 OK
|
|
content-length: 6
|
|
date: N/A
|
|
x-foo: Bar
|
|
|
|
a body
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn download() {
|
|
let dir = tempdir().unwrap();
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.body("file contents\n");
|
|
});
|
|
|
|
let outfile = dir.path().join("outfile");
|
|
get_command()
|
|
.arg("--download")
|
|
.arg("--output")
|
|
.arg(&outfile)
|
|
.arg(server.base_url())
|
|
.assert();
|
|
mock.assert();
|
|
assert_eq!(read_to_string(&outfile).unwrap(), "file contents\n");
|
|
}
|
|
|
|
fn get_proxy_command(
|
|
protocol_to_request: &str,
|
|
protocol_to_proxy: &str,
|
|
proxy_url: &str,
|
|
) -> Command {
|
|
let mut cmd = get_command();
|
|
cmd.arg("--pretty=format")
|
|
.arg("--check-status")
|
|
.arg(format!("--proxy={}:{}", protocol_to_proxy, proxy_url))
|
|
.arg("GET")
|
|
.arg(format!("{}://example.test/get", protocol_to_request));
|
|
cmd
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_http_proxy() {
|
|
let server = MockServer::start();
|
|
|
|
let mock = server.mock(|when, then| {
|
|
when.method(GET).header("host", "example.test");
|
|
then.status(200);
|
|
});
|
|
|
|
get_proxy_command("http", "http", &server.base_url())
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_https_proxy() {
|
|
let server = MockServer::start();
|
|
|
|
let mock = server.mock(|when, then| {
|
|
when.method(CONNECT);
|
|
then.status(502);
|
|
});
|
|
|
|
get_proxy_command("https", "https", &server.base_url())
|
|
.assert()
|
|
.stderr(predicate::str::contains("unsuccessful tunnel"))
|
|
.failure();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn download_generated_filename() {
|
|
let dir = tempdir().unwrap();
|
|
let server = MockServer::start();
|
|
server.mock(|_when, then| {
|
|
then.header("Content-Type", "application/json").body("file");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--download")
|
|
.arg(server.url("/foo/bar/"))
|
|
.current_dir(&dir)
|
|
.assert();
|
|
|
|
get_command()
|
|
.arg("--download")
|
|
.arg(server.url("/foo/bar/"))
|
|
.current_dir(&dir)
|
|
.assert();
|
|
|
|
assert_eq!(read_to_string(dir.path().join("bar.json")).unwrap(), "file");
|
|
assert_eq!(
|
|
read_to_string(dir.path().join("bar.json-1")).unwrap(),
|
|
"file"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn download_supplied_filename() {
|
|
let dir = tempdir().unwrap();
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("Content-Disposition", r#"attachment; filename="foo.bar""#)
|
|
.body("file");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--download")
|
|
.arg(server.base_url())
|
|
.current_dir(&dir)
|
|
.assert();
|
|
mock.assert();
|
|
assert_eq!(read_to_string(dir.path().join("foo.bar")).unwrap(), "file");
|
|
}
|
|
|
|
#[test]
|
|
fn download_supplied_unquoted_filename() {
|
|
let dir = tempdir().unwrap();
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("Content-Disposition", r#"attachment; filename=foo bar baz"#)
|
|
.body("file");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--download")
|
|
.arg(server.base_url())
|
|
.current_dir(&dir)
|
|
.assert();
|
|
mock.assert();
|
|
assert_eq!(
|
|
read_to_string(dir.path().join("foo bar baz")).unwrap(),
|
|
"file"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decode() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("Content-Type", "text/plain; charset=latin1")
|
|
.body(b"\xe9");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout("é\n");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_all_proxy() {
|
|
let server = MockServer::start();
|
|
|
|
let mock = server.mock(|when, then| {
|
|
when.method(CONNECT);
|
|
then.status(502);
|
|
});
|
|
|
|
get_proxy_command("https", "all", &server.base_url())
|
|
.assert()
|
|
.stderr(predicate::str::contains("unsuccessful tunnel"))
|
|
.failure();
|
|
mock.assert();
|
|
|
|
get_proxy_command("http", "all", &server.base_url())
|
|
.assert()
|
|
.failure();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn streaming_decode() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("Content-Type", "text/plain; charset=latin1")
|
|
.body(b"\xe9");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg("--stream")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout("é\n");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn only_decode_for_terminal() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("Content-Type", "text/plain; charset=latin1")
|
|
.body(b"\xe9");
|
|
});
|
|
|
|
let output = redirecting_command()
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.get_output()
|
|
.stdout
|
|
.clone();
|
|
assert_eq!(&output, b"\xe9"); // .stdout() doesn't support byte slices
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn do_decode_if_formatted() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("Content-Type", "text/plain; charset=latin1")
|
|
.body(b"\xe9");
|
|
});
|
|
|
|
redirecting_command()
|
|
.arg("--pretty=all")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout("é");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn never_decode_if_binary() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
// this mimetype with a charset may actually be incoherent
|
|
then.header("Content-Type", "application/octet-stream; charset=latin1")
|
|
.body(b"\xe9");
|
|
});
|
|
|
|
let output = redirecting_command()
|
|
.arg("--pretty=all")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.get_output()
|
|
.stdout
|
|
.clone();
|
|
assert_eq!(&output, b"\xe9");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn binary_detection() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.body(b"foo\0bar");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
+-----------------------------------------+
|
|
| NOTE: binary data not shown in terminal |
|
|
+-----------------------------------------+
|
|
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn streaming_binary_detection() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.body(b"foo\0bar");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg("--stream")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
+-----------------------------------------+
|
|
| NOTE: binary data not shown in terminal |
|
|
+-----------------------------------------+
|
|
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn request_binary_detection() {
|
|
let mut binary_file = tempfile().unwrap();
|
|
binary_file.write_all(b"foo\0bar").unwrap();
|
|
binary_file.seek(SeekFrom::Start(0)).unwrap();
|
|
redirecting_command()
|
|
.arg("--print=B")
|
|
.arg("--offline")
|
|
.arg(":")
|
|
.stdin(binary_file)
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
+-----------------------------------------+
|
|
| NOTE: binary data not shown in terminal |
|
|
+-----------------------------------------+
|
|
|
|
|
|
"#});
|
|
}
|
|
|
|
#[test]
|
|
fn timeout() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_, then| {
|
|
then.status(200).delay(Duration::from_secs_f32(0.5));
|
|
});
|
|
|
|
get_command()
|
|
.arg("--timeout=0.1")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicates::str::contains("operation timed out"));
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn timeout_no_limit() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_, then| {
|
|
then.status(200).delay(Duration::from_secs_f32(0.5));
|
|
});
|
|
|
|
get_command()
|
|
.arg("--timeout=0")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn timeout_invalid() {
|
|
get_command()
|
|
.arg("--timeout=-0.01")
|
|
.arg("--offline")
|
|
.arg(":")
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicates::str::contains(
|
|
"Invalid seconds as connection timeout",
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn last_supplied_proxy_wins() {
|
|
let first_server = MockServer::start();
|
|
let first_mock = first_server.mock(|when, then| {
|
|
when.method(GET).header("host", "example.test");
|
|
then.status(500);
|
|
});
|
|
let second_server = MockServer::start();
|
|
let second_mock = second_server.mock(|when, then| {
|
|
when.method(GET).header("host", "example.test");
|
|
then.status(200);
|
|
});
|
|
|
|
let mut cmd = get_command();
|
|
cmd.args(&[
|
|
format!("--proxy=http:{}", first_server.base_url()).as_str(),
|
|
format!("--proxy=http:{}", second_server.base_url()).as_str(),
|
|
"GET",
|
|
"http://example.test",
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
first_mock.assert_hits(0);
|
|
second_mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_multiple_valid_proxies() {
|
|
let mut cmd = get_command();
|
|
cmd.arg("--offline")
|
|
.arg("--pretty=format")
|
|
.arg("--proxy=http:https://127.0.0.1:8000")
|
|
.arg("--proxy=https:socks5://127.0.0.1:8000")
|
|
.arg("--proxy=all:http://127.0.0.1:8000")
|
|
.arg("GET")
|
|
.arg("http://httpbin.org/get");
|
|
|
|
cmd.assert().success();
|
|
}
|
|
|
|
#[test]
|
|
fn check_status() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.status(404);
|
|
});
|
|
|
|
get_command()
|
|
.arg("--check-status")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.code(4)
|
|
.stderr("");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn user_password_auth() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("Authorization", "Basic dXNlcjpwYXNz");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--auth=user:pass")
|
|
.arg(server.base_url())
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn netrc_env_user_password_auth() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("Authorization", "Basic dXNlcjpwYXNz");
|
|
});
|
|
|
|
let mut netrc = tempfile::NamedTempFile::new().unwrap();
|
|
writeln!(
|
|
netrc,
|
|
"machine {}\nlogin user\npassword pass",
|
|
server.host()
|
|
)
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.env("NETRC", netrc.path())
|
|
.arg(server.base_url())
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn netrc_file_user_password_auth() {
|
|
for netrc_file in [".netrc", "_netrc"].iter() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("Authorization", "Basic dXNlcjpwYXNz");
|
|
});
|
|
|
|
let homedir = tempfile::TempDir::new().unwrap();
|
|
let netrc_path = homedir.path().join(netrc_file);
|
|
let mut netrc = File::create(&netrc_path).unwrap();
|
|
writeln!(
|
|
netrc,
|
|
"machine {}\nlogin user\npassword pass",
|
|
server.host()
|
|
)
|
|
.unwrap();
|
|
|
|
netrc.flush().unwrap();
|
|
|
|
get_command()
|
|
.env("HOME", homedir.path())
|
|
.env("XH_TEST_MODE_WIN_HOME_DIR", homedir.path())
|
|
.arg(server.base_url())
|
|
.assert();
|
|
|
|
mock.assert();
|
|
|
|
drop(netrc);
|
|
homedir.close().unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn check_status_warning() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.status(501);
|
|
});
|
|
|
|
redirecting_command()
|
|
.arg("--check-status")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.code(5)
|
|
.stderr("\nxh: warning: HTTP 501 Not Implemented\n\n");
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn user_auth() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("Authorization", "Basic dXNlcjo=");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--auth=user:")
|
|
.arg(server.base_url())
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn bearer_auth() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("Authorization", "Bearer SomeToken");
|
|
});
|
|
|
|
get_command()
|
|
.arg("--bearer=SomeToken")
|
|
.arg(server.base_url())
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
// TODO: test implicit download filenames
|
|
// For this we have to pretend the output is a tty
|
|
// This intersects with both #41 and #59
|
|
|
|
#[test]
|
|
fn verify_default_yes() {
|
|
get_command()
|
|
.arg("-v")
|
|
.arg("--pretty=format")
|
|
.arg("get")
|
|
.arg("https://self-signed.badssl.com")
|
|
.assert()
|
|
.failure()
|
|
.stdout(predicates::str::contains("GET / HTTP/1.1"))
|
|
.stderr(predicates::str::contains("UnknownIssuer"));
|
|
}
|
|
|
|
#[test]
|
|
fn verify_explicit_yes() {
|
|
get_command()
|
|
.arg("-v")
|
|
.arg("--pretty=format")
|
|
.arg("--verify=yes")
|
|
.arg("get")
|
|
.arg("https://self-signed.badssl.com")
|
|
.assert()
|
|
.failure()
|
|
.stdout(predicates::str::contains("GET / HTTP/1.1"))
|
|
.stderr(predicates::str::contains("UnknownIssuer"));
|
|
}
|
|
|
|
#[test]
|
|
fn verify_no() {
|
|
get_command()
|
|
.arg("-v")
|
|
.arg("--pretty=format")
|
|
.arg("--verify=no")
|
|
.arg("get")
|
|
.arg("https://self-signed.badssl.com")
|
|
.assert()
|
|
.stdout(predicates::str::contains("GET / HTTP/1.1"))
|
|
.stdout(predicates::str::contains("HTTP/1.1 200 OK"))
|
|
.stderr(predicates::str::is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn verify_valid_file() {
|
|
get_command()
|
|
.arg("-v")
|
|
.arg("--pretty=format")
|
|
.arg("--verify=tests/fixtures/certs/wildcard-self-signed.pem")
|
|
.arg("get")
|
|
.arg("https://self-signed.badssl.com")
|
|
.assert()
|
|
.stdout(predicates::str::contains("GET / HTTP/1.1"))
|
|
.stdout(predicates::str::contains("HTTP/1.1 200 OK"))
|
|
.stderr(predicates::str::is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn cert_without_key() {
|
|
get_command()
|
|
.arg("-v")
|
|
.arg("--pretty=format")
|
|
.arg("get")
|
|
.arg("https://client.badssl.com")
|
|
.assert()
|
|
.stdout(predicates::str::contains(
|
|
"400 No required SSL certificate was sent",
|
|
))
|
|
.stderr(predicates::str::is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn cert_with_key() {
|
|
get_command()
|
|
.arg("-v")
|
|
.arg("--pretty=format")
|
|
.arg("--cert=tests/fixtures/certs/client.badssl.com.crt")
|
|
.arg("--cert-key=tests/fixtures/certs/client.badssl.com.key")
|
|
.arg("get")
|
|
.arg("https://client.badssl.com")
|
|
.assert()
|
|
.stdout(predicates::str::contains("HTTP/1.1 200 OK"))
|
|
.stdout(predicates::str::contains("client-authenticated"))
|
|
.stderr(predicates::str::is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn forced_json() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.method(GET)
|
|
.header("content-type", "application/json")
|
|
.header("accept", "application/json, */*;q=0.5");
|
|
});
|
|
get_command()
|
|
.arg("--json")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.success();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn forced_form() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.method(GET)
|
|
.header("content-type", "application/x-www-form-urlencoded");
|
|
});
|
|
get_command()
|
|
.arg("--form")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.success();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn forced_multipart() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.method(POST).header_exists("content-type").body("");
|
|
});
|
|
get_command()
|
|
.arg("--multipart")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.success();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn formatted_json_output() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("content-type", "application/json")
|
|
.body(r#"{"":0}"#);
|
|
});
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
{
|
|
"": 0
|
|
}
|
|
|
|
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn inferred_json_output() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("content-type", "text/plain").body(r#"{"":0}"#);
|
|
});
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
{
|
|
"": 0
|
|
}
|
|
|
|
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn inferred_json_javascript_output() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
then.header("content-type", "application/javascript")
|
|
.body(r#"{"":0}"#);
|
|
});
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
{
|
|
"": 0
|
|
}
|
|
|
|
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn inferred_nonjson_output() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
// Trailing comma makes it invalid JSON, though formatting would still work
|
|
then.header("content-type", "text/plain").body(r#"{"":0,}"#);
|
|
});
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
{"":0,}
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn noninferred_json_output() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_when, then| {
|
|
// Valid JSON, but not declared as text
|
|
then.header("content-type", "application/octet-stream")
|
|
.body(r#"{"":0}"#);
|
|
});
|
|
get_command()
|
|
.arg("--print=b")
|
|
.arg(server.base_url())
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
{"":0}
|
|
"#});
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn mixed_stdin_request_items() {
|
|
let input_file = tempfile().unwrap();
|
|
redirecting_command()
|
|
.arg("--offline")
|
|
.arg(":")
|
|
.arg("x=3")
|
|
.stdin(input_file)
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains(
|
|
"Request body (from stdin) and request data (key=value) cannot be mixed",
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn multipart_stdin() {
|
|
let input_file = tempfile().unwrap();
|
|
redirecting_command()
|
|
.arg("--offline")
|
|
.arg("--multipart")
|
|
.arg(":")
|
|
.stdin(input_file)
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains(
|
|
"Cannot build a multipart request body from stdin",
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn default_json_for_raw_body() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _then| {
|
|
when.header("content-type", "application/json");
|
|
});
|
|
let input_file = tempfile().unwrap();
|
|
redirecting_command()
|
|
.arg(server.base_url())
|
|
.stdin(input_file)
|
|
.assert()
|
|
.success();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn body_from_file() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.header("content-type", "text/plain")
|
|
.body("Hello world\n");
|
|
});
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let filename = dir.path().join("input.txt");
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.open(&filename)
|
|
.unwrap()
|
|
.write_all(b"Hello world\n")
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg(format!("@{}", filename.to_string_lossy()))
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn body_from_file_with_explicit_mimetype() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.header("content-type", "image/png")
|
|
.body("Hello world\n");
|
|
});
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let filename = dir.path().join("input.txt");
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.open(&filename)
|
|
.unwrap()
|
|
.write_all(b"Hello world\n")
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg(format!("@{};type=image/png", filename.to_string_lossy()))
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn body_from_file_with_fallback_mimetype() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.header("content-type", "application/json")
|
|
.body("Hello world\n");
|
|
});
|
|
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let filename = dir.path().join("input");
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.open(&filename)
|
|
.unwrap()
|
|
.write_all(b"Hello world\n")
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg(format!("@{}", filename.to_string_lossy()))
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn no_double_file_body() {
|
|
get_command()
|
|
.arg(":")
|
|
.arg("@foo")
|
|
.arg("@bar")
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains(
|
|
"Can't read request from multiple files",
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn print_body_from_file() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let filename = dir.path().join("input");
|
|
OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.open(&filename)
|
|
.unwrap()
|
|
.write_all(b"Hello world\n")
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.arg("--offline")
|
|
.arg(":")
|
|
.arg(format!("@{}", filename.to_string_lossy()))
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Hello world"));
|
|
}
|
|
|
|
#[test]
|
|
fn colored_headers() {
|
|
color_command()
|
|
.arg("--offline")
|
|
.arg(":")
|
|
.assert()
|
|
.success()
|
|
// Color
|
|
.stdout(predicate::str::contains("\x1b[4m"))
|
|
// Reset
|
|
.stdout(predicate::str::contains("\x1b[0m"));
|
|
}
|
|
|
|
#[test]
|
|
fn colored_body() {
|
|
color_command()
|
|
.arg("--offline")
|
|
.arg(":")
|
|
.arg("x:=3")
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("\x1b[34m3\x1b[0m"));
|
|
}
|
|
|
|
#[test]
|
|
fn force_color_pipe() {
|
|
redirecting_command()
|
|
.arg("--ignore-stdin")
|
|
.arg("--offline")
|
|
.arg("--pretty=colors")
|
|
.arg(":")
|
|
.arg("x:=3")
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("\x1b[34m3\x1b[0m"));
|
|
}
|
|
|
|
#[test]
|
|
fn request_json_keys_order_is_preserved() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.body(r#"{"name":"ali","age":24}"#);
|
|
});
|
|
|
|
get_command()
|
|
.arg("get")
|
|
.arg(server.base_url())
|
|
.arg("name=ali")
|
|
.arg("age:=24")
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn data_field_from_file() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.body(r#"{"ids":"[1,2,3]"}"#);
|
|
});
|
|
|
|
let mut text_file = tempfile::NamedTempFile::new().unwrap();
|
|
write!(text_file, "[1,2,3]").unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg(format!("ids=@{}", text_file.path().to_string_lossy()))
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn data_field_from_file_in_form_mode() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.body(r#"message=hello+world"#);
|
|
});
|
|
|
|
let mut text_file = tempfile::NamedTempFile::new().unwrap();
|
|
write!(text_file, "hello world").unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("--form")
|
|
.arg(format!("message=@{}", text_file.path().to_string_lossy()))
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn json_field_from_file() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|when, _| {
|
|
when.body(r#"{"ids":[1,2,3]}"#);
|
|
});
|
|
|
|
let mut json_file = tempfile::NamedTempFile::new().unwrap();
|
|
writeln!(json_file, "[1,2,3]").unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg(format!("ids:=@{}", json_file.path().to_string_lossy()))
|
|
.assert();
|
|
mock.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn can_unset_default_headers() {
|
|
get_command()
|
|
.arg(":")
|
|
.arg("user-agent:")
|
|
.arg("--offline")
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
GET / HTTP/1.1
|
|
accept: */*
|
|
accept-encoding: gzip, br
|
|
connection: keep-alive
|
|
host: http.mock
|
|
|
|
"#});
|
|
}
|
|
|
|
#[test]
|
|
fn can_unset_headers() {
|
|
get_command()
|
|
.arg(":")
|
|
.arg("hello:world")
|
|
.arg("goodby:world")
|
|
.arg("goodby:")
|
|
.arg("--offline")
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
GET / HTTP/1.1
|
|
accept: */*
|
|
accept-encoding: gzip, br
|
|
connection: keep-alive
|
|
hello: world
|
|
host: http.mock
|
|
user-agent: xh/0.0.0 (test mode)
|
|
|
|
"#});
|
|
}
|
|
|
|
#[test]
|
|
fn can_set_unset_header() {
|
|
get_command()
|
|
.arg(":")
|
|
.arg("hello:")
|
|
.arg("hello:world")
|
|
.arg("--offline")
|
|
.assert()
|
|
.stdout(indoc! {r#"
|
|
GET / HTTP/1.1
|
|
accept: */*
|
|
accept-encoding: gzip, br
|
|
connection: keep-alive
|
|
hello: world
|
|
host: http.mock
|
|
user-agent: xh/0.0.0 (test mode)
|
|
|
|
"#});
|
|
}
|
|
|
|
#[test]
|
|
fn named_sessions() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_, then| {
|
|
then.header("set-cookie", "cook1=one; Path=/");
|
|
});
|
|
|
|
let config_dir = tempdir().unwrap();
|
|
let random_name = random_string();
|
|
|
|
get_command()
|
|
.env("XH_TEST_CONFIG_DIR", config_dir.path())
|
|
.arg(server.base_url())
|
|
.arg(format!("--session={}", random_name))
|
|
.arg("cookie:lang=en")
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
|
|
let path_to_session = config_dir.path().join::<std::path::PathBuf>(
|
|
[
|
|
"xh",
|
|
"sessions",
|
|
&format!("127.0.0.1_{}", server.port()),
|
|
&format!("{}.json", random_name),
|
|
]
|
|
.iter()
|
|
.collect(),
|
|
);
|
|
|
|
let session_content = read_to_string(path_to_session).unwrap();
|
|
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&session_content).unwrap(),
|
|
serde_json::json!({
|
|
"__meta__": {
|
|
"about": "xh session file",
|
|
"xh": "0.0.0"
|
|
},
|
|
"cookies": {
|
|
"cook1": { "value": "one", "path": "/" },
|
|
"lang": { "value": "en" }
|
|
},
|
|
"headers": {}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn anonymous_sessions() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_, then| {
|
|
then.header("set-cookie", "cook1=one");
|
|
});
|
|
|
|
let mut path_to_session = std::env::temp_dir();
|
|
let file_name = random_string();
|
|
path_to_session.push(file_name);
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg(format!("--session={}", path_to_session.to_string_lossy()))
|
|
.arg("--auth=me:pass")
|
|
.arg("hello:world")
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
|
|
let session_content = read_to_string(path_to_session).unwrap();
|
|
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&session_content).unwrap(),
|
|
serde_json::json!({
|
|
"__meta__": {
|
|
"about": "xh session file",
|
|
"xh": "0.0.0"
|
|
},
|
|
"cookies": {
|
|
"cook1": { "value": "one" }
|
|
},
|
|
"headers": {
|
|
"hello": "world",
|
|
"authorization": "Basic bWU6cGFzcw=="
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn anonymous_read_only_session() {
|
|
let server = MockServer::start();
|
|
server.mock(|_, then| {
|
|
then.header("set-cookie", "lang=en");
|
|
});
|
|
|
|
let session_file = tempfile::NamedTempFile::new().unwrap();
|
|
let old_session_content = serde_json::json!({
|
|
"__meta__": { "about": "xh session file", "xh": "0.0.0" },
|
|
"cookies": {
|
|
"cookie1": { "value": "one" }
|
|
},
|
|
"headers": {
|
|
"hello": "world"
|
|
}
|
|
});
|
|
|
|
std::fs::write(&session_file, old_session_content.to_string()).unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("goodbye:world")
|
|
.arg(format!(
|
|
"--session-read-only={}",
|
|
session_file.path().to_string_lossy()
|
|
))
|
|
.assert()
|
|
.success();
|
|
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&read_to_string(session_file.path()).unwrap())
|
|
.unwrap(),
|
|
old_session_content
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn session_files_are_created_in_read_only_mode() {
|
|
let server = MockServer::start();
|
|
server.mock(|_, then| {
|
|
then.header("set-cookie", "lang=ar");
|
|
});
|
|
|
|
let mut path_to_session = std::env::temp_dir();
|
|
let file_name = random_string();
|
|
path_to_session.push(file_name);
|
|
assert_eq!(path_to_session.exists(), false);
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("hello:world")
|
|
.arg(format!(
|
|
"--session-read-only={}",
|
|
path_to_session.to_string_lossy()
|
|
))
|
|
.assert()
|
|
.success();
|
|
|
|
let session_content = read_to_string(path_to_session).unwrap();
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&session_content).unwrap(),
|
|
serde_json::json!({
|
|
"__meta__": {
|
|
"about": "xh session file",
|
|
"xh": "0.0.0"
|
|
},
|
|
"cookies": {
|
|
"lang": { "value": "ar" }
|
|
},
|
|
"headers": {
|
|
"hello": "world"
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn named_read_only_session() {
|
|
let server = MockServer::start();
|
|
server.mock(|_, then| {
|
|
then.header("set-cookie", "lang=en");
|
|
});
|
|
|
|
let config_dir = tempdir().unwrap();
|
|
let random_name = random_string();
|
|
let path_to_session = config_dir.path().join::<std::path::PathBuf>(
|
|
[
|
|
"xh",
|
|
"sessions",
|
|
&format!("127.0.0.1_{}", server.port()),
|
|
&format!("{}.json", random_name),
|
|
]
|
|
.iter()
|
|
.collect(),
|
|
);
|
|
let old_session_content = serde_json::json!({
|
|
"__meta__": { "about": "xh session file", "xh": "0.0.0" },
|
|
"cookies": {
|
|
"cookie1": { "value": "one" }
|
|
},
|
|
"headers": {
|
|
"hello": "world"
|
|
}
|
|
});
|
|
create_dir_all(path_to_session.parent().unwrap()).unwrap();
|
|
File::create(&path_to_session).unwrap();
|
|
std::fs::write(&path_to_session, old_session_content.to_string()).unwrap();
|
|
|
|
get_command()
|
|
.env("XH_TEST_CONFIG_DIR", config_dir.path())
|
|
.arg(server.base_url())
|
|
.arg("goodbye:world")
|
|
.arg(format!("--session-read-only={}", random_name))
|
|
.assert()
|
|
.success();
|
|
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&read_to_string(path_to_session).unwrap())
|
|
.unwrap(),
|
|
old_session_content
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn expired_cookies_are_removed_from_session() {
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
let future_timestamp = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs()
|
|
+ 1000;
|
|
let past_timestamp = 1114425967; // 2005-04-25
|
|
|
|
let session_file = tempfile::NamedTempFile::new().unwrap();
|
|
|
|
std::fs::write(
|
|
&session_file,
|
|
serde_json::json!({
|
|
"__meta__": { "about": "xh session file", "xh": "0.0.0" },
|
|
"cookies": {
|
|
"expired_cookie": {
|
|
"value": "random_string",
|
|
"expires": past_timestamp
|
|
},
|
|
"unexpired_cookie": {
|
|
"value": "random_string",
|
|
"expires": future_timestamp
|
|
},
|
|
"with_out_expiry": {
|
|
"value": "random_string",
|
|
}
|
|
},
|
|
"headers": {}
|
|
})
|
|
.to_string(),
|
|
)
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.arg(":")
|
|
.arg(format!(
|
|
"--session={}",
|
|
session_file.path().to_string_lossy()
|
|
))
|
|
.arg("--offline")
|
|
.assert()
|
|
.success();
|
|
|
|
let session_content = read_to_string(session_file.path()).unwrap();
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&session_content).unwrap(),
|
|
serde_json::json!({
|
|
"__meta__": { "about": "xh session file", "xh": "0.0.0" },
|
|
"cookies": {
|
|
"unexpired_cookie": {
|
|
"value": "random_string",
|
|
"expires": future_timestamp
|
|
},
|
|
"with_out_expiry": {
|
|
"value": "random_string",
|
|
}
|
|
},
|
|
"headers": {}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cookies_override_each_other_in_the_correct_order() {
|
|
let server = MockServer::start();
|
|
let mock = server.mock(|_, then| {
|
|
then.header("set-cookie", "lang=en")
|
|
.header("set-cookie", "cook1=one");
|
|
});
|
|
|
|
let session_file = tempfile::NamedTempFile::new().unwrap();
|
|
|
|
std::fs::write(
|
|
&session_file,
|
|
serde_json::json!({
|
|
"__meta__": { "about": "xh session file", "xh": "0.0.0" },
|
|
"cookies": {
|
|
"lang": { "value": "fr" },
|
|
"cook2": { "value": "three" }
|
|
},
|
|
"headers": {}
|
|
})
|
|
.to_string(),
|
|
)
|
|
.unwrap();
|
|
|
|
get_command()
|
|
.arg(server.base_url())
|
|
.arg("cookie:cook1=two;cook2=two")
|
|
.arg(format!(
|
|
"--session={}",
|
|
session_file.path().to_string_lossy()
|
|
))
|
|
.assert()
|
|
.success();
|
|
|
|
mock.assert();
|
|
|
|
let session_content = read_to_string(session_file.path()).unwrap();
|
|
assert_eq!(
|
|
serde_json::from_str::<serde_json::Value>(&session_content).unwrap(),
|
|
serde_json::json!({
|
|
"__meta__": { "about": "xh session file", "xh": "0.0.0" },
|
|
"cookies": {
|
|
"lang": { "value": "en" },
|
|
"cook1": { "value": "one" },
|
|
"cook2": { "value": "two" }
|
|
},
|
|
"headers": {}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn print_intermediate_requests_and_responses() {
|
|
let server1 = MockServer::start();
|
|
let server2 = MockServer::start();
|
|
server1.mock(|_, then| {
|
|
then.header("location", &server2.base_url())
|
|
.status(302)
|
|
.header("date", "N/A")
|
|
.body("redirecting...");
|
|
});
|
|
server2.mock(|_, then| {
|
|
then.header("date", "N/A").body("final destination");
|
|
});
|
|
|
|
get_command()
|
|
.arg(server1.base_url())
|
|
.arg("--follow")
|
|
.arg("--verbose")
|
|
.arg("--all")
|
|
.assert()
|
|
.stdout(formatdoc! {r#"
|
|
GET / HTTP/1.1
|
|
accept: */*
|
|
accept-encoding: gzip, br
|
|
connection: keep-alive
|
|
host: http.mock
|
|
user-agent: xh/0.0.0 (test mode)
|
|
|
|
HTTP/1.1 302 Found
|
|
content-length: 14
|
|
date: N/A
|
|
location: {url}
|
|
|
|
redirecting...
|
|
|
|
GET / HTTP/1.1
|
|
accept: */*
|
|
accept-encoding: gzip, br
|
|
connection: keep-alive
|
|
host: http.mock
|
|
user-agent: xh/0.0.0 (test mode)
|
|
|
|
HTTP/1.1 200 OK
|
|
content-length: 17
|
|
date: N/A
|
|
|
|
final destination
|
|
"#, url = server2.base_url() });
|
|
}
|
|
|
|
#[test]
|
|
fn max_redirects_is_enforced() {
|
|
let server1 = MockServer::start();
|
|
let server2 = MockServer::start();
|
|
server1.mock(|_, then| {
|
|
then.header("location", &server2.base_url())
|
|
.status(302)
|
|
.body("redirecting...");
|
|
});
|
|
server2.mock(|_, then| {
|
|
then.header("location", &server2.base_url()) // redirect to the same server
|
|
.status(302)
|
|
.body("redirecting...");
|
|
});
|
|
|
|
get_command()
|
|
.arg(server1.base_url())
|
|
.arg("--follow")
|
|
.arg("--max-redirects=5")
|
|
.assert()
|
|
.stderr(predicate::str::contains(
|
|
"Too many redirects (--max-redirects=5)",
|
|
))
|
|
.failure();
|
|
}
|
|
|
|
#[test]
|
|
fn method_is_changed_before_following_302_redirect() {
|
|
let server1 = MockServer::start();
|
|
let server2 = MockServer::start();
|
|
let mock1 = server1.mock(|when, then| {
|
|
when.method(POST)
|
|
.header_exists("content-length")
|
|
.body(r#"{"name":"ali"}"#);
|
|
then.header("location", &server2.base_url())
|
|
.status(302)
|
|
.body("redirecting...");
|
|
});
|
|
let mock2 = server2.mock(|when, then| {
|
|
when.method(GET).matches(|req: &HttpMockRequest| {
|
|
!req.headers
|
|
.as_ref()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|(key, _)| key == "content-length")
|
|
});
|
|
then.body("final destination");
|
|
});
|
|
|
|
get_command()
|
|
.arg("post")
|
|
.arg(server1.base_url())
|
|
.arg("--follow")
|
|
.arg("name=ali")
|
|
.assert()
|
|
.success();
|
|
|
|
mock1.assert();
|
|
mock2.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn method_is_not_changed_before_following_307_redirect() {
|
|
let server1 = MockServer::start();
|
|
let server2 = MockServer::start();
|
|
let mock1 = server1.mock(|when, then| {
|
|
when.method(POST).body(r#"{"name":"ali"}"#);
|
|
then.header("location", &server2.base_url())
|
|
.status(307)
|
|
.body("redirecting...");
|
|
});
|
|
let mock2 = server2.mock(|when, then| {
|
|
when.method(POST).body(r#"{"name":"ali"}"#);
|
|
then.body("final destination");
|
|
});
|
|
|
|
get_command()
|
|
.arg("post")
|
|
.arg(server1.base_url())
|
|
.arg("--follow")
|
|
.arg("name=ali")
|
|
.assert()
|
|
.success();
|
|
|
|
mock1.assert();
|
|
mock2.assert();
|
|
}
|
|
|
|
#[test]
|
|
fn sensitive_headers_are_removed_after_cross_domain_redirect() {
|
|
let server1 = MockServer::start();
|
|
let server2 = MockServer::start();
|
|
let mock1 = server1.mock(|when, then| {
|
|
when.header_exists("authorization").header_exists("hello");
|
|
then.header("location", &server2.base_url())
|
|
.status(302)
|
|
.body("redirecting...");
|
|
});
|
|
let mock2 = server2.mock(|when, then| {
|
|
when.header_exists("hello")
|
|
.matches(|req: &HttpMockRequest| {
|
|
!req.headers
|
|
.as_ref()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|(key, _)| key == "authorization")
|
|
});
|
|
then.header("date", "N/A").body("final destination");
|
|
});
|
|
|
|
get_command()
|
|
.arg(server1.base_url())
|
|
.arg("--follow")
|
|
.arg("--auth=user:pass")
|
|
.arg("hello:world")
|
|
.assert()
|
|
.success();
|
|
|
|
mock1.assert();
|
|
mock2.assert();
|
|
}
|
|
|
|
// TODO: test redirect behaviour for non-cloneable bodies
|