app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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 }