use std::str::FromStr;
use surrealdb_types::{Value, object};
use wiremock::matchers::{body_string, header, method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::dbs::capabilities::{NetTarget, Targets};
use crate::dbs::{Capabilities, Session};
use crate::kvs::Datastore;
#[tokio::test]
async fn test_fetch_get() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/hello"))
.and(header("some-header", "some-value"))
.respond_with(ResponseTemplate::new(200).set_body_string("some body once told me"))
.expect(1)
.mount(&server)
.await;
let ds = Datastore::builder()
.with_capabilities(Capabilities::all())
.build_with_path("memory")
.await
.unwrap();
let sess = Session::owner();
let sql = format!(
r#"
RETURN function() {{
let res = await fetch('{}/hello',{{
headers: {{
"some-header": "some-value",
}}
}});
let body = await res.text();
return {{ status: res.status, body: body }};
}}
"#,
server.uri()
);
let res = ds.execute(&sql, &sess, None).await;
let res = res.unwrap().remove(0).output().unwrap();
server.verify().await;
assert_eq!(
res,
Value::Object(object! {
body: "some body once told me".to_string(),
status: 200,
}),
"Unexpected result: {:?}",
res
);
}
#[tokio::test]
async fn test_fetch_put() {
let server = MockServer::start().await;
Mock::given(method("PUT"))
.and(path("/hello"))
.and(header("some-header", "some-value"))
.and(body_string("some text"))
.respond_with(ResponseTemplate::new(201).set_body_string("some body once told me"))
.expect(1)
.mount(&server)
.await;
let ds = Datastore::builder()
.with_capabilities(Capabilities::all())
.build_with_path("memory")
.await
.unwrap();
let sess = Session::owner();
let sql = format!(
r#"
RETURN function() {{
let res = await fetch('{}/hello',{{
method: "PuT",
headers: {{
"some-header": "some-value",
}},
body: "some text",
}});
let body = await res.text();
return {{ status: res.status, body: body }};
}}
"#,
server.uri()
);
let res = ds.execute(&sql, &sess, None).await;
let res = res.unwrap().remove(0).output().unwrap();
server.verify().await;
assert_eq!(
res,
Value::Object(object! {
body: "some body once told me".to_string(),
status: 201,
}),
"Unexpected result: {res:?}"
);
}
#[tokio::test]
async fn test_fetch_error() {
let server = MockServer::start().await;
Mock::given(method("PROPPATCH"))
.and(path("/hello"))
.and(header("some-header", "some-value"))
.and(body_string("some text"))
.respond_with(ResponseTemplate::new(500).set_body_json(serde_json::json!({
"foo": "bar",
"baz": 2,
})))
.expect(1)
.mount(&server)
.await;
let ds = Datastore::builder()
.with_capabilities(Capabilities::all())
.build_with_path("memory")
.await
.unwrap();
let sess = Session::owner();
let sql = format!(
r#"
RETURN function() {{
let res = await fetch('{}/hello',{{
method: "PROPPATCH",
headers: {{
"some-header": "some-value",
}},
body: "some text",
}});
let body = await res.json();
return {{ status: res.status, body: body }};
}}
"#,
server.uri()
);
let res = ds.execute(&sql, &sess, None).await;
let res = res.unwrap().remove(0).output().unwrap();
server.verify().await;
assert_eq!(
res,
Value::Object(object! {
body: Value::Object(object! {
baz: 2,
foo: "bar".to_string(),
}),
status: 500,
}),
"Unexpected result: {res:?}",
);
}
#[tokio::test]
async fn test_fetch_denied() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/hello"))
.and(header("some-header", "some-value"))
.respond_with(ResponseTemplate::new(200).set_body_string("some body once told me"))
.expect(0)
.mount(&server)
.await;
let ds = Datastore::builder()
.with_capabilities(Capabilities::all().without_network_targets(Targets::Some(
[NetTarget::from_str(&server.address().to_string()).unwrap()].into(),
)))
.build_with_path("memory")
.await
.unwrap();
let sess = Session::owner();
let sql = format!(
r#"
RETURN function() {{
let res = await fetch('{}/hello',{{
headers: {{
"some-header": "some-value",
}}
}});
let body = await res.text();
return {{ status: res.status, body: body }};
}}
"#,
server.uri()
);
let res = ds.execute(&sql, &sess, None).await;
let res = res.unwrap().remove(0).output().unwrap_err();
server.verify().await;
assert!(
res.to_string()
.contains(&format!("Access to network target '{}' is not allowed", server.address())),
"Unexpected result: {:?}",
res
);
}
async fn fetch_status(
capabilities: Capabilities,
url: &str,
redirect: &str,
) -> Result<Value, surrealdb_types::Error> {
let ds = Datastore::builder()
.with_capabilities(capabilities)
.build_with_path("memory")
.await
.unwrap();
let sql = format!(
r#"
RETURN function() {{
let res = await fetch('{url}', {{ redirect: '{redirect}' }});
return {{ status: res.status }};
}}
"#
);
ds.execute(&sql, &Session::owner(), None).await.unwrap().remove(0).output()
}
#[tokio::test]
async fn test_fetch_redirect_error_resolves_allowed_host() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/hello"))
.respond_with(ResponseTemplate::new(200).set_body_string("some body once told me"))
.expect(1)
.mount(&server)
.await;
let url = format!("http://localhost:{}/hello", server.address().port());
let res = fetch_status(Capabilities::all(), &url, "error").await;
server.verify().await;
let res = res.unwrap();
assert_eq!(
res,
Value::Object(object! {
status: 200,
}),
"Unexpected result: {res:?}"
);
}
#[tokio::test]
async fn test_fetch_redirect_manual_resolves_allowed_host() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/hello"))
.respond_with(ResponseTemplate::new(200).set_body_string("some body once told me"))
.expect(1)
.mount(&server)
.await;
let url = format!("http://localhost:{}/hello", server.address().port());
let res = fetch_status(Capabilities::all(), &url, "manual").await;
server.verify().await;
let res = res.unwrap();
assert_eq!(
res,
Value::Object(object! {
status: 200,
}),
"Unexpected result: {res:?}"
);
}
#[tokio::test]
async fn test_fetch_redirect_manual_returns_redirect_response() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/redirect"))
.respond_with(
ResponseTemplate::new(302).insert_header("location", format!("{}/hello", server.uri())),
)
.expect(1)
.mount(&server)
.await;
let url = format!("{}/redirect", server.uri());
let res = fetch_status(Capabilities::all(), &url, "manual").await;
server.verify().await;
let res = res.unwrap();
assert_eq!(
res,
Value::Object(object! {
status: 302,
}),
"Unexpected result: {res:?}"
);
}
#[tokio::test]
async fn test_fetch_denied_under_every_redirect_mode() {
for redirect in ["follow", "error", "manual"] {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/hello"))
.respond_with(ResponseTemplate::new(200))
.expect(0)
.mount(&server)
.await;
let capabilities = Capabilities::all().without_network_targets(Targets::Some(
[NetTarget::from_str(&server.address().to_string()).unwrap()].into(),
));
let url = format!("{}/hello", server.uri());
let res = fetch_status(capabilities, &url, redirect).await;
server.verify().await;
let err = res.unwrap_err();
assert!(
err.to_string().contains(&format!(
"Access to network target '{}' is not allowed",
server.address()
)),
"Unexpected result for redirect: {redirect:?}: {err:?}"
);
}
}