Skip to main content

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}