mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
test(harness): expose response headers + config override; verify per-route CORS
The integration harness dropped response headers (returned only status + JSON body),
which is exactly why CORS/security-header behavior couldn't be tested. Add:
- request_full(method, uri, extra_headers, body) -> (status, HeaderMap, body):
sends arbitrary request headers (e.g. an Origin) and returns response headers.
- try_app_with(|cfg| ...): tweak the validated Config before building the router
(e.g. restrict cors_allowed_origins). try_app now delegates to it.
New tests/cors.rs proves the well-known fix end-to-end: with the API CORS restricted
to one origin, /.well-known/nostr.json still serves ACAO: * to a foreign origin
(federation works) while /api/v1/* does NOT — the two policies are genuinely
distinct. Verified against Postgres (passes); existing auth_flow/capabilities unaffected.
This commit is contained in:
+47
-1
@@ -49,9 +49,16 @@ fn init_test_env(database_url: &str) {
|
||||
|
||||
/// The router + state under test, or `None` if no `TEST_DATABASE_URL` is set.
|
||||
pub async fn try_app() -> Option<TestApp> {
|
||||
try_app_with(|_| {}).await
|
||||
}
|
||||
|
||||
/// Like [`try_app`], but lets a test tweak the validated `Config` before the router
|
||||
/// is built — e.g. restrict `cors_allowed_origins` to assert per-route CORS policy.
|
||||
pub async fn try_app_with(mutate: impl FnOnce(&mut Config)) -> Option<TestApp> {
|
||||
let url = std::env::var("TEST_DATABASE_URL").ok()?;
|
||||
init_test_env(&url);
|
||||
let config = Config::from_env().expect("valid test config");
|
||||
let mut config = Config::from_env().expect("valid test config");
|
||||
mutate(&mut config);
|
||||
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
@@ -178,6 +185,45 @@ impl TestApp {
|
||||
(status, json)
|
||||
}
|
||||
|
||||
/// Issue a request with arbitrary extra request headers (e.g. an `Origin` for
|
||||
/// CORS assertions) and return the response STATUS + HEADERS + body. The plain
|
||||
/// `request` helper drops headers, which hid CORS/security-header regressions.
|
||||
pub async fn request_full(
|
||||
&self,
|
||||
method: &str,
|
||||
uri: &str,
|
||||
extra_headers: &[(&str, &str)],
|
||||
body: Option<Value>,
|
||||
) -> (StatusCode, axum::http::HeaderMap, Value) {
|
||||
let mut builder = Request::builder()
|
||||
.method(method)
|
||||
.uri(uri)
|
||||
.extension(axum::extract::ConnectInfo(std::net::SocketAddr::from((
|
||||
[127, 0, 0, 1],
|
||||
0,
|
||||
))));
|
||||
for (k, v) in extra_headers {
|
||||
builder = builder.header(*k, *v);
|
||||
}
|
||||
let req = match body {
|
||||
Some(b) => builder
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&b).unwrap()))
|
||||
.unwrap(),
|
||||
None => builder.body(Body::empty()).unwrap(),
|
||||
};
|
||||
let resp = self.router.clone().oneshot(req).await.unwrap();
|
||||
let status = resp.status();
|
||||
let headers = resp.headers().clone();
|
||||
let bytes = to_bytes(resp.into_body(), 1 << 20).await.unwrap();
|
||||
let json = if bytes.is_empty() {
|
||||
Value::Null
|
||||
} else {
|
||||
serde_json::from_slice(&bytes).unwrap_or(Value::Null)
|
||||
};
|
||||
(status, headers, json)
|
||||
}
|
||||
|
||||
/// Create a fresh user with a random identity. Returns the user id.
|
||||
pub async fn create_user(&self) -> Uuid {
|
||||
use smirk_backend_core::models::db::NewUser;
|
||||
|
||||
Reference in New Issue
Block a user