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 }