Skip to content

Commit 80e6067

Browse files
Merge pull request #23 from BitGo/BTC-2652.cli-parse-node-raw
feat(wasm-utxo/cli): implement PSBT parsing and improve test infrastructure
2 parents 5d9d325 + cefbd78 commit 80e6067

26 files changed

+3096
-338
lines changed

packages/wasm-utxo/cli/bip-0174.mediawiki

Lines changed: 1229 additions & 0 deletions
Large diffs are not rendered by default.

packages/wasm-utxo/cli/src/format/fixtures.rs

Lines changed: 48 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,75 @@
1+
#[cfg(test)]
12
use super::tree::{node_to_string_with_scheme, ColorScheme};
3+
#[cfg(test)]
24
use crate::node::Node;
5+
#[cfg(test)]
36
use std::env;
7+
#[cfg(test)]
48
use std::fs;
9+
#[cfg(test)]
510
use std::io::{self, Write};
6-
use std::path::PathBuf;
711

8-
/// Generate a tree representation of a Node without colors
9-
pub fn generate_tree_text(node: &Node) -> Result<String, io::Error> {
10-
// Use the no-color scheme for consistent fixture output
12+
/// Ensure the generated tree output matches the fixture file
13+
/// If the fixture doesn't exist, it will be created
14+
#[cfg(test)]
15+
pub fn assert_tree_matches_fixture(node: &Node, name: &str) -> Result<(), io::Error> {
1116
let no_color_scheme = ColorScheme::no_color();
12-
node_to_string_with_scheme(node, &no_color_scheme)
13-
}
14-
15-
/// Generate a tree representation of a Node with a specific color scheme
16-
pub fn generate_tree_text_with_scheme(
17-
node: &Node,
18-
color_scheme: &ColorScheme,
19-
) -> Result<String, io::Error> {
20-
node_to_string_with_scheme(node, color_scheme)
21-
}
17+
let generated = node_to_string_with_scheme(node, &no_color_scheme)?;
2218

23-
/// Returns the path to the fixture directory
24-
pub fn fixtures_directory() -> PathBuf {
2519
let project_dir = env::current_dir().expect("Failed to get current directory");
26-
project_dir.join("tests").join("fixtures")
27-
}
28-
29-
/// Write tree output to a fixture file
30-
pub fn write_fixture(name: &str, content: &str) -> Result<(), io::Error> {
31-
let fixtures_dir = fixtures_directory();
32-
fs::create_dir_all(&fixtures_dir)?;
33-
20+
let fixtures_dir = project_dir.join("test").join("fixtures");
3421
let fixture_path = fixtures_dir.join(format!("{}.txt", name));
3522

36-
// Write the content to the file
37-
let mut file = fs::File::create(&fixture_path)?;
38-
file.write_all(content.as_bytes())?;
23+
if fixture_path.exists() {
24+
let fixture_content = fs::read_to_string(&fixture_path)?;
25+
// Compare the generated output to the fixture
26+
assert_eq!(
27+
generated, fixture_content,
28+
"Generated tree output doesn't match fixture file: {}",
29+
name
30+
);
31+
} else {
32+
// Create the fixture if it doesn't exist
33+
fs::create_dir_all(&fixtures_dir)?;
34+
let mut file = fs::File::create(&fixture_path)?;
35+
file.write_all(generated.as_bytes())?;
36+
println!("Created new fixture: {}.txt", name);
37+
}
3938

4039
Ok(())
4140
}
4241

43-
/// Read the content of a fixture file if it exists
44-
pub fn read_fixture(name: &str) -> Result<Option<String>, io::Error> {
45-
let fixture_path = fixtures_directory().join(format!("{}.txt", name));
42+
/// Assert tree matches fixture, updating if needed or requested
43+
#[cfg(test)]
44+
pub fn assert_or_update_fixture(node: &Node, name: &str) -> Result<(), io::Error> {
45+
let no_color_scheme = ColorScheme::no_color();
46+
let generated = node_to_string_with_scheme(node, &no_color_scheme)?;
4647

47-
if fixture_path.exists() {
48-
let content = fs::read_to_string(&fixture_path)?;
49-
Ok(Some(content))
50-
} else {
51-
Ok(None)
52-
}
53-
}
48+
let project_dir = env::current_dir().expect("Failed to get current directory");
49+
let fixtures_dir = project_dir.join("test").join("fixtures");
50+
let fixture_path = fixtures_dir.join(format!("{}.txt", name));
5451

55-
/// Ensure the generated tree output matches the fixture file
56-
/// If the fixture doesn't exist, it will be created
57-
pub fn assert_tree_matches_fixture(node: &Node, name: &str) -> Result<(), io::Error> {
58-
let generated = generate_tree_text(node)?;
52+
let update_fixtures = env::var("UPDATE_FIXTURES").is_ok();
5953

60-
match read_fixture(name)? {
61-
Some(fixture_content) => {
62-
// Compare the generated output to the fixture
54+
if fixture_path.exists() {
55+
let fixture_content = fs::read_to_string(&fixture_path)?;
56+
if update_fixtures || generated != fixture_content {
57+
let mut file = fs::File::create(&fixture_path)?;
58+
file.write_all(generated.as_bytes())?;
59+
println!("Updated fixture: {}.txt", name);
60+
} else {
6361
assert_eq!(
6462
generated, fixture_content,
6563
"Generated tree output doesn't match fixture file: {}",
6664
name
6765
);
6866
}
69-
None => {
70-
// Create the fixture if it doesn't exist
71-
write_fixture(name, &generated)?;
72-
println!("Created new fixture: {}.txt", name);
73-
}
74-
}
75-
76-
Ok(())
77-
}
78-
79-
/// Force update of a fixture file with new content
80-
pub fn update_fixture(node: &Node, name: &str) -> Result<(), io::Error> {
81-
let generated = generate_tree_text(node)?;
82-
write_fixture(name, &generated)
83-
}
84-
85-
// Environment variable to force fixture updates
86-
const UPDATE_FIXTURES_ENV: &str = "UPDATE_FIXTURES";
87-
88-
/// Check if fixtures should be updated
89-
pub fn should_update_fixtures() -> bool {
90-
env::var(UPDATE_FIXTURES_ENV).is_ok()
91-
}
92-
93-
/// Assert tree matches fixture, updating if needed or requested
94-
pub fn assert_or_update_fixture(node: &Node, name: &str) -> Result<(), io::Error> {
95-
let generated = generate_tree_text(node)?;
96-
97-
match read_fixture(name)? {
98-
Some(fixture_content) => {
99-
if should_update_fixtures() || generated != fixture_content {
100-
write_fixture(name, &generated)?;
101-
println!("Updated fixture: {}.txt", name);
102-
} else {
103-
assert_eq!(
104-
generated, fixture_content,
105-
"Generated tree output doesn't match fixture file: {}",
106-
name
107-
);
108-
}
109-
}
110-
None => {
111-
// Create the fixture if it doesn't exist
112-
write_fixture(name, &generated)?;
113-
println!("Created new fixture: {}.txt", name);
114-
}
67+
} else {
68+
// Create the fixture if it doesn't exist
69+
fs::create_dir_all(&fixtures_dir)?;
70+
let mut file = fs::File::create(&fixture_path)?;
71+
file.write_all(generated.as_bytes())?;
72+
println!("Created new fixture: {}.txt", name);
11573
}
11674

11775
Ok(())

packages/wasm-utxo/cli/src/format/mod.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,4 @@ pub mod fixtures;
33
mod tests;
44
mod tree;
55

6-
pub use tree::{
7-
add_node_to_tree, add_node_to_tree_with_scheme, format_primitive_for_tree, node_to_string,
8-
node_to_string_with_scheme, render_tree, render_tree_with_scheme, ColorScheme,
9-
};
6+
pub use tree::{render_tree_with_scheme, ColorScheme};
Lines changed: 109 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,110 @@
1-
#[cfg(test)]
2-
mod tests {
3-
use crate::format::fixtures::assert_or_update_fixture;
4-
use crate::node::{Node, Primitive};
5-
use num_bigint::BigInt;
6-
7-
#[test]
8-
fn test_simple_tree() -> std::io::Result<()> {
9-
// Create a simple tree
10-
let child1 = Node::new("name", Primitive::String("Alice".to_string()));
11-
let child2 = Node::new("age", Primitive::U8(30));
12-
let child3 = Node::new("active", Primitive::Boolean(true));
13-
14-
let mut parent = Node::new("person", Primitive::None);
15-
parent.add_child(child1);
16-
parent.add_child(child2);
17-
parent.add_child(child3);
18-
19-
// Check against fixture
20-
assert_or_update_fixture(&parent, "simple_tree")?;
21-
Ok(())
22-
}
23-
24-
#[test]
25-
fn test_complex_tree() -> std::io::Result<()> {
26-
// Create a more complex tree
27-
let address_street = Node::new("street", Primitive::String("123 Main St".to_string()));
28-
let address_city = Node::new("city", Primitive::String("Anytown".to_string()));
29-
let address_zip = Node::new("zip", Primitive::U16(12345));
30-
31-
let mut address = Node::new("address", Primitive::None);
32-
address.add_child(address_street);
33-
address.add_child(address_city);
34-
address.add_child(address_zip);
35-
36-
let phone1 = Node::new("home", Primitive::String("555-1234".to_string()));
37-
let phone2 = Node::new("work", Primitive::String("555-5678".to_string()));
38-
39-
let mut phones = Node::new("phones", Primitive::None);
40-
phones.add_child(phone1);
41-
phones.add_child(phone2);
42-
43-
let account_number = Node::new(
44-
"number",
45-
Primitive::Integer(BigInt::parse_bytes(b"9876543210123456", 10).unwrap()),
46-
);
47-
let account_balance = Node::new("balance", Primitive::I32(5000));
48-
49-
let mut account = Node::new("account", Primitive::None);
50-
account.add_child(account_number);
51-
account.add_child(account_balance);
52-
53-
let name = Node::new("name", Primitive::String("John Doe".to_string()));
54-
let age = Node::new("age", Primitive::U8(35));
55-
56-
let mut person = Node::new("person", Primitive::None);
57-
person.add_child(name);
58-
person.add_child(age);
59-
person.add_child(address);
60-
person.add_child(phones);
61-
person.add_child(account);
62-
63-
// Check against fixture
64-
assert_or_update_fixture(&person, "complex_tree")?;
65-
Ok(())
66-
}
67-
68-
#[test]
69-
fn test_buffer_display() -> std::io::Result<()> {
70-
// Test how binary data is formatted in the tree
71-
let small_buffer = Node::new("small", Primitive::Buffer(vec![1, 2, 3, 4]));
72-
assert_or_update_fixture(&small_buffer, "small_buffer")?;
73-
74-
let large_buffer = Node::new("large", Primitive::Buffer((0..100).collect()));
75-
assert_or_update_fixture(&large_buffer, "large_buffer")?;
76-
77-
Ok(())
78-
}
79-
80-
#[test]
81-
fn test_numeric_types() -> std::io::Result<()> {
82-
// Create a tree with all the numeric types
83-
let mut numbers = Node::new("numbers", Primitive::None);
84-
85-
// Add signed integers
86-
numbers.add_child(Node::new("i8_min", Primitive::I8(i8::MIN)));
87-
numbers.add_child(Node::new("i8_max", Primitive::I8(i8::MAX)));
88-
numbers.add_child(Node::new("i16_min", Primitive::I16(i16::MIN)));
89-
numbers.add_child(Node::new("i16_max", Primitive::I16(i16::MAX)));
90-
numbers.add_child(Node::new("i32_min", Primitive::I32(i32::MIN)));
91-
numbers.add_child(Node::new("i32_max", Primitive::I32(i32::MAX)));
92-
numbers.add_child(Node::new("i64_min", Primitive::I64(i64::MIN)));
93-
numbers.add_child(Node::new("i64_max", Primitive::I64(i64::MAX)));
94-
95-
// Add unsigned integers
96-
numbers.add_child(Node::new("u8_max", Primitive::U8(u8::MAX)));
97-
numbers.add_child(Node::new("u16_max", Primitive::U16(u16::MAX)));
98-
numbers.add_child(Node::new("u32_max", Primitive::U32(u32::MAX)));
99-
numbers.add_child(Node::new("u64_max", Primitive::U64(u64::MAX)));
100-
101-
// Add a big integer
102-
numbers.add_child(Node::new(
103-
"bigint",
104-
Primitive::Integer(
105-
BigInt::parse_bytes(b"12345678901234567890123456789012345678901234567890", 10)
106-
.unwrap(),
107-
),
108-
));
109-
110-
// Check against fixture
111-
assert_or_update_fixture(&numbers, "numeric_types")?;
112-
Ok(())
113-
}
1+
use crate::format::fixtures::assert_or_update_fixture;
2+
use crate::node::{Node, Primitive};
3+
use num_bigint::BigInt;
4+
5+
#[test]
6+
fn test_simple_tree() -> std::io::Result<()> {
7+
// Create a simple tree
8+
let child1 = Node::new("name", Primitive::String("Alice".to_string()));
9+
let child2 = Node::new("age", Primitive::U8(30));
10+
let child3 = Node::new("active", Primitive::Boolean(true));
11+
12+
let mut parent = Node::new("person", Primitive::None);
13+
parent.add_child(child1);
14+
parent.add_child(child2);
15+
parent.add_child(child3);
16+
17+
// Check against fixture
18+
assert_or_update_fixture(&parent, "simple_tree")?;
19+
Ok(())
20+
}
21+
22+
#[test]
23+
fn test_complex_tree() -> std::io::Result<()> {
24+
// Create a more complex tree
25+
let address_street = Node::new("street", Primitive::String("123 Main St".to_string()));
26+
let address_city = Node::new("city", Primitive::String("Anytown".to_string()));
27+
let address_zip = Node::new("zip", Primitive::U16(12345));
28+
29+
let mut address = Node::new("address", Primitive::None);
30+
address.add_child(address_street);
31+
address.add_child(address_city);
32+
address.add_child(address_zip);
33+
34+
let phone1 = Node::new("home", Primitive::String("555-1234".to_string()));
35+
let phone2 = Node::new("work", Primitive::String("555-5678".to_string()));
36+
37+
let mut phones = Node::new("phones", Primitive::None);
38+
phones.add_child(phone1);
39+
phones.add_child(phone2);
40+
41+
let account_number = Node::new(
42+
"number",
43+
Primitive::Integer(BigInt::parse_bytes(b"9876543210123456", 10).unwrap()),
44+
);
45+
let account_balance = Node::new("balance", Primitive::I32(5000));
46+
47+
let mut account = Node::new("account", Primitive::None);
48+
account.add_child(account_number);
49+
account.add_child(account_balance);
50+
51+
let name = Node::new("name", Primitive::String("John Doe".to_string()));
52+
let age = Node::new("age", Primitive::U8(35));
53+
54+
let mut person = Node::new("person", Primitive::None);
55+
person.add_child(name);
56+
person.add_child(age);
57+
person.add_child(address);
58+
person.add_child(phones);
59+
person.add_child(account);
60+
61+
// Check against fixture
62+
assert_or_update_fixture(&person, "complex_tree")?;
63+
Ok(())
64+
}
65+
66+
#[test]
67+
fn test_buffer_display() -> std::io::Result<()> {
68+
// Test how binary data is formatted in the tree
69+
let small_buffer = Node::new("small", Primitive::Buffer(vec![1, 2, 3, 4]));
70+
assert_or_update_fixture(&small_buffer, "small_buffer")?;
71+
72+
let large_buffer = Node::new("large", Primitive::Buffer((0..100).collect()));
73+
assert_or_update_fixture(&large_buffer, "large_buffer")?;
74+
75+
Ok(())
76+
}
77+
78+
#[test]
79+
fn test_numeric_types() -> std::io::Result<()> {
80+
// Create a tree with all the numeric types
81+
let mut numbers = Node::new("numbers", Primitive::None);
82+
83+
// Add signed integers
84+
numbers.add_child(Node::new("i8_min", Primitive::I8(i8::MIN)));
85+
numbers.add_child(Node::new("i8_max", Primitive::I8(i8::MAX)));
86+
numbers.add_child(Node::new("i16_min", Primitive::I16(i16::MIN)));
87+
numbers.add_child(Node::new("i16_max", Primitive::I16(i16::MAX)));
88+
numbers.add_child(Node::new("i32_min", Primitive::I32(i32::MIN)));
89+
numbers.add_child(Node::new("i32_max", Primitive::I32(i32::MAX)));
90+
numbers.add_child(Node::new("i64_min", Primitive::I64(i64::MIN)));
91+
numbers.add_child(Node::new("i64_max", Primitive::I64(i64::MAX)));
92+
93+
// Add unsigned integers
94+
numbers.add_child(Node::new("u8_max", Primitive::U8(u8::MAX)));
95+
numbers.add_child(Node::new("u16_max", Primitive::U16(u16::MAX)));
96+
numbers.add_child(Node::new("u32_max", Primitive::U32(u32::MAX)));
97+
numbers.add_child(Node::new("u64_max", Primitive::U64(u64::MAX)));
98+
99+
// Add a big integer
100+
numbers.add_child(Node::new(
101+
"bigint",
102+
Primitive::Integer(
103+
BigInt::parse_bytes(b"12345678901234567890123456789012345678901234567890", 10).unwrap(),
104+
),
105+
));
106+
107+
// Check against fixture
108+
assert_or_update_fixture(&numbers, "numeric_types")?;
109+
Ok(())
114110
}

0 commit comments

Comments
 (0)