#![cfg(feature = "validate")]
mod common;
use common::fake_transport_pair;
use ocpp_client::ocpp_1_6::OCPP1_6Error;
use ocpp_client::ocpp_2_0_1::OCPP2_0_1Error;
use ocpp_client::ocpp_2_1::{OCPP2_1Client, OCPP2_1Error};
use ocpp_client::ocpp_types::v21::ClearVariableMonitoringRequest;
use ocpp_client::ocpp_types::v21::common::CustomData;
use ocpp_client::ocpp_types::validate::{Validate, ValidationError, ValidationErrorKind};
use ocpp_client::{
Client, ProtocolError, TokioExecutor, TokioTimer, TransportEvent, TransportSink,
};
use serde_json::{Value, json};
use std::time::Duration;
fn oversized_1_6_csr() -> ValidationError {
ocpp_client::ocpp_types::v16::SignCertificateRequest {
csr: "-".repeat(6000),
}
.validate()
.expect_err("a 6000-character csr breaks maxLength: 5500")
}
fn empty_2_1_monitor_list() -> ValidationError {
ClearVariableMonitoringRequest::<()> {
custom_data: None,
id: Vec::new(),
}
.validate()
.expect_err("an empty id list breaks minItems: 1")
}
#[test]
fn a_property_violation_maps_to_property_constraint_violation() {
let error: OCPP1_6Error = oversized_1_6_csr().into();
assert_eq!(error.code(), "PropertyConstraintViolation");
assert!(
error.description().contains("csr"),
"description should name the failing field, got {:?}",
error.description()
);
}
#[test]
fn a_1_6_occurrence_violation_keeps_the_one_r_spelling() {
let error: OCPP1_6Error =
ValidationError::new(ValidationErrorKind::TooFewItems { len: 0, min: 1 }).into();
assert_eq!(error.code(), "OccurenceConstraintViolation");
}
#[test]
fn a_2_0_1_occurrence_violation_uses_the_two_r_spelling() {
let error: OCPP2_0_1Error =
ValidationError::new(ValidationErrorKind::TooFewItems { len: 0, min: 1 }).into();
assert_eq!(error.code(), "OccurrenceConstraintViolation");
}
#[test]
fn a_2_1_occurrence_violation_uses_the_two_r_spelling() {
let error: OCPP2_1Error = empty_2_1_monitor_list().into();
assert_eq!(error.code(), "OccurrenceConstraintViolation");
assert!(
error.description().contains("id"),
"description should name the failing field, got {:?}",
error.description()
);
}
#[test]
fn details_carry_the_json_path_with_array_indices() {
let error: OCPP2_1Error = ClearVariableMonitoringRequest::<()> {
custom_data: None,
id: vec![-1],
}
.validate()
.expect_err("-1 breaks minimum: 0")
.into();
assert_eq!(error.code(), "PropertyConstraintViolation");
assert_eq!(error.details()["path"], json!("id[0]"));
}
#[test]
fn a_rootless_violation_still_renders_a_path() {
let error: OCPP2_1Error =
ValidationError::new(ValidationErrorKind::TooFewItems { len: 0, min: 1 }).into();
assert_eq!(error.details()["path"], json!("<payload>"));
}
#[test]
fn the_details_path_and_the_description_agree() {
for error in [
oversized_1_6_csr(),
empty_2_1_monitor_list(),
ClearVariableMonitoringRequest::<()> {
custom_data: None,
id: vec![-1],
}
.validate()
.expect_err("-1 breaks minimum: 0"),
ValidationError::new(ValidationErrorKind::TooFewItems { len: 0, min: 1 }),
] {
let upstream = error.to_string();
let mapped: OCPP2_1Error = error.into();
let path = mapped.details()["path"].as_str().unwrap().to_string();
assert!(
upstream.starts_with(&format!("{path}: ")),
"path {path:?} should prefix upstream's rendering {upstream:?}"
);
}
}
#[tokio::test]
async fn a_handler_can_reject_a_bad_payload_with_the_right_wire_code() {
let ((client_sink, client_source), (mut peer_sink, mut peer_source)) = fake_transport_pair();
let client: OCPP2_1Client = Client::from_transport(
Box::new(client_sink),
Box::new(client_source),
Duration::from_secs(5),
Box::new(TokioExecutor),
Box::new(TokioTimer),
);
client
.on_clear_variable_monitoring(
|request: ClearVariableMonitoringRequest<CustomData>, _client| async move {
request.validate()?;
unreachable!("the payload under test is invalid, so validate() returns early")
},
)
.await;
let call = json!([2, "req-1", "ClearVariableMonitoring", {"id": []}]);
peer_sink
.send(serde_json::to_string(&call).unwrap())
.await
.unwrap();
let frame: Value = match peer_source.recv_event().await.unwrap() {
TransportEvent::Frame(frame) => serde_json::from_str(&frame).unwrap(),
other => panic!("expected a frame, got {other:?}"),
};
assert_eq!(frame[0], 4, "should be a CALLERROR");
assert_eq!(frame[1], "req-1");
assert_eq!(frame[2], "OccurrenceConstraintViolation");
assert_eq!(frame[4]["path"], json!("id"));
}