use tokensave::types::*;
#[test]
fn node_kind_as_str_roundtrip() {
let kinds = vec![
NodeKind::File,
NodeKind::Module,
NodeKind::Struct,
NodeKind::Enum,
NodeKind::EnumVariant,
NodeKind::Trait,
NodeKind::Function,
NodeKind::Method,
NodeKind::Impl,
NodeKind::Const,
NodeKind::Static,
NodeKind::TypeAlias,
NodeKind::Field,
NodeKind::Macro,
NodeKind::Use,
];
for kind in kinds {
let s = kind.as_str();
let parsed = NodeKind::from_str(s)
.unwrap_or_else(|| panic!("failed to parse NodeKind from '{}'", s));
assert_eq!(kind, parsed, "roundtrip failed for NodeKind::{}", s);
}
}
#[test]
fn node_kind_from_str_unknown_returns_none() {
assert!(NodeKind::from_str("unknown_kind").is_none());
assert!(NodeKind::from_str("").is_none());
}
#[test]
fn edge_kind_as_str_roundtrip() {
let kinds = vec![
EdgeKind::Contains,
EdgeKind::Calls,
EdgeKind::Uses,
EdgeKind::Implements,
EdgeKind::TypeOf,
EdgeKind::Returns,
EdgeKind::DerivesMacro,
];
for kind in kinds {
let s = kind.as_str();
let parsed = EdgeKind::from_str(s)
.unwrap_or_else(|| panic!("failed to parse EdgeKind from '{}'", s));
assert_eq!(kind, parsed, "roundtrip failed for EdgeKind::{}", s);
}
}
#[test]
fn edge_kind_from_str_unknown_returns_none() {
assert!(EdgeKind::from_str("unknown_edge").is_none());
assert!(EdgeKind::from_str("").is_none());
}
#[test]
fn visibility_default_is_private() {
let vis: Visibility = Visibility::default();
assert_eq!(vis, Visibility::Private);
}
#[test]
fn generate_node_id_is_deterministic() {
let id1 = generate_node_id("src/main.rs", &NodeKind::Function, "main", 1);
let id2 = generate_node_id("src/main.rs", &NodeKind::Function, "main", 1);
assert_eq!(id1, id2, "same inputs must produce same ID");
}
#[test]
fn generate_node_id_format() {
let id = generate_node_id("src/lib.rs", &NodeKind::Struct, "MyStruct", 10);
let parts: Vec<&str> = id.splitn(2, ':').collect();
assert_eq!(parts.len(), 2, "ID should have exactly one colon separator");
assert_eq!(parts[0], "struct", "prefix should be the node kind");
assert_eq!(parts[1].len(), 32, "hex portion should be 32 characters");
assert!(
parts[1].chars().all(|c| c.is_ascii_hexdigit()),
"hex portion should contain only hex digits"
);
}
#[test]
fn generate_node_id_different_inputs_produce_different_ids() {
let id1 = generate_node_id("src/main.rs", &NodeKind::Function, "main", 1);
let id2 = generate_node_id("src/main.rs", &NodeKind::Function, "other", 1);
let id3 = generate_node_id("src/main.rs", &NodeKind::Function, "main", 2);
let id4 = generate_node_id("src/lib.rs", &NodeKind::Function, "main", 1);
let id5 = generate_node_id("src/main.rs", &NodeKind::Struct, "main", 1);
assert_ne!(id1, id2, "different names should produce different IDs");
assert_ne!(id1, id3, "different lines should produce different IDs");
assert_ne!(
id1, id4,
"different file paths should produce different IDs"
);
assert_ne!(id1, id5, "different kinds should produce different IDs");
}
#[test]
fn node_serde_roundtrip() {
let node = Node {
id: "function:abcdef01234567890abcdef012345678".to_string(),
kind: NodeKind::Function,
name: "my_function".to_string(),
qualified_name: "crate::module::my_function".to_string(),
file_path: "src/module.rs".to_string(),
start_line: 10,
end_line: 20,
start_column: 0,
end_column: 1,
signature: Some("fn my_function(x: i32) -> bool".to_string()),
docstring: Some("Does something useful.".to_string()),
visibility: Visibility::Pub,
is_async: true,
branches: 0,
loops: 0,
returns: 0,
max_nesting: 0,
unsafe_blocks: 0,
unchecked_calls: 0,
assertions: 0,
updated_at: 1700000000,
};
let json = serde_json::to_string(&node).expect("failed to serialize Node");
let deserialized: Node = serde_json::from_str(&json).expect("failed to deserialize Node");
assert_eq!(node.id, deserialized.id);
assert_eq!(node.kind, deserialized.kind);
assert_eq!(node.name, deserialized.name);
assert_eq!(node.qualified_name, deserialized.qualified_name);
assert_eq!(node.file_path, deserialized.file_path);
assert_eq!(node.start_line, deserialized.start_line);
assert_eq!(node.end_line, deserialized.end_line);
assert_eq!(node.start_column, deserialized.start_column);
assert_eq!(node.end_column, deserialized.end_column);
assert_eq!(node.signature, deserialized.signature);
assert_eq!(node.docstring, deserialized.docstring);
assert_eq!(node.visibility, deserialized.visibility);
assert_eq!(node.is_async, deserialized.is_async);
assert_eq!(node.updated_at, deserialized.updated_at);
}
#[test]
fn edge_serde_roundtrip() {
let edge = Edge {
source: "function:aaaa".to_string(),
target: "function:bbbb".to_string(),
kind: EdgeKind::Calls,
line: Some(15),
};
let json = serde_json::to_string(&edge).expect("failed to serialize Edge");
let deserialized: Edge = serde_json::from_str(&json).expect("failed to deserialize Edge");
assert_eq!(edge.source, deserialized.source);
assert_eq!(edge.target, deserialized.target);
assert_eq!(edge.kind, deserialized.kind);
assert_eq!(edge.line, deserialized.line);
}
#[test]
fn traversal_options_default() {
let opts = TraversalOptions::default();
assert_eq!(opts.max_depth, 3);
assert_eq!(opts.limit, 100);
assert!(opts.include_start);
assert_eq!(opts.direction, TraversalDirection::Outgoing);
assert!(opts.edge_kinds.is_none());
assert!(opts.node_kinds.is_none());
}
#[test]
fn build_context_options_default() {
let opts = BuildContextOptions::default();
assert_eq!(opts.max_nodes, 20);
assert_eq!(opts.max_code_blocks, 5);
assert_eq!(opts.max_code_block_size, 1500);
assert!(opts.include_code);
assert_eq!(opts.format, OutputFormat::Markdown);
assert_eq!(opts.search_limit, 3);
assert_eq!(opts.traversal_depth, 1);
assert!((opts.min_score - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_new_node_kinds_roundtrip() {
use tokensave::types::NodeKind;
let kinds = vec![
(NodeKind::Class, "class"),
(NodeKind::Interface, "interface"),
(NodeKind::Constructor, "constructor"),
(NodeKind::Annotation, "annotation"),
(NodeKind::AnnotationUsage, "annotation_usage"),
(NodeKind::Package, "package"),
(NodeKind::InnerClass, "inner_class"),
(NodeKind::InitBlock, "init_block"),
(NodeKind::AbstractMethod, "abstract_method"),
(NodeKind::InterfaceType, "interface_type"),
(NodeKind::StructMethod, "struct_method"),
(NodeKind::GoPackage, "go_package"),
(NodeKind::StructTag, "struct_tag"),
(NodeKind::ScalaObject, "object"),
(NodeKind::CaseClass, "case_class"),
(NodeKind::ScalaPackage, "scala_package"),
(NodeKind::ValField, "val"),
(NodeKind::VarField, "var"),
(NodeKind::GenericParam, "generic_param"),
];
for (kind, expected_str) in kinds {
assert_eq!(kind.as_str(), expected_str);
assert_eq!(NodeKind::from_str(expected_str), Some(kind));
}
}
#[test]
fn test_c_cpp_csharp_pascal_kotlin_dart_node_kinds_roundtrip() {
use tokensave::types::NodeKind;
let kinds = vec![
(NodeKind::ArrowFunction, "arrow_function"),
(NodeKind::Decorator, "decorator"),
(NodeKind::Export, "export"),
(NodeKind::Union, "union"),
(NodeKind::Typedef, "typedef"),
(NodeKind::Include, "include"),
(NodeKind::PreprocessorDef, "preprocessor_def"),
(NodeKind::Namespace, "namespace"),
(NodeKind::Template, "template"),
(NodeKind::Delegate, "delegate"),
(NodeKind::Event, "event"),
(NodeKind::Record, "record"),
(NodeKind::CSharpProperty, "csharp_property"),
(NodeKind::Procedure, "procedure"),
(NodeKind::PascalProgram, "pascal_program"),
(NodeKind::PascalUnit, "pascal_unit"),
(NodeKind::PascalRecord, "pascal_record"),
(NodeKind::Property, "property"),
(NodeKind::DataClass, "data_class"),
(NodeKind::SealedClass, "sealed_class"),
(NodeKind::KotlinObject, "kotlin_object"),
(NodeKind::KotlinPackage, "kotlin_package"),
(NodeKind::CompanionObject, "companion_object"),
(NodeKind::Mixin, "mixin"),
(NodeKind::Extension, "extension"),
(NodeKind::Library, "library"),
];
for (kind, expected_str) in kinds {
assert_eq!(kind.as_str(), expected_str);
assert_eq!(NodeKind::from_str(expected_str), Some(kind));
}
}
#[test]
fn test_new_edge_kinds_roundtrip() {
use tokensave::types::EdgeKind;
let kinds = vec![
(EdgeKind::Extends, "extends"),
(EdgeKind::Annotates, "annotates"),
(EdgeKind::Receives, "receives"),
];
for (kind, expected_str) in kinds {
assert_eq!(kind.as_str(), expected_str);
assert_eq!(EdgeKind::from_str(expected_str), Some(kind));
}
}