feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,250 @@
|
||||
use dever_runtime::api::{self, CookieOptions, SameSite};
|
||||
use dever_runtime::bytes::Bytes;
|
||||
use dever_runtime::http::{Header, Request};
|
||||
use dever_runtime::{http, net, task};
|
||||
use std::time::Duration;
|
||||
|
||||
fn request(path: &str, cookie: &str) -> Request {
|
||||
Request {
|
||||
method: "GET".into(),
|
||||
target: path.into(),
|
||||
headers: vec![Header {
|
||||
name: "cookie".into(),
|
||||
value: Bytes::from_text(cookie),
|
||||
}],
|
||||
body: Bytes::from_text(""),
|
||||
}
|
||||
}
|
||||
|
||||
fn cookie_options() -> CookieOptions {
|
||||
CookieOptions {
|
||||
path: "/".into(),
|
||||
domain: None,
|
||||
same_site: SameSite::Lax,
|
||||
secure: true,
|
||||
http_only: true,
|
||||
max_age: Some(3600),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_scope_is_isolated_and_merges_validated_response_metadata() {
|
||||
let first = api::scoped_request(
|
||||
request("/first?ignored=1", "session=v1.Zmlyc3Q; theme=dark"),
|
||||
|_| async {
|
||||
assert_eq!(api::method().unwrap(), "GET");
|
||||
assert_eq!(api::path().unwrap(), "/first");
|
||||
assert!(api::request_id().unwrap().parse::<u64>().is_ok());
|
||||
assert!(api::header("cookie").is_err());
|
||||
assert!(api::header("x-forwarded-for").is_err());
|
||||
assert_eq!(api::cookie("theme").unwrap().as_deref(), Some("dark"));
|
||||
assert!(api::cookie("session").is_err());
|
||||
assert_eq!(api::cookie("missing").unwrap(), None);
|
||||
let incoming = api::secret_cookie("session").unwrap().unwrap();
|
||||
assert_eq!(format!("{incoming:?}"), "Secret([REDACTED])");
|
||||
api::response_header("x-request-state", "first").unwrap();
|
||||
api::response_secret_cookie(
|
||||
"session",
|
||||
dever_runtime::secret::Secret::from_input(vec![0, 255, 0]),
|
||||
cookie_options(),
|
||||
)
|
||||
.unwrap();
|
||||
api::response_secret_cookie("copy", incoming, cookie_options()).unwrap();
|
||||
api::response_cookie("theme", "dark", cookie_options()).unwrap();
|
||||
let mut insecure = cookie_options();
|
||||
insecure.secure = false;
|
||||
assert!(
|
||||
api::response_secret_cookie(
|
||||
"session",
|
||||
dever_runtime::secret::Secret::from_input(b"unsafe".to_vec()),
|
||||
insecure,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
api::commit_response_metadata().unwrap();
|
||||
Ok(api::success(dever_runtime::wire::Encoded::null()))
|
||||
},
|
||||
);
|
||||
let second = api::scoped_request(request("/second", "session=v1.c2Vjb25k"), |_| async {
|
||||
assert_eq!(api::path().unwrap(), "/second");
|
||||
assert!(api::response_header("set-cookie", "bypass").is_err());
|
||||
assert!(api::response_header("content-type", "text/html").is_err());
|
||||
assert!(api::response_header("x-unsafe", "injected\r\nheader").is_err());
|
||||
api::response_header("x-request-state", "second").unwrap();
|
||||
api::commit_response_metadata().unwrap();
|
||||
Ok(api::success(dever_runtime::wire::Encoded::null()))
|
||||
});
|
||||
let (first, second) = tokio::join!(first, second);
|
||||
let first = first.unwrap();
|
||||
let second = second.unwrap();
|
||||
assert!(
|
||||
first
|
||||
.headers
|
||||
.iter()
|
||||
.any(|header| header.name == "x-request-state" && header.value.values() == b"first")
|
||||
);
|
||||
assert!(
|
||||
second
|
||||
.headers
|
||||
.iter()
|
||||
.any(|header| header.name == "x-request-state" && header.value.values() == b"second")
|
||||
);
|
||||
assert!(first.headers.iter().any(|header| {
|
||||
header.name == "set-cookie"
|
||||
&& header
|
||||
.value
|
||||
.values()
|
||||
.starts_with(b"session=v1.AP8A; Path=/")
|
||||
}));
|
||||
assert!(first.headers.iter().any(|header| {
|
||||
header.name == "set-cookie"
|
||||
&& header
|
||||
.value
|
||||
.values()
|
||||
.starts_with(b"copy=v1.Zmlyc3Q; Path=/")
|
||||
}));
|
||||
assert!(
|
||||
first
|
||||
.headers
|
||||
.iter()
|
||||
.any(|header| header.name == "set-cookie"
|
||||
&& header.value.values().starts_with(b"theme=dark; Path=/"))
|
||||
);
|
||||
assert!(
|
||||
!second
|
||||
.headers
|
||||
.iter()
|
||||
.any(|header| header.name == "set-cookie")
|
||||
);
|
||||
assert!(api::request_id().is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn malformed_or_duplicate_cookie_is_a_bad_request_before_handler_execution() {
|
||||
for cookie in ["session=one; session=two", "session=one; broken"] {
|
||||
let result = api::scoped_request(request("/profile", cookie), |_| async {
|
||||
Err("malformed Cookie reached the API handler".into())
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result.status, 400);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn quoted_cookie_value_uses_the_same_secret_codec() {
|
||||
let response = api::scoped_request(request("/profile", "session=\"v1.Zmlyc3Q\""), |_| async {
|
||||
let token = api::secret_cookie("session")?.ok_or("missing quoted session")?;
|
||||
api::response_secret_cookie("copy", token, cookie_options())?;
|
||||
api::commit_response_metadata()?;
|
||||
Ok(api::success(dever_runtime::wire::Encoded::null()))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(response.headers.iter().any(|header| {
|
||||
header.name == "set-cookie"
|
||||
&& header
|
||||
.value
|
||||
.values()
|
||||
.starts_with(b"copy=v1.Zmlyc3Q; Path=/")
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn uncommitted_response_metadata_is_discarded_on_failure() {
|
||||
let response = api::scoped_request(request("/profile", ""), |_| async {
|
||||
api::response_header("x-staged", "discarded")?;
|
||||
Ok(api::internal_error())
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status, 500);
|
||||
assert!(
|
||||
!response
|
||||
.headers
|
||||
.iter()
|
||||
.any(|header| header.name == "x-staged")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cookie_and_model_text_validation_reject_invalid_values() {
|
||||
assert!(api::text_bounds("name", Some(""), 1, Some(64)).is_err());
|
||||
assert!(api::text_bounds("name", Some("abc"), 1, Some(2)).is_err());
|
||||
assert!(api::text_bounds("name", None, 1, Some(2)).is_ok());
|
||||
assert!(api::response_cookie("session", "a;b", cookie_options()).is_err());
|
||||
assert!(api::response_cookie("session", "valid", cookie_options()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_address_comes_from_the_tcp_peer_not_forwarded_headers() {
|
||||
let limits = http::Limits {
|
||||
header_bytes: 8192,
|
||||
body_bytes: 1024,
|
||||
timeout_ms: 1000,
|
||||
connections: 2,
|
||||
http2: None,
|
||||
};
|
||||
task::run_entry_with(
|
||||
task::RuntimeConfig {
|
||||
worker_threads: 1,
|
||||
max_blocking_threads: 2,
|
||||
task_capacity: 16,
|
||||
},
|
||||
async {
|
||||
tokio::time::timeout(Duration::from_secs(8), async {
|
||||
let listener = net::listen("127.0.0.1", 0).await?;
|
||||
let port = net::port(&listener)?;
|
||||
let owned = listener.clone();
|
||||
let server = task::run(async move {
|
||||
http::serve(
|
||||
|request| {
|
||||
api::scoped_request(request, |_| async {
|
||||
let peer =
|
||||
api::client_address()?.ok_or("missing TCP peer address")?;
|
||||
let mut body = dever_runtime::wire::Encoder::default();
|
||||
body.text(&peer)?;
|
||||
Ok(api::success(body.finish()?))
|
||||
})
|
||||
},
|
||||
owned,
|
||||
limits,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await?;
|
||||
let response = http::send(
|
||||
"127.0.0.1",
|
||||
port,
|
||||
Request {
|
||||
method: "GET".into(),
|
||||
target: "/peer".into(),
|
||||
headers: vec![
|
||||
Header {
|
||||
name: "host".into(),
|
||||
value: Bytes::from_text("localhost"),
|
||||
},
|
||||
Header {
|
||||
name: "x-forwarded-for".into(),
|
||||
value: Bytes::from_text("203.0.113.99"),
|
||||
},
|
||||
],
|
||||
body: Bytes::new(vec![]),
|
||||
},
|
||||
limits,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(response.status, 200);
|
||||
let envelope: serde_json::Value = serde_json::from_slice(response.body.values())
|
||||
.map_err(|error| error.to_string())?;
|
||||
assert_eq!(envelope["data"], "127.0.0.1");
|
||||
task::stop(server).await?;
|
||||
listener.close()?;
|
||||
Ok::<(), String>(())
|
||||
})
|
||||
.await
|
||||
.map_err(|_| "API peer test deadline exceeded".to_owned())?
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user