hedl_test/lib.rs
1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License in the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Shared test fixtures and utilities for HEDL format converters.
19//!
20//! This crate provides canonical test documents, builders, error fixtures,
21//! and utilities to eliminate test code duplication across all HEDL format
22//! converters (JSON, XML, YAML, CSV, Neo4j, Parquet, etc.).
23//!
24//! # Features
25//!
26//! - **Pre-built Fixtures**: Comprehensive test documents covering all HEDL features
27//! - **Builder Pattern**: Fluent API for creating customized test data
28//! - **Error Fixtures**: Invalid documents and error cases for testing error handling
29//! - **Utilities**: Expression helpers, counting functions, and more
30//!
31//! # Quick Start
32//!
33//! ```rust
34//! use hedl_test::fixtures;
35//!
36//! // Use pre-built fixtures
37//! let doc = fixtures::scalars(); // All scalar types
38//! let doc = fixtures::user_list(); // MatrixList with users
39//! let doc = fixtures::with_references(); // Cross-references
40//! let doc = fixtures::comprehensive(); // Everything together
41//!
42//! // Build custom fixtures
43//! use hedl_test::fixtures::builders::{DocumentBuilder, ValueBuilder};
44//!
45//! let doc = DocumentBuilder::new()
46//! .scalar("name", ValueBuilder::string("Alice"))
47//! .scalar("age", ValueBuilder::int(30))
48//! .build();
49//!
50//! // Test error handling
51//! use hedl_test::fixtures::errors;
52//!
53//! for (name, invalid) in errors::invalid_hedl_samples() {
54//! // Test parser with invalid input
55//! }
56//!
57//! // Use utilities
58//! use hedl_test::{expr, count_nodes};
59//!
60//! let e = expr("now()"); // Create expression
61//! let n = count_nodes(&doc); // Count nodes in document
62//! ```
63//!
64//! # See Also
65//!
66//! - [Fixtures Guide](FIXTURES_GUIDE.md) - Comprehensive usage guide
67//! - Module documentation for detailed API information
68
69#![cfg_attr(not(test), warn(missing_docs))]
70use hedl_core::Document;
71
72/// Type alias for a list of fixture functions (name, generator).
73pub type FixtureList = Vec<(&'static str, fn() -> Document)>;
74
75/// Returns all fixtures as (name, `hedl_text`) pairs.
76#[must_use]
77pub fn fixtures_as_hedl() -> Vec<(&'static str, String)> {
78 fixtures::all()
79 .into_iter()
80 .map(|(name, fixture_fn)| {
81 let doc = fixture_fn();
82 let hedl_text = hedl_c14n::canonicalize(&doc)
83 .unwrap_or_else(|e| format!("# Error serializing: {e}"));
84 (name, hedl_text)
85 })
86 .collect()
87}
88
89/// Write all fixtures to a directory as .hedl files.
90#[cfg(feature = "generate")]
91pub fn write_fixtures_to_dir(dir: &std::path::Path) -> std::io::Result<()> {
92 std::fs::create_dir_all(dir)?;
93 for (name, hedl_text) in fixtures_as_hedl() {
94 let path = dir.join(format!("{name}.hedl"));
95 std::fs::write(path, hedl_text)?;
96 }
97 Ok(())
98}
99
100/// Canonical test fixtures covering all HEDL features.
101pub mod fixtures;
102
103/// Expression parsing utilities for tests.
104pub mod expression_parsing;
105
106/// Fixture counting utilities.
107pub mod counts;
108
109// Re-export all fixtures for backward compatibility
110pub use fixtures::*;
111
112// Re-export commonly used utilities
113pub use counts::{count_nodes, count_references};
114pub use expression_parsing::{expr, expr_value, try_expr, try_expr_value, ExprError};
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use hedl_core::Item;
120
121 #[test]
122 fn test_all_fixtures_valid() {
123 for (name, fixture_fn) in fixtures::all() {
124 let doc = fixture_fn();
125 assert_eq!(
126 doc.version,
127 (1, 2),
128 "Fixture {name} should have version 1.0"
129 );
130 }
131 }
132
133 #[test]
134 fn test_scalars_fixture() {
135 let doc = fixtures::scalars();
136 assert!(doc.root.contains_key("null_val"));
137 assert!(doc.root.contains_key("bool_true"));
138 assert!(doc.root.contains_key("int_positive"));
139 assert!(doc.root.contains_key("float_positive"));
140 assert!(doc.root.contains_key("string_simple"));
141 }
142
143 #[test]
144 fn test_user_list_fixture() {
145 let doc = fixtures::user_list();
146 if let Some(Item::List(list)) = doc.root.get("users") {
147 assert_eq!(list.type_name, "User");
148 assert_eq!(list.rows.len(), 3);
149 } else {
150 panic!("Expected users list");
151 }
152 }
153
154 #[test]
155 fn test_with_nest_fixture() {
156 let doc = fixtures::with_nest();
157 assert!(doc.nests.contains_key("User"));
158 assert_eq!(doc.nests.get("User"), Some(&vec!["Post".to_string()]));
159
160 // Check that alice has children
161 if let Some(Item::List(list)) = doc.root.get("users") {
162 let alice = list.rows.iter().find(|n| n.id == "alice").unwrap();
163 assert!(alice.children.is_some());
164 }
165 }
166
167 #[test]
168 fn test_comprehensive_fixture() {
169 let doc = fixtures::comprehensive();
170 assert!(!doc.root.is_empty());
171 assert!(!doc.structs.is_empty());
172 assert!(!doc.nests.is_empty());
173
174 // Should have various item types
175 let has_scalar = doc.root.values().any(|i| matches!(i, Item::Scalar(_)));
176 let has_list = doc.root.values().any(|i| matches!(i, Item::List(_)));
177 assert!(has_scalar);
178 assert!(has_list);
179 }
180
181 #[test]
182 fn test_count_nodes() {
183 let doc = fixtures::user_list();
184 assert_eq!(counts::count_nodes(&doc), 3);
185
186 let doc = fixtures::with_nest();
187 // 2 users + 3 posts
188 assert_eq!(counts::count_nodes(&doc), 5);
189 }
190
191 #[test]
192 fn test_count_references() {
193 let doc = fixtures::with_references();
194 assert_eq!(counts::count_references(&doc), 3); // 3 posts with author refs
195 }
196}