lib

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

job_util.rs (10250B)


      1 #[path = "../src/test_fixtures.rs"]
      2 mod test_fixtures;
      3 
      4 use radroots_events::job::{JobFeedbackStatus, JobInputType};
      5 use radroots_events_codec::job::error::JobParseError;
      6 use radroots_events_codec::job::util::{
      7     feedback_status_from_tag, feedback_status_tag, job_input_type_from_tag, job_input_type_tag,
      8     parse_amount_tag_sat, parse_bid_tag_sat, parse_bool_encrypted, parse_i_tags, parse_params,
      9     push_amount_tag_msat, push_bid_tag_sat,
     10 };
     11 use test_fixtures::{APP_PRIMARY_HTTPS, RELAY_PRIMARY_WSS};
     12 
     13 #[test]
     14 fn parse_bool_encrypted_detects_tag() {
     15     let tags = vec![vec!["encrypted".to_string()]];
     16     assert!(parse_bool_encrypted(&tags));
     17     assert!(!parse_bool_encrypted(&[]));
     18 }
     19 
     20 #[test]
     21 fn input_type_tag_roundtrip() {
     22     let t = job_input_type_tag(JobInputType::Url);
     23     assert_eq!(job_input_type_from_tag(t), Some(JobInputType::Url));
     24     assert_eq!(job_input_type_from_tag("unknown"), None);
     25 }
     26 
     27 #[test]
     28 fn input_type_tag_covers_all_variants() {
     29     assert_eq!(job_input_type_tag(JobInputType::Event), "event");
     30     assert_eq!(job_input_type_tag(JobInputType::Job), "job");
     31     assert_eq!(job_input_type_tag(JobInputType::Text), "text");
     32     assert_eq!(job_input_type_from_tag("event"), Some(JobInputType::Event));
     33     assert_eq!(job_input_type_from_tag("job"), Some(JobInputType::Job));
     34     assert_eq!(job_input_type_from_tag("text"), Some(JobInputType::Text));
     35 }
     36 
     37 #[test]
     38 fn feedback_status_tag_roundtrip() {
     39     let t = feedback_status_tag(JobFeedbackStatus::Processing);
     40     assert_eq!(
     41         feedback_status_from_tag(t),
     42         Some(JobFeedbackStatus::Processing)
     43     );
     44     assert_eq!(feedback_status_from_tag("unknown"), None);
     45 }
     46 
     47 #[test]
     48 fn feedback_status_tag_covers_all_variants() {
     49     assert_eq!(
     50         feedback_status_tag(JobFeedbackStatus::PaymentRequired),
     51         "payment-required"
     52     );
     53     assert_eq!(feedback_status_tag(JobFeedbackStatus::Error), "error");
     54     assert_eq!(feedback_status_tag(JobFeedbackStatus::Success), "success");
     55     assert_eq!(feedback_status_tag(JobFeedbackStatus::Partial), "partial");
     56     assert_eq!(
     57         feedback_status_from_tag("payment-required"),
     58         Some(JobFeedbackStatus::PaymentRequired)
     59     );
     60     assert_eq!(
     61         feedback_status_from_tag("error"),
     62         Some(JobFeedbackStatus::Error)
     63     );
     64     assert_eq!(
     65         feedback_status_from_tag("success"),
     66         Some(JobFeedbackStatus::Success)
     67     );
     68     assert_eq!(
     69         feedback_status_from_tag("partial"),
     70         Some(JobFeedbackStatus::Partial)
     71     );
     72 }
     73 
     74 #[test]
     75 fn parse_i_tags_handles_multiple_shapes() {
     76     let tags = vec![
     77         vec!["i".to_string(), APP_PRIMARY_HTTPS.to_string()],
     78         vec!["i".to_string(), "note1abcdef".to_string()],
     79         vec![
     80             "i".to_string(),
     81             "0123456789abcdef0123456789abcdef".to_string(),
     82         ],
     83         vec![
     84             "i".to_string(),
     85             "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(),
     86         ],
     87         vec![
     88             "i".to_string(),
     89             "job-id".to_string(),
     90             "job".to_string(),
     91             "wss://relay".to_string(),
     92             "marker".to_string(),
     93         ],
     94     ];
     95 
     96     let inputs = parse_i_tags(&tags);
     97     assert_eq!(inputs.len(), 5);
     98 
     99     assert_eq!(inputs[0].data, APP_PRIMARY_HTTPS);
    100     assert_eq!(inputs[0].input_type, JobInputType::Url);
    101     assert!(inputs[0].relay.is_none());
    102     assert!(inputs[0].marker.is_none());
    103 
    104     assert_eq!(inputs[1].data, "note1abcdef");
    105     assert_eq!(inputs[1].input_type, JobInputType::Event);
    106 
    107     assert_eq!(inputs[2].data, "0123456789abcdef0123456789abcdef");
    108     assert_eq!(inputs[2].input_type, JobInputType::Event);
    109     assert!(inputs[2].relay.is_none());
    110     assert!(inputs[2].marker.is_none());
    111 
    112     assert_eq!(
    113         inputs[3].data,
    114         "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    115     );
    116     assert_eq!(inputs[3].input_type, JobInputType::Event);
    117     assert!(inputs[3].relay.is_none());
    118     assert!(inputs[3].marker.is_none());
    119 
    120     assert_eq!(inputs[4].data, "job-id");
    121     assert_eq!(inputs[4].input_type, JobInputType::Job);
    122     assert_eq!(inputs[4].relay.as_deref(), Some("wss://relay"));
    123     assert_eq!(inputs[4].marker.as_deref(), Some("marker"));
    124 }
    125 
    126 #[test]
    127 fn parse_i_tags_http_url_uses_url_type() {
    128     let tags = vec![vec!["i".to_string(), "http://example.com".to_string()]];
    129     let inputs = parse_i_tags(&tags);
    130     assert_eq!(inputs.len(), 1);
    131     assert_eq!(inputs[0].input_type, JobInputType::Url);
    132     assert_eq!(inputs[0].data, "http://example.com");
    133 }
    134 
    135 #[test]
    136 fn parse_i_tags_covers_marker_and_fallback_shapes() {
    137     let tags = vec![
    138         vec!["i".to_string()],
    139         vec!["i".to_string(), "marker-only".to_string()],
    140         vec![
    141             "i".to_string(),
    142             "event-id".to_string(),
    143             "marker".to_string(),
    144         ],
    145         vec![
    146             "i".to_string(),
    147             "event-id".to_string(),
    148             "event".to_string(),
    149             "marker-4".to_string(),
    150         ],
    151         vec![
    152             "i".to_string(),
    153             "event-id".to_string(),
    154             "event".to_string(),
    155             RELAY_PRIMARY_WSS.to_string(),
    156         ],
    157         vec![
    158             "i".to_string(),
    159             "event-id".to_string(),
    160             "event".to_string(),
    161             "marker-5".to_string(),
    162             "fallback-marker".to_string(),
    163         ],
    164         vec![
    165             "i".to_string(),
    166             "event-id".to_string(),
    167             "event".to_string(),
    168             RELAY_PRIMARY_WSS.to_string(),
    169             "final-marker".to_string(),
    170         ],
    171         vec!["i".to_string(), "nostr:note1abcdef".to_string()],
    172         vec!["i".to_string(), "nevent1abcdef".to_string()],
    173         vec!["i".to_string(), "naddr1abcdef".to_string()],
    174         vec![
    175             "i".to_string(),
    176             "text-input".to_string(),
    177             "text".to_string(),
    178             "ws://relay.example.com".to_string(),
    179             "marker-text".to_string(),
    180         ],
    181     ];
    182 
    183     let inputs = parse_i_tags(&tags);
    184     assert_eq!(inputs.len(), 10);
    185     assert_eq!(inputs[0].marker.as_deref(), Some("marker-only"));
    186     assert_eq!(inputs[0].data, "");
    187     assert_eq!(inputs[1].marker.as_deref(), Some("marker"));
    188     assert_eq!(inputs[1].data, "event-id");
    189     assert_eq!(inputs[2].marker.as_deref(), Some("marker-4"));
    190     assert_eq!(inputs[2].relay, None);
    191     assert_eq!(inputs[3].relay.as_deref(), Some(RELAY_PRIMARY_WSS));
    192     assert_eq!(inputs[3].marker, None);
    193     assert_eq!(inputs[4].marker.as_deref(), Some("marker-5"));
    194     assert_eq!(inputs[4].relay, None);
    195     assert_eq!(inputs[5].relay.as_deref(), Some(RELAY_PRIMARY_WSS));
    196     assert_eq!(inputs[5].marker.as_deref(), Some("final-marker"));
    197     assert_eq!(inputs[6].input_type, JobInputType::Event);
    198     assert_eq!(inputs[7].input_type, JobInputType::Event);
    199     assert_eq!(inputs[8].input_type, JobInputType::Event);
    200     assert_eq!(inputs[9].input_type, JobInputType::Text);
    201     assert_eq!(inputs[9].relay.as_deref(), Some("ws://relay.example.com"));
    202     assert_eq!(inputs[9].marker.as_deref(), Some("marker-text"));
    203 }
    204 
    205 #[test]
    206 fn parse_params_extracts_key_value_pairs() {
    207     let tags = vec![
    208         vec!["param".to_string(), "k".to_string(), "v".to_string()],
    209         vec!["param".to_string(), "skip".to_string()],
    210     ];
    211 
    212     let params = parse_params(&tags);
    213     assert_eq!(params.len(), 1);
    214     assert_eq!(params[0].key, "k");
    215     assert_eq!(params[0].value, "v");
    216 }
    217 
    218 #[test]
    219 fn parse_amount_tag_sat_accepts_msat_and_bolt11() {
    220     let tags = vec![vec![
    221         "amount".to_string(),
    222         "1000".to_string(),
    223         "bolt11".to_string(),
    224     ]];
    225 
    226     let parsed = parse_amount_tag_sat(&tags).unwrap().unwrap();
    227     assert_eq!(parsed.0, 1);
    228     assert_eq!(parsed.1.as_deref(), Some("bolt11"));
    229 }
    230 
    231 #[test]
    232 fn parse_amount_tag_sat_handles_none_and_invalid_shapes() {
    233     assert!(parse_amount_tag_sat(&[]).unwrap().is_none());
    234 
    235     let err = parse_amount_tag_sat(&[vec!["amount".to_string()]]).unwrap_err();
    236     assert!(matches!(err, JobParseError::InvalidTag("amount")));
    237 
    238     let err = parse_amount_tag_sat(&[vec!["amount".to_string(), "abc".to_string()]]).unwrap_err();
    239     assert!(matches!(err, JobParseError::InvalidNumber("amount", _)));
    240 }
    241 
    242 #[test]
    243 fn parse_amount_tag_sat_rejects_non_whole_sats() {
    244     let tags = vec![vec!["amount".to_string(), "1500".to_string()]];
    245     let err = parse_amount_tag_sat(&tags).unwrap_err();
    246     assert!(matches!(err, JobParseError::NonWholeSats("amount")));
    247 }
    248 
    249 #[test]
    250 fn parse_amount_tag_sat_rejects_overflow() {
    251     let overflow = ((u32::MAX as u64) + 1) * 1000;
    252     let tags = vec![vec!["amount".to_string(), overflow.to_string()]];
    253     let err = parse_amount_tag_sat(&tags).unwrap_err();
    254     assert!(matches!(err, JobParseError::AmountOverflow("amount")));
    255 }
    256 
    257 #[test]
    258 fn push_amount_tag_msat_writes_msat() {
    259     let mut tags = Vec::new();
    260     push_amount_tag_msat(&mut tags, 12, Some("bolt".to_string()));
    261     assert_eq!(
    262         tags[0],
    263         vec![
    264             "amount".to_string(),
    265             "12000".to_string(),
    266             "bolt".to_string()
    267         ]
    268     );
    269 }
    270 
    271 #[test]
    272 fn parse_bid_tag_sat_accepts_sat() {
    273     let tags = vec![vec!["bid".to_string(), "2".to_string()]];
    274     let bid = parse_bid_tag_sat(&tags).unwrap().unwrap();
    275     assert_eq!(bid, 2);
    276 }
    277 
    278 #[test]
    279 fn parse_bid_tag_sat_handles_none_and_invalid_shape() {
    280     assert!(parse_bid_tag_sat(&[]).unwrap().is_none());
    281 
    282     let err = parse_bid_tag_sat(&[vec!["bid".to_string()]]).unwrap_err();
    283     assert!(matches!(err, JobParseError::InvalidTag("bid")));
    284 }
    285 
    286 #[test]
    287 fn parse_bid_tag_sat_rejects_non_numeric() {
    288     let tags = vec![vec!["bid".to_string(), "not-a-number".to_string()]];
    289     let err = parse_bid_tag_sat(&tags).unwrap_err();
    290     assert!(matches!(err, JobParseError::InvalidNumber("bid", _)));
    291 }
    292 
    293 #[test]
    294 fn parse_bid_tag_sat_rejects_overflow() {
    295     let overflow = (u32::MAX as u64) + 1;
    296     let tags = vec![vec!["bid".to_string(), overflow.to_string()]];
    297     let err = parse_bid_tag_sat(&tags).unwrap_err();
    298     assert!(matches!(err, JobParseError::AmountOverflow("bid")));
    299 }
    300 
    301 #[test]
    302 fn push_bid_tag_sat_writes_sat() {
    303     let mut tags = Vec::new();
    304     push_bid_tag_sat(&mut tags, 7);
    305     assert_eq!(tags[0], vec!["bid".to_string(), "7".to_string()]);
    306 }