// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs // version: 7 //! Dependency firewall canaries for the RAW transaction ingest Worker foundation. #[test] fn pre_002_manifest_dependency_surface_is_exact() { let manifest = include_str!("../Cargo.toml"); let dependencies = dependency_section(manifest); let names = manifest_dependency_names(dependencies); assert_eq!(names, vec!["ksp-core-lib", "ksp-logging-lib", "ksp-raw-transaction-lib", "ksp-store-lib", "ksp-worker-api", "sha2", "tokio",],); for required in [ "ksp-core-lib = { path = \"../ksp-core-lib\" }", "ksp-logging-lib = { path = \"../ksp-logging-lib\" }", "ksp-raw-transaction-lib = { path = \"../ksp-raw-transaction-lib\" }", "ksp-store-lib = { path = \"../ksp-store-lib\", default-features = false }", "ksp-worker-api = { path = \"../ksp-worker-api\" }", "sha2 = { workspace = true }", "tokio = { workspace = true, features = [\"macros\", \"rt\", \"sync\", \"time\"] }", ] { assert!(dependencies.contains(required), "required Worker dependency missing: {required}"); } return; } #[test] fn pre_002_manifest_keeps_forbidden_layers_and_live_sources_out() { let manifest = include_str!("../Cargo.toml"); let dependencies = dependency_section(manifest); for forbidden in [ "futures-util", "ksp-config-lib", "ksp-interface-lib", "ksp-job-api", "ksp-job-backfill-lib", "ksp-onchain-transport-lib", "ksp-program-api", "ksp-store-api", "ksp-store-postgres-lib", "ksp-wallet-lib", "reqwest", "solana-", "tokio-tungstenite", "tonic", "yellowstone-grpc-proto", ] { assert!(!dependencies.contains(forbidden), "forbidden Worker dependency present: {forbidden}"); } assert!(!manifest.contains("[dev-dependencies]"), "foundation requires no test-only dependency"); assert!(!manifest.contains("[build-dependencies]"), "foundation requires no build dependency"); return; } #[test] fn pre_008_source_surface_adds_one_latest_value_snapshot_watch_without_backend_or_live_source() { let root = include_str!("../src/lib.rs"); let runtime = include_str!("../src/runtime.rs"); let admission = include_str!("../src/admission.rs"); let persistence = include_str!("../src/persistence.rs"); let snapshot = include_str!("../src/snapshot.rs"); for required in [ "tokio::sync::mpsc::channel", "canonicalize_raw_transaction", "assemble_raw_transaction_acquisition", "RawTransactionIngestPersistencePort", "RawTransactionAcquisitionMode::Normal", "RawTransactionIngestSnapshot", "RawTransactionIngestSnapshotSource", "WorkerSnapshotSource", "tokio::sync::watch::channel", "ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED", ] { assert!( root.contains(required) || runtime.contains(required) || admission.contains(required) || persistence.contains(required) || snapshot.contains(required), "required pre.008 snapshot contract missing: {required}" ); } for forbidden in [ "pub use self::admission::RawTransactionAdmission", "pub use self::admission::RawTransactionIngress", "pub use self::persistence::RawTransactionIngestPersistencePort", "unbounded_channel", "ksp_store_postgres_lib::", "ksp_onchain_transport_lib::", "ForceRehydrate", "source_failed", "drain_timeout", ] { assert!( !root.contains(forbidden) && !runtime.contains(forbidden) && !admission.contains(forbidden) && !persistence.contains(forbidden) && !snapshot.contains(forbidden), "pre.008 crossed a forbidden runtime boundary: {forbidden}" ); } assert_eq!(runtime.matches("tokio::sync::watch::channel").count(), 2, "runtime retains only the external-control and private-source stop watches"); assert_eq!(snapshot.matches("tokio::sync::watch::channel").count(), 1, "exactly one shared concrete snapshot watch is allowed"); return; } fn dependency_section(manifest: &str) -> &str { let after_header = match manifest.split_once("[dependencies]") { std::option::Option::Some((_, section)) => section, std::option::Option::None => { return ""; }, }; let end = match after_header.find("\n[") { std::option::Option::Some(index) => index, std::option::Option::None => after_header.len(), }; return &after_header[..end]; } fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> { let mut names = std::vec::Vec::new(); for line in section.lines() { let content = match line.split('#').next() { std::option::Option::Some(value) => value.trim(), std::option::Option::None => continue, }; if content.is_empty() { continue; } let name = match content.split('=').next() { std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"), std::option::Option::None => continue, }; if !name.is_empty() { names.push(name); } } names.sort_unstable(); return names; }