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
78fn response_data<T>(response: Response<T>) -> Result<T, Error> {
81 if let Some(errors) = response.errors.filter(|errors| !errors.is_empty()) {
82 return Err(Error::SubgraphError(
83 errors
84 .iter()
85 .map(ToString::to_string)
86 .collect::<Vec<String>>()
87 .join("; "),
88 ));
89 }
90 response
91 .data
92 .ok_or_else(|| Error::SubgraphError("response carried neither data nor errors".to_string()))
93}
94
95fn decode_field(field: &str, value: Option<&str>) -> Result<Vec<u8>, Error> {
98 match value {
99 Some(value) => decode(value).map_err(|e| Error::CorruptRecord(format!("{}: {}", field, e))),
100 None => Err(Error::CorruptRecord(format!("{} is missing", field))),
101 }
102}
103
104pub(super) async fn process_meta_query(
109 client: Arc<Client>,
110 request_body: &QueryBody<meta_query::Variables>,
111 url: &str,
112) -> Result<MetaResponse, Error> {
113 let raw_bytes = response_data(
114 client
115 .post(url)
116 .json(request_body)
117 .send()
118 .await
119 .map_err(Error::ReqwestError)?
120 .json::<Response<meta_query::ResponseData>>()
121 .await
122 .map_err(Error::ReqwestError)?,
123 )?
124 .meta
125 .ok_or(Error::NoRecordFound)?
126 .raw_bytes;
127
128 Ok(MetaResponse {
129 bytes: decode_field("rawBytes", Some(raw_bytes.as_str()))?,
130 })
131}
132
133pub(super) async fn process_deployer_query(
138 client: Arc<Client>,
139 request_body: &QueryBody<deployer_query::Variables>,
140 url: &str,
141) -> Result<DeployerResponse, Error> {
142 let res = response_data(
143 client
144 .post(url)
145 .json(request_body)
146 .send()
147 .await
148 .map_err(Error::ReqwestError)?
149 .json::<Response<deployer_query::ResponseData>>()
150 .await
151 .map_err(Error::ReqwestError)?,
152 )?
153 .expression_deployers;
154
155 let deployer = res.first().ok_or(Error::NoRecordFound)?;
156
157 let bytecode_meta_hash = match deployer.meta.as_slice() {
158 [meta] => decode_field("meta[0].id", Some(meta.id.as_str()))?,
159 metas => {
160 return Err(Error::CorruptRecord(format!(
161 "expected exactly one meta, got {}",
162 metas.len()
163 )))
164 }
165 };
166
167 Ok(DeployerResponse {
168 meta_hash: decode_field(
169 "constructorMetaHash",
170 Some(deployer.constructor_meta_hash.as_str()),
171 )?,
172 meta_bytes: decode_field("constructorMeta", Some(deployer.constructor_meta.as_str()))?,
173 bytecode: decode_field("bytecode", deployer.bytecode.as_deref())?,
174 parser: decode_field(
175 "parser",
176 deployer
177 .parser
178 .as_ref()
179 .map(|v| v.parser.deployed_bytecode.as_str()),
180 )?,
181 store: decode_field(
182 "store",
183 deployer
184 .store
185 .as_ref()
186 .map(|v| v.store.deployed_bytecode.as_str()),
187 )?,
188 interpreter: decode_field(
189 "interpreter",
190 deployer
191 .interpreter
192 .as_ref()
193 .map(|v| v.interpreter.deployed_bytecode.as_str()),
194 )?,
195 bytecode_meta_hash,
196 tx_hash: decode_field(
197 "deployTransaction",
198 deployer.deploy_transaction.as_ref().map(|v| v.id.as_str()),
199 )?,
200 })
201}
202
203#[cfg(all(test, not(target_family = "wasm")))]
204mod tests {
205 use super::*;
206 use crate::meta::types::authoring::v1::AuthoringMetaItem;
207 use crate::meta::{ContentEncoding, ContentLanguage, ContentType};
208 use httpmock::Method::POST;
209 use httpmock::MockServer;
210 use serde_bytes::ByteBuf;
211 use serde_json::json;
212
213 fn request_body(hash: &str) -> QueryBody<meta_query::Variables> {
214 MetaQuery::build_query(meta_query::Variables {
215 hash: Some(hash.to_string()),
216 })
217 }
218
219 const HASH: &str = "0x1111111111111111111111111111111111111111111111111111111111111111";
220
221 #[tokio::test]
224 async fn test_process_meta_query_success_exact_bytes() {
225 let server = MockServer::start_async().await;
226 server.mock(|when, then| {
227 when.method(POST)
228 .path("/")
229 .json_body_partial(format!(r#"{{"variables":{{"hash":"{}"}}}}"#, HASH));
230 then.status(200)
231 .header("content-type", "application/json")
232 .body(r#"{"data":{"meta":{"__typename":"RainMetaV1","rawBytes":"0xff0a89c674ee7874deadbeef"}}}"#);
233 });
234 let result = process_meta_query(
235 Arc::new(Client::new()),
236 &request_body(HASH),
237 &server.url("/"),
238 )
239 .await
240 .unwrap();
241 assert_eq!(
242 result.bytes,
243 vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, 0xde, 0xad, 0xbe, 0xef]
244 );
245 }
246
247 #[tokio::test]
250 async fn test_process_meta_query_missing_data_is_subgraph_error() {
251 let server = MockServer::start_async().await;
252 server.mock(|when, then| {
253 when.method(POST).path("/");
254 then.status(200)
255 .header("content-type", "application/json")
256 .body(r#"{"data":null}"#);
257 });
258 let result = process_meta_query(
259 Arc::new(Client::new()),
260 &request_body(HASH),
261 &server.url("/"),
262 )
263 .await;
264 match result {
265 Err(Error::SubgraphError(message)) => {
266 assert_eq!(message, "response carried neither data nor errors")
267 }
268 other => panic!("expected subgraph error, got {other:?}"),
269 }
270 }
271
272 #[tokio::test]
275 async fn test_process_meta_query_graphql_errors_is_subgraph_error() {
276 let server = MockServer::start_async().await;
277 server.mock(|when, then| {
278 when.method(POST).path("/");
279 then.status(200)
280 .header("content-type", "application/json")
281 .body(r#"{"data":null,"errors":[{"message":"first"},{"message":"second"}]}"#);
282 });
283 let result = process_meta_query(
284 Arc::new(Client::new()),
285 &request_body(HASH),
286 &server.url("/"),
287 )
288 .await;
289 match result {
290 Err(Error::SubgraphError(message)) => {
291 assert!(message.contains("first"), "{message}");
292 assert!(message.contains("second"), "{message}");
293 }
294 other => panic!("expected subgraph error, got {other:?}"),
295 }
296 }
297
298 #[tokio::test]
301 async fn test_process_meta_query_empty_errors_array_is_success() {
302 let server = MockServer::start_async().await;
303 server.mock(|when, then| {
304 when.method(POST).path("/");
305 then.status(200)
306 .header("content-type", "application/json")
307 .body(
308 r#"{"data":{"meta":{"__typename":"RainMetaV1","rawBytes":"0x0102"}},"errors":[]}"#,
309 );
310 });
311 let result = process_meta_query(
312 Arc::new(Client::new()),
313 &request_body(HASH),
314 &server.url("/"),
315 )
316 .await
317 .unwrap();
318 assert_eq!(result.bytes, vec![0x01, 0x02]);
319 }
320
321 #[tokio::test]
323 async fn test_process_meta_query_missing_meta_is_no_record_found() {
324 let server = MockServer::start_async().await;
325 server.mock(|when, then| {
326 when.method(POST).path("/");
327 then.status(200)
328 .header("content-type", "application/json")
329 .body(r#"{"data":{"meta":null}}"#);
330 });
331 let result = process_meta_query(
332 Arc::new(Client::new()),
333 &request_body(HASH),
334 &server.url("/"),
335 )
336 .await;
337 assert!(matches!(result, Err(Error::NoRecordFound)), "{result:?}");
338 }
339
340 #[tokio::test]
343 async fn test_process_meta_query_bad_hex_is_corrupt_record() {
344 let server = MockServer::start_async().await;
345 server.mock(|when, then| {
346 when.method(POST).path("/");
347 then.status(200)
348 .header("content-type", "application/json")
349 .body(r#"{"data":{"meta":{"__typename":"RainMetaV1","rawBytes":"zz-not-hex"}}}"#);
350 });
351 let result = process_meta_query(
352 Arc::new(Client::new()),
353 &request_body(HASH),
354 &server.url("/"),
355 )
356 .await;
357 match result {
358 Err(Error::CorruptRecord(message)) => {
359 assert!(message.starts_with("rawBytes: "), "{message}")
360 }
361 other => panic!("expected corrupt record, got {other:?}"),
362 }
363 }
364
365 #[tokio::test]
368 async fn test_process_meta_query_send_error_is_reqwest_error() {
369 let port = {
372 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
373 listener.local_addr().unwrap().port()
374 };
375 let url = format!("http://127.0.0.1:{port}/");
376 let result = process_meta_query(Arc::new(Client::new()), &request_body(HASH), &url).await;
377 assert!(matches!(result, Err(Error::ReqwestError(_))), "{result:?}");
378 }
379
380 #[tokio::test]
383 async fn test_process_meta_query_non_json_is_reqwest_error() {
384 let server = MockServer::start_async().await;
385 server.mock(|when, then| {
386 when.method(POST).path("/");
387 then.status(200)
388 .header("content-type", "text/plain")
389 .body("not json");
390 });
391 let result = process_meta_query(
392 Arc::new(Client::new()),
393 &request_body(HASH),
394 &server.url("/"),
395 )
396 .await;
397 assert!(matches!(result, Err(Error::ReqwestError(_))), "{result:?}");
398 }
399
400 fn authoring_meta() -> AuthoringMeta {
401 AuthoringMeta(vec![AuthoringMetaItem {
402 word: "some-word".to_string(),
403 operand_parser_offset: 0,
404 description: "a description".to_string(),
405 }])
406 }
407
408 fn authoring_item() -> RainMetaDocumentV1Item {
409 RainMetaDocumentV1Item {
410 payload: serde_bytes::ByteBuf::from(authoring_meta().abi_encode_validate().unwrap()),
411 magic: KnownMagic::AuthoringMetaV1,
412 content_type: ContentType::None,
413 content_encoding: ContentEncoding::None,
414 content_language: ContentLanguage::None,
415 schema: None,
416 }
417 }
418
419 fn deployer_response_with(meta_bytes: Vec<u8>) -> DeployerResponse {
420 DeployerResponse {
421 tx_hash: vec![],
422 bytecode_meta_hash: vec![],
423 meta_hash: vec![],
424 meta_bytes,
425 bytecode: vec![],
426 parser: vec![],
427 store: vec![],
428 interpreter: vec![],
429 }
430 }
431
432 #[test]
435 fn test_get_authoring_meta_skips_unpack_failure() {
436 let bad_unpack = RainMetaDocumentV1Item {
438 payload: serde_bytes::ByteBuf::from(vec![0xffu8, 0xff, 0xff, 0xff]),
439 magic: KnownMagic::AuthoringMetaV1,
440 content_type: ContentType::None,
441 content_encoding: ContentEncoding::Deflate,
442 content_language: ContentLanguage::None,
443 schema: None,
444 };
445 assert!(bad_unpack.unpack().is_err());
446 let meta_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
447 &vec![bad_unpack, authoring_item()],
448 KnownMagic::RainMetaDocumentV1,
449 )
450 .unwrap();
451 assert_eq!(
452 deployer_response_with(meta_bytes).get_authoring_meta(),
453 Some(authoring_meta())
454 );
455 }
456
457 #[test]
460 fn test_get_authoring_meta_scans_beyond_first_item() {
461 let other_magic = RainMetaDocumentV1Item {
462 payload: serde_bytes::ByteBuf::from(b"_: int-add(1 2);".to_vec()),
463 magic: KnownMagic::RainlangV1,
464 content_type: ContentType::None,
465 content_encoding: ContentEncoding::None,
466 content_language: ContentLanguage::None,
467 schema: None,
468 };
469 let meta_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
470 &vec![other_magic, authoring_item()],
471 KnownMagic::RainMetaDocumentV1,
472 )
473 .unwrap();
474 assert_eq!(
475 deployer_response_with(meta_bytes).get_authoring_meta(),
476 Some(authoring_meta())
477 );
478 }
479
480 fn deployer_entry() -> serde_json::Value {
483 json!({
484 "constructorMetaHash": "0x0102",
485 "constructorMeta": "0x0304",
486 "deployTransaction": { "id": "0x0506" },
487 "bytecode": "0x0708",
488 "parser": { "parser": { "deployedBytecode": "0x090a" } },
489 "store": { "store": { "deployedBytecode": "0x0b0c" } },
490 "interpreter": { "interpreter": { "deployedBytecode": "0x0d0e" } },
491 "meta": [ { "__typename": "RainMetaV1", "id": "0x0f10" } ]
492 })
493 }
494
495 async fn run_deployer_query(entry: serde_json::Value) -> Result<DeployerResponse, Error> {
496 let server = MockServer::start_async().await;
497 server.mock(|when, then| {
498 when.method(POST).path("/");
499 then.status(200)
500 .json_body(json!({ "data": { "expressionDeployers": [entry] } }));
501 });
502 let request_body = DeployerQuery::build_query(deployer_query::Variables {
503 hash: Some("0xabcd".to_string()),
504 });
505 let client = Arc::new(Client::new());
506 process_deployer_query(client, &request_body, &server.url("/")).await
507 }
508
509 #[tokio::test]
510 async fn test_process_deployer_query_success_decodes_all_fields() {
511 let res = run_deployer_query(deployer_entry()).await.unwrap();
512 assert_eq!(res.meta_hash, vec![0x01, 0x02]);
513 assert_eq!(res.meta_bytes, vec![0x03, 0x04]);
514 assert_eq!(res.tx_hash, vec![0x05, 0x06]);
515 assert_eq!(res.bytecode, vec![0x07, 0x08]);
516 assert_eq!(res.parser, vec![0x09, 0x0a]);
517 assert_eq!(res.store, vec![0x0b, 0x0c]);
518 assert_eq!(res.interpreter, vec![0x0d, 0x0e]);
519 assert_eq!(res.bytecode_meta_hash, vec![0x0f, 0x10]);
520 }
521
522 async fn deployer_corrupt_record(entry: serde_json::Value) -> String {
525 match run_deployer_query(entry).await {
526 Err(Error::CorruptRecord(message)) => message,
527 other => panic!("expected corrupt record, got {other:?}"),
528 }
529 }
530
531 #[tokio::test]
532 async fn test_process_deployer_query_null_bytecode_is_corrupt_record() {
533 let mut entry = deployer_entry();
534 entry["bytecode"] = serde_json::Value::Null;
535 assert_eq!(deployer_corrupt_record(entry).await, "bytecode is missing");
536 }
537
538 #[tokio::test]
539 async fn test_process_deployer_query_null_parser_is_corrupt_record() {
540 let mut entry = deployer_entry();
541 entry["parser"] = serde_json::Value::Null;
542 assert_eq!(deployer_corrupt_record(entry).await, "parser is missing");
543 }
544
545 #[tokio::test]
546 async fn test_process_deployer_query_null_store_is_corrupt_record() {
547 let mut entry = deployer_entry();
548 entry["store"] = serde_json::Value::Null;
549 assert_eq!(deployer_corrupt_record(entry).await, "store is missing");
550 }
551
552 #[tokio::test]
553 async fn test_process_deployer_query_null_interpreter_is_corrupt_record() {
554 let mut entry = deployer_entry();
555 entry["interpreter"] = serde_json::Value::Null;
556 assert_eq!(
557 deployer_corrupt_record(entry).await,
558 "interpreter is missing"
559 );
560 }
561
562 #[tokio::test]
563 async fn test_process_deployer_query_null_deploy_transaction_is_corrupt_record() {
564 let mut entry = deployer_entry();
565 entry["deployTransaction"] = serde_json::Value::Null;
566 assert_eq!(
567 deployer_corrupt_record(entry).await,
568 "deployTransaction is missing"
569 );
570 }
571
572 #[tokio::test]
573 async fn test_process_deployer_query_zero_metas_is_corrupt_record() {
574 let mut entry = deployer_entry();
575 entry["meta"] = json!([]);
576 assert_eq!(
577 deployer_corrupt_record(entry).await,
578 "expected exactly one meta, got 0"
579 );
580 }
581
582 #[tokio::test]
583 async fn test_process_deployer_query_two_metas_is_corrupt_record() {
584 let mut entry = deployer_entry();
585 entry["meta"] = json!([
586 { "__typename": "RainMetaV1", "id": "0x0f10" },
587 { "__typename": "RainMetaV1", "id": "0x1112" }
588 ]);
589 assert_eq!(
590 deployer_corrupt_record(entry).await,
591 "expected exactly one meta, got 2"
592 );
593 }
594
595 #[tokio::test]
596 async fn test_process_deployer_query_invalid_bytecode_hex_is_corrupt_record() {
597 let mut entry = deployer_entry();
598 entry["bytecode"] = json!("0xZZ");
599 let message = deployer_corrupt_record(entry).await;
600 assert!(message.starts_with("bytecode: "), "{message}");
601 }
602
603 #[tokio::test]
604 async fn test_process_deployer_query_invalid_parser_hex_is_corrupt_record() {
605 let mut entry = deployer_entry();
606 entry["parser"]["parser"]["deployedBytecode"] = json!("0xZZ");
607 let message = deployer_corrupt_record(entry).await;
608 assert!(message.starts_with("parser: "), "{message}");
609 }
610
611 #[tokio::test]
612 async fn test_process_deployer_query_invalid_store_hex_is_corrupt_record() {
613 let mut entry = deployer_entry();
614 entry["store"]["store"]["deployedBytecode"] = json!("0xZZ");
615 let message = deployer_corrupt_record(entry).await;
616 assert!(message.starts_with("store: "), "{message}");
617 }
618
619 #[tokio::test]
620 async fn test_process_deployer_query_invalid_interpreter_hex_is_corrupt_record() {
621 let mut entry = deployer_entry();
622 entry["interpreter"]["interpreter"]["deployedBytecode"] = json!("0xZZ");
623 let message = deployer_corrupt_record(entry).await;
624 assert!(message.starts_with("interpreter: "), "{message}");
625 }
626
627 #[tokio::test]
628 async fn test_process_deployer_query_invalid_meta_id_hex_is_corrupt_record() {
629 let mut entry = deployer_entry();
630 entry["meta"][0]["id"] = json!("0xZZ");
631 let message = deployer_corrupt_record(entry).await;
632 assert!(message.starts_with("meta[0].id: "), "{message}");
633 }
634
635 #[tokio::test]
636 async fn test_process_deployer_query_invalid_tx_id_hex_is_corrupt_record() {
637 let mut entry = deployer_entry();
638 entry["deployTransaction"]["id"] = json!("0xZZ");
639 let message = deployer_corrupt_record(entry).await;
640 assert!(message.starts_with("deployTransaction: "), "{message}");
641 }
642
643 #[tokio::test]
644 async fn test_process_deployer_query_invalid_constructor_meta_hash_hex_is_corrupt_record() {
645 let mut entry = deployer_entry();
646 entry["constructorMetaHash"] = json!("0xZZ");
647 let message = deployer_corrupt_record(entry).await;
648 assert!(message.starts_with("constructorMetaHash: "), "{message}");
649 }
650
651 #[tokio::test]
652 async fn test_process_deployer_query_invalid_constructor_meta_hex_is_corrupt_record() {
653 let mut entry = deployer_entry();
654 entry["constructorMeta"] = json!("0xZZ");
655 let message = deployer_corrupt_record(entry).await;
656 assert!(message.starts_with("constructorMeta: "), "{message}");
657 }
658
659 #[tokio::test]
662 async fn test_process_deployer_query_graphql_errors_is_subgraph_error() {
663 let server = MockServer::start_async().await;
664 server.mock(|when, then| {
665 when.method(POST).path("/");
666 then.status(200)
667 .json_body(json!({ "errors": [ { "message": "deployer boom" } ] }));
668 });
669 let request_body = DeployerQuery::build_query(deployer_query::Variables {
670 hash: Some("0xabcd".to_string()),
671 });
672 let result =
673 process_deployer_query(Arc::new(Client::new()), &request_body, &server.url("/")).await;
674 match result {
675 Err(Error::SubgraphError(message)) => {
676 assert!(message.contains("deployer boom"), "{message}")
677 }
678 other => panic!("expected subgraph error, got {other:?}"),
679 }
680 }
681
682 fn sample_authoring_meta() -> (AuthoringMeta, Vec<u8>) {
683 let authoring_meta: AuthoringMeta = serde_json::from_str(
684 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
685 )
686 .unwrap();
687 let abi = authoring_meta.abi_encode_validate().unwrap();
688 (authoring_meta, abi)
689 }
690
691 fn authoring_doc(abi_payload: Vec<u8>, encoding: ContentEncoding) -> Vec<u8> {
692 let payload = encoding.encode(&abi_payload);
693 let item = RainMetaDocumentV1Item {
694 payload: ByteBuf::from(payload),
695 magic: KnownMagic::AuthoringMetaV1,
696 content_type: ContentType::Cbor,
697 content_encoding: encoding,
698 content_language: ContentLanguage::None,
699 schema: None,
700 };
701 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
702 .unwrap()
703 }
704
705 fn deployer_response(meta_bytes: Vec<u8>) -> DeployerResponse {
706 DeployerResponse {
707 tx_hash: vec![0x01],
708 bytecode_meta_hash: vec![0x02],
709 meta_hash: vec![0x03],
710 meta_bytes,
711 bytecode: vec![0x04],
712 parser: vec![0x05],
713 store: vec![0x06],
714 interpreter: vec![0x07],
715 }
716 }
717
718 #[test]
721 fn test_get_authoring_meta_found() {
722 let (authoring_meta, abi) = sample_authoring_meta();
723 let response = deployer_response(authoring_doc(abi, ContentEncoding::None));
724 assert_eq!(response.get_authoring_meta(), Some(authoring_meta));
725 }
726
727 #[test]
730 fn test_get_authoring_meta_deflate_unpack() {
731 let (authoring_meta, abi) = sample_authoring_meta();
732 let response = deployer_response(authoring_doc(abi, ContentEncoding::Deflate));
733 assert_eq!(response.get_authoring_meta(), Some(authoring_meta));
734 }
735
736 #[test]
739 fn test_get_authoring_meta_invalid_returns_none() {
740 let invalid = AuthoringMeta(vec![AuthoringMetaItem {
741 word: "NOTKEBAB".to_string(),
742 operand_parser_offset: 0,
743 description: "some description".to_string(),
744 }]);
745 let abi = invalid.abi_encode().unwrap();
746 let response = deployer_response(authoring_doc(abi, ContentEncoding::None));
747 assert_eq!(response.get_authoring_meta(), None);
748
749 let undecodable = deployer_response(authoring_doc(
750 vec![0xde, 0xad, 0xbe, 0xef],
751 ContentEncoding::None,
752 ));
753 assert_eq!(undecodable.get_authoring_meta(), None);
754 }
755
756 #[test]
759 fn test_get_authoring_meta_absent_or_undecodable() {
760 let item = RainMetaDocumentV1Item {
761 payload: ByteBuf::from("some dotrain".as_bytes()),
762 magic: KnownMagic::DotrainV1,
763 content_type: ContentType::OctetStream,
764 content_encoding: ContentEncoding::None,
765 content_language: ContentLanguage::None,
766 schema: None,
767 };
768 let doc =
769 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
770 .unwrap();
771 assert_eq!(deployer_response(doc).get_authoring_meta(), None);
772
773 assert_eq!(
774 deployer_response(vec![0xff, 0x00, 0x12]).get_authoring_meta(),
775 None
776 );
777 }
778
779 #[tokio::test]
782 async fn test_process_meta_query_paths() {
783 use httpmock::prelude::*;
784 let client = Arc::new(Client::builder().build().unwrap());
785 let body = MetaQuery::build_query(meta_query::Variables {
786 hash: Some("0xabc".to_string()),
787 });
788 let server = MockServer::start();
789
790 let no_data = server.mock(|when, then| {
791 when.method(POST).path("/nodata");
792 then.status(200)
793 .json_body(serde_json::json!({"errors": [{"message": "nope"}]}));
794 });
795 let result = process_meta_query(client.clone(), &body, &server.url("/nodata")).await;
796 assert!(matches!(result, Err(Error::SubgraphError(_))), "{result:?}");
797 no_data.assert();
798
799 let _no_meta = server.mock(|when, then| {
800 when.method(POST).path("/nometa");
801 then.status(200)
802 .json_body(serde_json::json!({"data": {"meta": null}}));
803 });
804 let result = process_meta_query(client.clone(), &body, &server.url("/nometa")).await;
805 assert!(matches!(result, Err(Error::NoRecordFound)), "{result:?}");
806
807 let _bad_hex = server.mock(|when, then| {
808 when.method(POST).path("/badhex");
809 then.status(200).json_body(serde_json::json!({
810 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": "0xzz"}}
811 }));
812 });
813 let result = process_meta_query(client.clone(), &body, &server.url("/badhex")).await;
814 assert!(matches!(result, Err(Error::CorruptRecord(_))), "{result:?}");
815
816 let _found = server.mock(|when, then| {
817 when.method(POST).path("/found");
818 then.status(200).json_body(serde_json::json!({
819 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": "0x0102"}}
820 }));
821 });
822 let result = process_meta_query(client.clone(), &body, &server.url("/found"))
823 .await
824 .unwrap();
825 assert_eq!(result.bytes, vec![0x01, 0x02]);
826 }
827
828 #[tokio::test]
831 async fn test_process_deployer_query_empty_no_record() {
832 use httpmock::prelude::*;
833 let client = Arc::new(Client::builder().build().unwrap());
834 let body = DeployerQuery::build_query(deployer_query::Variables {
835 hash: Some("0xabc".to_string()),
836 });
837 let server = MockServer::start();
838 let _empty = server.mock(|when, then| {
839 when.method(POST);
840 then.status(200)
841 .json_body(serde_json::json!({"data": {"expressionDeployers": []}}));
842 });
843 let result = process_deployer_query(client, &body, &server.url("/")).await;
844 assert!(matches!(result, Err(Error::NoRecordFound)));
845 }
846}