use somatize_core::cache::CacheKey;
use somatize_core::error::SomaError;
use somatize_core::event::MetricRecord;
use somatize_core::graph::{Edge, Graph, Node, linear_pipeline};
use somatize_core::search::{Scale, SearchDimension, SearchSpace};
use somatize_core::study::{Direction, Objective, SearchStrategy, Study, Trial, TrialState};
use somatize_core::value::Value;
#[test]
fn value_empty_tensor() {
let v = Value::tensor(vec![], vec![0]);
assert_eq!(v.size(), 0);
let (data, shape) = v.as_tensor().unwrap();
assert!(data.is_empty());
assert_eq!(shape, &[0]);
}
#[test]
fn value_single_element_tensor() {
let v = Value::tensor(vec![42.0], vec![1]);
assert_eq!(v.size(), 1);
}
#[test]
fn value_empty_json() {
let v = Value::json(serde_json::json!({}));
assert!(v.size() > 0); }
#[test]
fn value_nested_json() {
let v = Value::json(serde_json::json!({"a": {"b": {"c": 1}}}));
assert!(v.as_json().unwrap()["a"]["b"]["c"] == 1);
}
#[test]
fn cache_key_empty_data() {
let k = CacheKey::hash_data(b"");
let k2 = CacheKey::hash_data(b"");
assert_eq!(k, k2); }
#[test]
fn cache_key_empty_parts() {
let k = CacheKey::from_parts(&[]);
let k2 = CacheKey::from_parts(&[]);
assert_eq!(k, k2);
}
#[test]
fn cache_key_single_byte_differs() {
let k1 = CacheKey::hash_data(b"\x00");
let k2 = CacheKey::hash_data(b"\x01");
assert_ne!(k1, k2);
}
#[test]
fn graph_self_loop_detected_as_cycle() {
let mut g = Graph::new();
g.add_node(Node::new("a", "A", "F"));
g.add_edge(Edge::data("e1", "a", "a")); assert!(matches!(g.validate(), Err(SomaError::CycleDetected)));
}
#[test]
fn graph_empty_linear_pipeline() {
let g = linear_pipeline(vec![]);
assert!(g.nodes.is_empty());
assert!(g.edges.is_empty());
assert!(g.validate().is_ok());
}
#[test]
fn graph_disconnected_components() {
let mut g = Graph::new();
g.add_node(Node::new("a", "A", "F"));
g.add_node(Node::new("b", "B", "F"));
g.add_node(Node::new("c", "C", "F"));
g.add_edge(Edge::data("e1", "a", "b"));
assert!(g.validate().is_ok());
let sorted = g.topological_sort().unwrap();
assert_eq!(sorted.len(), 3);
assert!(sorted.contains(&"c"));
}
#[test]
fn graph_diamond_roots_and_leaves() {
let mut g = Graph::new();
g.add_node(Node::new("r", "Root", "F"));
g.add_node(Node::new("l", "Left", "F"));
g.add_node(Node::new("ri", "Right", "F"));
g.add_node(Node::new("m", "Merge", "F"));
g.add_edge(Edge::data("e1", "r", "l"));
g.add_edge(Edge::data("e2", "r", "ri"));
g.add_edge(Edge::data("e3", "l", "m"));
g.add_edge(Edge::data("e4", "ri", "m"));
assert_eq!(g.roots(), vec!["r"]);
assert_eq!(g.leaves(), vec!["m"]);
}
#[test]
fn search_space_merge_empty() {
let mut space = SearchSpace::new();
space.merge_with_prefix("Empty", SearchSpace::new());
assert!(space.is_empty());
}
#[test]
fn search_space_freeze_nonexistent() {
let mut space = SearchSpace::new();
space.add(SearchDimension::Float {
name: "lr".into(),
low: 0.001,
high: 0.1,
scale: Scale::Log,
default: None,
});
space.freeze("nonexistent_param", serde_json::json!(42));
assert_eq!(space.len(), 1); }
#[test]
fn search_dimension_int_single_value_range() {
let dim = SearchDimension::Int {
name: "n".into(),
low: 5,
high: 5, scale: Scale::Linear,
};
assert!(dim.validate().is_err());
}
#[test]
fn study_best_trial_single_trial() {
let mut study = Study::new(
"test",
SearchSpace::new(),
SearchStrategy::Random {
n_trials: 1,
seed: None,
},
vec![Objective {
metric: "f1".into(),
direction: Direction::Maximize,
}],
);
let mut t = Trial::new("t1", std::collections::HashMap::new());
t.state = TrialState::Completed;
t.metrics.push(MetricRecord {
name: "f1".into(),
value: 0.5,
step: 0,
timestamp: chrono::Utc::now(),
});
study.trials.push(t);
assert_eq!(study.best_trial().unwrap().id, "t1");
}
#[test]
fn study_best_trial_all_same_value() {
let mut study = Study::new(
"test",
SearchSpace::new(),
SearchStrategy::Random {
n_trials: 3,
seed: None,
},
vec![Objective {
metric: "f1".into(),
direction: Direction::Maximize,
}],
);
for i in 0..3 {
let mut t = Trial::new(format!("t{i}"), std::collections::HashMap::new());
t.state = TrialState::Completed;
t.metrics.push(MetricRecord {
name: "f1".into(),
value: 0.8, step: 0,
timestamp: chrono::Utc::now(),
});
study.trials.push(t);
}
assert!(study.best_trial().is_some());
}
#[test]
fn study_progress_no_total() {
let study = Study::new(
"test",
SearchSpace::new(),
SearchStrategy::Grid { points_per_dim: 5 }, vec![],
);
assert_eq!(study.progress(), 0.0);
}
#[test]
fn trial_best_metric_empty_metrics() {
let t = Trial::new("t1", std::collections::HashMap::new());
assert!(t.best_metric("f1", Direction::Maximize).is_none());
}
#[test]
fn trial_best_metric_multiple_steps() {
let mut t = Trial::new("t1", std::collections::HashMap::new());
t.state = TrialState::Completed;
for (step, val) in [(0, 0.5), (1, 0.7), (2, 0.6), (3, 0.9)] {
t.metrics.push(MetricRecord {
name: "f1".into(),
value: val,
step,
timestamp: chrono::Utc::now(),
});
}
assert_eq!(t.best_metric("f1", Direction::Maximize), Some(0.9));
assert_eq!(t.best_metric("f1", Direction::Minimize), Some(0.5));
}