* Remove Request::build * All mutations on Request follow builder pattern The previous `build()` on request was necessary because mutating functions did not follow a proper builder pattern (taking `&mut self` instead of `mut self`). With a proper builder pattern, the need for `.build()` goes away. * All Request body and call methods consume self Anything which "executes" the request will now consume the `Request` to produce a `Result<Response>`. * Move all config from request to agent builder Timeouts, redirect config, proxy settings and TLS config are now on `AgentBuilder`. * Rename max_pool_connections -> max_idle_connections * Rename max_pool_connections_per_host -> max_idle_connections_per_host Consistent internal and external naming. * Introduce new AgentConfig for static config created by builder. `Agent` can be seen as having two parts. Static config and a mutable shared state between all states. The static config goes into `AgentConfig` and the mutable shared state into `AgentState`. * Replace all use of `Default` for `new`. Deriving or implementing `Default` makes for a secondary instantiation API. It is useful in some cases, but gets very confusing when there is both `new` _and_ a `Default`. It's especially devious for derived values where a reasonable default is not `0`, `false` or `None`. * Remove feature native_tls, we want only native rustls. This feature made for very clunky handling throughout the code. From a security point of view, it's better to stick with one single TLS API. Rustls recently got an official audit (very positive). https://github.com/ctz/rustls/tree/master/audit Rustls deliberately omits support for older, insecure TLS such as TLS 1.1 or RC4. This might be a problem for a user of ureq, but on balance not considered important enough to keep native_tls. * Remove auth and support for basic auth. The API just wasn't enough. A future reintroduction should at least also provide a `Bearer` mechanism and possibly more. * Rename jar -> cookie_store * Rename jar -> cookie_tin Just make some field names sync up with the type. * Drop "cookies" as default feature The need for handling cookies is probably rare, let's not enable it by default. * Change all feature checks for "cookie" to "cookies" The outward facing feature is "cookies" and I think it's better form that the code uses the official feature name instead of the optional library "cookies". * Keep `set` on Agent level as well as AgentBuilder. The idea is that an auth exchange might result in a header that need to be set _after_ the agent has been built.
217 lines
6.1 KiB
Rust
217 lines
6.1 KiB
Rust
use crate::test;
|
|
use std::io::Read;
|
|
|
|
use super::super::*;
|
|
|
|
#[test]
|
|
fn header_passing() {
|
|
test::set_handler("/header_passing", |unit| {
|
|
assert!(unit.has("X-Foo"));
|
|
assert_eq!(unit.header("X-Foo").unwrap(), "bar");
|
|
test::make_response(200, "OK", vec!["X-Bar: foo"], vec![])
|
|
});
|
|
let resp = get("test://host/header_passing")
|
|
.set("X-Foo", "bar")
|
|
.call()
|
|
.unwrap();
|
|
assert_eq!(resp.status(), 200);
|
|
assert!(resp.has("X-Bar"));
|
|
assert_eq!(resp.header("X-Bar").unwrap(), "foo");
|
|
}
|
|
|
|
#[test]
|
|
fn repeat_non_x_header() {
|
|
test::set_handler("/repeat_non_x_header", |unit| {
|
|
assert!(unit.has("Accept"));
|
|
assert_eq!(unit.header("Accept").unwrap(), "baz");
|
|
test::make_response(200, "OK", vec![], vec![])
|
|
});
|
|
let resp = get("test://host/repeat_non_x_header")
|
|
.set("Accept", "bar")
|
|
.set("Accept", "baz")
|
|
.call()
|
|
.unwrap();
|
|
assert_eq!(resp.status(), 200);
|
|
}
|
|
|
|
#[test]
|
|
fn repeat_x_header() {
|
|
test::set_handler("/repeat_x_header", |unit| {
|
|
assert!(unit.has("X-Forwarded-For"));
|
|
assert_eq!(unit.header("X-Forwarded-For").unwrap(), "130.240.19.2");
|
|
assert_eq!(
|
|
unit.all("X-Forwarded-For"),
|
|
vec!["130.240.19.2", "130.240.19.3"]
|
|
);
|
|
test::make_response(200, "OK", vec![], vec![])
|
|
});
|
|
let resp = get("test://host/repeat_x_header")
|
|
.set("X-Forwarded-For", "130.240.19.2")
|
|
.set("X-Forwarded-For", "130.240.19.3")
|
|
.call()
|
|
.unwrap();
|
|
assert_eq!(resp.status(), 200);
|
|
}
|
|
|
|
#[test]
|
|
fn body_as_text() {
|
|
test::set_handler("/body_as_text", |_unit| {
|
|
test::make_response(200, "OK", vec![], "Hello World!".to_string().into_bytes())
|
|
});
|
|
let resp = get("test://host/body_as_text").call().unwrap();
|
|
let text = resp.into_string().unwrap();
|
|
assert_eq!(text, "Hello World!");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "json")]
|
|
fn body_as_json() {
|
|
test::set_handler("/body_as_json", |_unit| {
|
|
test::make_response(
|
|
200,
|
|
"OK",
|
|
vec![],
|
|
"{\"hello\":\"world\"}".to_string().into_bytes(),
|
|
)
|
|
});
|
|
let resp = get("test://host/body_as_json").call().unwrap();
|
|
let json = resp.into_json().unwrap();
|
|
assert_eq!(json["hello"], "world");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "json")]
|
|
fn body_as_json_deserialize() {
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Deserialize)]
|
|
struct Hello {
|
|
hello: String,
|
|
}
|
|
|
|
test::set_handler("/body_as_json_deserialize", |_unit| {
|
|
test::make_response(
|
|
200,
|
|
"OK",
|
|
vec![],
|
|
"{\"hello\":\"world\"}".to_string().into_bytes(),
|
|
)
|
|
});
|
|
let resp = get("test://host/body_as_json_deserialize").call().unwrap();
|
|
let json = resp.into_json_deserialize::<Hello>().unwrap();
|
|
assert_eq!(json.hello, "world");
|
|
}
|
|
|
|
#[test]
|
|
fn body_as_reader() {
|
|
test::set_handler("/body_as_reader", |_unit| {
|
|
test::make_response(200, "OK", vec![], "abcdefgh".to_string().into_bytes())
|
|
});
|
|
let resp = get("test://host/body_as_reader").call().unwrap();
|
|
let mut reader = resp.into_reader();
|
|
let mut text = String::new();
|
|
reader.read_to_string(&mut text).unwrap();
|
|
assert_eq!(text, "abcdefgh");
|
|
}
|
|
|
|
#[test]
|
|
fn escape_path() {
|
|
test::set_handler("/escape_path%20here", |_unit| {
|
|
test::make_response(200, "OK", vec![], vec![])
|
|
});
|
|
let resp = get("test://host/escape_path here").call().unwrap();
|
|
let vec = resp.to_write_vec();
|
|
let s = String::from_utf8_lossy(&vec);
|
|
assert!(s.contains("GET /escape_path%20here HTTP/1.1"))
|
|
}
|
|
|
|
#[test]
|
|
fn request_debug() {
|
|
let req = get("http://localhost/my/page")
|
|
.set("Authorization", "abcdef")
|
|
.set("Content-Length", "1234")
|
|
.set("Content-Type", "application/json");
|
|
|
|
let s = format!("{:?}", req);
|
|
|
|
assert_eq!(
|
|
s,
|
|
"Request(GET /my/page, [Authorization: abcdef, \
|
|
Content-Length: 1234, Content-Type: application/json])"
|
|
);
|
|
|
|
let req = get("http://localhost/my/page?q=z")
|
|
.query("foo", "bar baz")
|
|
.set("Authorization", "abcdef");
|
|
|
|
let s = format!("{:?}", req);
|
|
|
|
assert_eq!(
|
|
s,
|
|
"Request(GET /my/page?q=z&foo=bar%20baz, [Authorization: abcdef])"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn non_ascii_header() {
|
|
test::set_handler("/non_ascii_header", |_unit| {
|
|
test::make_response(200, "OK", vec!["Wörse: Hädör"], vec![])
|
|
});
|
|
let resp = get("test://host/non_ascii_header")
|
|
.set("Bäd", "Headör")
|
|
.call();
|
|
assert!(
|
|
matches!(resp, Err(Error::BadHeader)),
|
|
"expected Some(&BadHeader), got {:?}",
|
|
resp
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn no_status_text() {
|
|
// this one doesn't return the status text
|
|
// let resp = get("https://www.okex.com/api/spot/v3/products")
|
|
test::set_handler("/no_status_text", |_unit| {
|
|
test::make_response(200, "", vec![], vec![])
|
|
});
|
|
let resp = get("test://host/no_status_text").call().unwrap();
|
|
assert!(resp.ok());
|
|
assert_eq!(resp.status(), 200);
|
|
}
|
|
|
|
#[test]
|
|
pub fn header_with_spaces_before_value() {
|
|
test::set_handler("/space_before_value", |unit| {
|
|
assert!(unit.has("X-Test"));
|
|
assert_eq!(unit.header("X-Test").unwrap(), "value");
|
|
test::make_response(200, "OK", vec![], vec![])
|
|
});
|
|
let resp = get("test://host/space_before_value")
|
|
.set("X-Test", " value")
|
|
.call()
|
|
.unwrap();
|
|
assert_eq!(resp.status(), 200);
|
|
}
|
|
|
|
#[test]
|
|
pub fn host_no_port() {
|
|
test::set_handler("/host_no_port", |_| {
|
|
test::make_response(200, "OK", vec![], vec![])
|
|
});
|
|
let resp = get("test://myhost/host_no_port").call().unwrap();
|
|
let vec = resp.to_write_vec();
|
|
let s = String::from_utf8_lossy(&vec);
|
|
assert!(s.contains("\r\nHost: myhost\r\n"));
|
|
}
|
|
|
|
#[test]
|
|
pub fn host_with_port() {
|
|
test::set_handler("/host_with_port", |_| {
|
|
test::make_response(200, "OK", vec![], vec![])
|
|
});
|
|
let resp = get("test://myhost:234/host_with_port").call().unwrap();
|
|
let vec = resp.to_write_vec();
|
|
let s = String::from_utf8_lossy(&vec);
|
|
assert!(s.contains("\r\nHost: myhost:234\r\n"));
|
|
}
|