lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

tests.rs (6074B)


      1 use super::*;
      2 use radroots_event::envelope::EventTags;
      3 
      4 const HASH: &str = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
      5 
      6 #[test]
      7 fn normalizes_tolerant_decimal_and_currency_values() {
      8     let tags = focused_tags(vec![
      9         tag(&["price", "0003.5000", "cad"]),
     10         tag(&["radroots:price_unit", "lb"]),
     11         tag(&["radroots:quantity", "0010.00", "lb"]),
     12     ]);
     13 
     14     let projection = focused(project(&tags).unwrap());
     15     assert_eq!(projection.price().amount(), "3.5");
     16     assert_eq!(projection.price().currency().as_str(), "CAD");
     17     assert_eq!(projection.quantity().unwrap().amount(), "10");
     18 }
     19 
     20 #[test]
     21 fn partitions_raw_markers_before_shape_or_capability_validation() {
     22     let operational = tags(vec![tag(&["radroots:bin"]), tag(&["delivery"])]);
     23     assert_eq!(
     24         project(&operational).unwrap(),
     25         RadrootsFoodAvailabilityProjectionOutcome::Excluded(
     26             ClassifiedListingPartition::OperationalListing
     27         )
     28     );
     29 
     30     let mixed = tags(vec![
     31         tag(&["radroots:price_unit"]),
     32         tag(&["radroots:price"]),
     33     ]);
     34     assert_eq!(
     35         project(&mixed).unwrap_err().code(),
     36         "food_profile_ambiguous"
     37     );
     38 
     39     let malformed_focused = tags(vec![tag(&["radroots:price_unit"])]);
     40     assert_eq!(
     41         project(&malformed_focused).unwrap_err().code(),
     42         "food_tag_invalid"
     43     );
     44     assert_eq!(
     45         project_inbound_food_availability_parts(
     46             KIND_CLASSIFIED_LISTING,
     47             200,
     48             &malformed_focused,
     49             "",
     50         )
     51         .unwrap_err()
     52         .code(),
     53         "food_tag_invalid"
     54     );
     55 }
     56 
     57 #[test]
     58 fn focused_marker_errors_and_prohibited_capabilities_are_stable() {
     59     let mut malformed_unit = base_tags();
     60     malformed_unit.push(tag(&["price", "3", "CAD"]));
     61     malformed_unit.push(tag(&["radroots:price_unit"]));
     62     malformed_unit.push(tag(&["status", "active"]));
     63     assert_eq!(
     64         project(&tags(malformed_unit)).unwrap_err().code(),
     65         "price_unit_invalid"
     66     );
     67 
     68     let mut prohibited = base_tags();
     69     prohibited.push(tag(&["radroots:price_unit", "lb"]));
     70     prohibited.push(tag(&["status", "active"]));
     71     prohibited.push(tag(&["delivery", "tomorrow"]));
     72     assert_eq!(
     73         project(&tags(prohibited)).unwrap_err().code(),
     74         "prohibited_capability"
     75     );
     76 }
     77 
     78 #[test]
     79 fn retains_ordered_bounded_image_diagnostics() {
     80     let url = format!("https://media.example/{HASH}.webp");
     81     let mut extra = focused_tags(Vec::new()).to_vec();
     82     extra.extend([
     83         tag(&["image", &url]),
     84         tag(&["image", &url, "0x600", "extra"]),
     85     ]);
     86     let projection = focused(project(&tags(extra)).unwrap());
     87     assert_eq!(
     88         projection.diagnostics(),
     89         &[
     90             RadrootsFoodAvailabilityImageDiagnostic::ShapeInvalid,
     91             RadrootsFoodAvailabilityImageDiagnostic::DimensionsMissing,
     92             RadrootsFoodAvailabilityImageDiagnostic::ShapeInvalid,
     93             RadrootsFoodAvailabilityImageDiagnostic::DimensionsInvalid,
     94             RadrootsFoodAvailabilityImageDiagnostic::DuplicateUrl,
     95             RadrootsFoodAvailabilityImageDiagnostic::DuplicateDigest,
     96         ]
     97     );
     98 
     99     let mut bounded = focused_tags(Vec::new()).to_vec();
    100     bounded.extend((0..=RADROOTS_FOOD_IMAGE_MAX_COUNT).map(|index| {
    101         tag(&[
    102             "image",
    103             &format!("https://media.example/{index:064x}.webp"),
    104             "800x600",
    105         ])
    106     }));
    107     let projection = focused(project(&tags(bounded)).unwrap());
    108     assert_eq!(projection.images().len(), RADROOTS_FOOD_IMAGE_MAX_COUNT);
    109     assert_eq!(
    110         projection.diagnostics().first(),
    111         Some(&RadrootsFoodAvailabilityImageDiagnostic::CountExceeded)
    112     );
    113 }
    114 
    115 #[test]
    116 fn accepts_optional_standard_tags_but_bounds_raw_decimal_digits() {
    117     let mut optional = focused_tags(Vec::new()).to_vec();
    118     optional.extend([tag(&["t", "vegetables"]), tag(&["g", "c28"])]);
    119     assert!(matches!(
    120         project(&tags(optional)).unwrap(),
    121         RadrootsFoodAvailabilityProjectionOutcome::Focused(_)
    122     ));
    123 
    124     let mut oversized = base_tags();
    125     oversized.push(tag(&["price", "00000000000000000000000000003", "CAD"]));
    126     oversized.push(tag(&["radroots:price_unit", "lb"]));
    127     oversized.push(tag(&["status", "active"]));
    128     assert_eq!(
    129         project(&tags(oversized)).unwrap_err().code(),
    130         "price_invalid"
    131     );
    132 }
    133 
    134 fn project(
    135     tags: &EventTags,
    136 ) -> Result<RadrootsFoodAvailabilityProjectionOutcome, RadrootsFoodAvailabilityProjectionError> {
    137     project_inbound_food_availability_parts(
    138         KIND_CLASSIFIED_LISTING,
    139         200,
    140         tags,
    141         "Carrots available this week.",
    142     )
    143 }
    144 
    145 fn focused(
    146     outcome: RadrootsFoodAvailabilityProjectionOutcome,
    147 ) -> RadrootsInboundFoodAvailabilityProjection {
    148     match outcome {
    149         RadrootsFoodAvailabilityProjectionOutcome::Focused(projection) => *projection,
    150         RadrootsFoodAvailabilityProjectionOutcome::Excluded(partition) => {
    151             panic!("unexpected exclusion: {partition:?}")
    152         }
    153     }
    154 }
    155 
    156 fn focused_tags(mut additional: Vec<Vec<String>>) -> EventTags {
    157     let mut values = base_tags();
    158     if !additional
    159         .iter()
    160         .any(|value| value.first().is_some_and(|name| name == "price"))
    161     {
    162         values.push(tag(&["price", "3", "CAD"]));
    163     }
    164     values.append(&mut additional);
    165     if !values.iter().any(|value| {
    166         value
    167             .first()
    168             .is_some_and(|name| name == "radroots:price_unit")
    169     }) {
    170         values.push(tag(&["radroots:price_unit", "lb"]));
    171     }
    172     values.push(tag(&["status", "active"]));
    173     tags(values)
    174 }
    175 
    176 fn base_tags() -> Vec<Vec<String>> {
    177     vec![
    178         tag(&["d", "nantes-carrots"]),
    179         tag(&["title", "Nantes Carrots"]),
    180         tag(&["summary", "Fresh bunches"]),
    181         tag(&["published_at", "100"]),
    182         tag(&["location", "Central Saanich, BC"]),
    183     ]
    184 }
    185 
    186 fn tags(values: Vec<Vec<String>>) -> EventTags {
    187     EventTags::new(values).unwrap()
    188 }
    189 
    190 fn tag(values: &[&str]) -> Vec<String> {
    191     values.iter().map(|value| (*value).to_string()).collect()
    192 }