use dcp::binary::{ArgType, BinaryMessageEnvelope, Flags, MessageType};
use dcp::dispatch::SharedArgs;
use dcp::protocol::parser::{MessageFlags, MessageParser};
use dcp::protocol::schema::{FieldDef, InputSchema, SchemaValidator};
use dcp::DCPError;
use proptest::prelude::*;
fn message_type_strategy() -> impl Strategy<Value = u8> {
prop_oneof![
Just(MessageType::Tool as u8),
Just(MessageType::Resource as u8),
Just(MessageType::Prompt as u8),
Just(MessageType::Response as u8),
Just(MessageType::Error as u8),
Just(MessageType::Stream as u8),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_rejects_required_field_missing_from_shared_args_layout() {
let mut schema = InputSchema::new();
schema.add_field(FieldDef::new("enabled", ArgType::Bool, 0, 1));
schema.set_required(0);
let args = SharedArgs::new(&[1], 0);
assert_eq!(
SchemaValidator::validate_shared_args(&schema, &args),
Err(DCPError::ValidationFailed)
);
}
#[test]
fn schema_rejects_shared_args_type_mismatch() {
let mut schema = InputSchema::new();
schema.add_field(FieldDef::new("enabled", ArgType::Bool, 0, 1));
let args = SharedArgs::new(&[1], ArgType::String as u64);
assert_eq!(
SchemaValidator::validate_shared_args(&schema, &args),
Err(DCPError::ValidationFailed)
);
}
#[test]
fn schema_rejects_shared_args_out_of_bounds_field() {
let mut schema = InputSchema::new();
schema.add_field(FieldDef::new("count", ArgType::I32, 0, 4));
let args = SharedArgs::new(&[1, 2, 3], ArgType::I32 as u64);
assert_eq!(
SchemaValidator::validate_shared_args(&schema, &args),
Err(DCPError::OutOfBounds)
);
}
#[test]
fn parser_rejects_trailing_bytes_after_declared_payload() {
let envelope = BinaryMessageEnvelope::new(MessageType::Tool, 0, 1);
let mut message = envelope.as_bytes().to_vec();
message.extend([0xAA, 0xBB]);
assert_eq!(
MessageParser::parse(&message),
Err(DCPError::ValidationFailed)
);
}
#[test]
fn parser_rejects_reserved_flag_bits() {
let envelope = BinaryMessageEnvelope::new(MessageType::Tool, 0b1000_0000, 0);
let message = envelope.as_bytes().to_vec();
assert_eq!(
MessageParser::parse(&message),
Err(DCPError::ValidationFailed)
);
}
#[test]
fn parser_rejects_unknown_message_type_before_dispatch() {
let envelope = BinaryMessageEnvelope::new(MessageType::Tool, 0, 0);
let mut message = envelope.as_bytes().to_vec();
message[2] = 0;
assert_eq!(
MessageParser::parse(&message),
Err(DCPError::UnknownMessageType)
);
}
}
fn flags_strategy() -> impl Strategy<Value = u8> {
0u8..=7u8 }
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_message_parsing_correctness(
msg_type in message_type_strategy(),
flags in flags_strategy(),
payload_len in 0u32..1024,
payload in prop::collection::vec(any::<u8>(), 0..1024usize),
) {
let msg_type_enum = MessageType::from_u8(msg_type).unwrap();
let envelope = BinaryMessageEnvelope::new(msg_type_enum, flags, payload_len);
let mut message = envelope.as_bytes().to_vec();
message.extend(&payload[..payload_len.min(payload.len() as u32) as usize]);
let parsed = MessageParser::parse(&message);
if payload_len as usize <= payload.len() {
let parsed = parsed.unwrap();
prop_assert_eq!(parsed.envelope.magic, 0xDC01);
prop_assert_eq!(parsed.envelope.message_type, msg_type);
prop_assert!(MessageParser::validate_message_type(msg_type).is_ok());
let extracted_flags = MessageParser::extract_flags(parsed.envelope);
prop_assert_eq!(extracted_flags.streaming, flags & Flags::STREAMING != 0);
prop_assert_eq!(extracted_flags.compressed, flags & Flags::COMPRESSED != 0);
prop_assert_eq!(extracted_flags.signed, flags & Flags::SIGNED != 0);
prop_assert_eq!(parsed.envelope.payload_len, payload_len);
}
}
#[test]
fn prop_flags_round_trip(
streaming in any::<bool>(),
compressed in any::<bool>(),
signed in any::<bool>(),
) {
let flags = MessageFlags {
streaming,
compressed,
signed,
};
let byte = flags.to_u8();
let envelope = BinaryMessageEnvelope::new(MessageType::Tool, byte, 0);
let extracted = MessageParser::extract_flags(&envelope);
prop_assert_eq!(extracted.streaming, streaming);
prop_assert_eq!(extracted.compressed, compressed);
prop_assert_eq!(extracted.signed, signed);
}
#[test]
fn prop_schema_required_validation(
required_mask in any::<u64>(),
present_mask in any::<u64>(),
) {
let mut schema = InputSchema::new();
schema.required = required_mask;
let result = SchemaValidator::validate_required(&schema, present_mask);
let missing = required_mask & !present_mask;
if missing == 0 {
prop_assert!(result.is_ok());
} else {
prop_assert_eq!(result, Err(DCPError::ValidationFailed));
}
}
#[test]
fn prop_schema_enum_validation(
min in 0u8..128,
max_offset in 1u8..128,
value in any::<u8>(),
) {
let max = min.saturating_add(max_offset);
let field = FieldDef::new_enum("test_enum", 0, 1, min, max);
let result = SchemaValidator::validate_enum(&field, value);
if value >= min && value <= max {
prop_assert!(result.is_ok());
} else {
prop_assert_eq!(result, Err(DCPError::ValidationFailed));
}
}
#[test]
fn prop_schema_complete_validation(
required_bits in 0u8..4, present_bits in 0u8..16, enum_min in 1u8..10,
enum_max_offset in 1u8..10,
enum_value in any::<u8>(),
) {
let enum_max = enum_min.saturating_add(enum_max_offset);
let mut schema = InputSchema::new();
schema.required = required_bits as u64;
schema.add_field(FieldDef::new_enum("enum_field", 0, 1, enum_min, enum_max));
let field_values = vec![(0, enum_value)];
let result = SchemaValidator::validate_input(
&schema,
present_bits as u64,
&field_values,
);
let missing_required = (required_bits as u64) & !(present_bits as u64);
let enum_valid = enum_value >= enum_min && enum_value <= enum_max;
if missing_required != 0 || !enum_valid {
prop_assert_eq!(result, Err(DCPError::ValidationFailed));
} else {
prop_assert!(result.is_ok());
}
}
}