1use std::sync::Arc;
35
36use crate::server::{
37 CdrsReceiver, CdrsSender, Created, CredentialsHandler, Handled, InMemoryTokenStore, LocationsReceiver,
38 LocationsSender, OcpiRouter, RequestContext, SessionsReceiver, SessionsSender, TariffsReceiver,
39 TariffsSender, TokensReceiver, TokensSender,
40};
41use crate::testkit;
42use crate::transport::{OcpiError, Page, PageQuery, Patch, StatusCode};
43use crate::types::{PartyRef, Url};
44use crate::v2_3_0::cdrs::Cdr;
45use crate::v2_3_0::credentials::Credentials;
46use crate::v2_3_0::locations::{Connector, Evse, Location};
47use crate::v2_3_0::sessions::{ChargingPreferences, ChargingPreferencesResponse, Session};
48use crate::v2_3_0::tariffs::Tariff;
49use crate::v2_3_0::tokens::{AllowedType, AuthorizationInfo, LocationReferences, Token, TokenType};
50use crate::v2_3_0::types::Role;
51
52use super::stores::{InMemoryCdrs, InMemoryLocations, InMemorySessions, InMemoryTariffs, InMemoryTokens};
53
54#[derive(Clone, Debug)]
59pub struct MockPeer(Arc<MockPeerStores>);
60
61#[derive(Debug)]
66pub struct MockPeerStores {
67 pub locations: InMemoryLocations,
69 pub sessions: InMemorySessions,
71 pub cdrs: InMemoryCdrs,
73 pub tariffs: InMemoryTariffs,
75 pub tokens: InMemoryTokens,
77 base: Url,
78 party: PartyRef,
79 role: Role,
80}
81
82impl core::ops::Deref for MockPeer {
83 type Target = MockPeerStores;
84 fn deref(&self) -> &MockPeerStores {
85 &self.0
86 }
87}
88
89impl MockPeer {
90 #[must_use]
92 pub fn new(base: Url, party: PartyRef, role: Role) -> Self {
93 Self(Arc::new(MockPeerStores {
94 base,
95 party,
96 role,
97 locations: InMemoryLocations::new(),
98 sessions: InMemorySessions::new(),
99 cdrs: InMemoryCdrs::new(),
100 tariffs: InMemoryTariffs::new(),
101 tokens: InMemoryTokens::new(),
102 }))
103 }
104
105 #[must_use]
107 pub fn cpo(base: Url) -> Self {
108 Self::new(base, super::test_cpo(), Role::Cpo)
109 }
110
111 #[must_use]
113 pub fn msp(base: Url) -> Self {
114 Self::new(base, super::test_msp(), Role::Emsp)
115 }
116
117 #[must_use]
123 pub fn seeded(self) -> Self {
124 self.locations.put(testkit::sample::location("LOC1").expect("a valid sample Location"));
125 self.sessions.put(testkit::sample::session("101").expect("a valid sample Session"));
126 self.cdrs.put(testkit::sample::cdr("CDR1").expect("a valid sample CDR"));
127 self.tariffs.put(testkit::sample::tariff("T1", "0.25").expect("a valid sample Tariff"));
128 self.tokens.put(testkit::sample::token("012345678").expect("a valid sample Token"));
129 self
130 }
131
132 #[must_use]
134 pub fn party(&self) -> &PartyRef {
135 &self.0.party
136 }
137
138 #[must_use]
144 pub fn token_store(&self) -> Arc<InMemoryTokenStore> {
145 let partner = if self.role == Role::Cpo { super::test_msp() } else { super::test_cpo() };
146 let store = Arc::new(InMemoryTokenStore::new());
147 store.insert(super::test_token("c"), super::registered_peer("partner", vec![partner.clone()]));
148 store.insert(super::test_token("a"), super::bootstrap_peer("bootstrap", vec![partner]));
149 store
150 }
151
152 #[must_use]
159 pub fn mount(&self, router: OcpiRouter) -> OcpiRouter {
160 router
161 .credentials(self.clone())
162 .locations_sender(self.clone())
163 .locations_receiver(self.clone())
164 .sessions_sender(self.clone())
165 .sessions_receiver(self.clone())
166 .cdrs_sender(self.clone())
167 .cdrs_receiver(self.clone())
168 .tariffs_sender(self.clone())
169 .tariffs_receiver(self.clone())
170 .tokens_sender(self.clone())
171 .tokens_receiver(self.clone())
172 }
173
174 fn endpoint(&self, module: &str) -> Url {
175 self.base.join(module)
176 }
177}
178
179fn missing(what: &str, id: &str) -> OcpiError {
181 OcpiError::NotFound(format!("no {what} {id}"))
182}
183
184impl LocationsSender for MockPeer {
189 async fn list(&self, query: PageQuery, _c: RequestContext) -> Handled<Page<Location>> {
190 Ok(self.locations.page(&query, &self.endpoint("locations")))
191 }
192
193 async fn location(&self, location_id: String, _c: RequestContext) -> Handled<Location> {
194 self.locations.get(&location_id).ok_or_else(|| missing("Location", &location_id))
195 }
196
197 async fn evse(&self, location_id: String, evse_uid: String, _c: RequestContext) -> Handled<Evse> {
198 self.locations
199 .get(&location_id)
200 .and_then(|l| l.evse(&evse_uid).cloned())
201 .ok_or_else(|| missing("EVSE", &evse_uid))
202 }
203
204 async fn connector(
205 &self,
206 location_id: String,
207 evse_uid: String,
208 connector_id: String,
209 _c: RequestContext,
210 ) -> Handled<Connector> {
211 self.locations
212 .get(&location_id)
213 .and_then(|l| l.evse(&evse_uid).and_then(|e| e.connector(&connector_id).cloned()))
214 .ok_or_else(|| missing("Connector", &connector_id))
215 }
216}
217
218impl LocationsReceiver for MockPeer {
219 async fn location(&self, _o: PartyRef, location_id: String, _c: RequestContext) -> Handled<Location> {
220 self.locations.get(&location_id).ok_or_else(|| missing("Location", &location_id))
221 }
222
223 async fn put_location(&self, _o: PartyRef, location: Location, _c: RequestContext) -> Handled<Created> {
224 Ok(Created::from(self.locations.put(location)))
225 }
226
227 async fn put_evse(
228 &self,
229 _o: PartyRef,
230 location_id: String,
231 evse: Evse,
232 _c: RequestContext,
233 ) -> Handled<Created> {
234 let mut location =
235 self.locations.get(&location_id).ok_or_else(|| missing("Location", &location_id))?;
236 let created = location.evse(evse.uid.as_str()).is_none();
237 location.evses.retain(|e| !e.uid.eq_ignore_case(evse.uid.as_str()));
238 location.evses.push(evse);
239 self.locations.put(location);
240 Ok(Created::from(created))
241 }
242
243 async fn put_connector(
244 &self,
245 _o: PartyRef,
246 location_id: String,
247 evse_uid: String,
248 connector: Connector,
249 _c: RequestContext,
250 ) -> Handled<Created> {
251 let mut location =
252 self.locations.get(&location_id).ok_or_else(|| missing("Location", &location_id))?;
253 let evse = location
254 .evses
255 .iter_mut()
256 .find(|e| e.uid.eq_ignore_case(&evse_uid))
257 .ok_or_else(|| missing("EVSE", &evse_uid))?;
258 let created = evse.connector(connector.id.as_str()).is_none();
259 evse.connectors.retain(|c| !c.id.eq_ignore_case(connector.id.as_str()));
260 evse.connectors.push(connector);
261 self.locations.put(location);
262 Ok(Created::from(created))
263 }
264
265 async fn patch(
266 &self,
267 _o: PartyRef,
268 location_id: String,
269 evse_uid: Option<String>,
270 connector_id: Option<String>,
271 patch: Patch<serde_json::Value>,
272 _c: RequestContext,
273 ) -> Handled<()> {
274 let mut location =
275 self.locations.get(&location_id).ok_or_else(|| missing("Location", &location_id))?;
276 match (evse_uid, connector_id) {
277 (None, _) => location = patch.retype::<Location>().apply(&location)?,
278 (Some(uid), None) => {
279 let evse = location
280 .evses
281 .iter_mut()
282 .find(|e| e.uid.eq_ignore_case(&uid))
283 .ok_or_else(|| missing("EVSE", &uid))?;
284 *evse = patch.retype::<Evse>().apply(evse)?;
285 }
286 (Some(uid), Some(id)) => {
287 let evse = location
288 .evses
289 .iter_mut()
290 .find(|e| e.uid.eq_ignore_case(&uid))
291 .ok_or_else(|| missing("EVSE", &uid))?;
292 let connector = evse
293 .connectors
294 .iter_mut()
295 .find(|c| c.id.eq_ignore_case(&id))
296 .ok_or_else(|| missing("Connector", &id))?;
297 *connector = patch.retype::<Connector>().apply(connector)?;
298 }
299 }
300 self.locations.put(location);
301 Ok(())
302 }
303}
304
305impl SessionsSender for MockPeer {
310 async fn list(&self, query: PageQuery, _c: RequestContext) -> Handled<Page<Session>> {
311 Ok(self.sessions.page(&query, &self.endpoint("sessions")))
312 }
313
314 async fn set_charging_preferences(
315 &self,
316 session_id: String,
317 _preferences: ChargingPreferences,
318 _c: RequestContext,
319 ) -> Handled<ChargingPreferencesResponse> {
320 self.sessions.get(&session_id).ok_or_else(|| missing("Session", &session_id))?;
326 Ok(ChargingPreferencesResponse::NotPossible)
327 }
328}
329
330impl SessionsReceiver for MockPeer {
331 async fn session(&self, _o: PartyRef, session_id: String, _c: RequestContext) -> Handled<Session> {
332 self.sessions.get(&session_id).ok_or_else(|| missing("Session", &session_id))
333 }
334
335 async fn put_session(&self, _o: PartyRef, session: Session, _c: RequestContext) -> Handled<Created> {
336 Ok(Created::from(self.sessions.put(session)))
337 }
338
339 async fn patch_session(
340 &self,
341 _o: PartyRef,
342 session_id: String,
343 patch: Patch<Session>,
344 _c: RequestContext,
345 ) -> Handled<()> {
346 let current = self.sessions.get(&session_id).ok_or_else(|| missing("Session", &session_id))?;
347 self.sessions.put(patch.apply(¤t)?);
348 Ok(())
349 }
350}
351
352impl CdrsSender for MockPeer {
357 async fn list(&self, query: PageQuery, _c: RequestContext) -> Handled<Page<Cdr>> {
358 Ok(self.cdrs.page(&query, &self.endpoint("cdrs")))
359 }
360}
361
362impl CdrsReceiver for MockPeer {
363 async fn cdr(&self, cdr_id: String, _c: RequestContext) -> Handled<Cdr> {
364 self.cdrs.get(&cdr_id).ok_or_else(|| missing("CDR", &cdr_id))
365 }
366
367 async fn post_cdr(&self, cdr: Cdr, _c: RequestContext) -> Handled<Url> {
368 if self.cdrs.get(cdr.id.as_str()).is_some() {
371 return Err(OcpiError::Remote {
372 status_code: StatusCode::INVALID_PARAMETERS,
373 status_message: Some(format!("CDR {} has already been received", cdr.id)),
374 });
375 }
376 let url = self.endpoint("cdrs").join(cdr.id.as_str());
377 self.cdrs.put(cdr);
378 Ok(url)
379 }
380}
381
382impl TariffsSender for MockPeer {
387 async fn list(&self, query: PageQuery, _c: RequestContext) -> Handled<Page<Tariff>> {
388 Ok(self.tariffs.page(&query, &self.endpoint("tariffs")))
389 }
390}
391
392impl TariffsReceiver for MockPeer {
393 async fn tariff(&self, _o: PartyRef, tariff_id: String, _c: RequestContext) -> Handled<Tariff> {
394 self.tariffs.get(&tariff_id).ok_or_else(|| missing("Tariff", &tariff_id))
395 }
396
397 async fn put_tariff(&self, _o: PartyRef, tariff: Tariff, _c: RequestContext) -> Handled<Created> {
398 Ok(Created::from(self.tariffs.put(tariff)))
399 }
400
401 async fn delete_tariff(&self, _o: PartyRef, tariff_id: String, _c: RequestContext) -> Handled<()> {
402 if self.tariffs.remove(&tariff_id) { Ok(()) } else { Err(missing("Tariff", &tariff_id)) }
403 }
404}
405
406impl TokensSender for MockPeer {
411 async fn list(&self, query: PageQuery, _c: RequestContext) -> Handled<Page<Token>> {
412 Ok(self.tokens.page(&query, &self.endpoint("tokens")))
413 }
414
415 async fn authorize(
416 &self,
417 token_uid: String,
418 _token_type: Option<TokenType>,
419 location: Option<LocationReferences>,
420 _c: RequestContext,
421 ) -> Handled<AuthorizationInfo> {
422 let token = self.tokens.get(&token_uid).ok_or(OcpiError::Remote {
423 status_code: StatusCode::UNKNOWN_TOKEN,
424 status_message: Some(format!("no Token {token_uid}")),
425 })?;
426 let allowed = if token.valid { AllowedType::Allowed } else { AllowedType::Blocked };
427 let location = location.filter(|_| allowed == AllowedType::Allowed);
431 Ok(AuthorizationInfo::builder().allowed(allowed).token(token).maybe_location(location).build())
432 }
433}
434
435impl TokensReceiver for MockPeer {
436 async fn token(
437 &self,
438 _o: PartyRef,
439 token_uid: String,
440 _token_type: Option<TokenType>,
441 _c: RequestContext,
442 ) -> Handled<Token> {
443 self.tokens.get(&token_uid).ok_or_else(|| missing("Token", &token_uid))
444 }
445
446 async fn put_token(&self, _o: PartyRef, token: Token, _c: RequestContext) -> Handled<Created> {
447 Ok(Created::from(self.tokens.put(token)))
448 }
449
450 async fn patch_token(
451 &self,
452 _o: PartyRef,
453 token_uid: String,
454 _token_type: Option<TokenType>,
455 patch: Patch<Token>,
456 _c: RequestContext,
457 ) -> Handled<()> {
458 let current = self.tokens.get(&token_uid).ok_or_else(|| missing("Token", &token_uid))?;
459 self.tokens.put(patch.apply(¤t)?);
460 Ok(())
461 }
462}
463
464impl CredentialsHandler for MockPeer {
469 async fn get(&self, _c: RequestContext) -> Handled<Credentials> {
470 Ok(self.credentials())
471 }
472
473 async fn post(&self, _credentials: Credentials, _c: RequestContext) -> Handled<Credentials> {
474 Ok(self.credentials())
479 }
480
481 async fn put(&self, _credentials: Credentials, _c: RequestContext) -> Handled<Credentials> {
482 Ok(self.credentials())
483 }
484
485 async fn delete(&self, _c: RequestContext) -> Handled<()> {
486 Ok(())
487 }
488}
489
490impl MockPeer {
491 #[must_use]
493 pub fn credentials(&self) -> Credentials {
494 use crate::v2_3_0::credentials::CredentialsRole;
495 use crate::v2_3_0::locations::BusinessDetails;
496 Credentials::builder()
497 .token(super::test_token("c").to_credentials_field())
498 .url(self.base.join("versions"))
499 .roles(vec![
500 CredentialsRole::builder()
501 .role(self.role)
502 .business_details(BusinessDetails::builder().name("ocpi-kit mock peer").build())
503 .party_id(self.party.party_id.clone())
504 .country_code(self.party.country_code.clone())
505 .build(),
506 ])
507 .build()
508 }
509}