mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-15 01:55:13 +02:00
Opt-in: with the env set, loading a legacy omnigraph.yaml is a hard error pointing at config migrate — the regression guard for migrated teams (a stray legacy file would otherwise silently outrank operator config during the window) and the rehearsal for stage 5's removal. Strict refuses the FILE, never its absence: flag-less invocations on migrated setups are untouched. Inert unless set. The RFC's stages-1-3-then-4 release gap collapsed honestly: no version boundary was crossed between them, so all four ship in the same release (noted in the RFC). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
648 lines
21 KiB
Rust
648 lines
21 KiB
Rust
//! init/config scaffolding, schema plan/apply, graphs listing, version.
|
|
//! Moved verbatim from tests/cli.rs in the modularization.
|
|
|
|
use std::fs;
|
|
|
|
use lance::index::DatasetIndexExt;
|
|
use omnigraph::db::{Omnigraph, ReadTarget};
|
|
use serde_json::Value;
|
|
use tempfile::tempdir;
|
|
|
|
mod support;
|
|
|
|
use support::*;
|
|
|
|
|
|
#[test]
|
|
fn version_command_prints_current_cli_version() {
|
|
let output = output_success(cli().arg("version"));
|
|
let stdout = stdout_string(&output);
|
|
|
|
assert_eq!(
|
|
stdout.trim(),
|
|
format!("omnigraph {}", env!("CARGO_PKG_VERSION"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_creates_graph_successfully_on_missing_local_directory() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema = fixture("test.pg");
|
|
|
|
let output = output_success(cli().arg("init").arg("--schema").arg(&schema).arg(&graph));
|
|
let stdout = stdout_string(&output);
|
|
|
|
assert!(stdout.contains("initialized"));
|
|
assert!(graph.join("_schema.pg").exists());
|
|
assert!(graph.join("__manifest").exists());
|
|
// RFC-008 stage 3: init no longer scaffolds the legacy config file.
|
|
assert!(!temp.path().join("omnigraph.yaml").exists());
|
|
}
|
|
|
|
#[test]
|
|
fn schema_plan_json_reports_supported_additive_change() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("next.pg");
|
|
init_graph(&graph);
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
" age: I32?\n}",
|
|
" age: I32?\n nickname: String?\n}",
|
|
);
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("plan")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
assert_eq!(payload["supported"], true);
|
|
assert_eq!(payload["step_count"], 1);
|
|
assert_eq!(payload["steps"][0]["kind"], "add_property");
|
|
assert_eq!(payload["steps"][0]["type_kind"], "node");
|
|
assert_eq!(payload["steps"][0]["type_name"], "Person");
|
|
assert_eq!(payload["steps"][0]["property_name"], "nickname");
|
|
}
|
|
|
|
#[test]
|
|
fn schema_plan_json_reports_unsupported_type_change() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("breaking.pg");
|
|
init_graph(&graph);
|
|
|
|
let breaking_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace("age: I32?", "age: I64?");
|
|
fs::write(&schema_path, breaking_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("plan")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
assert_eq!(payload["supported"], false);
|
|
assert!(payload["steps"].as_array().unwrap().iter().any(|step| {
|
|
step["kind"] == "unsupported_change"
|
|
&& step["entity"]
|
|
.as_str()
|
|
.unwrap_or_default()
|
|
.contains("Person.age")
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_json_applies_supported_migration() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("next.pg");
|
|
init_graph(&graph);
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
" age: I32?\n}",
|
|
" age: I32?\n nickname: String?\n}",
|
|
);
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
|
|
assert_eq!(payload["supported"], true);
|
|
assert_eq!(payload["applied"], true);
|
|
assert_eq!(payload["step_count"], 1);
|
|
|
|
let db = tokio::runtime::Runtime::new()
|
|
.unwrap()
|
|
.block_on(Omnigraph::open(graph.to_string_lossy().as_ref()))
|
|
.unwrap();
|
|
assert!(
|
|
db.catalog().node_types["Person"]
|
|
.properties
|
|
.contains_key("nickname")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_human_reports_noop() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = fixture("test.pg");
|
|
init_graph(&graph);
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg(&graph),
|
|
);
|
|
let stdout = stdout_string(&output);
|
|
|
|
assert!(stdout.contains("applied: no"));
|
|
assert!(stdout.contains("no schema changes"));
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_json_renames_type_and_updates_snapshot() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("rename.pg");
|
|
init_graph(&graph);
|
|
|
|
let renamed_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace("node Person {\n", "node Human @rename_from(\"Person\") {\n")
|
|
.replace("edge Knows: Person -> Person", "edge Knows: Human -> Human")
|
|
.replace(
|
|
"edge WorksAt: Person -> Company",
|
|
"edge WorksAt: Human -> Company",
|
|
);
|
|
fs::write(&schema_path, renamed_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
let db = tokio::runtime::Runtime::new()
|
|
.unwrap()
|
|
.block_on(Omnigraph::open(graph.to_string_lossy().as_ref()))
|
|
.unwrap();
|
|
let snapshot = tokio::runtime::Runtime::new()
|
|
.unwrap()
|
|
.block_on(db.snapshot_of(ReadTarget::branch("main")))
|
|
.unwrap();
|
|
assert!(snapshot.entry("node:Human").is_some());
|
|
assert!(snapshot.entry("node:Person").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_json_renames_property_and_updates_catalog() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("rename-property.pg");
|
|
init_graph(&graph);
|
|
|
|
let renamed_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace("age: I32?", "years: I32? @rename_from(\"age\")");
|
|
fs::write(&schema_path, renamed_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
let db = tokio::runtime::Runtime::new()
|
|
.unwrap()
|
|
.block_on(Omnigraph::open(graph.to_string_lossy().as_ref()))
|
|
.unwrap();
|
|
let person = &db.catalog().node_types["Person"];
|
|
assert!(person.properties.contains_key("years"));
|
|
assert!(!person.properties.contains_key("age"));
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_json_adds_index_for_existing_property() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("index.pg");
|
|
init_graph(&graph);
|
|
|
|
let before_index_count = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
let db = Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
.await
|
|
.unwrap();
|
|
let snapshot = db.snapshot_of(ReadTarget::branch("main")).await.unwrap();
|
|
let dataset = snapshot.open("node:Person").await.unwrap();
|
|
dataset.load_indices().await.unwrap().len()
|
|
});
|
|
|
|
let indexed_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace("name: String @key", "name: String @key @index");
|
|
fs::write(&schema_path, indexed_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
let after_index_count = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
let db = Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
.await
|
|
.unwrap();
|
|
let snapshot = db.snapshot_of(ReadTarget::branch("main")).await.unwrap();
|
|
let dataset = snapshot.open("node:Person").await.unwrap();
|
|
dataset.load_indices().await.unwrap().len()
|
|
});
|
|
assert!(after_index_count > before_index_count);
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_rejects_unsupported_plan() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("breaking.pg");
|
|
init_graph(&graph);
|
|
|
|
let breaking_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace("age: I32?", "age: I64?");
|
|
fs::write(&schema_path, breaking_schema).unwrap();
|
|
|
|
let output = output_failure(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg(&graph),
|
|
);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(stderr.contains("changing property type"));
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_rejects_when_non_main_branch_exists() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("next.pg");
|
|
init_graph(&graph);
|
|
output_success(
|
|
cli()
|
|
.arg("branch")
|
|
.arg("create")
|
|
.arg("--from")
|
|
.arg("main")
|
|
.arg("--uri")
|
|
.arg(&graph)
|
|
.arg("feature"),
|
|
);
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
" age: I32?\n}",
|
|
" age: I32?\n nickname: String?\n}",
|
|
);
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
let output = output_failure(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg(&graph),
|
|
);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(stderr.contains("schema apply requires a graph with only main"));
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_allow_data_loss_flag_promotes_drops_to_hard() {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("drop-age.pg");
|
|
init_graph(&graph);
|
|
|
|
// Drop the nullable `age` column.
|
|
let next_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace(" age: I32?\n", "");
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--allow-data-loss")
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
let drop_step = payload["steps"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|s| s["kind"] == "drop_property")
|
|
.expect("plan should include a drop_property step");
|
|
assert_eq!(
|
|
drop_step["mode"], "hard",
|
|
"--allow-data-loss should promote Soft → Hard; full step: {drop_step}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn schema_apply_without_allow_data_loss_keeps_soft_drops() {
|
|
// Symmetric to the above: same schema change without the flag →
|
|
// drops stay Soft. Pins default semantics against accidental Hard
|
|
// promotion if a future refactor changes the option threading.
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
let schema_path = temp.path().join("drop-age-soft.pg");
|
|
init_graph(&graph);
|
|
|
|
let next_schema = fs::read_to_string(fixture("test.pg"))
|
|
.unwrap()
|
|
.replace(" age: I32?\n", "");
|
|
fs::write(&schema_path, next_schema).unwrap();
|
|
|
|
let output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("apply")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
|
assert_eq!(payload["applied"], true);
|
|
|
|
let drop_step = payload["steps"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|s| s["kind"] == "drop_property")
|
|
.expect("plan should include a drop_property step");
|
|
assert_eq!(
|
|
drop_step["mode"], "soft",
|
|
"no flag should leave drops Soft; full step: {drop_step}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn schema_plan_parity_cli_and_sdk() {
|
|
// Same .pg through `Omnigraph::plan_schema_with_options` (SDK) and
|
|
// `omnigraph schema plan --json` (CLI). Asserts the steps array is
|
|
// byte-identical after JSON round-trip. HTTP doesn't expose a
|
|
// separate /schema/plan route — that side of parity is covered by
|
|
// the HTTP soft/hard drop tests, which exercise apply with
|
|
// identical fixtures.
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
init_graph(&graph);
|
|
let schema_path = temp.path().join("plan-parity.pg");
|
|
let next_schema = fs::read_to_string(fixture("test.pg")).unwrap().replace(
|
|
" age: I32?\n}",
|
|
" age: I32?\n nickname: String?\n}",
|
|
);
|
|
fs::write(&schema_path, &next_schema).unwrap();
|
|
|
|
// CLI side.
|
|
let cli_output = output_success(
|
|
cli()
|
|
.arg("schema")
|
|
.arg("plan")
|
|
.arg("--schema")
|
|
.arg(&schema_path)
|
|
.arg("--json")
|
|
.arg(&graph),
|
|
);
|
|
let cli_payload: Value = serde_json::from_slice(&cli_output.stdout).unwrap();
|
|
|
|
// SDK side: open graph, call plan_schema.
|
|
let plan = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
let db = Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
.await
|
|
.unwrap();
|
|
db.plan_schema(&next_schema).await.unwrap()
|
|
});
|
|
let sdk_steps = serde_json::to_value(&plan.steps).unwrap();
|
|
|
|
assert_eq!(
|
|
cli_payload["steps"], sdk_steps,
|
|
"CLI plan steps must match SDK plan steps for identical input",
|
|
);
|
|
assert_eq!(cli_payload["supported"], plan.supported);
|
|
}
|
|
|
|
#[test]
|
|
fn graphs_subcommand_help_lists_list_only() {
|
|
let output = output_success(cli().arg("graphs").arg("--help"));
|
|
let stdout = stdout_string(&output);
|
|
assert!(
|
|
stdout.contains("list"),
|
|
"expected `list` subcommand in help output:\n{stdout}"
|
|
);
|
|
let lowered = stdout.to_lowercase();
|
|
assert!(
|
|
!lowered.contains("create a new graph"),
|
|
"graph create should not be in v0.6.0 help; got:\n{stdout}"
|
|
);
|
|
assert!(
|
|
!lowered.contains("delete a graph"),
|
|
"graph delete should not be in v0.6.0 help; got:\n{stdout}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn graphs_list_against_local_uri_errors_with_remote_only_message() {
|
|
let output = output_failure(
|
|
cli()
|
|
.arg("graphs")
|
|
.arg("list")
|
|
.arg("--uri")
|
|
.arg("/tmp/local"),
|
|
);
|
|
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
|
|
assert!(
|
|
stderr.contains("remote multi-graph server URL"),
|
|
"expected 'remote multi-graph server URL' rejection in stderr; got:\n{stderr}"
|
|
);
|
|
}
|
|
|
|
/// RFC-008 stage 1: loading a legacy omnigraph.yaml emits the per-key
|
|
/// deprecation block (the migration map applied to THIS file), suppressible
|
|
/// via OMNIGRAPH_SUPPRESS_YAML_DEPRECATION.
|
|
#[test]
|
|
fn legacy_config_load_warns_per_key_and_suppression_silences() {
|
|
let temp = tempdir().unwrap();
|
|
fs::write(
|
|
temp.path().join("omnigraph.yaml"),
|
|
"cli:\n actor: act-x\ngraphs:\n g:\n uri: /tmp/never-opened\n",
|
|
)
|
|
.unwrap();
|
|
|
|
// `graphs list --json` loads the config and exits without touching the
|
|
// graph URI.
|
|
let output = cli()
|
|
.current_dir(temp.path())
|
|
.arg("graphs")
|
|
.arg("list")
|
|
.arg("--json")
|
|
.output()
|
|
.unwrap();
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("deprecated (RFC-008)") && stderr.contains("`cli.actor` -> `operator.actor`"),
|
|
"{stderr}"
|
|
);
|
|
assert!(stderr.contains("config migrate"), "{stderr}");
|
|
|
|
let output = cli()
|
|
.current_dir(temp.path())
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
.arg("graphs")
|
|
.arg("list")
|
|
.arg("--json")
|
|
.output()
|
|
.unwrap();
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(!stderr.contains("deprecated (RFC-008)"), "{stderr}");
|
|
}
|
|
|
|
/// RFC-008 stage 2: `config migrate` proposes the split read-only, applies
|
|
/// it with --write (operator merge never clobbers; cluster.yaml emitted),
|
|
/// and a second --write is idempotent.
|
|
#[test]
|
|
fn config_migrate_splits_legacy_config() {
|
|
let temp = tempdir().unwrap();
|
|
fs::write(
|
|
temp.path().join("omnigraph.yaml"),
|
|
"graphs:\n prod:\n uri: https://graph.example.com\n bearer_token_env: PROD_TOKEN\ncli:\n actor: act-me\n output_format: json\npolicy:\n file: ./top.policy.yaml\n",
|
|
)
|
|
.unwrap();
|
|
let operator_home = tempfile::tempdir().unwrap();
|
|
fs::write(
|
|
operator_home.path().join("config.yaml"),
|
|
"operator:\n actor: act-existing\n",
|
|
)
|
|
.unwrap();
|
|
|
|
// Read-only proposal: names both halves, writes nothing.
|
|
let output = cli()
|
|
.current_dir(temp.path())
|
|
.env("OMNIGRAPH_HOME", operator_home.path())
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
.arg("config")
|
|
.arg("migrate")
|
|
.output()
|
|
.unwrap();
|
|
assert!(output.status.success(), "{output:?}");
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(stdout.contains("team half -> cluster.yaml"), "{stdout}");
|
|
assert!(stdout.contains("operator.actor: act-me"), "{stdout}");
|
|
assert!(stdout.contains("omnigraph login prod"), "{stdout}");
|
|
assert!(!temp.path().join("cluster.yaml").exists());
|
|
|
|
// --write: cluster.yaml lands; the existing operator actor is KEPT.
|
|
let output = cli()
|
|
.current_dir(temp.path())
|
|
.env("OMNIGRAPH_HOME", operator_home.path())
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
.arg("config")
|
|
.arg("migrate")
|
|
.arg("--write")
|
|
.output()
|
|
.unwrap();
|
|
assert!(output.status.success(), "{output:?}");
|
|
let cluster = fs::read_to_string(temp.path().join("cluster.yaml")).unwrap();
|
|
assert!(cluster.contains("version: 1") && cluster.contains(" prod:"), "{cluster}");
|
|
let operator_text =
|
|
fs::read_to_string(operator_home.path().join("config.yaml")).unwrap();
|
|
assert!(operator_text.contains("act-existing"), "{operator_text}");
|
|
assert!(!operator_text.contains("act-me"), "existing keys win: {operator_text}");
|
|
assert!(operator_text.contains("output: json"), "{operator_text}");
|
|
assert!(
|
|
operator_text.contains("url: https://graph.example.com"),
|
|
"{operator_text}"
|
|
);
|
|
|
|
// Second --write: cluster.yaml exists -> proposal file, no clobber.
|
|
let output = cli()
|
|
.current_dir(temp.path())
|
|
.env("OMNIGRAPH_HOME", operator_home.path())
|
|
.env("OMNIGRAPH_SUPPRESS_YAML_DEPRECATION", "1")
|
|
.arg("config")
|
|
.arg("migrate")
|
|
.arg("--write")
|
|
.output()
|
|
.unwrap();
|
|
assert!(output.status.success(), "{output:?}");
|
|
assert!(temp.path().join("cluster.yaml.proposed").exists());
|
|
}
|
|
|
|
/// RFC-008 stage 4: OMNIGRAPH_NO_LEGACY_CONFIG refuses a present legacy
|
|
/// file (pointing at config migrate) but changes nothing on migrated
|
|
/// setups with no file.
|
|
#[test]
|
|
fn strict_mode_refuses_legacy_file_but_not_its_absence() {
|
|
let temp = tempdir().unwrap();
|
|
fs::write(temp.path().join("omnigraph.yaml"), "cli:\n actor: a\n").unwrap();
|
|
let output = cli()
|
|
.current_dir(temp.path())
|
|
.env("OMNIGRAPH_NO_LEGACY_CONFIG", "1")
|
|
.arg("graphs")
|
|
.arg("list")
|
|
.arg("--json")
|
|
.output()
|
|
.unwrap();
|
|
assert!(!output.status.success());
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("OMNIGRAPH_NO_LEGACY_CONFIG") && stderr.contains("config migrate"),
|
|
"{stderr}"
|
|
);
|
|
|
|
// Migrated setup (no file): strict mode is a no-op — a config-loading
|
|
// command that tolerates empty defaults succeeds.
|
|
let clean = tempdir().unwrap();
|
|
let output = cli()
|
|
.current_dir(clean.path())
|
|
.env("OMNIGRAPH_NO_LEGACY_CONFIG", "1")
|
|
.arg("queries")
|
|
.arg("list")
|
|
.arg("--json")
|
|
.output()
|
|
.unwrap();
|
|
assert!(output.status.success(), "{output:?}");
|
|
}
|