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
174 changes: 174 additions & 0 deletions crates/cli/src/cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,178 @@ mod tests {

Ok(())
}

use clap::Parser;
use std::io::Write;
use super::{Build, build};

/// Conversion normalizes the payload for the item's magic and then
/// applies the content encoding.
#[test]
fn test_item_normalize_then_encode() -> anyhow::Result<()> {
// "[ ]" normalizes to "[]" for a solidity ABI, then deflates.
let build_item = BuildItem {
data: "[ ]".as_bytes().to_vec(),
magic: KnownMagic::SolidityAbiV2,
content_type: ContentType::Json,
content_encoding: ContentEncoding::Deflate,
content_language: ContentLanguage::En,
};
let meta_document = RainMetaDocumentV1Item::try_from(&build_item)?;
assert_eq!(
meta_document.payload.as_ref(),
ContentEncoding::Deflate.encode("[]".as_bytes())
);

// Un-normalizable data is rejected.
let invalid_item = BuildItem {
data: "not json".as_bytes().to_vec(),
..build_item
};
assert!(RainMetaDocumentV1Item::try_from(&invalid_item).is_err());
Ok(())
}

fn parse_build(args: &[&str]) -> Build {
Build::try_parse_from(args).unwrap()
}

/// Each arity guard fires with its own message, before any file IO:
/// the input path never exists and yet the mismatch is what errors.
#[test]
fn test_build_arity_guards() {
let b = parse_build(&[
"build",
"-i",
"does-not-exist.json",
"-m",
"solidity-abi-v2",
"-m",
"solidity-abi-v2",
]);
assert_eq!(
build(b).unwrap_err().to_string(),
"1 inputs does not match 2 magic numbers."
);

let b = parse_build(&[
"build",
"-i",
"does-not-exist.json",
"-m",
"solidity-abi-v2",
"-t",
"json",
"-t",
"json",
]);
assert_eq!(
build(b).unwrap_err().to_string(),
"1 inputs does not match 2 content types."
);

let b = parse_build(&[
"build",
"-i",
"does-not-exist.json",
"-m",
"solidity-abi-v2",
"-t",
"json",
"-e",
"identity",
"-e",
"identity",
]);
assert_eq!(
build(b).unwrap_err().to_string(),
"1 inputs does not match 2 content encodings."
);

let b = parse_build(&[
"build",
"-i",
"does-not-exist.json",
"-m",
"solidity-abi-v2",
"-t",
"json",
"-e",
"identity",
"-l",
"en",
"-l",
"en",
]);
assert_eq!(
build(b).unwrap_err().to_string(),
"1 inputs does not match 2 content languages."
);
}

/// build() reads each input file, builds the document under the
/// global magic and writes it to the output path; the hex output
/// encoding is honored.
#[test]
fn test_build_reads_files_and_encodes_output() -> anyhow::Result<()> {
let mut input = tempfile::NamedTempFile::new()?;
input.write_all("[ ]".as_bytes())?;
let output = tempfile::NamedTempFile::new()?;

let expected = build_bytes(
KnownMagic::RainMetaDocumentV1,
vec![BuildItem {
data: "[ ]".as_bytes().to_vec(),
magic: KnownMagic::SolidityAbiV2,
content_type: ContentType::Json,
content_encoding: ContentEncoding::Identity,
content_language: ContentLanguage::En,
}],
)?;

let input_path = input.path().to_str().unwrap().to_string();
let output_path = output.path().to_str().unwrap().to_string();

let b = parse_build(&[
"build",
"-i",
&input_path,
"-m",
"solidity-abi-v2",
"-t",
"json",
"-e",
"identity",
"-l",
"en",
"-o",
&output_path,
]);
build(b)?;
assert_eq!(std::fs::read(output.path())?, expected);

let b = parse_build(&[
"build",
"-i",
&input_path,
"-m",
"solidity-abi-v2",
"-t",
"json",
"-e",
"identity",
"-l",
"en",
"-o",
&output_path,
"-E",
"hex",
]);
build(b)?;
assert_eq!(
std::fs::read_to_string(output.path())?,
alloy::primitives::hex::encode_prefixed(&expected)
);
Ok(())
}
}
32 changes: 31 additions & 1 deletion crates/cli/src/cli/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,12 @@ mod tests {
#[test]
fn test_read_input_content_nonexistent_file() {
let result = read_input_content(Some(PathBuf::from("/nonexistent/file.rain")));
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("Failed to read file '/nonexistent/file.rain'"),
"unexpected error: {err}"
);
}

#[test]
Expand All @@ -144,6 +149,31 @@ mod tests {
assert!(written_content.contains("0x1234567890abcdef"));
assert!(written_content.contains("0xdeadbeef"));
assert!(written_content.contains("0xcafebabe"));

// The output is pretty-printed with two-space indentation, in
// field declaration order.
let expected = "{\n \"subject\": \"0x1234567890abcdef\",\n \"meta_bytes\": \"0xdeadbeef\",\n \"calldata\": \"0xcafebabe\"\n}";
assert_eq!(written_content, expected);
}

/// write_output creates missing parent directories of the output
/// path before writing.
#[test]
fn test_write_output_creates_parent_dirs() {
let deployment_data = DotrainSourceEmitData {
subject: "0x01".to_string(),
meta_bytes: "0x02".to_string(),
calldata: "0x03".to_string(),
};

let dir = tempfile::tempdir().unwrap();
let nested = dir.path().join("a").join("b").join("out.json");
write_output(&deployment_data, Some(nested.clone())).unwrap();

let written = fs::read_to_string(&nested).unwrap();
assert!(written.contains("0x01"));
assert!(written.contains("0x02"));
assert!(written.contains("0x03"));
}

#[test]
Expand Down
33 changes: 33 additions & 0 deletions crates/cli/src/cli/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,36 @@ pub fn output(
}
Ok(())
}

#[cfg(all(test, not(target_family = "wasm")))]
mod tests {
use super::*;

/// Binary encoding writes the bytes through unchanged.
#[test]
fn test_output_binary_writes_exact_bytes_to_file() {
let file = tempfile::NamedTempFile::new().unwrap();
let path = file.path().to_path_buf();
output(
&Some(path.clone()),
SupportedOutputEncoding::Binary,
&[0x00, 0x01, 0xff],
)
.unwrap();
assert_eq!(std::fs::read(&path).unwrap(), vec![0x00, 0x01, 0xff]);
}

/// Hex encoding writes 0x-prefixed lowercase hex.
#[test]
fn test_output_hex_writes_prefixed_hex_to_file() {
let file = tempfile::NamedTempFile::new().unwrap();
let path = file.path().to_path_buf();
output(
&Some(path.clone()),
SupportedOutputEncoding::Hex,
&[0x00, 0x01, 0xff],
)
.unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "0x0001ff");
}
}
72 changes: 72 additions & 0 deletions crates/cli/src/cli/schema/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,75 @@ pub fn show(s: Show) -> anyhow::Result<()> {
schema_string.as_bytes(),
)
}

#[cfg(all(test, not(target_family = "wasm")))]
mod tests {
use super::*;

fn show_to_string(schema: KnownMeta, pretty_print: bool) -> anyhow::Result<String> {
let file = tempfile::NamedTempFile::new().unwrap();
let path = file.path().to_path_buf();
show(Show {
schema,
output_path: Some(path.clone()),
pretty_print,
})?;
Ok(std::fs::read_to_string(&path).unwrap())
}

/// Each supported meta produces its own schema; compact output by
/// default (no newlines).
#[test]
fn test_show_op_v1_schema_compact() {
let s = show_to_string(KnownMeta::OpV1, false).unwrap();
let v: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(v["title"], "OpMeta.");
assert!(!s.contains('\n'));
}

/// The pretty flag pretty-prints the same schema.
#[test]
fn test_show_pretty_print() {
let s = show_to_string(KnownMeta::OpV1, true).unwrap();
assert!(s.starts_with("{\n"));
let v: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(v["title"], "OpMeta.");
}

/// All four supported arms return the schema of their own meta type.
#[test]
fn test_show_supported_schemas_are_distinct() {
let op = show_to_string(KnownMeta::OpV1, false).unwrap();
assert!(op.contains("OpMeta"));
let authoring = show_to_string(KnownMeta::AuthoringMetaV1, false).unwrap();
assert!(authoring.contains("AuthoringMeta"));
let solidity = show_to_string(KnownMeta::SolidityAbiV2, false).unwrap();
assert!(solidity.contains("SolidityAbi"));
let caller = show_to_string(KnownMeta::InterpreterCallerMetaV1, false).unwrap();
assert!(caller.contains("InterpreterCallerMeta"));
for pair in [
(&op, &authoring),
(&op, &solidity),
(&op, &caller),
(&authoring, &solidity),
(&authoring, &caller),
(&solidity, &caller),
] {
assert_ne!(pair.0, pair.1);
}
}

/// Metas without a JSON schema error with the exact unsupported
/// message.
#[test]
fn test_show_unsupported_meta_error() {
let file = tempfile::NamedTempFile::new().unwrap();
let err = show(Show {
schema: KnownMeta::DotrainV1,
output_path: Some(file.path().to_path_buf()),
pretty_print: false,
})
.unwrap_err();
assert_eq!(err.to_string(), "Unsupported for dotrain-v1 meta");
}
}
31 changes: 31 additions & 0 deletions crates/cli/src/cli/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,34 @@ pub fn validate(v: Validate) -> anyhow::Result<()> {
let _normalized = v.meta.normalize(&data)?;
Ok(())
}

#[cfg(all(test, not(target_family = "wasm")))]
mod tests {
use super::*;
use std::io::Write;

/// A meta that normalizes is valid.
#[test]
fn test_validate_ok_for_valid_meta() {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(b"[]").unwrap();
let v = Validate {
meta: KnownMeta::SolidityAbiV2,
input_path: file.path().to_path_buf(),
};
assert!(validate(v).is_ok());
}

/// A meta that does not normalize is invalid: validity IS
/// normalizability.
#[test]
fn test_validate_err_for_invalid_meta() {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(b"{\"not\": \"an abi\"}").unwrap();
let v = Validate {
meta: KnownMeta::SolidityAbiV2,
input_path: file.path().to_path_buf(),
};
assert!(validate(v).is_err());
}
}
Loading
Loading