//! # Integration tests canonical v0.0.18 — Mock runtime E2E //! //! 5 scenarios canonical PROV-3 attest_asset flow end-to-end via mock runtime: //! //! | Scenario | Verdict | Severity | OIT Level | Expected outcome | //! |----------|---------|----------|-----------|------------------| //! | passed_info_anchored | Passed | Info | 3 | ✅ AssetAttested event + storage insert | //! | warning_anchored | Warning | Warning | 3 | ✅ AssetAttested event + storage insert | //! | failed_critical_rejected | Failed | Critical | 3 | ❌ NotAnchorable error | //! | passed_block_security_rejected | Passed | Block | 3 | ❌ NotAnchorable error | //! | anonymous_level_0_rejected | Passed | Info | 0 | ❌ ValidatorTierInsufficient error | //! //! Plus 4 additional canonical: //! - idempotency: already attested asset rejected con `AlreadyAttested` error //! - tampered_signature: Ed25519 verify fails con `InvalidSignature` error //! - tampered_payload: signing_payload mismatch con `InvalidSignature` error //! - 6_step_walkthrough: explicit PROV-3 flow assertions in sequence //! //! Total: 9 integration tests canonical. //! //! ## Own fixtures pattern //! //! Per audit canonical sesión 3+ (`AUDIT_CHARON_SESION_3_STATE_V1.md`), Ergon //! E2E fixture diary claim aspirational sin shipped commit verified. //! Charon construye own fixtures basados en vendor copy `ValidationAttestation` //! real existente en `origin-chain-types/src/attestation.rs`. Cuando Ergon //! ship `arkhe-noesis-attestation-types` sub-crate Cap 42+, fixtures can swap. #![cfg(test)] use crate::mock::{alice_origin, bob_origin, new_test_ext, Provenance, RuntimeEvent, System, Test, TEST_SIGNING_SEED}; use crate::{Attestations, Error, Event}; use ed25519_dalek::{Signer, SigningKey}; use frame_support::{assert_noop, assert_ok}; use origin_chain_types::{ AssetHash, OitIdentity, SeverityTier, ValidationAttestation, ValidationVerdict, }; // ─── Fixture builders canonical ────────────────────────────────────── /// Build canonical signed ValidationAttestation con (verdict, severity, oit_level) /// parameters. Deterministic Ed25519 signature. /// /// asset_id es derivado de seed para uniqueness per scenario. fn build_signed_attestation( asset_seed: u8, verdict: ValidationVerdict, severity: SeverityTier, oit_level: u8, ) -> ValidationAttestation { let signing_key = SigningKey::from_bytes(&TEST_SIGNING_SEED); let verifying_key = signing_key.verifying_key(); let mut att = ValidationAttestation { schema_version: *b"1.0.0 ", asset_id: AssetHash::new([asset_seed; 32]), validator_oit: OitIdentity { public_key: verifying_key.to_bytes(), level: oit_level, }, validated_at_us: 1_234_567_890, verdict, severity, constraints_checked: 42, signature: [0u8; 64], // placeholder, replaced post sign }; let payload = att.signing_payload(); let signature = signing_key.sign(&payload); att.signature = signature.to_bytes(); att } /// Build canonical fixture passed_info (most common anchorable scenario). fn fixture_passed_info(asset_seed: u8) -> ValidationAttestation { build_signed_attestation(asset_seed, ValidationVerdict::Passed, SeverityTier::Info, 3) } // ─── Scenario 1: passed_info anchored canonical ────────────────────── #[test] fn scenario_1_passed_info_anchored_canonical() { new_test_ext().execute_with(|| { let attestation = fixture_passed_info(1); let asset_hash = attestation.asset_id; // PROV-3 attest_asset extrinsic canonical assert_ok!(Provenance::attest_asset(alice_origin(), attestation.clone())); // PROV-2 storage insert verified let stored = Attestations::::get(asset_hash); assert!(stored.is_some(), "PROV-2 canonical: AssetAttestation inserted in StorageMap"); let stored_unwrap = stored.unwrap(); assert_eq!(stored_unwrap.hash, asset_hash); assert_eq!(stored_unwrap.signature.as_bytes(), &attestation.signature); // PROV-7 event emitted canonical System::assert_last_event(RuntimeEvent::Provenance(Event::AssetAttested { asset_hash, block_number: 1, })); }); } // ─── Scenario 2: warning anchored canonical (non-blocking) ──────────── #[test] fn scenario_2_warning_anchored_canonical() { new_test_ext().execute_with(|| { let attestation = build_signed_attestation( 2, ValidationVerdict::Warning, SeverityTier::Warning, 3, ); let asset_hash = attestation.asset_id; assert_ok!(Provenance::attest_asset(alice_origin(), attestation)); assert!(Attestations::::get(asset_hash).is_some()); System::assert_last_event(RuntimeEvent::Provenance(Event::AssetAttested { asset_hash, block_number: 1, })); }); } // ─── Scenario 3: failed_critical rejected canonical (PROV-6) ────────── #[test] fn scenario_3_failed_critical_rejected_canonical() { new_test_ext().execute_with(|| { let attestation = build_signed_attestation( 3, ValidationVerdict::Failed, SeverityTier::Critical, 3, ); let asset_hash = attestation.asset_id; // PROV-6 is_anchorable canonical check fails (Failed verdict + Critical severity) assert_noop!( Provenance::attest_asset(alice_origin(), attestation), Error::::NotAnchorable ); // Storage NOT inserted assert!(Attestations::::get(asset_hash).is_none()); }); } // ─── Scenario 4: passed_block security rejected canonical (PROV-6) ──── #[test] fn scenario_4_passed_block_security_rejected_canonical() { new_test_ext().execute_with(|| { // Passed verdict pero Block severity (security violation override) let attestation = build_signed_attestation( 4, ValidationVerdict::Passed, SeverityTier::Block, 3, ); let asset_hash = attestation.asset_id; // PROV-6 is_anchorable rejects Block severity even with Passed verdict assert_noop!( Provenance::attest_asset(alice_origin(), attestation), Error::::NotAnchorable ); assert!(Attestations::::get(asset_hash).is_none()); }); } // ─── Scenario 5: anonymous_level_0 rejected canonical (PROV-5) ──────── #[test] fn scenario_5_anonymous_level_0_rejected_canonical() { new_test_ext().execute_with(|| { // Valid verdict + severity, pero OIT level 0 (anonymous) — PROV-5 reject let attestation = build_signed_attestation( 5, ValidationVerdict::Passed, SeverityTier::Info, 0, // ANONYMOUS — institutional level >= 3 required ); let asset_hash = attestation.asset_id; // PROV-5 validator OIT tier check canonical fails assert_noop!( Provenance::attest_asset(alice_origin(), attestation), Error::::ValidatorTierInsufficient ); assert!(Attestations::::get(asset_hash).is_none()); }); } // ─── Additional canonical: idempotency ──────────────────────────────── #[test] fn additional_idempotency_already_attested_rejected_canonical() { new_test_ext().execute_with(|| { let attestation = fixture_passed_info(6); let asset_hash = attestation.asset_id; // First attest_asset succeeds canonical assert_ok!(Provenance::attest_asset(alice_origin(), attestation.clone())); assert!(Attestations::::get(asset_hash).is_some()); // Second attest_asset (same hash) — idempotency canonical rejects assert_noop!( Provenance::attest_asset(bob_origin(), attestation), Error::::AlreadyAttested ); }); } // ─── Additional canonical: tampered signature (PROV-4) ──────────────── #[test] fn additional_tampered_signature_rejected_canonical() { new_test_ext().execute_with(|| { let mut attestation = fixture_passed_info(7); let asset_hash = attestation.asset_id; // Flip bit en signature canonical post-sign attestation.signature[0] ^= 0xFF; // PROV-4 Ed25519 verify canonical fails assert_noop!( Provenance::attest_asset(alice_origin(), attestation), Error::::InvalidSignature ); assert!(Attestations::::get(asset_hash).is_none()); }); } // ─── Additional canonical: tampered payload (PROV-4) ────────────────── #[test] fn additional_tampered_payload_rejected_canonical() { new_test_ext().execute_with(|| { let mut attestation = fixture_passed_info(8); let original_asset_hash = attestation.asset_id; // Tamper asset_id post-sign — cambia signing_payload pero mantiene signature original attestation.asset_id = AssetHash::new([99u8; 32]); let new_asset_hash = attestation.asset_id; // PROV-4 signing_payload mismatch canonical, Ed25519 verify fails assert_noop!( Provenance::attest_asset(alice_origin(), attestation), Error::::InvalidSignature ); // Ninguno de los dos hashes está en storage assert!(Attestations::::get(original_asset_hash).is_none()); assert!(Attestations::::get(new_asset_hash).is_none()); }); } // ─── Additional canonical: 6-step PROV-3 walkthrough explicit ───────── #[test] fn additional_6_step_prov_3_walkthrough_canonical() { new_test_ext().execute_with(|| { let attestation = fixture_passed_info(9); let asset_hash = attestation.asset_id; // Pre-flight assertions canonical assert!( attestation.is_anchorable(), "PRE-flight PROV-6: fixture anchorable canonical" ); assert!( attestation.validator_oit.is_institutional(), "PRE-flight PROV-5: validator institutional canonical" ); assert!( Attestations::::get(asset_hash).is_none(), "PRE-flight PROV-2: storage empty para asset_hash canonical" ); // Step 1: PROV-4 canonical Ed25519 signature verification (implicit via dispatchable) // Step 2: PROV-5 canonical validate validator_oit.level >= 3 institutional // Step 3: PROV-6 canonical check is_anchorable (verdict + severity) // Step 4: PROV-2 canonical convert ValidationAttestation → AssetAttestation // Step 5: PROV-2 canonical StorageMap insert // Step 6: PROV-7 canonical emit AssetAttested event assert_ok!(Provenance::attest_asset(alice_origin(), attestation.clone())); // Post-flight assertions canonical let stored = Attestations::::get(asset_hash) .expect("POST-flight PROV-2: AssetAttestation persisted canonical"); assert_eq!(stored.hash, asset_hash, "POST-flight: hash preserved"); assert_eq!( stored.signature.as_bytes(), &attestation.signature, "POST-flight: signature preserved canonical" ); System::assert_last_event(RuntimeEvent::Provenance(Event::AssetAttested { asset_hash, block_number: 1, })); }); }