Skip to content

Commit 8ba0d3f

Browse files
committed
Add auth tests
1 parent 2fcae31 commit 8ba0d3f

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ url = "2.5.2"
3131

3232
[dev-dependencies]
3333
async-std = { version = "1.13.0", features = ["attributes"], default-features = false }
34+
httpmock = { version = "0.7.0", default-features = false }
3435
rstest = "0.23.0"

src/graphql/auth.rs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ struct Response {
1818
}
1919

2020
#[derive(Debug, Serialize)]
21+
#[cfg_attr(test, derive(Deserialize))]
2122
pub struct AccessRequest<'a> {
2223
token: &'a str,
2324
audience: &'a str,
@@ -39,6 +40,7 @@ impl<'a> AccessRequest<'a> {
3940
}
4041

4142
#[derive(Debug, Serialize)]
43+
#[cfg_attr(test, derive(Deserialize))]
4244
pub struct AdminRequest<'a> {
4345
token: &'a str,
4446
audience: &'a str,
@@ -157,3 +159,217 @@ impl From<reqwest::Error> for AuthError {
157159
Self::ServerError(value)
158160
}
159161
}
162+
163+
#[cfg(test)]
164+
mod tests {
165+
use std::str::FromStr as _;
166+
167+
use axum::http::HeaderValue;
168+
use axum_extra::headers::authorization::{Bearer, Credentials};
169+
use axum_extra::headers::Authorization;
170+
use httpmock::MockServer;
171+
use rstest::rstest;
172+
173+
use super::{AccessRequest, AdminRequest, InvalidVisit, PolicyCheck, Visit, AUDIENCE};
174+
use crate::cli::PolicyOptions;
175+
use crate::graphql::auth::AuthError;
176+
177+
fn token(name: &'static str) -> Option<Authorization<Bearer>> {
178+
Some(Authorization(
179+
Bearer::decode(&HeaderValue::from_str(&format!("Bearer {name}")).unwrap()).unwrap(),
180+
))
181+
}
182+
183+
#[test]
184+
fn valid_visit() {
185+
let visit = Visit::from_str("cm12345-1").unwrap();
186+
assert_eq!(visit.session, 1);
187+
assert_eq!(visit.proposal, 12345);
188+
}
189+
190+
#[rstest]
191+
#[case::no_proposal("cm-3")]
192+
#[case::no_session("cm12345")]
193+
#[case::invalid_session("cm12345-abc")]
194+
#[case::invalid_proposal("cm123abc-12")]
195+
#[case::negative_session("cm1234--12")]
196+
fn invalid_visit(#[case] visit: &str) {
197+
assert!(matches!(Visit::from_str(visit), Err(InvalidVisit)))
198+
}
199+
200+
#[tokio::test]
201+
async fn successful_access_check() {
202+
let server = MockServer::start();
203+
let mock = server
204+
.mock_async(|when, then| {
205+
when.method("POST")
206+
.path("/demo/access")
207+
.json_body_obj(&AccessRequest {
208+
token: "token",
209+
beamline: "i22",
210+
visit: 4,
211+
proposal: 1234,
212+
audience: AUDIENCE,
213+
});
214+
then.status(200).body(r#"{"result": true}"#);
215+
})
216+
.await;
217+
let check = PolicyCheck::new(PolicyOptions {
218+
policy_host: server.url(""),
219+
visit_query: "demo/access".into(),
220+
admin_query: "demo/admin".into(),
221+
});
222+
check
223+
.check_access(token("token").as_ref(), "i22", "cm1234-4")
224+
.await
225+
.unwrap();
226+
mock.assert();
227+
}
228+
229+
#[tokio::test]
230+
async fn successful_admin_check() {
231+
let server = MockServer::start();
232+
let mock = server
233+
.mock_async(|when, then| {
234+
when.method("POST")
235+
.path("/demo/admin")
236+
.json_body_obj(&AdminRequest {
237+
token: "token",
238+
beamline: "i22",
239+
audience: AUDIENCE,
240+
});
241+
then.status(200).body(r#"{"result": true}"#);
242+
})
243+
.await;
244+
let check = PolicyCheck::new(PolicyOptions {
245+
policy_host: server.url(""),
246+
visit_query: "demo/access".into(),
247+
admin_query: "demo/admin".into(),
248+
});
249+
check
250+
.check_admin(token("token").as_ref(), "i22")
251+
.await
252+
.unwrap();
253+
mock.assert();
254+
}
255+
256+
#[tokio::test]
257+
async fn denied_access_check() {
258+
let server = MockServer::start();
259+
let mock = server
260+
.mock_async(|when, then| {
261+
when.method("POST")
262+
.path("/demo/access")
263+
.json_body_obj(&AccessRequest {
264+
token: "token",
265+
beamline: "i22",
266+
proposal: 1234,
267+
visit: 4,
268+
audience: AUDIENCE,
269+
});
270+
then.status(200).body(r#"{"result": false}"#);
271+
})
272+
.await;
273+
let check = PolicyCheck::new(PolicyOptions {
274+
policy_host: server.url(""),
275+
visit_query: "demo/access".into(),
276+
admin_query: "demo/admin".into(),
277+
});
278+
279+
let result = check
280+
.check_access(token("token").as_ref(), "i22", "cm1234-4")
281+
.await;
282+
let Err(AuthError::Failed) = result else {
283+
panic!("Unexpected result from unauthorised check: {result:?}");
284+
};
285+
mock.assert();
286+
}
287+
288+
#[tokio::test]
289+
async fn denied_admin_check() {
290+
let server = MockServer::start();
291+
let mock = server
292+
.mock_async(|when, then| {
293+
when.method("POST")
294+
.path("/demo/admin")
295+
.json_body_obj(&AdminRequest {
296+
token: "token",
297+
beamline: "i22",
298+
audience: AUDIENCE,
299+
});
300+
then.status(200).body(r#"{"result": false}"#);
301+
})
302+
.await;
303+
let check = PolicyCheck::new(PolicyOptions {
304+
policy_host: server.url(""),
305+
visit_query: "demo/access".into(),
306+
admin_query: "demo/admin".into(),
307+
});
308+
let result = check.check_admin(token("token").as_ref(), "i22").await;
309+
let Err(AuthError::Failed) = result else {
310+
panic!("Unexpected result from unauthorised check: {result:?}");
311+
};
312+
mock.assert();
313+
}
314+
315+
#[tokio::test]
316+
async fn unauthorised_access_check() {
317+
let server = MockServer::start();
318+
let mock = server
319+
.mock_async(|_, _| {
320+
// mock that rejects every request
321+
})
322+
.await;
323+
let check = PolicyCheck::new(PolicyOptions {
324+
policy_host: server.url(""),
325+
visit_query: "demo/access".into(),
326+
admin_query: "demo/admin".into(),
327+
});
328+
let result = check.check_access(None, "i22", "cm1234-4").await;
329+
let Err(AuthError::Missing) = result else {
330+
panic!("Unexpected result from unauthorised check: {result:?}");
331+
};
332+
mock.assert_hits(0);
333+
}
334+
335+
#[tokio::test]
336+
async fn unauthorised_admin_check() {
337+
let server = MockServer::start();
338+
let mock = server
339+
.mock_async(|_, _| {
340+
// mock that rejects every request
341+
})
342+
.await;
343+
let check = PolicyCheck::new(PolicyOptions {
344+
policy_host: server.url(""),
345+
visit_query: "demo/access".into(),
346+
admin_query: "demo/admin".into(),
347+
});
348+
let result = check.check_admin(None, "i22").await;
349+
let Err(AuthError::Missing) = result else {
350+
panic!("Unexpected result from unauthorised check: {result:?}");
351+
};
352+
mock.assert_hits(0);
353+
}
354+
355+
#[tokio::test]
356+
async fn server_error() {
357+
let server = MockServer::start();
358+
let mock = server
359+
.mock_async(|when, then| {
360+
when.method("POST");
361+
then.status(503);
362+
})
363+
.await;
364+
let check = PolicyCheck::new(PolicyOptions {
365+
policy_host: server.url(""),
366+
visit_query: "demo/access".into(),
367+
admin_query: "demo/admin".into(),
368+
});
369+
let result = check.check_admin(token("token").as_ref(), "i22").await;
370+
let Err(AuthError::ServerError(_)) = result else {
371+
panic!("Unexpected result from unauthorised check: {result:?}");
372+
};
373+
mock.assert();
374+
}
375+
}

0 commit comments

Comments
 (0)