mod test_store; use { ocpi::{ types::{self, CiString, CredentialsToken, CsString}, CredentialsModule, }, serde_json::json, wiremock::{matchers, Mock, MockServer, ResponseTemplate}, }; use std::{ str::FromStr, sync::atomic::{AtomicUsize, Ordering}, }; use ocpi::{NoCommandsHandler, Party}; use once_cell::sync::OnceCell; pub use test_store::TestStore; use wiremock::http::HeaderValue; pub fn create_cpo_and_store() -> (ocpi::Cpo, TestStore) { let store = TestStore::default(); ( ocpi::Cpo::new( "http://localhost:8000".parse().expect("parsing unused url"), store.clone(), ocpi::Client::default(), ), store, ) } #[cfg(test)] #[rustfmt::skip::macros(json)] async fn create_versions_mock(cpo_token: impl AsRef) -> (types::Url, MockServer) { let mock = MockServer::start().await; let mock_url = mock.uri().parse::().expect("Mock URI as URL"); let b64_token = base64::encode(cpo_token.as_ref()); let authorization_header = format!("Token {}", b64_token); Mock::given(matchers::method("GET")) .and(matchers::path("/versions")) .and(matchers::header( "Authorization", HeaderValue::from_str(&authorization_header).expect("HeaderValue"), )) .and(matchers::header_exists("X-Request-Id")) .and(matchers::header_exists("X-Correlation-Id")) .respond_with(ResponseTemplate::new(200).set_body_json(json!( { "status_code": 1000, "status_message": "Success", "timestamp": "2015-06-30T21:59:59Z", "data": [ { "version": "2.1.1", "url": "http://www.server.com/ocpi/2.1.1/" }, { "version": "2.2", "url": format!("{}/2.2", mock.uri()) } ] } ))) .mount(&mock) .await; Mock::given(matchers::method("GET")) .and(matchers::path("/2.2")) .and(matchers::header( "Authorization", HeaderValue::from_str(&format!("Token {}", b64_token)).expect("HeaderValue"), )) .and(matchers::header_exists("X-Request-Id")) .and(matchers::header_exists("X-Correlation-Id")) .respond_with(ResponseTemplate::new(200).set_body_json(json!( { "status_code": 1000, "status_message": "Success", "timestamp": "2015-06-30T21:59:59Z", "data": { "version": "2.2", "endpoints": [ { "identifier": "credentials", "role": "SENDER", "url": format!("{}/2.2/credentials", mock.uri()) } ] } } ))) .mount(&mock) .await; (mock_url, mock) } fn ctx(credentials_token: CredentialsToken) -> ocpi::Context { static COUNTER: OnceCell = OnceCell::new(); let c = COUNTER.get_or_init(|| AtomicUsize::new(1)); let req_id = c.fetch_add(1, Ordering::Relaxed); let corr_id = c.fetch_add(1, Ordering::Relaxed); ocpi::Context { request_id: format!("{req_id}"), correlation_id: format!("{corr_id}"), credentials_token, } } /// Tests registration by using a mock server as EMP initiator /// and a test store. #[tokio::test] async fn test_registration() { // The token we give CPO and expect it to use back. // Token used for cpo -> emsp let cpo_token = "" .parse::() .expect("Parsing dummy token"); let (mock_url, _mock) = create_versions_mock(cpo_token.as_str()).await; let (cpo, store) = create_cpo_and_store(); let reg_token = store.create_reg_token(); // token to use in registration let emsp_name: CsString<100> = "TestingEMSPParty".parse().expect("emsp_name"); let emsp_party_id: CiString<3> = "WUT".parse().expect("emsp_party_id"); let emsp_country_code: CiString<2> = "se".parse().expect("emsp_country_code"); let roles = vec![ocpi::types::CredentialsRole { role: ocpi::types::Role::Emsp, business_details: ocpi::types::BusinessDetails { name: emsp_name.clone(), website: None, logo: None, }, party_id: emsp_party_id.clone(), country_code: emsp_country_code.clone(), }]; let cpo_credentials = cpo .credentials_post( ctx(reg_token), ocpi::types::Credential { token: cpo_token.clone(), url: mock_url.join("/versions").expect("Versions url"), roles, }, ) .await .expect("POST credentials"); let emsp_party = store .by_token_we_use(&cpo_token) .expect("Expected a party to be created"); assert_ne!(cpo_credentials.token, cpo_token); assert_eq!(cpo_credentials.token, emsp_party.token_they_use()); assert_eq!(emsp_party.name, emsp_name); assert_eq!(emsp_party.roles.len(), 1); assert_eq!(emsp_party.roles[0].business_details.name, emsp_name); assert_eq!(emsp_party.roles[0].party_id, emsp_party_id); assert_eq!(emsp_party.roles[0].country_code, emsp_country_code); } /// Tests registration by using a mock server as EMP initiator /// and a test store. #[tokio::test] async fn test_put_registration() { // The token we give CPO and expect it to use back. // Token used for cpo -> emsp let cpo_token = "" .parse::() .expect("Parsing dummy token"); let (mock_url, _mock) = create_versions_mock(cpo_token.as_str()).await; let (cpo, store) = create_cpo_and_store(); let reg_token = store.create_reg_token(); // token to use in registration let mut roles = vec![ocpi::types::CredentialsRole { role: ocpi::types::Role::Emsp, business_details: ocpi::types::BusinessDetails { name: "TestingEMSPPartyBeforeUpdate".parse().expect("emsp_name"), website: None, logo: None, }, party_id: "WUT".parse().expect("emsp_party_id"), country_code: "se".parse().expect("emsp_country_code"), }]; let cpo_credentials = cpo .credentials_post( ctx(reg_token), ocpi::types::Credential { token: cpo_token.clone(), url: mock_url.join("/versions").expect("Versions url"), roles: roles.clone(), }, ) .await .expect("POST credentials"); let token_to_send = cpo_credentials.token; let new_cpo_token = "" .parse::() .expect("Parsing dummy token"); let (new_mock_url, _mock) = create_versions_mock(new_cpo_token.as_str()).await; let new_business_name: CsString<100> = "TestingEMSPPartyAfterUpdate".parse().expect("emsp_name"); roles[0].business_details.name = new_business_name.clone(); let cpo_credentials = cpo .credentials_put( ctx(token_to_send.clone()), ocpi::types::Credential { token: new_cpo_token.clone(), url: new_mock_url.join("/versions").expect("Versions url"), roles, }, ) .await .expect("PUT credentials"); let party = store.by_token_we_use(&new_cpo_token).unwrap(); assert_ne!(party.token_they_use, token_to_send); assert_eq!(cpo_credentials.token, party.token_they_use); assert_eq!(party.token_we_use, new_cpo_token); }