use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::env;
use std::fs;
use std::path::Path;
use tap_msg::didcomm::PlainMessage;
use tap_msg::message::{DIDCommPresentation, TapMessageBody};
use tap_msg::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestVector {
description: String,
purpose: String,
#[serde(rename = "shouldPass")]
should_pass: bool,
version: String,
taips: Vec<String>,
message: Value,
#[serde(rename = "expectedResult")]
expected_result: ExpectedResult,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ExpectedResult {
valid: bool,
#[serde(default)]
errors: Vec<TestVectorError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestVectorError {
field: String,
message: String,
}
fn load_test_vectors(directory: &str) -> Vec<TestVector> {
let root_path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let path = root_path.join(directory);
if !path.exists() || !path.is_dir() {
panic!("Test vector directory not found: {:?}", path);
}
let mut test_vectors = Vec::new();
for entry in fs::read_dir(path).expect("Failed to read directory") {
let entry = entry.expect("Failed to read directory entry");
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "json") {
let file_content = fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("Failed to read file: {:?}", path));
match serde_json::from_str::<TestVector>(&file_content) {
Ok(test_vector) => test_vectors.push(test_vector),
Err(e) => eprintln!("Failed to parse test vector {:?}: {}", path, e),
}
}
}
test_vectors
}
fn test_presentation_message(test_vector: &TestVector) -> Result<()> {
let didcomm_result = serde_json::from_str::<PlainMessage>(&test_vector.message.to_string());
if let Err(parse_error) = &didcomm_result {
if !test_vector.should_pass {
println!("✅ Expected parsing failure: {}", parse_error);
return Ok(());
} else {
return Err(tap_msg::Error::Validation(format!(
"Test vector '{}' should pass but failed to parse as DIDComm: {}",
test_vector.description, parse_error
)));
}
}
let didcomm_message = didcomm_result.unwrap();
let presentation_result = DIDCommPresentation::from_didcomm(&didcomm_message);
if let Err(conversion_error) = &presentation_result {
if !test_vector.should_pass {
println!("✅ Expected conversion failure: {}", conversion_error);
return Ok(());
} else {
return Err(tap_msg::Error::Validation(format!(
"Test vector '{}' should pass but failed conversion: {}",
test_vector.description, conversion_error
)));
}
}
let presentation = presentation_result.unwrap();
let validation_result = presentation.validate();
if let Err(validation_error) = &validation_result {
if !test_vector.should_pass {
println!("✅ Expected validation failure: {}", validation_error);
return Ok(());
} else {
return Err(tap_msg::Error::Validation(format!(
"Test vector '{}' should pass but failed validation: {}",
test_vector.description, validation_error
)));
}
}
if !test_vector.should_pass {
return Err(tap_msg::Error::Validation(format!(
"Test vector '{}' should fail but passed all checks",
test_vector.description
)));
}
let to_didcomm_result = presentation.to_didcomm("did:example:sender");
if let Err(round_trip_error) = &to_didcomm_result {
return Err(tap_msg::Error::Validation(format!(
"Test vector '{}' passed validation but failed round-trip conversion: {}",
test_vector.description, round_trip_error
)));
}
Ok(())
}
#[tokio::test]
#[ignore = "Test vectors not available in this branch"]
async fn test_presentation_vectors() {
let test_vectors = load_test_vectors("prds/taips/test-vectors/presentation");
println!("Loaded {} test vectors", test_vectors.len());
let mut success_count = 0;
let mut error_count = 0;
for test_vector in &test_vectors {
println!("\nTesting: {}", test_vector.description);
println!("Should pass: {}", test_vector.should_pass);
match test_presentation_message(test_vector) {
Ok(_) => {
println!("✅ Passed: {}", test_vector.description);
success_count += 1;
}
Err(e) => {
eprintln!("❌ Failed: {} - {}", test_vector.description, e);
error_count += 1;
}
}
}
println!(
"\nTest vectors summary: {} passed, {} failed, {} total",
success_count,
error_count,
test_vectors.len()
);
assert_eq!(error_count, 0, "{} test vectors failed", error_count);
}
#[tokio::test]
#[ignore = "Test vectors not available in this branch"]
async fn test_presentation_round_trip() {
let test_vectors = load_test_vectors("prds/taips/test-vectors/presentation");
let valid_vector = test_vectors
.iter()
.find(|v| v.should_pass)
.expect("No valid presentation test vector found");
let message_str = valid_vector.message.to_string();
let didcomm_message: PlainMessage =
serde_json::from_str(&message_str).expect("Failed to parse message as DIDComm");
let presentation = DIDCommPresentation::from_didcomm(&didcomm_message)
.expect("Failed to convert DIDComm message to DIDCommPresentation");
let round_trip_message = presentation
.to_didcomm("did:example:sender")
.expect("Failed to convert DIDCommPresentation back to DIDComm message");
let round_trip_presentation = DIDCommPresentation::from_didcomm(&round_trip_message)
.expect("Failed to convert round-trip DIDComm message back to DIDCommPresentation");
assert_eq!(
presentation.thid, round_trip_presentation.thid,
"Thread ID does not match after round trip"
);
assert_eq!(
presentation.attachments.len(),
round_trip_presentation.attachments.len(),
"Attachment count does not match after round trip"
);
if !presentation.attachments.is_empty() {
assert_eq!(
presentation.attachments[0].id, round_trip_presentation.attachments[0].id,
"Attachment ID does not match after round trip"
);
assert_eq!(
presentation.attachments[0].media_type,
round_trip_presentation.attachments[0].media_type,
"Attachment media type does not match after round trip"
);
}
}