248 lines
12 KiB
Rust
248 lines
12 KiB
Rust
// file: crates/ksp-config-lib/unit_tests/composite.rs
|
|
// version: 2
|
|
|
|
const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test";
|
|
const TEST_COMPOSITE_FILENAME: &str = "examples/composite.example.json";
|
|
|
|
#[test]
|
|
fn committed_composite_example_resolves_default_document_profile() {
|
|
let engine = committed_engine();
|
|
let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID);
|
|
assert!(engine.is_ok(), "composite test engine should be constructible: {engine:?}");
|
|
assert!(file_id.is_ok(), "composite test file_id should be valid: {file_id:?}");
|
|
if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) {
|
|
let resolved = engine.load_resolved_composite(&file_id, std::option::Option::None);
|
|
assert!(resolved.is_ok(), "committed composite example should resolve: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved {
|
|
assert_eq!(resolved.profile_id(), "local_default");
|
|
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile);
|
|
let logging = resolved.component("logging");
|
|
assert!(logging.is_some(), "logging component should be resolved");
|
|
if let std::option::Option::Some(logging) = logging {
|
|
assert_eq!(logging.resolved().file_id().as_str(), crate::FILE_ID_STD_LOGGING);
|
|
assert_eq!(logging.resolved().profile_id(), "local_dev");
|
|
assert_eq!(logging.resolved().selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile);
|
|
assert_eq!(logging.resolved().origin("logs_directory"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn composite_profile_override_marks_referenced_profile_selection_as_composite() {
|
|
let engine = committed_engine();
|
|
let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID);
|
|
assert!(engine.is_ok(), "composite test engine should be constructible: {engine:?}");
|
|
assert!(file_id.is_ok(), "composite test file_id should be valid: {file_id:?}");
|
|
if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) {
|
|
let resolved = engine.load_resolved_composite(&file_id, std::option::Option::Some("local_explicit"));
|
|
assert!(resolved.is_ok(), "explicit composite profile should resolve: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved {
|
|
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
|
let logging = resolved.component("logging");
|
|
assert!(logging.is_some(), "logging component should be resolved");
|
|
if let std::option::Option::Some(logging) = logging {
|
|
assert_eq!(logging.resolved().profile_id(), "local_dev");
|
|
assert_eq!(logging.resolved().selection_source(), crate::ConfigProfileSelectionSource::Composite);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_composite_profile_has_profile_not_found_error() {
|
|
let engine = committed_engine();
|
|
let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID);
|
|
assert!(engine.is_ok(), "composite test engine should be constructible: {engine:?}");
|
|
assert!(file_id.is_ok(), "composite test file_id should be valid: {file_id:?}");
|
|
if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) {
|
|
let result = engine.load_resolved_composite(&file_id, std::option::Option::Some("missing"));
|
|
assert!(result.is_err(), "unknown composite profile must be rejected: {result:?}");
|
|
if let std::result::Result::Err(error) = result {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_PROFILE_NOT_FOUND);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_referenced_file_id_is_rejected_during_composite_validation() {
|
|
let fixture = fixture_roots("unknown-reference");
|
|
let composite = r#"{
|
|
"format_version": 1,
|
|
"default_profile": "test",
|
|
"profiles": [{
|
|
"profile_id": "test",
|
|
"documents": [{"component_id": "missing", "file_id": "cfg.std.missing"}]
|
|
}]
|
|
}"#;
|
|
let prepared = prepare_fixture(&fixture, composite);
|
|
assert!(prepared.is_ok(), "composite fixture should be writable: {prepared:?}");
|
|
if prepared.is_ok() {
|
|
let engine = fixture_engine(&fixture);
|
|
let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID);
|
|
assert!(engine.is_ok(), "fixture engine should be constructible: {engine:?}");
|
|
if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) {
|
|
let result = engine.load_validated_document(&file_id);
|
|
assert!(result.is_err(), "unknown composite reference must be rejected: {result:?}");
|
|
if let std::result::Result::Err(error) = result {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_COMPOSITE_REFERENCE_INVALID);
|
|
}
|
|
}
|
|
}
|
|
cleanup_fixture(&fixture);
|
|
}
|
|
|
|
#[test]
|
|
fn duplicate_component_ids_are_rejected_inside_one_composite_profile() {
|
|
let fixture = fixture_roots("duplicate-component");
|
|
let composite = r#"{
|
|
"format_version": 1,
|
|
"default_profile": "test",
|
|
"profiles": [{
|
|
"profile_id": "test",
|
|
"documents": [
|
|
{"component_id": "logging", "file_id": "cfg.std.logging"},
|
|
{"component_id": "logging", "file_id": "cfg.std.logging"}
|
|
]
|
|
}]
|
|
}"#;
|
|
let prepared = prepare_fixture(&fixture, composite);
|
|
assert!(prepared.is_ok(), "composite fixture should be writable: {prepared:?}");
|
|
if prepared.is_ok() {
|
|
let engine = fixture_engine(&fixture);
|
|
let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID);
|
|
assert!(engine.is_ok(), "fixture engine should be constructible: {engine:?}");
|
|
if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) {
|
|
let result = engine.load_validated_document(&file_id);
|
|
assert!(result.is_err(), "duplicate component_id must be rejected: {result:?}");
|
|
if let std::result::Result::Err(error) = result {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID);
|
|
}
|
|
}
|
|
}
|
|
cleanup_fixture(&fixture);
|
|
}
|
|
|
|
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
|
let workspace = workspace_root();
|
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
|
let bootstrap = match bootstrap {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let registry = test_registry(TEST_COMPOSITE_FILENAME);
|
|
return match registry {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, value)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
fn fixture_engine(fixture: &FixtureRoots) -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture.config.as_path(), fixture.schemas.as_path());
|
|
let bootstrap = match bootstrap {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let registry = test_registry("composite.test.json");
|
|
return match registry {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, value)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
fn test_registry(composite_filename: &'static str) -> ksp_core_lib::Result<crate::ConfigFileRegistry> {
|
|
let logging = crate::registry::ConfigFileDescriptor::new(
|
|
crate::FILE_ID_STD_LOGGING,
|
|
crate::ConfigFileKind::Config,
|
|
crate::DEFAULT_STD_LOGGING_FILENAME,
|
|
std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_LOGGING),
|
|
);
|
|
let logging = match logging {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let logging_schema = crate::registry::ConfigFileDescriptor::new(
|
|
crate::FILE_ID_SCHEMA_STD_LOGGING,
|
|
crate::ConfigFileKind::Schema,
|
|
crate::DEFAULT_STD_LOGGING_SCHEMA_FILENAME,
|
|
std::option::Option::None,
|
|
);
|
|
let logging_schema = match logging_schema {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let composite_schema = crate::registry::ConfigFileDescriptor::new(
|
|
crate::FILE_ID_SCHEMA_COMPOSITE,
|
|
crate::ConfigFileKind::Schema,
|
|
crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME,
|
|
std::option::Option::None,
|
|
);
|
|
let composite_schema = match composite_schema {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let composite = crate::registry::ConfigFileDescriptor::new(
|
|
TEST_COMPOSITE_FILE_ID,
|
|
crate::ConfigFileKind::Config,
|
|
composite_filename,
|
|
std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE),
|
|
);
|
|
let composite = match composite {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return crate::registry::build_registry([logging, logging_schema, composite_schema, composite]);
|
|
}
|
|
|
|
fn prepare_fixture(fixture: &FixtureRoots, composite: &str) -> std::io::Result<()> {
|
|
cleanup_fixture(fixture);
|
|
let config = std::fs::create_dir_all(fixture.config.as_path());
|
|
if let std::result::Result::Err(error) = config {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let schemas = std::fs::create_dir_all(fixture.schemas.as_path());
|
|
if let std::result::Result::Err(error) = schemas {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let workspace = workspace_root();
|
|
let logging = std::fs::copy(workspace.join("config/std.logging.json"), fixture.config.join(crate::DEFAULT_STD_LOGGING_FILENAME));
|
|
if let std::result::Result::Err(error) = logging {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let logging_schema =
|
|
std::fs::copy(workspace.join("config/schemas/std.logging.schema.json"), fixture.schemas.join(crate::DEFAULT_STD_LOGGING_SCHEMA_FILENAME));
|
|
if let std::result::Result::Err(error) = logging_schema {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let composite_schema =
|
|
std::fs::copy(workspace.join("config/schemas/composite.schema.json"), fixture.schemas.join(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME));
|
|
if let std::result::Result::Err(error) = composite_schema {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
return std::fs::write(fixture.config.join("composite.test.json"), composite);
|
|
}
|
|
|
|
fn workspace_root() -> std::path::PathBuf {
|
|
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
}
|
|
|
|
struct FixtureRoots {
|
|
root: std::path::PathBuf,
|
|
config: std::path::PathBuf,
|
|
schemas: std::path::PathBuf,
|
|
}
|
|
|
|
fn fixture_roots(name: &str) -> FixtureRoots {
|
|
let root = std::env::temp_dir().join(format!("ksp-config-composite-{name}-{}", std::process::id()));
|
|
return FixtureRoots { config: root.join("config"), schemas: root.join("schemas"), root };
|
|
}
|
|
|
|
fn cleanup_fixture(fixture: &FixtureRoots) {
|
|
let removal = std::fs::remove_dir_all(fixture.root.as_path());
|
|
if let std::result::Result::Err(error) = removal
|
|
&& error.kind() != std::io::ErrorKind::NotFound
|
|
{
|
|
eprintln!("unable to cleanup Config composite fixture {}: {error}", fixture.root.display());
|
|
}
|
|
}
|