Skip to main content

accumulate_client/
types_matrix.rs

1//! Type matrix for comprehensive roundtrip testing
2//!
3//! This module contains all protocol types that need to be tested for
4//! encode → decode → re-encode roundtrip consistency.
5
6use serde::{Deserialize, Serialize};
7use crate::codec::transaction_codec::{TransactionHeader, TransactionSignature};
8
9/// All protocol type names that must pass roundtrip tests
10pub const TYPE_NAMES: &[&str] = &[
11    // Core transaction types
12    "TransactionEnvelope",
13    "TransactionHeader",
14    "TransactionSignature",
15    "TransactionKeyPage",
16
17    // Transaction body builders and components
18    "TokenRecipient",
19    "KeySpec",
20
21    // API response types
22    "StatusResponse",
23    "NodeInfo",
24    "QueryResponse",
25    "TransactionResponse",
26    "TransactionResult",
27    "Event",
28    "Attribute",
29    "SignedTransaction",
30    "Signature",
31    "Account",
32    "FaucetResponse",
33
34    // V3 specific types
35    "V3SubmitRequest",
36    "V3SubmitResponse",
37    "SubmitResult",
38    "V3Signature",
39
40    // Protocol types
41    "ProtocolTransactionEnvelope",
42    "ProtocolTransactionSignature",
43    "ProtocolTransactionHeader",
44
45    // Codec support types
46    "BinaryReader",
47    "BinaryWriter",
48    "EncodingError",
49    "DecodingError",
50    "FieldReader",
51
52    // Crypto types
53    "Ed25519Signer",
54
55    // Client configuration
56    "AccOptions",
57];
58
59/// Trait for types that can generate safe sample instances for testing
60pub trait SampleGenerator {
61    /// Generate a safe sample instance suitable for roundtrip testing
62    fn generate_sample() -> Self;
63
64    /// Generate multiple sample instances with different characteristics
65    fn generate_samples() -> Vec<Self> where Self: Sized {
66        vec![Self::generate_sample()]
67    }
68}
69
70/// Trait for types that support roundtrip encoding/decoding
71pub trait RoundtripTestable: Serialize + for<'de> Deserialize<'de> + Clone + PartialEq {
72    /// Test JSON roundtrip: serialize → deserialize → re-serialize
73    fn test_json_roundtrip(&self) -> Result<(), String> {
74        // Serialize to JSON
75        let json = serde_json::to_string(self)
76            .map_err(|e| format!("Failed to serialize to JSON: {}", e))?;
77
78        // Deserialize from JSON
79        let deserialized: Self = serde_json::from_str(&json)
80            .map_err(|e| format!("Failed to deserialize from JSON: {}", e))?;
81
82        // Re-serialize to JSON
83        let json2 = serde_json::to_string(&deserialized)
84            .map_err(|e| format!("Failed to re-serialize to JSON: {}", e))?;
85
86        // Compare original and deserialized objects
87        if self != &deserialized {
88            return Err("Deserialized object differs from original".to_string());
89        }
90
91        // Compare JSON strings
92        if json != json2 {
93            return Err(format!(
94                "Re-serialized JSON differs from original\nOriginal: {}\nRe-serialized: {}",
95                json, json2
96            ));
97        }
98
99        Ok(())
100    }
101
102    /// Test binary roundtrip if the type supports binary encoding
103    fn test_binary_roundtrip(&self) -> Result<(), String> {
104        // Default implementation - override for types with binary encoding
105        Ok(())
106    }
107}
108
109// Implement SampleGenerator for core transaction types
110
111impl SampleGenerator for crate::codec::TransactionEnvelope {
112    fn generate_sample() -> Self {
113        use crate::codec::{TransactionHeader, TransactionSignature};
114
115        Self {
116            header: TransactionHeader {
117                principal: "acc://alice.acme/tokens".to_string(),
118                initiator: Some("acc://alice.acme".to_string()),
119                timestamp: 1234567890123,
120                nonce: Some(42),
121                memo: Some("Test transaction".to_string()),
122                metadata: Some(serde_json::json!({"test": "metadata"})),
123            },
124            body: serde_json::json!({
125                "type": "send-tokens",
126                "to": [{
127                    "url": "acc://bob.acme/tokens",
128                    "amount": "1000"
129                }]
130            }),
131            signatures: vec![TransactionSignature::generate_sample()],
132        }
133    }
134
135    fn generate_samples() -> Vec<Self> {
136        vec![
137            Self::generate_sample(),
138            // Minimal envelope
139            Self {
140                header: TransactionHeader {
141                    principal: "acc://test.acme".to_string(),
142                    initiator: None,
143                    timestamp: 1000000000000,
144                    nonce: None,
145                    memo: None,
146                    metadata: None,
147                },
148                body: serde_json::json!({"type": "create-identity"}),
149                signatures: vec![],
150            },
151            // Complex envelope with multiple signatures
152            Self {
153                header: TransactionHeader {
154                    principal: "acc://complex.acme/tokens".to_string(),
155                    initiator: Some("acc://initiator.acme".to_string()),
156                    timestamp: 9999999999999,
157                    nonce: Some(999999),
158                    memo: Some("Complex test with unicode: test nono".to_string()),
159                    metadata: Some(serde_json::json!({
160                        "version": "1.0",
161                        "flags": ["test", "complex"],
162                        "nested": {"deep": {"value": 42}}
163                    })),
164                },
165                body: serde_json::json!({
166                    "type": "send-tokens",
167                    "to": [
168                        {"url": "acc://recipient1.acme/tokens", "amount": "100"},
169                        {"url": "acc://recipient2.acme/tokens", "amount": "200"},
170                        {"url": "acc://recipient3.acme/tokens", "amount": "300"}
171                    ]
172                }),
173                signatures: vec![
174                    TransactionSignature::generate_sample(),
175                    TransactionSignature {
176                        signature: vec![0x99; 64],
177                        signer: "acc://signer2.acme/book/1".to_string(),
178                        timestamp: 1234567890124,
179                        vote: Some("approve".to_string()),
180                        public_key: Some(vec![0xAA; 32]),
181                        key_page: None,
182                    }
183                ],
184            }
185        ]
186    }
187}
188
189impl SampleGenerator for crate::codec::TransactionHeader {
190    fn generate_sample() -> Self {
191        Self {
192            principal: "acc://sample.acme/tokens".to_string(),
193            initiator: Some("acc://sample.acme".to_string()),
194            timestamp: 1234567890123,
195            nonce: Some(1),
196            memo: Some("Sample header".to_string()),
197            metadata: Some(serde_json::json!({"sample": true})),
198        }
199    }
200
201    fn generate_samples() -> Vec<Self> {
202        vec![
203            Self::generate_sample(),
204            // Minimal header
205            Self {
206                principal: "acc://minimal.acme".to_string(),
207                initiator: None,
208                timestamp: 0,
209                nonce: None,
210                memo: None,
211                metadata: None,
212            },
213            // Unicode and special characters
214            Self {
215                principal: "acc://üñíçødé.acme/tøkeñs".to_string(),
216                initiator: Some("acc://spëçîál.acme".to_string()),
217                timestamp: u64::MAX,
218                nonce: Some(u64::MAX),
219                memo: Some("Unicode test: star nono cafe resume".to_string()),
220                metadata: Some(serde_json::json!({
221                    "unicode": "test",
222                    "special": "special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?",
223                    "nested": {"array": [1, 2, 3], "object": {"key": "value"}}
224                })),
225            }
226        ]
227    }
228}
229
230impl SampleGenerator for crate::codec::TransactionSignature {
231    fn generate_sample() -> Self {
232        Self {
233            signature: vec![0x42; 64], // 64-byte signature
234            signer: "acc://signer.acme/book/0".to_string(),
235            timestamp: 1234567890000,
236            vote: Some("approve".to_string()),
237            public_key: Some(vec![0x33; 32]), // 32-byte public key
238            key_page: Some(crate::codec::TransactionKeyPage {
239                height: 1000,
240                index: 0,
241            }),
242        }
243    }
244
245    fn generate_samples() -> Vec<Self> {
246        vec![
247            Self::generate_sample(),
248            // Minimal signature
249            Self {
250                signature: vec![],
251                signer: "acc://min.acme/book/0".to_string(),
252                timestamp: 0,
253                vote: None,
254                public_key: None,
255                key_page: None,
256            },
257            // Maximum values
258            Self {
259                signature: vec![0xFF; 128], // Large signature
260                signer: "acc://very-long-signer-name-with-many-characters.acme/book/999".to_string(),
261                timestamp: u64::MAX,
262                vote: Some("reject".to_string()),
263                public_key: Some(vec![0x00; 64]), // Large public key
264                key_page: Some(crate::codec::TransactionKeyPage {
265                    height: u64::MAX,
266                    index: u32::MAX,
267                }),
268            }
269        ]
270    }
271}
272
273impl SampleGenerator for crate::codec::TransactionKeyPage {
274    fn generate_sample() -> Self {
275        Self {
276            height: 12345,
277            index: 42,
278        }
279    }
280
281    fn generate_samples() -> Vec<Self> {
282        vec![
283            Self::generate_sample(),
284            Self { height: 0, index: 0 },
285            Self { height: u64::MAX, index: u32::MAX },
286        ]
287    }
288}
289
290impl SampleGenerator for crate::codec::TokenRecipient {
291    fn generate_sample() -> Self {
292        Self {
293            url: "acc://recipient.acme/tokens".to_string(),
294            amount: "1000".to_string(),
295        }
296    }
297
298    fn generate_samples() -> Vec<Self> {
299        vec![
300            Self::generate_sample(),
301            Self {
302                url: "acc://zero.acme/tokens".to_string(),
303                amount: "0".to_string(),
304            },
305            Self {
306                url: "acc://big.acme/tokens".to_string(),
307                amount: "999999999999999999999999".to_string(),
308            },
309            Self {
310                url: "acc://üñíçødé.acme/tøkeñs".to_string(),
311                amount: "42.123456789".to_string(),
312            }
313        ]
314    }
315}
316
317impl SampleGenerator for crate::codec::transaction_codec::KeySpec {
318    fn generate_sample() -> Self {
319        Self {
320            public_key_hash: "abcdef1234567890abcdef1234567890abcdef12".to_string(),
321            delegate: None,
322        }
323    }
324
325    fn generate_samples() -> Vec<Self> {
326        vec![
327            Self::generate_sample(),
328            Self {
329                public_key_hash: "0000000000000000000000000000000000000000".to_string(),
330                delegate: None,
331            },
332            Self {
333                public_key_hash: "ffffffffffffffffffffffffffffffffffffffff".to_string(),
334                delegate: Some("acc://example.acme/delegate".to_string()),
335            }
336        ]
337    }
338}
339
340// Implement RoundtripTestable for all the main types
341impl RoundtripTestable for crate::codec::TransactionEnvelope {}
342impl RoundtripTestable for crate::codec::TransactionHeader {}
343impl RoundtripTestable for crate::codec::TransactionSignature {}
344impl RoundtripTestable for crate::codec::TransactionKeyPage {}
345impl RoundtripTestable for crate::codec::TokenRecipient {}
346impl RoundtripTestable for crate::codec::KeySpec {}
347
348// Also implement for types from the types.rs file
349impl RoundtripTestable for crate::types::StatusResponse {}
350impl RoundtripTestable for crate::types::NodeInfo {}
351impl RoundtripTestable for crate::types::TransactionResponse {}
352impl RoundtripTestable for crate::types::TransactionResult {}
353impl RoundtripTestable for crate::types::Event {}
354impl RoundtripTestable for crate::types::Attribute {}
355impl RoundtripTestable for crate::types::SignedTransaction {}
356impl RoundtripTestable for crate::types::Signature {}
357impl RoundtripTestable for crate::types::Account {}
358impl RoundtripTestable for crate::types::FaucetResponse {}
359impl RoundtripTestable for crate::types::V3SubmitRequest {}
360impl RoundtripTestable for crate::types::V3SubmitResponse {}
361impl RoundtripTestable for crate::types::SubmitResult {}
362impl RoundtripTestable for crate::types::TransactionEnvelope {}
363impl RoundtripTestable for crate::types::V3Signature {}
364
365/// Get type name for a given type (for debugging and reporting)
366pub fn get_type_name<T>() -> &'static str {
367    std::any::type_name::<T>()
368}
369
370/// Check if all types in TYPE_NAMES are covered by tests
371pub fn verify_type_coverage() -> Result<(), Vec<String>> {
372    let mut missing_types = Vec::new();
373
374    // Check that each type in TYPE_NAMES has some form of test coverage
375    // This is a basic implementation - could be enhanced with more sophisticated checks
376    for type_name in TYPE_NAMES {
377        match *type_name {
378            // Core types that have SampleGenerator implementations
379            "TransactionEnvelope" | "TransactionHeader" | "TransactionSignature"
380            | "TransactionKeyPage" | "TokenRecipient" | "KeySpec" => {
381                // These are covered by SampleGenerator
382            }
383
384            // Types that have manual test implementations
385            "StatusResponse" | "NodeInfo" | "TransactionResponse" | "TransactionResult"
386            | "Event" | "Attribute" | "Account" | "FaucetResponse" | "V3Signature" => {
387                // These are covered by manual tests
388            }
389
390            // Types that might need implementation
391            "QueryResponse" | "SignedTransaction" | "Signature" | "V3SubmitRequest"
392            | "V3SubmitResponse" | "SubmitResult" | "ProtocolTransactionEnvelope"
393            | "ProtocolTransactionSignature" | "ProtocolTransactionHeader"
394            | "BinaryReader" | "BinaryWriter" | "EncodingError" | "DecodingError"
395            | "FieldReader" | "Ed25519Signer" | "AccOptions" => {
396                // These types might need test implementations
397                missing_types.push(type_name.to_string());
398            }
399
400            _ => {
401                // Unknown type
402                missing_types.push(format!("Unknown type: {}", type_name));
403            }
404        }
405    }
406
407    if missing_types.is_empty() {
408        Ok(())
409    } else {
410        Err(missing_types)
411    }
412}
413
414/// Generate a comprehensive test report for all types
415pub fn generate_type_test_report() -> String {
416    let mut report = String::new();
417
418    report.push_str("# Type Matrix Test Coverage Report\n\n");
419    report.push_str(&format!("Total types in matrix: {}\n\n", TYPE_NAMES.len()));
420
421    report.push_str("## Core Transaction Types\n");
422    let core_types = [
423        "TransactionEnvelope", "TransactionHeader", "TransactionSignature",
424        "TransactionKeyPage", "TokenRecipient", "KeySpec"
425    ];
426
427    for type_name in core_types {
428        if TYPE_NAMES.contains(&type_name) {
429            report.push_str(&format!("- [OK] {}\n", type_name));
430        } else {
431            report.push_str(&format!("- [MISSING] {} (missing from TYPE_NAMES)\n", type_name));
432        }
433    }
434
435    report.push_str("\n## API Response Types\n");
436    let api_types = [
437        "StatusResponse", "NodeInfo", "QueryResponse", "TransactionResponse",
438        "TransactionResult", "Event", "Attribute", "Account", "FaucetResponse"
439    ];
440
441    for type_name in api_types {
442        if TYPE_NAMES.contains(&type_name) {
443            report.push_str(&format!("- [OK] {}\n", type_name));
444        } else {
445            report.push_str(&format!("- [MISSING] {} (missing from TYPE_NAMES)\n", type_name));
446        }
447    }
448
449    report.push_str("\n## V3 Protocol Types\n");
450    let v3_types = [
451        "V3SubmitRequest", "V3SubmitResponse", "SubmitResult", "V3Signature"
452    ];
453
454    for type_name in v3_types {
455        if TYPE_NAMES.contains(&type_name) {
456            report.push_str(&format!("- [OK] {}\n", type_name));
457        } else {
458            report.push_str(&format!("- [MISSING] {} (missing from TYPE_NAMES)\n", type_name));
459        }
460    }
461
462    report.push_str("\n## Coverage Status\n");
463    match verify_type_coverage() {
464        Ok(()) => {
465            report.push_str("[OK] All types have test coverage\n");
466        }
467        Err(missing) => {
468            report.push_str(&format!("[MISSING] {} types need test implementations:\n", missing.len()));
469            for missing_type in missing {
470                report.push_str(&format!("  - {}\n", missing_type));
471            }
472        }
473    }
474
475    report
476}
477
478/// Utility to count the number of samples generated for a type
479pub fn count_samples<T: SampleGenerator>() -> usize {
480    T::generate_samples().len()
481}