use abi_loader::{flatten, flatten_to_yaml, AbiFile};
use std::path::PathBuf;
fn type_library_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../type-library")
}
#[test]
fn flatten_token_program() {
let file = type_library_path().join("tn_token_program.abi.yaml");
let include_dirs = vec![type_library_path()];
let result = flatten(&file, &include_dirs).expect("flatten should succeed");
assert!(
result.abi.imports.is_empty(),
"Flattened ABI should have no imports"
);
let type_names: Vec<&str> = result.types.iter().map(|t| t.name.as_str()).collect();
assert!(
type_names.contains(&"Hash"),
"Should contain Hash from primitives"
);
assert!(
type_names.contains(&"Pubkey"),
"Should contain Pubkey from primitives"
);
assert!(
type_names.contains(&"Signature"),
"Should contain Signature from primitives"
);
assert!(
type_names.contains(&"StateProof"),
"Should contain StateProof"
);
assert!(
type_names.contains(&"TokenInstruction"),
"Should contain TokenInstruction"
);
assert!(
type_names.contains(&"TokenProgramAccount"),
"Should contain TokenProgramAccount"
);
assert_eq!(
result
.abi
.options
.program_metadata
.root_types
.instruction_root,
Some("TokenInstruction".to_string())
);
assert_eq!(
result.abi.options.program_metadata.root_types.account_root,
Some("TokenProgramAccount".to_string())
);
assert_eq!(result.abi.package, "thru.program.token");
}
#[test]
fn flatten_primitives_no_imports() {
let file = type_library_path().join("thru_primitives.abi.yaml");
let include_dirs = vec![type_library_path()];
let result = flatten(&file, &include_dirs).expect("flatten should succeed");
assert!(result.abi.imports.is_empty());
let type_names: Vec<&str> = result.types.iter().map(|t| t.name.as_str()).collect();
assert!(type_names.contains(&"Hash"));
assert!(type_names.contains(&"Pubkey"));
assert!(type_names.contains(&"Signature"));
assert!(type_names.contains(&"Timestamp"));
assert!(type_names.contains(&"Duration"));
assert!(type_names.contains(&"Date"));
assert!(type_names.contains(&"FixedPoint"));
assert!(type_names.contains(&"InstructionData"));
assert_eq!(type_names.len(), 8);
}
#[test]
fn flatten_state_proof_with_single_import() {
let file = type_library_path().join("state_proof.abi.yaml");
let include_dirs = vec![type_library_path()];
let result = flatten(&file, &include_dirs).expect("flatten should succeed");
assert!(result.abi.imports.is_empty());
let type_names: Vec<&str> = result.types.iter().map(|t| t.name.as_str()).collect();
assert!(type_names.contains(&"Hash"));
assert!(type_names.contains(&"Pubkey"));
assert!(type_names.contains(&"StateProof"));
}
#[test]
fn flatten_to_yaml_produces_valid_yaml() {
let file = type_library_path().join("thru_primitives.abi.yaml");
let include_dirs = vec![type_library_path()];
let yaml =
abi_loader::flatten_to_yaml(&file, &include_dirs).expect("flatten_to_yaml should succeed");
let parsed: AbiFile = serde_yml::from_str(&yaml).expect("Should parse as valid ABI YAML");
assert_eq!(parsed.abi.package, "thru.common.primitives");
assert!(parsed.abi.imports.is_empty());
}
#[test]
fn flatten_fails_for_missing_import() {
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join("test_missing_import.abi.yaml");
let content = r#"
abi:
package: test.missing
abi-version: 1
package-version: "1.0.0"
description: "Test file with missing import"
imports:
- type: path
path: "nonexistent_file.abi.yaml"
types: []
"#;
std::fs::write(&temp_file, content).expect("Failed to write temp file");
let result = flatten(&temp_file, &[]);
assert!(result.is_err(), "Should fail when import is not found");
let _ = std::fs::remove_file(&temp_file);
}
#[test]
fn import_resolver_handles_circular_imports() {
use abi_loader::ImportResolver;
let temp_dir = std::env::temp_dir();
let file_a = temp_dir.join("circular_a.abi.yaml");
let file_b = temp_dir.join("circular_b.abi.yaml");
let content_a = format!(
r#"
abi:
package: test.circular.a
abi-version: 1
package-version: "1.0.0"
description: "Circular import test A"
imports:
- type: path
path: "{}"
types:
- name: TypeA
kind:
primitive: u32
"#,
file_b.file_name().unwrap().to_str().unwrap()
);
let content_b = format!(
r#"
abi:
package: test.circular.b
abi-version: 1
package-version: "1.0.0"
description: "Circular import test B"
imports:
- type: path
path: "{}"
types:
- name: TypeB
kind:
primitive: u64
"#,
file_a.file_name().unwrap().to_str().unwrap()
);
std::fs::write(&file_a, content_a).expect("Failed to write file_a");
std::fs::write(&file_b, content_b).expect("Failed to write file_b");
let mut resolver = ImportResolver::new(vec![temp_dir.clone()]);
let result = resolver.load_file_with_imports(&file_a, false);
assert!(result.is_ok(), "Should handle circular imports gracefully");
assert_eq!(resolver.loaded_file_count(), 2);
let _ = std::fs::remove_file(&file_a);
let _ = std::fs::remove_file(&file_b);
}
#[test]
fn name_field_parsed_from_yaml() {
let yaml = r#"
abi:
package: thru.program.token
name: "Token Program"
abi-version: 1
package-version: "0.1.0"
description: "Test"
types: []
"#;
let parsed: AbiFile = serde_yml::from_str(yaml).expect("Should parse YAML with name");
assert_eq!(parsed.abi.name, Some("Token Program".to_string()));
assert_eq!(parsed.name(), Some("Token Program"));
}
#[test]
fn name_field_defaults_to_none() {
let yaml = r#"
abi:
package: thru.common.primitives
abi-version: 1
package-version: "0.1.0"
description: "Test without name"
types: []
"#;
let parsed: AbiFile = serde_yml::from_str(yaml).expect("Should parse YAML without name");
assert_eq!(parsed.abi.name, None);
assert_eq!(parsed.name(), None);
}
#[test]
fn name_field_preserved_through_flatten() {
let file = type_library_path().join("tn_token_program.abi.yaml");
let include_dirs = vec![type_library_path()];
let result = flatten(&file, &include_dirs).expect("flatten should succeed");
assert_eq!(result.abi.name, Some("Token Program".to_string()));
assert_eq!(result.name(), Some("Token Program"));
}
#[test]
fn name_field_survives_yaml_roundtrip() {
let file = type_library_path().join("tn_token_program.abi.yaml");
let include_dirs = vec![type_library_path()];
let yaml = flatten_to_yaml(&file, &include_dirs).expect("flatten_to_yaml should succeed");
let parsed: AbiFile = serde_yml::from_str(&yaml).expect("Should parse roundtripped YAML");
assert_eq!(parsed.abi.name, Some("Token Program".to_string()));
}
#[test]
fn name_field_absent_after_flatten_when_not_set() {
let file = type_library_path().join("thru_primitives.abi.yaml");
let include_dirs = vec![type_library_path()];
let result = flatten(&file, &include_dirs).expect("flatten should succeed");
assert_eq!(result.abi.name, None);
}
#[test]
fn flatten_nft_market_program() {
let file = type_library_path().join("tn_nft_market_program.abi.yaml");
let include_dirs = vec![type_library_path()];
let result = flatten(&file, &include_dirs).expect("flatten should succeed");
assert!(
result.abi.imports.is_empty(),
"Flattened ABI should have no imports"
);
let type_names: Vec<&str> = result.types.iter().map(|t| t.name.as_str()).collect();
assert!(
type_names.contains(&"Pubkey"),
"Should contain Pubkey from primitives"
);
assert!(
type_names.contains(&"Hash"),
"Should contain Hash from primitives"
);
assert!(
type_names.contains(&"StateProof"),
"Should contain StateProof"
);
assert!(
type_names.contains(&"NftMarketInstruction"),
"Should contain NftMarketInstruction"
);
assert!(
type_names.contains(&"NftMarketOrder"),
"Should contain NftMarketOrder"
);
assert!(
type_names.contains(&"NftMarketConfig"),
"Should contain NftMarketConfig"
);
assert!(
type_names.contains(&"NftMarketProgramAccount"),
"Should contain NftMarketProgramAccount"
);
assert!(
type_names.contains(&"NftMarketEvent"),
"Should contain NftMarketEvent"
);
fn assert_no_packages(kind: &abi_types::TypeKind) {
match kind {
abi_types::TypeKind::TypeRef(tr) => {
assert!(
tr.package.is_none(),
"TypeRef '{}' should have no package in flattened output",
tr.name
);
}
abi_types::TypeKind::Struct(s) => {
for f in &s.fields {
assert_no_packages(&f.field_type);
}
}
abi_types::TypeKind::Enum(e) => {
for v in &e.variants {
assert_no_packages(&v.variant_type);
}
}
abi_types::TypeKind::Array(a) => {
assert_no_packages(&a.element_type);
}
abi_types::TypeKind::Union(u) => {
for v in &u.variants {
assert_no_packages(&v.variant_type);
}
}
abi_types::TypeKind::SizeDiscriminatedUnion(sdu) => {
for v in &sdu.variants {
assert_no_packages(&v.variant_type);
}
}
abi_types::TypeKind::Primitive(_) => {}
}
}
for typedef in &result.types {
assert_no_packages(&typedef.kind);
}
let yaml = serde_yml::to_string(&result).expect("Should serialize to YAML");
let reparsed: AbiFile = serde_yml::from_str(&yaml).expect("Should re-parse flattened YAML");
assert_eq!(reparsed.abi.package, "thru.program.nft_market");
assert!(reparsed.types.iter().any(|t| t.name == "Pubkey"));
assert!(reparsed.types.iter().any(|t| t.name == "StateProof"));
}
#[test]
fn flatten_preserves_package_in_non_flat_roundtrip() {
let yaml = r#"
abi:
package: test.with.packages
abi-version: 1
package-version: "1.0.0"
description: "Test package field preservation"
types:
- name: MyStruct
kind:
struct:
packed: true
fields:
- name: key
field-type:
type-ref:
name: Pubkey
package: "thru.common.primitives"
"#;
let parsed: AbiFile = serde_yml::from_str(yaml).expect("Should parse YAML with package field");
let field = &parsed.types[0];
if let abi_types::TypeKind::Struct(s) = &field.kind {
if let abi_types::TypeKind::TypeRef(tr) = &s.fields[0].field_type {
assert_eq!(tr.package.as_deref(), Some("thru.common.primitives"));
} else {
panic!("Expected TypeRef");
}
} else {
panic!("Expected Struct");
}
}