// //! Tests that verify pattern analysis using the real catalog. // use std::collections::{BTreeMap, BTreeSet}; // use std::fmt::Write; // use brk_bindgen::ClientMetadata; // use brk_types::TreeNode; // /// Load the catalog from the JSON file. // fn load_catalog() -> TreeNode { // let path = concat!(env!("CARGO_MANIFEST_DIR"), "/catalog.json"); // let catalog_json = std::fs::read_to_string(path).expect("Failed to read catalog.json"); // serde_json::from_str(&catalog_json).expect("Failed to parse catalog.json") // } // /// Load OpenAPI spec from openapi.json. // fn load_openapi_json() -> String { // let path = concat!(env!("CARGO_MANIFEST_DIR"), "/openapi.json"); // std::fs::read_to_string(path).expect("Failed to read openapi.json") // } // /// Load metadata from the catalog. // #[allow(unused)] // fn load_metadata() -> ClientMetadata { // ClientMetadata::from_catalog(load_catalog()) // } // /// Collect all leaf metric names from a tree. // fn collect_leaf_names(node: &TreeNode, names: &mut BTreeSet) { // match node { // TreeNode::Leaf(leaf) => { // names.insert(leaf.name().to_string()); // } // TreeNode::Branch(children) => { // for child in children.values() { // collect_leaf_names(child, names); // } // } // } // } // #[test] // fn test_catalog_loads() { // let catalog = load_catalog(); // // Should be a branch with top-level categories // let TreeNode::Branch(categories) = &catalog else { // panic!("Expected catalog to be a branch"); // }; // // Check some expected top-level categories exist // assert!( // categories.contains_key("addresses"), // "Missing addresses category" // ); // assert!(categories.contains_key("blocks"), "Missing blocks category"); // assert!(categories.contains_key("market"), "Missing market category"); // assert!(categories.contains_key("supply"), "Missing supply category"); // println!("Catalog has {} top-level categories", categories.len()); // } // #[test] // fn test_all_leaves_have_names() { // let catalog = load_catalog(); // let mut names = BTreeSet::new(); // collect_leaf_names(&catalog, &mut names); // println!("Catalog has {} unique metric names", names.len()); // assert!(!names.is_empty(), "Should have at least some metrics"); // // All names should be non-empty // for name in &names { // assert!(!name.is_empty(), "Found empty metric name"); // } // } // #[test] // fn test_pattern_detection() { // let catalog = load_catalog(); // let (patterns, concrete_to_pattern, concrete_to_type_param, _node_bases) = // brk_bindgen::detect_structural_patterns(&catalog); // println!("Detected {} structural patterns", patterns.len()); // println!( // "Concrete to pattern mappings: {}", // concrete_to_pattern.len() // ); // println!("Type parameter mappings: {}", concrete_to_type_param.len()); // // Print pattern details // for pattern in &patterns { // let mode_str = match &pattern.mode { // Some(brk_bindgen::PatternMode::Suffix { relatives }) => { // format!("Suffix({})", relatives.len()) // } // Some(brk_bindgen::PatternMode::Prefix { prefixes }) => { // format!("Prefix({})", prefixes.len()) // } // None => "None".to_string(), // }; // println!( // " {} (fields: {}, generic: {}, mode: {})", // pattern.name, // pattern.fields.len(), // pattern.is_generic, // mode_str // ); // } // // Should have detected some patterns // assert!(!patterns.is_empty(), "Should detect at least some patterns"); // // Check that parameterizable patterns have valid modes // for pattern in &patterns { // if pattern.is_parameterizable() { // let mode = pattern.mode.as_ref().unwrap(); // match mode { // brk_bindgen::PatternMode::Suffix { relatives } => { // assert_eq!( // relatives.len(), // pattern.fields.len(), // "Pattern {} should have relative for each field", // pattern.name // ); // } // brk_bindgen::PatternMode::Prefix { prefixes } => { // assert_eq!( // prefixes.len(), // pattern.fields.len(), // "Pattern {} should have prefix for each field", // pattern.name // ); // } // } // } // } // } // #[test] // fn test_cost_basis_pattern() { // let catalog = load_catalog(); // let (patterns, _, _, _) = brk_bindgen::detect_structural_patterns(&catalog); // // Find CostBasisPattern2 and inspect it // let cost_basis = patterns // .iter() // .find(|p| p.name == "CostBasisPattern2") // .expect("CostBasisPattern2 should exist"); // println!("CostBasisPattern2:"); // println!( // " Fields: {:?}", // cost_basis // .fields // .iter() // .map(|f| &f.name) // .collect::>() // ); // println!(" Mode: {:?}", cost_basis.mode); // println!(" Is generic: {}", cost_basis.is_generic); // // With suffix naming convention (cost_basis_max, cost_basis_min, cost_basis): // // // // At root level: common prefix is "cost_basis_" -> suffix mode // // max -> "max" // // min -> "min" // // percentiles -> "" (identity) // // // // At lth_ level: common prefix is "lth_cost_basis_" -> suffix mode // // max -> "max" // // min -> "min" // // percentiles -> "" (identity) // // // // Both use suffix mode with same relatives, so pattern IS parameterizable! // assert!( // cost_basis.is_parameterizable(), // "CostBasisPattern2 should be parameterizable with consistent suffix mode" // ); // } // #[test] // fn test_realized_pattern3_fields() { // let catalog = load_catalog(); // let metadata = ClientMetadata::from_catalog(catalog); // let pattern = metadata // .find_pattern("RealizedPattern3") // .expect("RealizedPattern3 should exist"); // println!("RealizedPattern3 fields:"); // for field in &pattern.fields { // let is_branch = field.is_branch(); // let is_pattern = metadata.find_pattern(&field.rust_type).is_some(); // let is_param = metadata.is_parameterizable(&field.rust_type); // println!( // " {} -> {} (branch={}, pattern={}, param={})", // field.name, field.rust_type, is_branch, is_pattern, is_param // ); // } // // Check if RealizedPattern3 is considered parameterizable // println!( // "\nRealizedPattern3 is_parameterizable (metadata): {}", // metadata.is_parameterizable("RealizedPattern3") // ); // } // #[test] // fn test_parameterizable_patterns_have_mode() { // let catalog = load_catalog(); // let (patterns, _, _, _) = brk_bindgen::detect_structural_patterns(&catalog); // // All patterns that appear 2+ times should either: // // 1. Be parameterizable (have a mode) // // 2. Or have inconsistent instances (mode = None) // // // // Patterns with mode = None should be inlined, not generate factories // let parameterizable: Vec<_> = patterns.iter().filter(|p| p.is_parameterizable()).collect(); // let non_parameterizable: Vec<_> = patterns // .iter() // .filter(|p| !p.is_parameterizable()) // .collect(); // println!("\nParameterizable patterns ({}):", parameterizable.len()); // for p in ¶meterizable { // let mode = p.mode.as_ref().unwrap(); // let mode_type = match mode { // brk_bindgen::PatternMode::Suffix { .. } => "Suffix", // brk_bindgen::PatternMode::Prefix { .. } => "Prefix", // }; // println!(" {} ({} fields, {})", p.name, p.fields.len(), mode_type); // } // println!( // "\nNon-parameterizable patterns ({}):", // non_parameterizable.len() // ); // for p in &non_parameterizable { // println!(" {} ({} fields)", p.name, p.fields.len()); // } // // Verify all parameterizable patterns have valid modes with all fields // for pattern in ¶meterizable { // let mode = pattern.mode.as_ref().unwrap(); // let field_names: BTreeSet<_> = pattern.fields.iter().map(|f| f.name.clone()).collect(); // match mode { // brk_bindgen::PatternMode::Suffix { relatives } => { // let mode_fields: BTreeSet<_> = relatives.keys().cloned().collect(); // assert_eq!( // field_names, mode_fields, // "Pattern {} suffix mode should have all fields", // pattern.name // ); // } // brk_bindgen::PatternMode::Prefix { prefixes } => { // let mode_fields: BTreeSet<_> = prefixes.keys().cloned().collect(); // assert_eq!( // field_names, mode_fields, // "Pattern {} prefix mode should have all fields", // pattern.name // ); // } // } // } // } // #[test] // fn test_fee_rate_pattern_relatives() { // let catalog = load_catalog(); // let (patterns, _, _, _) = brk_bindgen::detect_structural_patterns(&catalog); // let fee_rate_pattern = patterns // .iter() // .find(|p| p.name == "FeeRatePattern") // .expect("FeeRatePattern should exist"); // println!("FeeRatePattern mode:"); // if let Some(mode) = &fee_rate_pattern.mode { // match mode { // brk_bindgen::PatternMode::Suffix { relatives } => { // println!(" Suffix mode:"); // for (field, relative) in relatives { // println!(" {} -> '{}'", field, relative); // } // } // brk_bindgen::PatternMode::Prefix { prefixes } => { // println!(" Prefix mode:"); // for (field, prefix) in prefixes { // println!(" {} -> '{}'", field, prefix); // } // } // } // } else { // println!(" No mode (not parameterizable)"); // } // // Check that relatives are correct - should be "average", "max", etc. // // NOT "tx_weight_average", "tx_weight_max", etc. // if let Some(brk_bindgen::PatternMode::Suffix { relatives }) = &fee_rate_pattern.mode { // assert_eq!( // relatives.get("average"), // Some(&"average".to_string()), // "average relative should be 'average', not 'tx_weight_average'" // ); // } // } // #[test] // fn test_index_patterns() { // let catalog = load_catalog(); // let index_patterns = brk_bindgen::detect_index_patterns(&catalog); // // println!("Used indexes: {:?}", used_indexes); // println!("Index set patterns: {}", index_patterns.len()); // for pattern in &index_patterns { // println!(" {} -> {:?}", pattern.name, pattern.indexes); // } // // Should have detected some index patterns // assert!(!index_patterns.is_empty(), "Should detect index patterns"); // } // #[test] // fn test_generated_rust_output() { // let catalog = load_catalog(); // let metadata = ClientMetadata::from_catalog(catalog.clone()); // // Collect all metric names from the catalog // let mut all_metrics = BTreeSet::new(); // collect_leaf_names(&catalog, &mut all_metrics); // // Generate Rust client output // let mut rust_output = String::new(); // brk_bindgen::rust::client::generate_imports(&mut rust_output); // brk_bindgen::rust::client::generate_base_client(&mut rust_output); // brk_bindgen::rust::client::generate_metric_pattern_trait(&mut rust_output); // brk_bindgen::rust::client::generate_endpoint(&mut rust_output); // brk_bindgen::rust::client::generate_index_accessors( // &mut rust_output, // &metadata.index_set_patterns, // ); // brk_bindgen::rust::client::generate_pattern_structs( // &mut rust_output, // &metadata.structural_patterns, // &metadata, // ); // brk_bindgen::rust::tree::generate_tree(&mut rust_output, &metadata.catalog, &metadata); // brk_bindgen::rust::api::generate_main_client(&mut rust_output, &[]); // // Count metrics that appear as direct string literals // let mut direct_metrics = 0; // for metric in &all_metrics { // if rust_output.contains(&format!("\"{}\"", metric)) { // direct_metrics += 1; // } // } // println!("\nGenerated Rust output stats:"); // println!(" Total metrics in catalog: {}", all_metrics.len()); // println!(" Direct string literals: {}", direct_metrics); // println!( // " Via pattern factories: {}", // all_metrics.len() - direct_metrics // ); // println!(" Output size: {} bytes", rust_output.len()); // // Write output to test directory (not actual client) // let output_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output"); // std::fs::create_dir_all(output_dir).ok(); // let output_path = format!("{}/rust_client.rs", output_dir); // std::fs::write(&output_path, &rust_output).expect("Failed to write client output"); // println!(" Wrote output to: {}", output_path); // // Verify the output contains the key components // assert!(rust_output.contains("fn _m("), "Should define _m helper"); // assert!( // rust_output.contains("pub struct MetricsTree"), // "Should have MetricsTree" // ); // assert!( // rust_output.contains("impl MetricsTree"), // "Should have MetricsTree impl" // ); // // Count parameterizable patterns (these use _m for dynamic metric names) // // Use metadata.is_parameterizable() for full recursive check // let parameterizable_count = metadata // .structural_patterns // .iter() // .filter(|p| metadata.is_parameterizable(&p.name)) // .count(); // println!(" Parameterizable patterns: {}", parameterizable_count); // // Verify all pattern structs are generated (parameterizable and non) // for pattern in &metadata.structural_patterns { // assert!( // rust_output.contains(&format!("pub struct {}", pattern.name)), // "Missing pattern struct: {}", // pattern.name // ); // } // println!("\nGenerated Rust client is complete!"); // } // #[test] // fn test_generated_javascript_output() { // let catalog = load_catalog(); // let metadata = ClientMetadata::from_catalog(catalog.clone()); // // Collect all metric names from the catalog // let mut all_metrics = BTreeSet::new(); // collect_leaf_names(&catalog, &mut all_metrics); // // Load schemas from OpenAPI spec only (catalog schemas require runtime data) // let openapi_json = load_openapi_json(); // let schemas = brk_bindgen::extract_schemas(&openapi_json); // // Generate JavaScript client output // let mut js_output = String::new(); // writeln!(js_output, "// Auto-generated BRK JavaScript client").unwrap(); // writeln!(js_output, "// Do not edit manually\n").unwrap(); // brk_bindgen::javascript::types::generate_type_definitions(&mut js_output, &schemas); // brk_bindgen::javascript::client::generate_base_client(&mut js_output); // brk_bindgen::javascript::client::generate_index_accessors( // &mut js_output, // &metadata.index_set_patterns, // ); // brk_bindgen::javascript::client::generate_structural_patterns( // &mut js_output, // &metadata.structural_patterns, // &metadata, // ); // brk_bindgen::javascript::tree::generate_tree_typedefs( // &mut js_output, // &metadata.catalog, // &metadata, // ); // brk_bindgen::javascript::tree::generate_main_client( // &mut js_output, // &metadata.catalog, // &metadata, // &[], // ); // // Count metrics that appear as direct string literals // let mut direct_metrics = 0; // for metric in &all_metrics { // if js_output.contains(&format!("'{}'", metric)) // || js_output.contains(&format!("\"{}\"", metric)) // { // direct_metrics += 1; // } // } // println!("\nGenerated JavaScript output stats:"); // println!(" Total metrics in catalog: {}", all_metrics.len()); // println!(" Direct string literals: {}", direct_metrics); // println!( // " Via pattern factories: {}", // all_metrics.len() - direct_metrics // ); // println!(" Output size: {} bytes", js_output.len()); // println!(" Output lines: {}", js_output.lines().count()); // // Write output to test directory (not actual client) // let output_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output"); // std::fs::create_dir_all(output_dir).ok(); // let output_path = format!("{}/js_client.js", output_dir); // std::fs::write(&output_path, &js_output).expect("Failed to write JS client output"); // println!(" Wrote output to: {}", output_path); // // Verify the output contains key components // assert!(js_output.contains("const _m ="), "Should define _m helper"); // assert!(js_output.contains("const _p ="), "Should define _p helper"); // assert!( // js_output.contains("@typedef {Object} MetricsTree"), // "Should have MetricsTree typedef" // ); // assert!( // js_output.contains("class BrkClient"), // "Should have BrkClient class" // ); // // Verify all pattern factories are generated // for pattern in &metadata.structural_patterns { // assert!( // js_output.contains(&format!("function create{}(", pattern.name)), // "Missing pattern factory: {}", // pattern.name // ); // } // println!("\nGenerated JavaScript client is complete!"); // } // #[test] // fn test_generated_python_output() { // let catalog = load_catalog(); // let metadata = ClientMetadata::from_catalog(catalog.clone()); // // Collect all metric names from the catalog // let mut all_metrics = BTreeSet::new(); // collect_leaf_names(&catalog, &mut all_metrics); // // Load schemas from OpenAPI spec only (catalog schemas require runtime data) // let openapi_json = load_openapi_json(); // let schemas = brk_bindgen::extract_schemas(&openapi_json); // // Generate Python client output // let mut py_output = String::new(); // writeln!(py_output, "# Auto-generated BRK Python client").unwrap(); // writeln!(py_output, "# Do not edit manually\n").unwrap(); // writeln!(py_output, "from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload, Iterator, Tuple, TYPE_CHECKING").unwrap(); // writeln!(py_output, "\nif TYPE_CHECKING:").unwrap(); // writeln!(py_output, " import pandas as pd # type: ignore[import-not-found]").unwrap(); // writeln!(py_output, " import polars as pl # type: ignore[import-not-found]").unwrap(); // writeln!( // py_output, // "from http.client import HTTPSConnection, HTTPConnection" // ) // .unwrap(); // writeln!(py_output, "from urllib.parse import urlparse").unwrap(); // writeln!(py_output, "from datetime import date, timedelta").unwrap(); // writeln!(py_output, "from dataclasses import dataclass").unwrap(); // writeln!(py_output, "import json\n").unwrap(); // writeln!(py_output, "T = TypeVar('T')\n").unwrap(); // brk_bindgen::python::types::generate_type_definitions(&mut py_output, &schemas); // brk_bindgen::python::client::generate_base_client(&mut py_output); // brk_bindgen::python::client::generate_endpoint_class(&mut py_output); // brk_bindgen::python::client::generate_index_accessors( // &mut py_output, // &metadata.index_set_patterns, // ); // brk_bindgen::python::client::generate_structural_patterns( // &mut py_output, // &metadata.structural_patterns, // &metadata, // ); // brk_bindgen::python::tree::generate_tree_classes(&mut py_output, &metadata.catalog, &metadata); // brk_bindgen::python::api::generate_main_client(&mut py_output, &[]); // // Count metrics that appear as direct string literals // let mut direct_metrics = 0; // for metric in &all_metrics { // if py_output.contains(&format!("'{}'", metric)) // || py_output.contains(&format!("\"{}\"", metric)) // { // direct_metrics += 1; // } // } // println!("\nGenerated Python output stats:"); // println!(" Total metrics in catalog: {}", all_metrics.len()); // println!(" Direct string literals: {}", direct_metrics); // println!( // " Via pattern factories: {}", // all_metrics.len() - direct_metrics // ); // println!(" Output size: {} bytes", py_output.len()); // println!(" Output lines: {}", py_output.lines().count()); // // Write output to test directory (not actual client) // let output_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/output"); // std::fs::create_dir_all(output_dir).ok(); // let output_path = format!("{}/python_client.py", output_dir); // std::fs::write(&output_path, &py_output).expect("Failed to write Python client output"); // println!(" Wrote output to: {}", output_path); // // Verify the output contains key components // assert!(py_output.contains("def _m("), "Should define _m helper"); // assert!(py_output.contains("def _p("), "Should define _p helper"); // assert!( // py_output.contains("class MetricsTree:"), // "Should have MetricsTree class" // ); // assert!( // py_output.contains("class BrkClient"), // "Should have BrkClient class" // ); // // Verify all pattern classes have constructors // for pattern in &metadata.structural_patterns { // assert!( // py_output.contains(&format!("class {}:", pattern.name)) // || py_output.contains(&format!("class {}(", pattern.name)), // "Missing pattern class: {}", // pattern.name // ); // } // println!("\nGenerated Python client is complete!"); // } // #[test] // fn test_cost_basis_relatives() { // let catalog = load_catalog(); // // Find cost_basis branches that have 3 direct children (max, min, percentiles) // fn find_cost_basis_with_percentiles( // node: &TreeNode, // path: &str, // ) -> Vec<(String, Vec<(String, String)>)> { // let mut results = Vec::new(); // if let TreeNode::Branch(children) = node { // for (name, child) in children { // let child_path = if path.is_empty() { // name.clone() // } else { // format!("{}.{}", path, name) // }; // if name == "cost_basis" // && let TreeNode::Branch(cb_children) = child // && cb_children.contains_key("percentiles") // { // // Found a cost_basis with percentiles // let mut metrics = Vec::new(); // for (field_name, field_node) in cb_children { // match field_node { // TreeNode::Leaf(leaf) => { // metrics.push((field_name.clone(), leaf.name().to_string())); // } // TreeNode::Branch(pct_children) => { // // Get first percentile as example // if let Some((_, TreeNode::Leaf(first))) = pct_children.iter().next() // { // metrics.push(( // format!("{}.first", field_name), // first.name().to_string(), // )); // } // } // } // } // results.push((child_path.clone(), metrics)); // } // results.extend(find_cost_basis_with_percentiles(child, &child_path)); // } // } // results // } // let instances = find_cost_basis_with_percentiles(&catalog, ""); // println!("\nCostBasisPattern2 instances (with percentiles):"); // for (path, metrics) in instances.iter().take(10) { // println!(" {}:", path); // for (field, metric) in metrics { // println!(" {} -> {}", field, metric); // } // } // // Now compute what relatives the pattern detection would see // // The key is: percentiles returns its BASE (common prefix of pct05, pct10, etc.) // // not the individual percentile metrics // use brk_bindgen::find_common_prefix; // println!("\nComputing relatives (simulating branch base returns):"); // for (path, metrics) in instances.iter().take(5) { // println!(" Instance: {}", path); // // For leaves (max, min), the base is the metric name // // For branches (percentiles), the base is the common prefix of its children // let mut child_bases: std::collections::BTreeMap = // std::collections::BTreeMap::new(); // for (field, metric) in metrics { // if field.starts_with("percentiles.") { // // This is a percentile metric - compute what the percentiles branch would return // // The base is the metric name with the pct suffix stripped // let base = metric // .strip_suffix("_pct05") // .or_else(|| metric.strip_suffix("_pct10")) // .unwrap_or(metric) // .to_string(); // child_bases.insert("percentiles".to_string(), base); // } else { // child_bases.insert(field.clone(), metric.clone()); // } // } // let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect(); // println!(" Child bases:"); // for (field, base) in &child_bases { // println!(" {} -> {}", field, base); // } // if let Some(prefix) = find_common_prefix(&bases) { // println!(" Common prefix: '{}'", prefix); // for (field, base) in &child_bases { // let relative = base.strip_prefix(&prefix).unwrap_or(base); // println!(" {} -> relative '{}'", field, relative); // } // } else { // println!(" No common prefix found!"); // } // } // } // #[test] // fn test_debug_cost_basis_pattern2_mode() { // // Debug why CostBasisPattern2 has mode=None // let catalog = load_catalog(); // let metadata = brk_bindgen::ClientMetadata::from_catalog(catalog.clone()); // let pattern_lookup = metadata.pattern_lookup(); // let pattern = metadata // .find_pattern("CostBasisPattern2") // .expect("CostBasisPattern2 should exist"); // println!("\nCostBasisPattern2 fields:"); // for field in &pattern.fields { // println!(" {} (type: {})", field.name, field.rust_type); // } // println!("Mode: {:?}", pattern.mode); // // Now debug the instance collection // #[derive(Debug, Clone)] // struct DebugInstanceAnalysis { // base: String, // field_parts: std::collections::BTreeMap, // is_suffix_mode: bool, // } // fn collect_debug( // node: &TreeNode, // pattern_lookup: &std::collections::BTreeMap, String>, // all_analyses: &mut std::collections::BTreeMap>, // ) -> Option { // match node { // TreeNode::Leaf(leaf) => Some(leaf.name().to_string()), // TreeNode::Branch(children) => { // let mut child_bases: std::collections::BTreeMap = // std::collections::BTreeMap::new(); // for (field_name, child_node) in children { // if let Some(base) = collect_debug(child_node, pattern_lookup, all_analyses) { // child_bases.insert(field_name.clone(), base); // } // } // if child_bases.is_empty() { // return None; // } // // Analyze this instance // let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect(); // let (base, field_parts, is_suffix_mode) = // if let Some(common_prefix) = brk_bindgen::find_common_prefix(&bases) { // let base = common_prefix.trim_end_matches('_').to_string(); // let mut parts = std::collections::BTreeMap::new(); // for (field_name, child_base) in &child_bases { // let relative = if *child_base == base { // String::new() // } else { // child_base // .strip_prefix(&common_prefix) // .unwrap_or(child_base) // .to_string() // }; // parts.insert(field_name.clone(), relative); // } // (base, parts, true) // } else { // let base = child_bases.values().next().cloned().unwrap_or_default(); // let parts = child_bases // .iter() // .map(|(k, v)| (k.clone(), v.clone())) // .collect(); // (base, parts, true) // }; // let analysis = DebugInstanceAnalysis { // base: base.clone(), // field_parts, // is_suffix_mode, // }; // // Get the pattern name for this node // let fields = brk_bindgen::get_node_fields(children, pattern_lookup); // if let Some(pattern_name) = pattern_lookup.get(&fields) { // all_analyses // .entry(pattern_name.clone()) // .or_default() // .push(analysis); // } // Some(base) // } // } // } // let mut all_analyses: BTreeMap> = BTreeMap::new(); // collect_debug(&catalog, &pattern_lookup, &mut all_analyses); // if let Some(analyses) = all_analyses.get("CostBasisPattern2") { // println!( // "\nCollected {} instances of CostBasisPattern2:", // analyses.len() // ); // for (i, a) in analyses.iter().enumerate() { // println!(" Instance {}:", i); // println!(" base: {}", a.base); // println!(" is_suffix: {}", a.is_suffix_mode); // println!(" field_parts:"); // for (f, p) in &a.field_parts { // println!(" {} -> '{}'", f, p); // } // } // // Check consistency // if analyses.len() >= 2 { // let first = &analyses[0]; // for (i, a) in analyses.iter().enumerate().skip(1) { // if a.is_suffix_mode != first.is_suffix_mode { // println!(" INCONSISTENT: Instance {} has different mode", i); // } // for (field, part) in &a.field_parts { // if first.field_parts.get(field) != Some(part) { // println!( // " INCONSISTENT: Instance {} field '{}' has part '{}' vs '{}'", // i, // field, // part, // first // .field_parts // .get(field) // .unwrap_or(&"".to_string()) // ); // } // } // } // } // } else { // println!("\nNo instances collected for CostBasisPattern2!"); // } // } // #[test] // fn test_root_cost_basis_prefix() { // use brk_bindgen::find_common_prefix; // // Root-level cost_basis has: // // max -> "max_cost_basis" // // min -> "min_cost_basis" // // percentiles -> "cost_basis" (base of pct05, pct10, etc.) // let bases = vec!["max_cost_basis", "min_cost_basis", "cost_basis"]; // let prefix = find_common_prefix(&bases); // println!("Root cost_basis prefix: {:?}", prefix); // // Compare with nested cost_basis // let nested_bases = vec![ // "utxos_at_least_15y_old_max_cost_basis", // "utxos_at_least_15y_old_min_cost_basis", // "utxos_at_least_15y_old_cost_basis", // ]; // let nested_prefix = find_common_prefix(&nested_bases); // println!("Nested cost_basis prefix: {:?}", nested_prefix); // } // #[test] // fn test_utxo_cohorts_all_activity_base() { // // Test that distribution.utxo_cohorts.all.activity uses empty base // // because its children (coinblocks_destroyed, coindays_destroyed, etc.) // // have no common prefix or suffix. // let catalog = load_catalog(); // let metadata = ClientMetadata::from_catalog(catalog.clone()); // // Generate JavaScript output // let mut js_output = String::new(); // writeln!(js_output, "// Test output").unwrap(); // brk_bindgen::javascript::client::generate_base_client(&mut js_output); // brk_bindgen::javascript::client::generate_index_accessors( // &mut js_output, // &metadata.index_set_patterns, // ); // brk_bindgen::javascript::client::generate_structural_patterns( // &mut js_output, // &metadata.structural_patterns, // &metadata, // ); // brk_bindgen::javascript::tree::generate_tree_typedefs( // &mut js_output, // &metadata.catalog, // &metadata, // ); // brk_bindgen::javascript::tree::generate_main_client( // &mut js_output, // &metadata.catalog, // &metadata, // &[], // ); // // The all.activity should use empty base, so metrics don't get duplicated // // Look for: activity: createActivityPattern2(this, '') // // NOT: activity: createActivityPattern2(this, 'coinblocks_destroyed') // assert!( // !js_output.contains("createActivityPattern2(this, 'coinblocks_destroyed')"), // "all.activity should NOT use 'coinblocks_destroyed' as base (causes duplication)" // ); // // Check that it uses empty string as base // assert!( // js_output.contains("activity: createActivityPattern2(this, '')"), // "all.activity should use empty base" // ); // println!("utxo_cohorts.all.activity base test passed!"); // }