1use std::sync::Arc;
2use reqwest::Client;
3use alloy::primitives::hex::decode;
4use serde::{Deserialize, Serialize};
5use graphql_client::{GraphQLQuery, Response, QueryBody};
6use super::{
7 RainMetaDocumentV1Item, KnownMagic, types::authoring::v1::AuthoringMeta, super::error::Error,
8};
9
10type Bytes = String;
11
12#[derive(GraphQLQuery)]
13#[graphql(
14 schema_path = "src/meta/query/schema.json",
15 query_path = "src/meta/query/meta.graphql",
16 response_derives = "Debug, Serialize, Deserialize"
17)]
18pub(super) struct MetaQuery;
19
20#[derive(GraphQLQuery)]
21#[graphql(
22 schema_path = "src/meta/query/schema.json",
23 query_path = "src/meta/query/deployer.graphql",
24 response_derives = "Debug, Serialize, Deserialize"
25)]
26pub(super) struct DeployerQuery;
27
28#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
30pub struct MetaResponse {
31 #[serde(with = "serde_bytes")]
32 pub bytes: Vec<u8>,
33}
34
35#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
37#[serde(rename_all = "camelCase")]
38pub struct DeployerResponse {
39 #[serde(with = "serde_bytes")]
40 pub tx_hash: Vec<u8>,
41 #[serde(with = "serde_bytes")]
42 pub bytecode_meta_hash: Vec<u8>,
43 #[serde(with = "serde_bytes")]
44 pub meta_hash: Vec<u8>,
45 #[serde(with = "serde_bytes")]
46 pub meta_bytes: Vec<u8>,
47 #[serde(with = "serde_bytes")]
48 pub bytecode: Vec<u8>,
49 #[serde(with = "serde_bytes")]
50 pub parser: Vec<u8>,
51 #[serde(with = "serde_bytes")]
52 pub store: Vec<u8>,
53 #[serde(with = "serde_bytes")]
54 pub interpreter: Vec<u8>,
55}
56
57impl DeployerResponse {
58 pub fn get_authoring_meta(&self) -> Option<AuthoringMeta> {
60 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(&self.meta_bytes) {
61 for meta_map in &meta_maps {
62 if meta_map.magic == KnownMagic::AuthoringMetaV1 {
63 if let Ok(v) = meta_map.unpack() {
64 match AuthoringMeta::abi_decode_validate(&v) {
65 Ok(am) => return Some(am),
66 Err(_) => return None,
67 }
68 }
69 }
70 }
71 None
72 } else {
73 None
74 }
75 }
76}
77
78pub(super) async fn process_meta_query(
81 client: Arc<Client>,
82 request_body: &QueryBody<meta_query::Variables>,
83 url: &str,
84) -> Result<MetaResponse, Error> {
85 Ok(MetaResponse {
86 bytes: decode(
87 client
88 .post(url)
89 .json(request_body)
90 .send()
91 .await
92 .map_err(Error::ReqwestError)?
93 .json::<Response<meta_query::ResponseData>>()
94 .await
95 .map_err(Error::ReqwestError)?
96 .data
97 .ok_or(Error::NoRecordFound)?
98 .meta
99 .ok_or(Error::NoRecordFound)?
100 .raw_bytes,
101 )
102 .or(Err(Error::NoRecordFound))?,
103 })
104}
105
106pub(super) async fn process_deployer_query(
109 client: Arc<Client>,
110 request_body: &QueryBody<deployer_query::Variables>,
111 url: &str,
112) -> Result<DeployerResponse, Error> {
113 let res = client
114 .post(url)
115 .json(request_body)
116 .send()
117 .await
118 .map_err(Error::ReqwestError)?
119 .json::<Response<deployer_query::ResponseData>>()
120 .await
121 .map_err(Error::ReqwestError)?
122 .data
123 .ok_or(Error::NoRecordFound)?
124 .expression_deployers;
125
126 if !res.is_empty() {
127 let bytecode = if let Some(v) = &res[0].bytecode {
128 decode(v).or(Err(Error::NoRecordFound))?
129 } else {
130 return Err(Error::NoRecordFound);
131 };
132 let parser = if let Some(v) = &res[0].parser {
133 decode(&v.parser.deployed_bytecode).or(Err(Error::NoRecordFound))?
134 } else {
135 return Err(Error::NoRecordFound);
136 };
137 let store = if let Some(v) = &res[0].store {
138 decode(&v.store.deployed_bytecode).or(Err(Error::NoRecordFound))?
139 } else {
140 return Err(Error::NoRecordFound);
141 };
142 let interpreter = if let Some(v) = &res[0].interpreter {
143 decode(&v.interpreter.deployed_bytecode).or(Err(Error::NoRecordFound))?
144 } else {
145 return Err(Error::NoRecordFound);
146 };
147 let bytecode_meta_hash = if res[0].meta.len() == 1 {
148 decode(&res[0].meta[0].id).or(Err(Error::NoRecordFound))?
149 } else {
150 return Err(Error::NoRecordFound);
151 };
152 let tx_hash = if let Some(v) = &res[0].deploy_transaction {
153 decode(&v.id).or(Err(Error::NoRecordFound))?
154 } else {
155 return Err(Error::NoRecordFound);
156 };
157 let meta_hash = decode(&res[0].constructor_meta_hash).or(Err(Error::NoRecordFound))?;
158 let meta_bytes = decode(&res[0].constructor_meta).or(Err(Error::NoRecordFound))?;
159 Ok(DeployerResponse {
160 meta_hash,
161 meta_bytes,
162 bytecode,
163 parser,
164 store,
165 interpreter,
166 bytecode_meta_hash,
167 tx_hash,
168 })
169 } else {
170 Err(Error::NoRecordFound)
171 }
172}
173
174#[cfg(all(test, not(target_family = "wasm")))]
175mod tests {
176 use super::*;
177 use crate::meta::types::authoring::v1::AuthoringMetaItem;
178 use crate::meta::{ContentEncoding, ContentLanguage, ContentType};
179 use httpmock::Method::POST;
180 use httpmock::MockServer;
181 use serde_bytes::ByteBuf;
182 use serde_json::json;
183
184 fn request_body(hash: &str) -> QueryBody<meta_query::Variables> {
185 MetaQuery::build_query(meta_query::Variables {
186 hash: Some(hash.to_string()),
187 })
188 }
189
190 const HASH: &str = "0x1111111111111111111111111111111111111111111111111111111111111111";
191
192 #[tokio::test]
195 async fn test_process_meta_query_success_exact_bytes() {
196 let server = MockServer::start_async().await;
197 server.mock(|when, then| {
198 when.method(POST)
199 .path("/")
200 .json_body_partial(format!(r#"{{"variables":{{"hash":"{}"}}}}"#, HASH));
201 then.status(200)
202 .header("content-type", "application/json")
203 .body(r#"{"data":{"meta":{"__typename":"RainMetaV1","rawBytes":"0xff0a89c674ee7874deadbeef"}}}"#);
204 });
205 let result = process_meta_query(
206 Arc::new(Client::new()),
207 &request_body(HASH),
208 &server.url("/"),
209 )
210 .await
211 .unwrap();
212 assert_eq!(
213 result.bytes,
214 vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, 0xde, 0xad, 0xbe, 0xef]
215 );
216 }
217
218 #[tokio::test]
220 async fn test_process_meta_query_missing_data_is_no_record_found() {
221 let server = MockServer::start_async().await;
222 server.mock(|when, then| {
223 when.method(POST).path("/");
224 then.status(200)
225 .header("content-type", "application/json")
226 .body(r#"{"data":null}"#);
227 });
228 let result = process_meta_query(
229 Arc::new(Client::new()),
230 &request_body(HASH),
231 &server.url("/"),
232 )
233 .await;
234 assert!(matches!(result, Err(Error::NoRecordFound)), "{result:?}");
235 }
236
237 #[tokio::test]
239 async fn test_process_meta_query_missing_meta_is_no_record_found() {
240 let server = MockServer::start_async().await;
241 server.mock(|when, then| {
242 when.method(POST).path("/");
243 then.status(200)
244 .header("content-type", "application/json")
245 .body(r#"{"data":{"meta":null}}"#);
246 });
247 let result = process_meta_query(
248 Arc::new(Client::new()),
249 &request_body(HASH),
250 &server.url("/"),
251 )
252 .await;
253 assert!(matches!(result, Err(Error::NoRecordFound)), "{result:?}");
254 }
255
256 #[tokio::test]
259 async fn test_process_meta_query_bad_hex_is_no_record_found() {
260 let server = MockServer::start_async().await;
261 server.mock(|when, then| {
262 when.method(POST).path("/");
263 then.status(200)
264 .header("content-type", "application/json")
265 .body(r#"{"data":{"meta":{"__typename":"RainMetaV1","rawBytes":"zz-not-hex"}}}"#);
266 });
267 let result = process_meta_query(
268 Arc::new(Client::new()),
269 &request_body(HASH),
270 &server.url("/"),
271 )
272 .await;
273 assert!(matches!(result, Err(Error::NoRecordFound)), "{result:?}");
274 }
275
276 #[tokio::test]
279 async fn test_process_meta_query_send_error_is_reqwest_error() {
280 let port = {
283 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
284 listener.local_addr().unwrap().port()
285 };
286 let url = format!("http://127.0.0.1:{port}/");
287 let result = process_meta_query(Arc::new(Client::new()), &request_body(HASH), &url).await;
288 assert!(matches!(result, Err(Error::ReqwestError(_))), "{result:?}");
289 }
290
291 #[tokio::test]
294 async fn test_process_meta_query_non_json_is_reqwest_error() {
295 let server = MockServer::start_async().await;
296 server.mock(|when, then| {
297 when.method(POST).path("/");
298 then.status(200)
299 .header("content-type", "text/plain")
300 .body("not json");
301 });
302 let result = process_meta_query(
303 Arc::new(Client::new()),
304 &request_body(HASH),
305 &server.url("/"),
306 )
307 .await;
308 assert!(matches!(result, Err(Error::ReqwestError(_))), "{result:?}");
309 }
310
311 fn authoring_meta() -> AuthoringMeta {
312 AuthoringMeta(vec![AuthoringMetaItem {
313 word: "some-word".to_string(),
314 operand_parser_offset: 0,
315 description: "a description".to_string(),
316 }])
317 }
318
319 fn authoring_item() -> RainMetaDocumentV1Item {
320 RainMetaDocumentV1Item {
321 payload: serde_bytes::ByteBuf::from(authoring_meta().abi_encode_validate().unwrap()),
322 magic: KnownMagic::AuthoringMetaV1,
323 content_type: ContentType::None,
324 content_encoding: ContentEncoding::None,
325 content_language: ContentLanguage::None,
326 schema: None,
327 }
328 }
329
330 fn deployer_response_with(meta_bytes: Vec<u8>) -> DeployerResponse {
331 DeployerResponse {
332 tx_hash: vec![],
333 bytecode_meta_hash: vec![],
334 meta_hash: vec![],
335 meta_bytes,
336 bytecode: vec![],
337 parser: vec![],
338 store: vec![],
339 interpreter: vec![],
340 }
341 }
342
343 #[test]
346 fn test_get_authoring_meta_skips_unpack_failure() {
347 let bad_unpack = RainMetaDocumentV1Item {
349 payload: serde_bytes::ByteBuf::from(vec![0xffu8, 0xff, 0xff, 0xff]),
350 magic: KnownMagic::AuthoringMetaV1,
351 content_type: ContentType::None,
352 content_encoding: ContentEncoding::Deflate,
353 content_language: ContentLanguage::None,
354 schema: None,
355 };
356 assert!(bad_unpack.unpack().is_err());
357 let meta_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
358 &vec![bad_unpack, authoring_item()],
359 KnownMagic::RainMetaDocumentV1,
360 )
361 .unwrap();
362 assert_eq!(
363 deployer_response_with(meta_bytes).get_authoring_meta(),
364 Some(authoring_meta())
365 );
366 }
367
368 #[test]
371 fn test_get_authoring_meta_scans_beyond_first_item() {
372 let other_magic = RainMetaDocumentV1Item {
373 payload: serde_bytes::ByteBuf::from(b"_: int-add(1 2);".to_vec()),
374 magic: KnownMagic::RainlangV1,
375 content_type: ContentType::None,
376 content_encoding: ContentEncoding::None,
377 content_language: ContentLanguage::None,
378 schema: None,
379 };
380 let meta_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
381 &vec![other_magic, authoring_item()],
382 KnownMagic::RainMetaDocumentV1,
383 )
384 .unwrap();
385 assert_eq!(
386 deployer_response_with(meta_bytes).get_authoring_meta(),
387 Some(authoring_meta())
388 );
389 }
390
391 fn deployer_entry() -> serde_json::Value {
394 json!({
395 "constructorMetaHash": "0x0102",
396 "constructorMeta": "0x0304",
397 "deployTransaction": { "id": "0x0506" },
398 "bytecode": "0x0708",
399 "parser": { "parser": { "deployedBytecode": "0x090a" } },
400 "store": { "store": { "deployedBytecode": "0x0b0c" } },
401 "interpreter": { "interpreter": { "deployedBytecode": "0x0d0e" } },
402 "meta": [ { "__typename": "RainMetaV1", "id": "0x0f10" } ]
403 })
404 }
405
406 async fn run_deployer_query(entry: serde_json::Value) -> Result<DeployerResponse, Error> {
407 let server = MockServer::start_async().await;
408 server.mock(|when, then| {
409 when.method(POST).path("/");
410 then.status(200)
411 .json_body(json!({ "data": { "expressionDeployers": [entry] } }));
412 });
413 let request_body = DeployerQuery::build_query(deployer_query::Variables {
414 hash: Some("0xabcd".to_string()),
415 });
416 let client = Arc::new(Client::new());
417 process_deployer_query(client, &request_body, &server.url("/")).await
418 }
419
420 #[tokio::test]
421 async fn test_process_deployer_query_success_decodes_all_fields() {
422 let res = run_deployer_query(deployer_entry()).await.unwrap();
423 assert_eq!(res.meta_hash, vec![0x01, 0x02]);
424 assert_eq!(res.meta_bytes, vec![0x03, 0x04]);
425 assert_eq!(res.tx_hash, vec![0x05, 0x06]);
426 assert_eq!(res.bytecode, vec![0x07, 0x08]);
427 assert_eq!(res.parser, vec![0x09, 0x0a]);
428 assert_eq!(res.store, vec![0x0b, 0x0c]);
429 assert_eq!(res.interpreter, vec![0x0d, 0x0e]);
430 assert_eq!(res.bytecode_meta_hash, vec![0x0f, 0x10]);
431 }
432
433 #[tokio::test]
434 async fn test_process_deployer_query_null_bytecode_is_no_record_found() {
435 let mut entry = deployer_entry();
436 entry["bytecode"] = serde_json::Value::Null;
437 assert!(matches!(
438 run_deployer_query(entry).await,
439 Err(Error::NoRecordFound)
440 ));
441 }
442
443 #[tokio::test]
444 async fn test_process_deployer_query_null_parser_is_no_record_found() {
445 let mut entry = deployer_entry();
446 entry["parser"] = serde_json::Value::Null;
447 assert!(matches!(
448 run_deployer_query(entry).await,
449 Err(Error::NoRecordFound)
450 ));
451 }
452
453 #[tokio::test]
454 async fn test_process_deployer_query_null_store_is_no_record_found() {
455 let mut entry = deployer_entry();
456 entry["store"] = serde_json::Value::Null;
457 assert!(matches!(
458 run_deployer_query(entry).await,
459 Err(Error::NoRecordFound)
460 ));
461 }
462
463 #[tokio::test]
464 async fn test_process_deployer_query_null_interpreter_is_no_record_found() {
465 let mut entry = deployer_entry();
466 entry["interpreter"] = serde_json::Value::Null;
467 assert!(matches!(
468 run_deployer_query(entry).await,
469 Err(Error::NoRecordFound)
470 ));
471 }
472
473 #[tokio::test]
474 async fn test_process_deployer_query_null_deploy_transaction_is_no_record_found() {
475 let mut entry = deployer_entry();
476 entry["deployTransaction"] = serde_json::Value::Null;
477 assert!(matches!(
478 run_deployer_query(entry).await,
479 Err(Error::NoRecordFound)
480 ));
481 }
482
483 #[tokio::test]
484 async fn test_process_deployer_query_zero_metas_is_no_record_found() {
485 let mut entry = deployer_entry();
486 entry["meta"] = json!([]);
487 assert!(matches!(
488 run_deployer_query(entry).await,
489 Err(Error::NoRecordFound)
490 ));
491 }
492
493 #[tokio::test]
494 async fn test_process_deployer_query_two_metas_is_no_record_found() {
495 let mut entry = deployer_entry();
496 entry["meta"] = json!([
497 { "__typename": "RainMetaV1", "id": "0x0f10" },
498 { "__typename": "RainMetaV1", "id": "0x1112" }
499 ]);
500 assert!(matches!(
501 run_deployer_query(entry).await,
502 Err(Error::NoRecordFound)
503 ));
504 }
505
506 #[tokio::test]
507 async fn test_process_deployer_query_invalid_bytecode_hex_is_no_record_found() {
508 let mut entry = deployer_entry();
509 entry["bytecode"] = json!("0xZZ");
510 assert!(matches!(
511 run_deployer_query(entry).await,
512 Err(Error::NoRecordFound)
513 ));
514 }
515
516 #[tokio::test]
517 async fn test_process_deployer_query_invalid_parser_hex_is_no_record_found() {
518 let mut entry = deployer_entry();
519 entry["parser"]["parser"]["deployedBytecode"] = json!("0xZZ");
520 assert!(matches!(
521 run_deployer_query(entry).await,
522 Err(Error::NoRecordFound)
523 ));
524 }
525
526 #[tokio::test]
527 async fn test_process_deployer_query_invalid_store_hex_is_no_record_found() {
528 let mut entry = deployer_entry();
529 entry["store"]["store"]["deployedBytecode"] = json!("0xZZ");
530 assert!(matches!(
531 run_deployer_query(entry).await,
532 Err(Error::NoRecordFound)
533 ));
534 }
535
536 #[tokio::test]
537 async fn test_process_deployer_query_invalid_interpreter_hex_is_no_record_found() {
538 let mut entry = deployer_entry();
539 entry["interpreter"]["interpreter"]["deployedBytecode"] = json!("0xZZ");
540 assert!(matches!(
541 run_deployer_query(entry).await,
542 Err(Error::NoRecordFound)
543 ));
544 }
545
546 #[tokio::test]
547 async fn test_process_deployer_query_invalid_meta_id_hex_is_no_record_found() {
548 let mut entry = deployer_entry();
549 entry["meta"][0]["id"] = json!("0xZZ");
550 assert!(matches!(
551 run_deployer_query(entry).await,
552 Err(Error::NoRecordFound)
553 ));
554 }
555
556 #[tokio::test]
557 async fn test_process_deployer_query_invalid_tx_id_hex_is_no_record_found() {
558 let mut entry = deployer_entry();
559 entry["deployTransaction"]["id"] = json!("0xZZ");
560 assert!(matches!(
561 run_deployer_query(entry).await,
562 Err(Error::NoRecordFound)
563 ));
564 }
565
566 #[tokio::test]
567 async fn test_process_deployer_query_invalid_constructor_meta_hash_hex_is_no_record_found() {
568 let mut entry = deployer_entry();
569 entry["constructorMetaHash"] = json!("0xZZ");
570 assert!(matches!(
571 run_deployer_query(entry).await,
572 Err(Error::NoRecordFound)
573 ));
574 }
575
576 #[tokio::test]
577 async fn test_process_deployer_query_invalid_constructor_meta_hex_is_no_record_found() {
578 let mut entry = deployer_entry();
579 entry["constructorMeta"] = json!("0xZZ");
580 assert!(matches!(
581 run_deployer_query(entry).await,
582 Err(Error::NoRecordFound)
583 ));
584 }
585
586 fn sample_authoring_meta() -> (AuthoringMeta, Vec<u8>) {
587 let authoring_meta: AuthoringMeta = serde_json::from_str(
588 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
589 )
590 .unwrap();
591 let abi = authoring_meta.abi_encode_validate().unwrap();
592 (authoring_meta, abi)
593 }
594
595 fn authoring_doc(abi_payload: Vec<u8>, encoding: ContentEncoding) -> Vec<u8> {
596 let payload = encoding.encode(&abi_payload);
597 let item = RainMetaDocumentV1Item {
598 payload: ByteBuf::from(payload),
599 magic: KnownMagic::AuthoringMetaV1,
600 content_type: ContentType::Cbor,
601 content_encoding: encoding,
602 content_language: ContentLanguage::None,
603 schema: None,
604 };
605 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
606 .unwrap()
607 }
608
609 fn deployer_response(meta_bytes: Vec<u8>) -> DeployerResponse {
610 DeployerResponse {
611 tx_hash: vec![0x01],
612 bytecode_meta_hash: vec![0x02],
613 meta_hash: vec![0x03],
614 meta_bytes,
615 bytecode: vec![0x04],
616 parser: vec![0x05],
617 store: vec![0x06],
618 interpreter: vec![0x07],
619 }
620 }
621
622 #[test]
625 fn test_get_authoring_meta_found() {
626 let (authoring_meta, abi) = sample_authoring_meta();
627 let response = deployer_response(authoring_doc(abi, ContentEncoding::None));
628 assert_eq!(response.get_authoring_meta(), Some(authoring_meta));
629 }
630
631 #[test]
634 fn test_get_authoring_meta_deflate_unpack() {
635 let (authoring_meta, abi) = sample_authoring_meta();
636 let response = deployer_response(authoring_doc(abi, ContentEncoding::Deflate));
637 assert_eq!(response.get_authoring_meta(), Some(authoring_meta));
638 }
639
640 #[test]
643 fn test_get_authoring_meta_invalid_returns_none() {
644 let invalid = AuthoringMeta(vec![AuthoringMetaItem {
645 word: "NOTKEBAB".to_string(),
646 operand_parser_offset: 0,
647 description: "some description".to_string(),
648 }]);
649 let abi = invalid.abi_encode().unwrap();
650 let response = deployer_response(authoring_doc(abi, ContentEncoding::None));
651 assert_eq!(response.get_authoring_meta(), None);
652
653 let undecodable = deployer_response(authoring_doc(
654 vec![0xde, 0xad, 0xbe, 0xef],
655 ContentEncoding::None,
656 ));
657 assert_eq!(undecodable.get_authoring_meta(), None);
658 }
659
660 #[test]
663 fn test_get_authoring_meta_absent_or_undecodable() {
664 let item = RainMetaDocumentV1Item {
665 payload: ByteBuf::from("some dotrain".as_bytes()),
666 magic: KnownMagic::DotrainV1,
667 content_type: ContentType::OctetStream,
668 content_encoding: ContentEncoding::None,
669 content_language: ContentLanguage::None,
670 schema: None,
671 };
672 let doc =
673 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
674 .unwrap();
675 assert_eq!(deployer_response(doc).get_authoring_meta(), None);
676
677 assert_eq!(
678 deployer_response(vec![0xff, 0x00, 0x12]).get_authoring_meta(),
679 None
680 );
681 }
682
683 #[tokio::test]
686 async fn test_process_meta_query_paths() {
687 use httpmock::prelude::*;
688 let client = Arc::new(Client::builder().build().unwrap());
689 let body = MetaQuery::build_query(meta_query::Variables {
690 hash: Some("0xabc".to_string()),
691 });
692 let server = MockServer::start();
693
694 let no_data = server.mock(|when, then| {
695 when.method(POST).path("/nodata");
696 then.status(200)
697 .json_body(serde_json::json!({"errors": [{"message": "nope"}]}));
698 });
699 let result = process_meta_query(client.clone(), &body, &server.url("/nodata")).await;
700 assert!(matches!(result, Err(Error::NoRecordFound)));
701 no_data.assert();
702
703 let _no_meta = server.mock(|when, then| {
704 when.method(POST).path("/nometa");
705 then.status(200)
706 .json_body(serde_json::json!({"data": {"meta": null}}));
707 });
708 let result = process_meta_query(client.clone(), &body, &server.url("/nometa")).await;
709 assert!(matches!(result, Err(Error::NoRecordFound)));
710
711 let _bad_hex = server.mock(|when, then| {
712 when.method(POST).path("/badhex");
713 then.status(200).json_body(serde_json::json!({
714 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": "0xzz"}}
715 }));
716 });
717 let result = process_meta_query(client.clone(), &body, &server.url("/badhex")).await;
718 assert!(matches!(result, Err(Error::NoRecordFound)));
719
720 let _found = server.mock(|when, then| {
721 when.method(POST).path("/found");
722 then.status(200).json_body(serde_json::json!({
723 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": "0x0102"}}
724 }));
725 });
726 let result = process_meta_query(client.clone(), &body, &server.url("/found"))
727 .await
728 .unwrap();
729 assert_eq!(result.bytes, vec![0x01, 0x02]);
730 }
731
732 #[tokio::test]
735 async fn test_process_deployer_query_empty_no_record() {
736 use httpmock::prelude::*;
737 let client = Arc::new(Client::builder().build().unwrap());
738 let body = DeployerQuery::build_query(deployer_query::Variables {
739 hash: Some("0xabc".to_string()),
740 });
741 let server = MockServer::start();
742 let _empty = server.mock(|when, then| {
743 when.method(POST);
744 then.status(200)
745 .json_body(serde_json::json!({"data": {"expressionDeployers": []}}));
746 });
747 let result = process_deployer_query(client, &body, &server.url("/")).await;
748 assert!(matches!(result, Err(Error::NoRecordFound)));
749 }
750}