Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions crates/cli/src/meta/types/dotrain/order_builder_state_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,37 @@ mod tests {
_ => panic!("Expected SerdeCborError"),
}
}

#[test]
fn test_extract_from_meta_nested_rain_document() {
// A decoded item whose magic is RainMetaDocumentV1 carries a complete
// prefixed document as payload; extract_from_meta must recurse into
// it and surface the instance found inside.
let original_instance = create_test_instance();
let inner_item: RainMetaDocumentV1Item = original_instance.clone().try_into().unwrap();
let inner_doc_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
&vec![inner_item],
KnownMagic::RainMetaDocumentV1,
)
.unwrap();

let outer_item = RainMetaDocumentV1Item {
payload: serde_bytes::ByteBuf::from(inner_doc_bytes),
magic: KnownMagic::RainMetaDocumentV1,
content_type: ContentType::OctetStream,
content_encoding: ContentEncoding::None,
content_language: ContentLanguage::None,
schema: None,
};
let outer_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
&vec![outer_item],
KnownMagic::RainMetaDocumentV1,
)
.unwrap();

let extracted = OrderBuilderStateV1::extract_from_meta(&outer_bytes)
.unwrap()
.unwrap();
assert_eq!(extracted, original_instance);
}
}
91 changes: 91 additions & 0 deletions crates/cli/src/meta/types/dotrain/source_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,95 @@ mod tests {
let _ = DotrainSourceV1::fetch_by_subject(subject, mock_url).await;
mock.assert();
}

#[test]
fn test_hash_known_keccak256_vectors() {
// Vectors derived from the Keccak-256 reference values (independent
// of this implementation): keccak256("") and keccak256("hello world").
// Pins hash() to keccak256 over the exact utf8 bytes of the source.
assert_eq!(
DotrainSourceV1(String::new()).hash(),
"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
.parse::<B256>()
.unwrap()
);
assert_eq!(
DotrainSourceV1("hello world".to_string()).hash(),
"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"
.parse::<B256>()
.unwrap()
);
}

#[tokio::test]
async fn test_fetch_by_subject_takes_first_decoded_item() {
use httpmock::prelude::*;
let server = MockServer::start();
let mock_url = Url::parse(&server.url("/")).unwrap();
let subject = [0x42; 32];

// One meta blob that cbor-decodes to TWO dotrain items: the first
// one must win.
let first: RainMetaDocumentV1Item = DotrainSourceV1("first".to_string()).into();
let second: RainMetaDocumentV1Item = DotrainSourceV1("second".to_string()).into();
let cbor_bytes = RainMetaDocumentV1Item::cbor_encode_seq(
&vec![first, second],
KnownMagic::RainMetaDocumentV1,
)
.unwrap();
let cbor_hex = hex::encode(&cbor_bytes);

let mock = server.mock(|when, then| {
when.method(POST).path("/").body_contains("subject");
then.status(200)
.header("content-type", "application/json")
.json_body(serde_json::json!({
"data": {
"metaV1S": [
{
"meta": format!("0x{}", cbor_hex),
"metaHash": "0x1234567890abcdef",
"sender": "0x1234567890123456789012345678901234567890",
"id": "0x123",
"metaBoard": {
"address": "0x1234567890123456789012345678901234567890"
},
"subject": hex::encode(subject)
}
]
}
}));
});

let result = DotrainSourceV1::fetch_by_subject(subject, mock_url)
.await
.unwrap()
.unwrap();
assert_eq!(result.0, "first");
mock.assert();
}

#[tokio::test]
async fn test_fetch_by_subject_propagates_non_empty_client_errors() {
use httpmock::prelude::*;
let server = MockServer::start();
let mock_url = Url::parse(&server.url("/")).unwrap();

// An HTTP-level failure is not "no meta found": it must surface as
// Err(MetaboardSubgraphClientError), never Ok(None).
let mock = server.mock(|when, then| {
when.method(POST).path("/");
then.status(500);
});

let result = DotrainSourceV1::fetch_by_subject([0x42; 32], mock_url).await;
match result {
Err(Error::MetaboardSubgraphClientError(_)) => {}
other => panic!(
"Expected Err(MetaboardSubgraphClientError), got {:?}",
other
),
}
mock.assert();
}
}
114 changes: 114 additions & 0 deletions crates/cli/src/meta/types/interpreter_caller/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,117 @@ pub struct ContextCell {
#[validate]
pub alias: Option<RainSymbol>,
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

fn base_json() -> serde_json::Value {
json!({
"name": "Test Caller",
"abiName": "TestCaller",
"methods": [{
"name": "Add Order",
"abiName": "addOrder",
"inputs": [{
"name": "Config",
"abiName": "config",
"path": "[7].inputs[0]"
}],
"expressions": [{
"name": "Calculate",
"abiName": "calculateIO",
"path": "[7].expressions[0]",
"contextColumns": [{
"name": "Base",
"cells": [{ "name": "Cell" }]
}]
}]
}]
})
}

fn try_parse(value: &serde_json::Value) -> Result<InterpreterCallerMeta, Error> {
InterpreterCallerMeta::try_from(serde_json::to_vec(value).unwrap())
}

#[test]
fn test_base_fixture_is_valid() {
let meta = try_parse(&base_json()).unwrap();
assert_eq!(meta.name.value, "Test Caller");
assert_eq!(meta.abi_name.value, "TestCaller");
assert_eq!(meta.methods.len(), 1);
assert_eq!(meta.methods[0].inputs.len(), 1);
assert_eq!(meta.methods[0].expressions[0].context_columns.len(), 1);
}

#[test]
fn test_deny_unknown_fields_top_level() {
let mut v = base_json();
v["unknownField"] = json!(1);
assert!(matches!(try_parse(&v), Err(Error::SerdeJsonError(_))));
}

#[test]
fn test_deny_unknown_fields_method() {
let mut v = base_json();
v["methods"][0]["unknownField"] = json!(1);
assert!(matches!(try_parse(&v), Err(Error::SerdeJsonError(_))));
}

#[test]
fn test_deny_unknown_fields_method_input() {
let mut v = base_json();
v["methods"][0]["inputs"][0]["unknownField"] = json!(1);
assert!(matches!(try_parse(&v), Err(Error::SerdeJsonError(_))));
}

#[test]
fn test_deny_unknown_fields_expression() {
let mut v = base_json();
v["methods"][0]["expressions"][0]["unknownField"] = json!(1);
assert!(matches!(try_parse(&v), Err(Error::SerdeJsonError(_))));
}

#[test]
fn test_deny_unknown_fields_context_column() {
let mut v = base_json();
v["methods"][0]["expressions"][0]["contextColumns"][0]["unknownField"] = json!(1);
assert!(matches!(try_parse(&v), Err(Error::SerdeJsonError(_))));
}

#[test]
fn test_deny_unknown_fields_context_cell() {
let mut v = base_json();
v["methods"][0]["expressions"][0]["contextColumns"][0]["cells"][0]["unknownField"] =
json!(1);
assert!(matches!(try_parse(&v), Err(Error::SerdeJsonError(_))));
}

#[test]
fn test_methods_min_length_one() {
let mut v = base_json();
v["methods"] = json!([]);
assert!(matches!(try_parse(&v), Err(Error::ValidationErrors(_))));
}

#[test]
fn test_method_inputs_min_length_one() {
let mut v = base_json();
v["methods"][0]["inputs"] = json!([]);
assert!(matches!(try_parse(&v), Err(Error::ValidationErrors(_))));
}

#[test]
fn test_context_columns_max_255() {
let column = json!({ "name": "Base" });
let mut v = base_json();
v["methods"][0]["expressions"][0]["contextColumns"] =
serde_json::Value::Array(vec![column.clone(); 255]);
assert!(try_parse(&v).is_ok());
v["methods"][0]["expressions"][0]["contextColumns"] =
serde_json::Value::Array(vec![column; 256]);
assert!(matches!(try_parse(&v), Err(Error::ValidationErrors(_))));
}
}
Loading
Loading