package_boundary.rs (6165B)
1 use std::collections::BTreeSet; 2 use std::fs; 3 use std::path::Path; 4 5 const MANIFEST: &str = include_str!("../Cargo.toml"); 6 const README: &str = include_str!("../README.md"); 7 const EXAMPLE: &str = include_str!("../examples/prepare_query.rs"); 8 const PUBLIC_API: &str = include_str!("../../../contracts/api_baselines/radroots_geonames.txt"); 9 const ROOT: &str = include_str!("../src/lib.rs"); 10 const PUBLISH_POLICY: &str = include_str!("../../../contracts/releases/publish_policy.toml"); 11 12 #[test] 13 fn manifest_and_root_match_the_governed_provider_boundary() { 14 for required in [ 15 "name = \"radroots_geonames\"", 16 "version = \"0.1.0-alpha\"", 17 "publish = [\"crates-io\"]", 18 "[lib]\nname = \"radroots_geonames\"", 19 ] { 20 assert!( 21 MANIFEST.contains(required), 22 "manifest is missing `{required}`" 23 ); 24 } 25 assert!(!MANIFEST.contains("[features]")); 26 assert!(radroots_dependency_keys(MANIFEST).is_empty()); 27 assert_eq!( 28 public_modules(ROOT), 29 BTreeSet::from(["asset", "database", "download", "model", "query"]) 30 ); 31 assert_eq!(private_modules(ROOT), BTreeSet::from(["error"])); 32 for export in [ 33 "pub use asset::{AssetSpec, AssetStatus};", 34 "pub use database::Geocoder;", 35 "pub use error::Error;", 36 "pub use model::{Candidate, Point};", 37 "pub use query::Query;", 38 ] { 39 assert!(ROOT.contains(export), "crate root is missing `{export}`"); 40 } 41 } 42 43 #[test] 44 fn documentation_example_and_reviewed_api_baseline_are_complete() { 45 for required in [ 46 "## Prepare a query without I/O", 47 "## Asset identity and acquisition", 48 "## Database and query behavior", 49 "## Errors, serialization, and side effects", 50 "## Intended consumers", 51 "radroots_crates_release_v1.toml", 52 "examples/prepare_query.rs", 53 "contracts/api_baselines/radroots_geonames.txt", 54 ] { 55 assert!(README.contains(required), "README is missing `{required}`"); 56 } 57 for required in [ 58 "official_asset_spec()", 59 "Query::locality(\"Victoria\")", 60 "Query::reverse(Point::new(", 61 "without I/O", 62 ] { 63 assert!( 64 EXAMPLE.contains(required), 65 "example is missing `{required}`" 66 ); 67 } 68 for required in [ 69 "pub struct radroots_geonames::AssetSpec", 70 "pub enum radroots_geonames::AssetStatus", 71 "pub struct radroots_geonames::Candidate", 72 "pub struct radroots_geonames::Geocoder", 73 "pub struct radroots_geonames::Point", 74 "pub struct radroots_geonames::Query", 75 "pub enum radroots_geonames::Error", 76 "Geocoder::open", 77 "Geocoder::query", 78 "Geocoder::close", 79 "pub async fn radroots_geonames::database::Geocoder::close", 80 "pub async fn radroots_geonames::database::Geocoder::open", 81 "pub async fn radroots_geonames::database::Geocoder::query", 82 ] { 83 assert!( 84 PUBLIC_API.contains(required), 85 "public API is missing `{required}`" 86 ); 87 } 88 for forbidden in [ 89 "rusqlite", 90 "sqlx", 91 "reqwest", 92 "tokio", 93 "runtime_paths", 94 "GeocoderLocality", 95 "GeoNamesAsset", 96 ] { 97 assert!( 98 !PUBLIC_API.contains(forbidden), 99 "public API exposes `{forbidden}`" 100 ); 101 } 102 } 103 104 #[test] 105 fn provider_source_has_no_hidden_runtime_path_or_download_implementation() { 106 let source_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); 107 let source = fs::read_dir(source_root) 108 .expect("source directory") 109 .map(|entry| { 110 let path = entry.expect("source entry").path(); 111 fs::read_to_string(path).expect("utf-8 source") 112 }) 113 .collect::<String>(); 114 for forbidden in [ 115 "radroots_runtime_paths", 116 "reqwest::", 117 "tokio::runtime", 118 "Runtime::new", 119 "Builder::new_", 120 "tokio::spawn", 121 "spawn_blocking", 122 "pub use sqlx", 123 "std::env::", 124 "directories::", 125 "test-fixture-geonames-asset", 126 ] { 127 assert!( 128 !source.contains(forbidden), 129 "provider source contains `{forbidden}`" 130 ); 131 } 132 for required in [ 133 "use sqlx::{", 134 "use tokio::sync::Mutex;", 135 "pub async fn open", 136 "pub async fn query", 137 "pub async fn close", 138 ] { 139 assert!( 140 source.contains(required), 141 "provider source is missing `{required}`" 142 ); 143 } 144 for required in [ 145 "caller-driven async operations", 146 "provides the async runtime", 147 "does not create one or spawn crate-owned", 148 ] { 149 assert!(README.contains(required), "README is missing `{required}`"); 150 } 151 } 152 153 #[test] 154 fn superseded_geocoder_package_is_removed() { 155 let crate_root = Path::new(env!("CARGO_MANIFEST_DIR")); 156 assert!( 157 !crate_root 158 .parent() 159 .expect("crates root") 160 .join("geocoder") 161 .exists() 162 ); 163 assert!(!PUBLISH_POLICY.contains("\"radroots_geocoder\"")); 164 } 165 166 fn radroots_dependency_keys(manifest: &str) -> BTreeSet<&str> { 167 dependency_keys(manifest) 168 .into_iter() 169 .filter(|key| key.starts_with("radroots_")) 170 .collect() 171 } 172 173 fn dependency_keys(manifest: &str) -> BTreeSet<&str> { 174 manifest 175 .split_once("[dependencies]") 176 .map(|(_, dependencies)| dependencies) 177 .unwrap_or_default() 178 .lines() 179 .skip(1) 180 .take_while(|line| !line.starts_with('[')) 181 .filter_map(|line| line.split_once('=').map(|(key, _)| key.trim())) 182 .filter(|key| !key.is_empty()) 183 .collect() 184 } 185 186 fn public_modules(root: &str) -> BTreeSet<&str> { 187 root.lines() 188 .filter_map(|line| line.trim().strip_prefix("pub mod ")) 189 .filter_map(|module| module.strip_suffix(';')) 190 .collect() 191 } 192 193 fn private_modules(root: &str) -> BTreeSet<&str> { 194 root.lines() 195 .filter_map(|line| line.trim().strip_prefix("mod ")) 196 .filter_map(|module| module.strip_suffix(';')) 197 .collect() 198 }