lib.rs (6824B)
1 //! Validated, generated HarvestCircle product coordinates and provenance. 2 3 pub mod parser; 4 pub mod provenance; 5 6 include!(concat!(env!("OUT_DIR"), "/product_coordinates.rs")); 7 8 #[cfg(test)] 9 mod tests { 10 use super::parser::{REQUIRED_KEYS, canonicalize, digest, generate_rust_constants, parse}; 11 use super::provenance; 12 use super::{ 13 DESKTOP_APPLICATION_ID, DEVELOPMENT_DATA_DIR_ENVIRONMENT, FFI_CDYLIB_NAME, 14 KOTLIN_ROOT_NAMESPACE, PRODUCT_COORDINATE_DIGEST, PRODUCT_NAME, 15 }; 16 17 #[test] 18 fn generated_coordinates_are_exact_and_digest_is_sha256() { 19 assert_eq!(PRODUCT_NAME, "HarvestCircle"); 20 assert_eq!(KOTLIN_ROOT_NAMESPACE, "org.harvestcircle"); 21 assert_eq!(DESKTOP_APPLICATION_ID, "org.harvestcircle.desktop"); 22 assert_eq!(FFI_CDYLIB_NAME, "harvestcircle_ffi"); 23 assert_eq!( 24 DEVELOPMENT_DATA_DIR_ENVIRONMENT, 25 "HARVESTCIRCLE_DEVELOPMENT_DATA_DIR" 26 ); 27 assert_eq!(PRODUCT_COORDINATE_DIGEST.len(), 64); 28 assert!( 29 PRODUCT_COORDINATE_DIGEST 30 .bytes() 31 .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) 32 ); 33 } 34 35 #[test] 36 fn parser_rejects_missing_duplicate_unknown_and_changed_coordinates() { 37 let source = include_str!("../../../../config/product/harvestcircle-v1.properties"); 38 assert!(parse(source).is_ok()); 39 assert!(parse(&source.replacen("schema=harvestcircle.product.v1\n", "", 1)).is_err()); 40 assert!(parse(&format!("{source}\nschema=harvestcircle.product.v1")).is_err()); 41 assert!(parse(&format!("{source}\nunknown=value")).is_err()); 42 assert!( 43 parse(&source.replace("product.slug=harvestcircle", "product.slug=OTHER")).is_err() 44 ); 45 assert!( 46 parse(&source.replace( 47 "storage.database_filename=state.sqlite", 48 "storage.database_filename=other.sqlite" 49 )) 50 .is_err() 51 ); 52 assert!( 53 parse(&source.replace( 54 "legacy.database.filename=harvestcircle.sqlite3", 55 "legacy.database.filename=../other.sqlite3" 56 )) 57 .is_err() 58 ); 59 assert!(parse(&source.replace("limit.identities=256", "limit.identities=257")).is_err()); 60 assert!( 61 parse(&source.replace( 62 "platform.linux.architecture=x86_64", 63 "platform.linux.architecture=aarch64" 64 )) 65 .is_err() 66 ); 67 assert!(parse(&format!("\u{feff}{source}")).is_err()); 68 } 69 70 #[test] 71 fn mutable_presentation_coordinates_are_manifest_owned_and_generated() { 72 let source = include_str!("../../../../config/product/harvestcircle-v1.properties"); 73 let mutations = [ 74 ("product.name", "Harvest Circle Test"), 75 ("product.slug", "harvestcircle_test"), 76 ("kotlin.root_namespace", "org.example"), 77 ("desktop.application_id", "org.example.desktop"), 78 ("desktop.bundle_id", "org.example.bundle"), 79 ("desktop.main_class", "org.example.MainKt"), 80 ("ffi.kotlin_package", "org.example.ffi"), 81 ("ffi.cdylib_name", "example_ffi"), 82 ("keyring.service", "org.example.desktop.nostr"), 83 ("environment.prefix", "EXAMPLE_"), 84 ("vendor.name", "Example Cooperative"), 85 ("copyright.notice", "Copyright Example contributors"), 86 ]; 87 for (key, replacement) in mutations { 88 let original = parse(source).unwrap().remove(key).unwrap(); 89 let mutated = source.replace( 90 &format!("{key}={original}"), 91 &format!("{key}={replacement}"), 92 ); 93 let parsed = parse(&mutated).expect("valid manifest-owned coordinate mutation"); 94 assert_eq!(parsed.get(key).map(String::as_str), Some(replacement)); 95 assert_ne!(digest(&mutated).unwrap(), digest(source).unwrap()); 96 assert!( 97 generate_rust_constants(&mutated) 98 .unwrap() 99 .contains(&format!("= {replacement:?};")) 100 ); 101 } 102 assert!(REQUIRED_KEYS.len() > mutations.len()); 103 } 104 105 #[test] 106 fn product_digest_is_semantic_and_line_ending_independent() { 107 let source = include_str!("../../../../config/product/harvestcircle-v1.properties"); 108 let expected = digest(source).expect("canonical product digest"); 109 assert_eq!( 110 expected, 111 "bf50f9ea6c2537406de255f025463e670eb6263c295f992f7e4c4db36d957064" 112 ); 113 assert_eq!(digest(&source.replace('\n', "\r\n")).unwrap(), expected); 114 assert_eq!( 115 digest(&format!("\n# comment\n{source}\n")).unwrap(), 116 expected 117 ); 118 assert_eq!( 119 digest( 120 &source 121 .lines() 122 .map(|line| line.replacen('=', " = ", 1)) 123 .collect::<Vec<_>>() 124 .join("\n") 125 ) 126 .unwrap(), 127 expected 128 ); 129 assert!(canonicalize(&format!("\u{feff}{source}")).is_err()); 130 } 131 132 #[test] 133 fn provenance_digest_is_semantic_and_strict() { 134 let source = std::fs::read_to_string( 135 std::path::Path::new(env!("CARGO_MANIFEST_DIR")) 136 .join("../../provenance") 137 .join("harvestcircle-v1.toml"), 138 ) 139 .expect("read canonical provenance fixture"); 140 let expected = provenance::digest(&source).expect("canonical provenance digest"); 141 assert_eq!( 142 expected, 143 "6cc524ffa028e7958dabc7d721302b967b7895fb2531f2e1cb6a7efca47bbe3b" 144 ); 145 assert_eq!( 146 provenance::digest(&source.replace('\n', "\r\n")).unwrap(), 147 expected 148 ); 149 assert_eq!(provenance::digest(source.trim_end()).unwrap(), expected); 150 assert_eq!( 151 provenance::digest(&format!("# comment\n{source}")).unwrap(), 152 expected 153 ); 154 let reordered = source.replace( 155 "component = \"domain\"\ncommit = \"a4d7deebec3e2ce2c1daa455de6d79857839aed0\"", 156 "commit = \"a4d7deebec3e2ce2c1daa455de6d79857839aed0\"\ncomponent = \"domain\"", 157 ); 158 assert_eq!(provenance::digest(&reordered).unwrap(), expected); 159 assert!(provenance::digest(&format!("\u{feff}{source}")).is_err()); 160 assert!(provenance::digest(&format!("unknown = \"value\"\n{source}")).is_err()); 161 assert_ne!( 162 provenance::digest(&source.replace( 163 "a4d7deebec3e2ce2c1daa455de6d79857839aed0", 164 "b4d7deebec3e2ce2c1daa455de6d79857839aed0" 165 )) 166 .unwrap(), 167 expected 168 ); 169 } 170 }