use actix_web::{dev::ServiceResponse, test}; use futures::Future; use labrinth::models::pats::Scopes; use super::{ api_common::Api, database::USER_USER_ID_PARSED, environment::TestEnvironment, pats::create_test_pat, }; // A reusable test type that works for any scope test testing an endpoint that: // - returns a known 'expected_failure_code' if the scope is not present (defaults to 401) // - returns a 200-299 if the scope is present // - returns failure and success JSON bodies for requests that are 200 (for performing non-simple follow-up tests on) // This uses a builder format, so you can chain methods to set the parameters to non-defaults (most will probably be not need to be set). pub struct ScopeTest<'a, A> { test_env: &'a TestEnvironment, // Scopes expected to fail on this test. By default, this is all scopes except the success scopes. // (To ensure we have isolated the scope we are testing) failure_scopes: Option, // User ID to use for the PATs. By default, this is the USER_USER_ID_PARSED constant. user_id: i64, // The code that is expected to be returned if the scope is not present. By default, this is 401 (Unauthorized) expected_failure_code: u16, } impl<'a, A: Api> ScopeTest<'a, A> { pub fn new(test_env: &'a TestEnvironment) -> Self { Self { test_env, failure_scopes: None, user_id: USER_USER_ID_PARSED, expected_failure_code: 401, } } // Set non-standard failure scopes // If not set, it will be set to all scopes except the success scopes // (eg: if a combination of scopes is needed, but you want to make sure that the endpoint does not work with all-but-one of them) pub fn with_failure_scopes(mut self, scopes: Scopes) -> Self { self.failure_scopes = Some(scopes); self } // Set the user ID to use // (eg: a moderator, or friend) pub fn with_user_id(mut self, user_id: i64) -> Self { self.user_id = user_id; self } // If a non-401 code is expected. // (eg: a 404 for a hidden resource, or 200 for a resource with hidden values deeper in) pub fn with_failure_code(mut self, code: u16) -> Self { self.expected_failure_code = code; self } // Call the endpoint generated by req_gen twice, once with a PAT with the failure scopes, and once with the success scopes. // success_scopes : the scopes that we are testing that should succeed // returns a tuple of (failure_body, success_body) // Should return a String error if on unexpected status code, allowing unwrapping in tests. pub async fn test( &self, req_gen: T, success_scopes: Scopes, ) -> Result<(serde_json::Value, serde_json::Value), String> where T: Fn(Option) -> Fut, Fut: Future, // Ensure Fut is Send and 'static { // First, create a PAT with failure scopes let failure_scopes = self .failure_scopes .unwrap_or(Scopes::all() ^ success_scopes); let access_token_all_others = create_test_pat(failure_scopes, self.user_id, &self.test_env.db) .await; // Create a PAT with the success scopes let access_token = create_test_pat(success_scopes, self.user_id, &self.test_env.db) .await; // Perform test twice, once with each PAT // the first time, we expect a 401 (or known failure code) let resp = req_gen(Some(access_token_all_others.clone())).await; if resp.status().as_u16() != self.expected_failure_code { return Err(format!( "Expected failure code {}, got {} ({:#?})", self.expected_failure_code, resp.status().as_u16(), resp.response() )); } let failure_body = if resp.status() == 200 && resp.headers().contains_key("Content-Type") && resp.headers().get("Content-Type").unwrap() == "application/json" { test::read_body_json(resp).await } else { serde_json::Value::Null }; // The second time, we expect a success code let resp = req_gen(Some(access_token.clone())).await; if !(resp.status().is_success() || resp.status().is_redirection()) { return Err(format!( "Expected success code, got {} ({:#?})", resp.status().as_u16(), resp.response() )); } let success_body = if resp.status() == 200 && resp.headers().contains_key("Content-Type") && resp.headers().get("Content-Type").unwrap() == "application/json" { test::read_body_json(resp).await } else { serde_json::Value::Null }; Ok((failure_body, success_body)) } }