use super::error::{AssertionViolationDetail, GateDecisionMismatch, SpecViolation};
use crate::error::ReceivedExpectedError;
use crate::testing::assertions::AssertionContract;
use crate::trace::{ConsumedTrace, ExecutionMode};
#[cfg(feature = "instrument")]
use crate::utils::urn::Urn;
use crate::Frame;
#[cfg(feature = "instrument")]
use super::error::EventOrderViolationDetail;
#[cfg(feature = "policy")]
use crate::policy::TransitStatus;
pub trait TBSpec {
fn id(&self) -> &'static str;
fn mode(&self) -> ExecutionMode;
#[cfg(feature = "instrument")]
fn required_events(&self) -> &[Urn<'static>] {
&[]
}
fn required_assertions(&self) -> &[AssertionContract];
fn expected_gate_decision(&self) -> Option<TransitStatus> {
None
}
fn validate_response(&self, _frame: &Frame) -> bool {
true
}
fn validate_trace(&self, _trace: &ConsumedTrace) -> Result<(), SpecViolation> {
Ok(())
}
}
pub fn verify_trace<S: TBSpec>(spec: &S, trace: &ConsumedTrace) -> Result<(), SpecViolation> {
let actual_mode = trace.execution_mode();
if actual_mode != spec.mode() {
return Err(SpecViolation::ModeMismatch(ReceivedExpectedError {
received: actual_mode,
expected: spec.mode(),
}));
}
if let Some(expected_decision) = spec.expected_gate_decision() {
if trace.gate_decision != Some(expected_decision) {
return Err(SpecViolation::GateDecisionMismatch(GateDecisionMismatch {
expected: expected_decision,
actual: trace.gate_decision,
}));
}
}
for contract in spec.required_assertions() {
if !contract.is_satisfied_by(&trace.assertions) {
let actual_count = trace.count_assertions(&contract.label, contract.tag_filter.as_deref());
return Err(SpecViolation::AssertionViolation(AssertionViolationDetail {
label: contract.label.clone(),
tags: contract.tag_filter.clone(),
expected: contract.cardinality.describe(),
actual: actual_count,
}));
}
}
#[cfg(feature = "instrument")]
{
let required_kinds = spec.required_events();
if !required_kinds.is_empty() {
let mut idx = 0;
for ev in trace.instrument_events.iter() {
if idx < required_kinds.len() && ev.urn == required_kinds[idx] {
idx += 1;
}
}
if idx != required_kinds.len() {
return Err(SpecViolation::EventOrderViolation(EventOrderViolationDetail {
expected_kind: required_kinds[idx].clone(),
position: idx,
}));
}
}
}
if let Some(ref response) = trace.response {
if !spec.validate_response(response) {
return Err(SpecViolation::ResponseValidationFailed);
}
}
spec.validate_trace(trace)?;
Ok(())
}