lib

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

job_request.rs (5492B)


      1 #[path = "../src/test_fixtures.rs"]
      2 mod test_fixtures;
      3 
      4 use radroots_event::envelope::kind::{
      5     KIND_JOB_FEEDBACK, KIND_JOB_REQUEST_MIN, KIND_JOB_RESULT_MIN,
      6 };
      7 use radroots_event::social::job::JobInputType;
      8 use radroots_event::social::job_request::{JobInput, JobParam, JobRequest};
      9 use radroots_event_codec::decode::job::JobParseError;
     10 use radroots_event_codec::decode::job::request::{job_request_from_tags, parsed_from_event};
     11 use radroots_event_codec::encode::job::JobEncodeError;
     12 use radroots_event_codec::encode::job::request::to_wire_parts;
     13 use test_fixtures::{APP_PRIMARY_HTTPS, RELAY_PRIMARY_WSS};
     14 
     15 fn sample_request() -> JobRequest {
     16     JobRequest {
     17         kind: u16::try_from(KIND_JOB_REQUEST_MIN + 1).expect("request kind must fit NIP-01"),
     18         inputs: vec![JobInput {
     19             data: APP_PRIMARY_HTTPS.to_string(),
     20             input_type: JobInputType::Url,
     21             relay: Some(RELAY_PRIMARY_WSS.to_string()),
     22             marker: Some("source".to_string()),
     23         }],
     24         output: Some("json".to_string()),
     25         params: vec![JobParam {
     26             key: "foo".to_string(),
     27             value: "bar".to_string(),
     28         }],
     29         bid_sat: Some(250),
     30         relays: vec![RELAY_PRIMARY_WSS.to_string()],
     31         providers: vec!["provider".to_string()],
     32         topics: vec!["topic".to_string()],
     33         encrypted: false,
     34     }
     35 }
     36 
     37 #[test]
     38 fn job_request_roundtrip_from_tags() {
     39     let req = sample_request();
     40     let parts = to_wire_parts(&req, "payload").unwrap();
     41 
     42     let decoded = job_request_from_tags(parts.kind, &parts.tags).unwrap();
     43     assert_eq!(decoded, req);
     44 }
     45 
     46 #[test]
     47 fn job_request_from_tags_rejects_kind_overflow() {
     48     let kind = u32::from(u16::MAX) + 1;
     49     assert!(matches!(
     50         job_request_from_tags(kind, &[]),
     51         Err(JobParseError::KindOutOfRange(actual)) if actual == kind
     52     ));
     53 }
     54 
     55 #[test]
     56 fn job_request_requires_valid_kind() {
     57     let mut req = sample_request();
     58     req.kind = u16::try_from(KIND_JOB_FEEDBACK).expect("feedback kind must fit NIP-01");
     59 
     60     let err = to_wire_parts(&req, "payload").unwrap_err();
     61     assert!(matches!(
     62         err,
     63         JobEncodeError::InvalidKind(KIND_JOB_FEEDBACK)
     64     ));
     65 }
     66 
     67 #[test]
     68 fn job_request_requires_providers_when_encrypted() {
     69     let mut req = sample_request();
     70     req.encrypted = true;
     71     req.providers.clear();
     72 
     73     let err = to_wire_parts(&req, "payload").unwrap_err();
     74     assert!(matches!(err, JobEncodeError::MissingProvidersForEncrypted));
     75 
     76     let tags = vec![vec!["encrypted".to_string()]];
     77     let err = job_request_from_tags(KIND_JOB_REQUEST_MIN + 1, &tags).unwrap_err();
     78     assert!(matches!(err, JobParseError::MissingTag("p")));
     79 }
     80 
     81 #[test]
     82 fn job_request_from_tags_accepts_encrypted_with_provider() {
     83     let request = job_request_from_tags(
     84         KIND_JOB_REQUEST_MIN + 1,
     85         &[
     86             vec!["encrypted".to_string()],
     87             vec!["p".to_string(), "provider".to_string()],
     88         ],
     89     )
     90     .unwrap();
     91     assert!(request.encrypted);
     92     assert_eq!(request.providers, vec!["provider".to_string()]);
     93 }
     94 
     95 #[test]
     96 fn job_request_to_wire_parts_allows_encrypted_when_provider_present() {
     97     let mut req = sample_request();
     98     req.encrypted = true;
     99     let parts = to_wire_parts(&req, "payload").unwrap();
    100     assert!(
    101         parts
    102             .tags
    103             .iter()
    104             .any(|tag| tag.first().map(|v| v.as_str()) == Some("encrypted"))
    105     );
    106 }
    107 
    108 #[test]
    109 fn job_request_from_tags_rejects_invalid_bid_tag() {
    110     let err = job_request_from_tags(
    111         KIND_JOB_REQUEST_MIN + 1,
    112         &[vec!["bid".to_string(), "not-a-number".to_string()]],
    113     )
    114     .unwrap_err();
    115     assert!(matches!(err, JobParseError::InvalidNumber("bid", _)));
    116 }
    117 
    118 #[test]
    119 fn job_request_metadata_rejects_wrong_kind() {
    120     let err = radroots_event_codec::decode::job::request::data_from_event(
    121         "id".to_string(),
    122         "author".to_string(),
    123         1,
    124         KIND_JOB_RESULT_MIN,
    125         Vec::new(),
    126     )
    127     .unwrap_err();
    128 
    129     assert!(matches!(
    130         err,
    131         JobParseError::InvalidTag("kind (expected 5000-5999)")
    132     ));
    133 }
    134 
    135 #[test]
    136 fn job_request_data_from_event_success_path() {
    137     let request = sample_request();
    138     let parts = to_wire_parts(&request, "payload").expect("wire parts");
    139     let data = radroots_event_codec::decode::job::request::data_from_event(
    140         "id".to_string(),
    141         "author".to_string(),
    142         1,
    143         parts.kind,
    144         parts.tags,
    145     )
    146     .expect("job request data");
    147     assert_eq!(data.id, "id");
    148     assert_eq!(data.author, "author");
    149     assert_eq!(data.kind, KIND_JOB_REQUEST_MIN + 1);
    150     assert_eq!(data.data.providers, vec!["provider".to_string()]);
    151 }
    152 
    153 #[test]
    154 fn job_request_data_from_event_propagates_decode_errors_with_valid_kind() {
    155     let err = radroots_event_codec::decode::job::request::data_from_event(
    156         "id".to_string(),
    157         "author".to_string(),
    158         1,
    159         KIND_JOB_REQUEST_MIN + 1,
    160         vec![vec!["bid".to_string(), "not-a-number".to_string()]],
    161     )
    162     .unwrap_err();
    163     assert!(matches!(err, JobParseError::InvalidNumber("bid", _)));
    164 }
    165 
    166 #[test]
    167 fn job_request_index_from_event_propagates_parse_errors() {
    168     let err = parsed_from_event(
    169         "id".to_string(),
    170         "author".to_string(),
    171         1,
    172         KIND_JOB_RESULT_MIN,
    173         "payload".to_string(),
    174         Vec::new(),
    175         "sig".to_string(),
    176     )
    177     .unwrap_err();
    178     assert!(matches!(
    179         err,
    180         JobParseError::InvalidTag("kind (expected 5000-5999)")
    181     ));
    182 }