ocpi_kit/testkit/
sample.rs1use crate::types::{CiString, DateTime, Extensions, InvalidString, Number, Url};
8use crate::v2_3_0::cdrs::{
9 AuthMethod, Cdr, CdrDimension, CdrDimensionType, CdrLocation, CdrToken, ChargingPeriod,
10};
11use crate::v2_3_0::locations::{
12 Connector, ConnectorFormat, ConnectorType, Evse, GeoLocation, Location, PowerType, Status,
13};
14use crate::v2_3_0::sessions::{Session, SessionStatus};
15use crate::v2_3_0::tariffs::{PriceComponent, Tariff, TariffDimensionType, TariffElement, TaxIncluded};
16use crate::v2_3_0::tokens::{Token, TokenType, WhitelistType};
17use crate::v2_3_0::types::Price;
18
19#[must_use]
21pub fn timestamp() -> DateTime {
22 "2024-01-15T10:00:00Z".parse().expect("a valid RFC 3339 timestamp")
23}
24
25#[must_use]
27pub fn coordinates() -> GeoLocation {
28 GeoLocation::new("51.047599", "3.729944").expect("valid WGS 84 coordinates")
29}
30
31pub fn connector(id: &str) -> Result<Connector, InvalidString> {
37 Ok(Connector {
38 id: CiString::new(id)?,
39 standard: ConnectorType::Iec62196T2,
40 format: ConnectorFormat::Socket,
41 power_type: PowerType::Ac3Phase,
42 max_voltage: 400,
43 max_amperage: 32,
44 max_electric_power: Some(22_000),
45 tariff_ids: Vec::new(),
46 terms_and_conditions: None,
47 capabilities: Vec::new(),
48 last_updated: timestamp(),
49 extensions: Extensions::new(),
50 })
51}
52
53pub fn evse(uid: &str) -> Result<Evse, InvalidString> {
59 Ok(Evse {
60 uid: CiString::new(uid)?,
61 evse_id: Some(CiString::new("BE*BEC*E041503001")?),
62 status: Status::Available,
63 status_schedule: Vec::new(),
64 capabilities: Vec::new(),
65 connectors: vec![connector("1")?],
66 floor_level: None,
67 coordinates: None,
68 physical_reference: None,
69 directions: Vec::new(),
70 parking_restrictions: Vec::new(),
71 parking: Vec::new(),
72 images: Vec::new(),
73 accepted_service_providers: Vec::new(),
74 last_updated: timestamp(),
75 extensions: Extensions::new(),
76 })
77}
78
79pub fn location(id: &str) -> Result<Location, InvalidString> {
85 Ok(Location::builder()
86 .country_code(CiString::new("NL")?)
87 .party_id(CiString::new("TNM")?)
88 .id(CiString::new(id)?)
89 .publish(true)
90 .name("Gent Zuid")
91 .address("F.Rooseveltlaan 3A")
92 .city("Gent")
93 .postal_code("9000")
94 .country("BEL")
95 .coordinates(coordinates())
96 .evses(vec![evse("3256")?])
97 .time_zone("Europe/Brussels")
98 .last_updated(timestamp())
99 .build())
100}
101
102pub fn token(uid: &str) -> Result<Token, InvalidString> {
108 Ok(Token::builder()
109 .country_code(CiString::new("DE")?)
110 .party_id(CiString::new("TNM")?)
111 .uid(CiString::new(uid)?)
112 .token_type(TokenType::AppUser)
113 .contract_id(CiString::new("DE8ACC12E46L89")?)
114 .issuer("TheNewMotion")
115 .valid(true)
116 .whitelist(WhitelistType::Never)
117 .last_updated(timestamp())
118 .build())
119}
120
121pub fn cdr_token(uid: &str) -> Result<CdrToken, InvalidString> {
127 Ok(CdrToken::builder()
128 .country_code(CiString::new("DE")?)
129 .party_id(CiString::new("TNM")?)
130 .uid(CiString::new(uid)?)
131 .token_type(TokenType::AppUser)
132 .contract_id(CiString::new("DE8ACC12E46L89")?)
133 .build())
134}
135
136pub fn tariff(id: &str, price_per_kwh: &str) -> Result<Tariff, InvalidString> {
142 let price: Number = price_per_kwh.parse().unwrap_or(Number::ZERO);
143 Ok(Tariff::builder()
144 .country_code(CiString::new("NL")?)
145 .party_id(CiString::new("TNM")?)
146 .id(CiString::new(id)?)
147 .currency("EUR")
148 .elements(vec![
149 TariffElement::builder()
150 .price_components(vec![PriceComponent {
151 component_type: TariffDimensionType::Energy,
152 price,
153 vat: Some(Number::from(10u32)),
154 step_size: 1,
155 extensions: Extensions::new(),
156 }])
157 .build(),
158 ])
159 .tax_included(TaxIncluded::No)
160 .last_updated(timestamp())
161 .build())
162}
163
164pub fn session(id: &str) -> Result<Session, InvalidString> {
170 Ok(Session::builder()
171 .country_code(CiString::new("NL")?)
172 .party_id(CiString::new("TNM")?)
173 .id(CiString::new(id)?)
174 .start_date_time(timestamp())
175 .kwh(Number::from(12u32))
176 .cdr_token(cdr_token("012345678")?)
177 .auth_method(AuthMethod::Whitelist)
178 .location_id(CiString::new("LOC1")?)
179 .evse_uid(CiString::new("3256")?)
180 .connector_id(CiString::new("1")?)
181 .currency("EUR")
182 .charging_periods(vec![charging_period()])
183 .status(SessionStatus::Active)
184 .last_updated(timestamp())
185 .build())
186}
187
188#[must_use]
190pub fn charging_period() -> ChargingPeriod {
191 ChargingPeriod::builder()
192 .start_date_time(timestamp())
193 .dimensions(vec![
194 CdrDimension::new(CdrDimensionType::Energy, Number::from(12u32)),
195 CdrDimension::new(CdrDimensionType::Time, Number::ONE),
196 ])
197 .build()
198}
199
200pub fn cdr(id: &str) -> Result<Cdr, InvalidString> {
206 let energy_cost: Number = "3.00".parse().unwrap_or(Number::ZERO);
207 Ok(Cdr::builder()
208 .country_code(CiString::new("NL")?)
209 .party_id(CiString::new("TNM")?)
210 .id(CiString::new(id)?)
211 .start_date_time(timestamp())
212 .end_date_time("2024-01-15T11:00:00Z".parse::<DateTime>().unwrap_or_else(|_| timestamp()))
213 .session_id(CiString::new("101")?)
214 .cdr_token(cdr_token("012345678")?)
215 .auth_method(AuthMethod::Whitelist)
216 .cdr_location(cdr_location()?)
217 .currency("EUR")
218 .charging_periods(vec![charging_period()])
219 .total_cost(Price::new(energy_cost))
220 .total_energy(Number::from(12u32))
221 .total_time(Number::ONE)
222 .last_updated(timestamp())
223 .build())
224}
225
226pub fn cdr_location() -> Result<CdrLocation, InvalidString> {
232 Ok(CdrLocation::builder()
233 .id(CiString::new("LOC1")?)
234 .address("F.Rooseveltlaan 3A")
235 .city("Gent")
236 .postal_code("9000")
237 .country("BEL")
238 .coordinates(coordinates())
239 .evse_uid(CiString::new("3256")?)
240 .evse_id(CiString::new("BE*BEC*E041503001")?)
241 .connector_id(CiString::new("1")?)
242 .connector_standard(ConnectorType::Iec62196T2)
243 .connector_format(ConnectorFormat::Socket)
244 .connector_power_type(PowerType::Ac3Phase)
245 .build())
246}
247
248pub fn credentials(
254 token: &str,
255 versions_url: &str,
256) -> Result<crate::v2_3_0::credentials::Credentials, InvalidString> {
257 use crate::v2_3_0::credentials::{Credentials, CredentialsRole};
258 use crate::v2_3_0::locations::BusinessDetails;
259 use crate::v2_3_0::types::Role;
260 Ok(Credentials::builder()
261 .token(crate::types::OcpiString::<64>::new(token)?)
262 .url(Url::new_lenient(versions_url))
263 .roles(vec![
264 CredentialsRole::builder()
265 .role(Role::Cpo)
266 .business_details(BusinessDetails::builder().name("Example Operations").build())
267 .party_id(CiString::new("TNM")?)
268 .country_code(CiString::new("NL")?)
269 .build(),
270 ])
271 .build())
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::types::Validate;
278
279 #[test]
280 fn every_sample_object_is_conformant() {
281 location("LOC1").unwrap().validate().unwrap();
282 evse("3256").unwrap().validate().unwrap();
283 connector("1").unwrap().validate().unwrap();
284 token("012345678").unwrap().validate().unwrap();
285 tariff("T1", "0.25").unwrap().validate().unwrap();
286 session("101").unwrap().validate().unwrap();
287 cdr("CDR1").unwrap().validate().unwrap();
288 credentials("test-token", "https://example.com/ocpi/versions").unwrap().validate().unwrap();
289 }
290
291 #[test]
292 fn every_sample_object_round_trips_through_json() {
293 let original = location("LOC1").unwrap();
294 let json = serde_json::to_string(&original).unwrap();
295 let decoded: Location = serde_json::from_str(&json).unwrap();
296 assert_eq!(decoded, original);
297 }
298}