use super::*;
use crate::latent::KindTag;
use crate::manifest::SourceKind;
#[test]
fn config_roundtrips_through_json() {
let json = r#"{
"project": "helena",
"experiment_name": "sensor_to_texture_v0",
"sample_rate": 44100,
"clip_duration_seconds": 10,
"source_data": { "type": "time_series", "path": "data/source.parquet", "alignment": "paired" },
"target_audio": { "path": "data/audio/", "segmentation": { "window_seconds": 10, "overlap_seconds": 2 } },
"data_encoder": { "type": "temporal_transformer", "latent_dim": 256 },
"audio_codec": { "type": "neural_codec", "latent_type": "continuous", "freeze": true },
"conditional_generator": { "type": "token_transformer", "layers": 12, "hidden_dim": 768 },
"training": { "batch_size": 16, "learning_rate": 0.0001, "max_steps": 100000, "seed": 42 }
}"#;
let cfg = RawExperimentConfig::from_json_str(json).expect("parse");
assert_eq!(cfg.project, "helena");
assert_eq!(cfg.source_data.kind, SourceKind::TimeSeries);
assert_eq!(cfg.audio_codec.latent_type, KindTag::Continuous);
assert!(cfg.audio_codec.freeze);
assert_eq!(cfg.conditional_generator.extra.get("layers").unwrap(), 12);
assert_eq!(cfg.generation.num_candidates, 1);
let spec = cfg.resolve().expect("resolve");
assert_eq!(spec.data_encoder().latent_dim().get(), 256);
assert_eq!(spec.audio_codec().latent_kind(), KindTag::Continuous);
}
#[test]
fn config_parses_from_toml() {
let toml = r#"
project = "helena"
experiment_name = "sensor_to_texture_v0"
sample_rate = 44100
clip_duration_seconds = 10.0
[source_data]
type = "time_series"
path = "data/source.parquet"
alignment = "paired"
[target_audio]
path = "data/audio/"
[target_audio.segmentation]
window_seconds = 10.0
overlap_seconds = 2.0
[data_encoder]
type = "temporal_transformer"
latent_dim = 256
[audio_codec]
type = "neural_codec"
latent_type = "continuous"
freeze = true
[conditional_generator]
type = "token_transformer"
layers = 12
[training]
batch_size = 16
learning_rate = 0.0001
max_steps = 100000
seed = 42
"#;
let cfg = RawExperimentConfig::from_toml_str(toml).expect("parse toml");
assert_eq!(cfg.experiment_name, "sensor_to_texture_v0");
assert_eq!(cfg.data_encoder.latent_dim, 256);
assert_eq!(cfg.training.max_steps, 100_000);
assert_eq!(cfg.conditional_generator.extra.get("layers").unwrap(), 12);
}
#[test]
fn from_toml_str_types_parse_failures_distinctly() {
let malformed = RawExperimentConfig::from_toml_str("[unclosed").unwrap_err();
assert!(
matches!(malformed, crate::Error::Toml(_)),
"got {malformed:?}"
);
let missing_field = RawExperimentConfig::from_toml_str("project = \"p\"").unwrap_err();
assert!(
matches!(missing_field, crate::Error::Toml(_)),
"a missing required field is a parse failure, not validation: {missing_field:?}"
);
}
#[test]
fn parse_and_resolve_are_separate_steps() {
let mut cfg = valid_config();
cfg.sample_rate = 0;
assert!(matches!(
cfg.clone().resolve(),
Err(crate::Error::Validation(_))
));
assert_eq!(cfg.sample_rate, 0);
}
#[test]
fn deny_unknown_fields_rejects_top_level_typo() {
let json = r#"{
"project": "p", "experiment_name": "e", "sample_rate": 8,
"clip_duration_seconds": 0.5,
"source_data": { "type": "tabular", "path": "m.json" },
"target_audio": { "path": "audio" },
"data_encoder": { "type": "vector", "latent_dim": 2 },
"audio_codec": { "type": "pcm", "latent_type": "continuous" },
"conditional_generator": { "type": "mlp" },
"training": { "batch_size": 1, "learning_rate": 0.001, "max_steps": 1 },
"surprise": true
}"#;
assert!(RawExperimentConfig::from_json_str(json).is_err());
}
#[test]
fn legacy_rvq_latent_type_no_longer_parses() {
let json = r#"{
"project": "p", "experiment_name": "e", "sample_rate": 8,
"clip_duration_seconds": 0.5,
"source_data": { "type": "tabular", "path": "m.json" },
"target_audio": { "path": "audio" },
"data_encoder": { "type": "vector", "latent_dim": 2 },
"audio_codec": { "type": "pcm", "latent_type": "rvq" },
"conditional_generator": { "type": "mlp" },
"training": { "batch_size": 1, "learning_rate": 0.001, "max_steps": 1 }
}"#;
assert!(RawExperimentConfig::from_json_str(json).is_err());
}