//! Asset-manifest gates (TRAFFIC-12). //! //! Required signs/hazards/models are bundled, licensed, bounded, and //! validated. Missing visual assets disable dependent lessons rather than //! silently substituting cubes. Rule geometry stays independent of visuals. use nigig_traffic::traffic::assets::{AssetEntry, AssetManifest}; fn entry(id: &str) -> AssetEntry { AssetEntry { logical_id: id.into(), content_hash: "sha256:abc".into(), license: "CC0".into(), source: "kenney.nl".into(), bytes: 1024, triangles: 100, } } #[test] fn traversal_absolute_and_unsupported_types_rejected() { assert!(AssetManifest::validate_id("../evil.glb").is_err()); assert!(AssetManifest::validate_id("/abs/path.glb").is_err()); assert!(AssetManifest::validate_id("a\\b.glb").is_err()); assert!(AssetManifest::validate_id("model.exe").is_err()); assert!(AssetManifest::validate_id("").is_err()); assert!(AssetManifest::validate_id("sign_stop.glb").is_ok()); assert!(AssetManifest::validate_id("car.png").is_ok()); } #[test] fn oversized_unlicensed_or_unhashed_rejected() { let mut m = AssetManifest::default(); let mut bad = entry("a.glb"); bad.license = String::new(); assert!(m.insert(bad).is_err()); let mut bad = entry("b.glb"); bad.content_hash = String::new(); assert!(m.insert(bad).is_err()); let mut bad = entry("c.glb"); bad.triangles = 500_001; assert!(m.insert(bad).is_err()); let mut bad = entry("d.glb"); bad.bytes = 150 * 1024 * 1024 + 1; assert!(m.insert(bad).is_err()); assert!(m.insert(entry("ok.glb")).is_ok()); } #[test] fn current_catalog_has_no_hard_visual_dependencies() { // Practice preview runs on reviewed primitives: no scenario category // names a required visual asset, so nothing can silently cube-substitute // for an assessment. When curriculum review adds required models, this // test must grow with it (missing asset ⇒ lesson disabled). for cat in [ "lanes", "roundabout", "parking", "uturn", "signs", "priority", "hazards", "speed", ] { assert_eq!(AssetManifest::required_for_category(cat), None, "{cat}"); } }