mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-15 01:55:13 +02:00
schema: HTTP allow_data_loss exposure + e2e drop coverage (MR-694 follow-up) (#107)
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
The schema-lint chassis v1.2 (PR #100) shipped `--allow-data-loss` on the CLI, but `SchemaApplyRequest` had no equivalent field — Hard-mode drops were CLI-only. This commit closes that feature gap and adds e2e test coverage for drop modes across HTTP + CLI, plus data preservation on additive apply, plus a CLI↔SDK plan-parity assertion. Feature gap closed: - `crates/omnigraph-server/src/api.rs` — added `allow_data_loss: bool` (default false via `#[serde(default)]`) to `SchemaApplyRequest`. Added `Default` derive so test usages can use `..Default::default()`. - `crates/omnigraph-server/src/lib.rs` — `server_schema_apply` now constructs `SchemaApplyOptions { allow_data_loss: request.allow_data_loss }` and threads through to `apply_schema_as`. - `crates/omnigraph-cli/src/main.rs` — remote-URI schema-apply path used to bail with "--allow-data-loss not yet supported on remote"; now forwards the flag into the JSON payload so the CLI behaves identically against local and remote URIs. - `openapi.json` — regenerated; only diff is the new field on `SchemaApplyRequest`. Tests added (8 new): * `crates/omnigraph-server/tests/server.rs` (+5): - `schema_apply_route_soft_drops_property_via_http` — POST schema removing nullable property, verify catalog reflects the drop AND `snapshot_at_version(pre)` still has `age` in the field list (time-travel reachability is the Soft contract). - `schema_apply_route_soft_drops_node_type_via_http` — POST schema removing `Company` node + cascading `WorksAt` edge. - `schema_apply_route_hard_drops_property_with_allow_data_loss` — POST with `allow_data_loss: true`, verify plan step reports `mode: hard`. - `schema_apply_route_keeps_drops_soft_without_flag` — same schema without flag, verify `mode: soft`. Pins default semantics against accidental Hard promotion. - `schema_apply_route_additive_property_preserves_existing_rows` — load fixture, POST adding nullable property, verify row count preserved (SDK suite covers data preservation on drops + renames; additive AddProperty wasn't pinned). Plus helpers `schema_without_age` and `schema_without_company`. * `crates/omnigraph-cli/tests/cli.rs` (+3): - `schema_apply_allow_data_loss_flag_promotes_drops_to_hard` — CLI `omnigraph schema apply --allow-data-loss --schema X.pg --json`, verify plan step has `mode: hard`. - `schema_apply_without_allow_data_loss_keeps_soft_drops` — without flag, verify Soft. - `schema_plan_parity_cli_and_sdk` — same `.pg` source through `Omnigraph::plan_schema` (SDK) and `omnigraph schema plan --json` (CLI), assert the steps array is byte-identical post-JSON. HTTP has no `/schema/plan` endpoint; apply-side parity is implicitly covered by the HTTP drop tests + CLI drop tests using identical fixtures. Docs: - `docs/user/schema-language.md` — new "Destructive drops" section documenting Soft vs Hard semantics and that `allow_data_loss` is now honored uniformly across CLI / HTTP / SDK. Verification: every new test passes; full `cargo test --workspace --locked` green; `scripts/check-agents-md.sh` passes. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e8fec2fa0f
commit
aadfa11ecb
7 changed files with 478 additions and 8 deletions
|
|
@ -2091,18 +2091,18 @@ async fn main() -> Result<()> {
|
|||
let uri = resolve_uri(&config, uri, target.as_deref())?;
|
||||
let schema_source = fs::read_to_string(&schema)?;
|
||||
let output = if is_remote_uri(&uri) {
|
||||
if allow_data_loss {
|
||||
bail!(
|
||||
"--allow-data-loss is not yet supported on remote (HTTP) schema apply; \
|
||||
use `omnigraph schema apply` against a local path or s3:// URI for now"
|
||||
);
|
||||
}
|
||||
// MR-694 PR B: SchemaApplyRequest gained an
|
||||
// allow_data_loss field so Hard-mode drops are no
|
||||
// longer CLI-only. The previous bail is gone; the
|
||||
// field is forwarded into the JSON payload, and
|
||||
// the server's `server_schema_apply` honors it.
|
||||
remote_json::<SchemaApplyOutput>(
|
||||
&http_client,
|
||||
Method::POST,
|
||||
remote_url(&uri, "/schema/apply"),
|
||||
Some(serde_json::to_value(SchemaApplyRequest {
|
||||
schema_source: schema_source.clone(),
|
||||
allow_data_loss,
|
||||
})?),
|
||||
bearer_token.as_deref(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1909,3 +1909,134 @@ fn cli_fails_for_invalid_merge_requests() {
|
|||
// alongside the run state machine. Direct-to-target writes leave nothing
|
||||
// for these CLIs to manage. Audit history is now visible via
|
||||
// `omnigraph commit list` reading the commit graph.
|
||||
|
||||
// ─── MR-694 PR B: --allow-data-loss flag end-to-end ──────────────────────
|
||||
//
|
||||
// The schema-lint chassis v1.2 (PR #100) shipped the `--allow-data-loss`
|
||||
// flag at the CLI layer; the SDK suite verifies promotion to Hard mode
|
||||
// via `apply_schema_with_options(.., SchemaApplyOptions { allow_data_loss })`.
|
||||
// These CLI tests close the integration gap so a future change that
|
||||
// drops the flag wiring in `main.rs` turns red.
|
||||
|
||||
#[test]
|
||||
fn schema_apply_allow_data_loss_flag_promotes_drops_to_hard() {
|
||||
let temp = tempdir().unwrap();
|
||||
let repo = repo_path(temp.path());
|
||||
let schema_path = temp.path().join("drop-age.pg");
|
||||
init_repo(&repo);
|
||||
|
||||
// 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(&repo),
|
||||
);
|
||||
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 repo = repo_path(temp.path());
|
||||
let schema_path = temp.path().join("drop-age-soft.pg");
|
||||
init_repo(&repo);
|
||||
|
||||
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(&repo),
|
||||
);
|
||||
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 repo = repo_path(temp.path());
|
||||
init_repo(&repo);
|
||||
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(&repo),
|
||||
);
|
||||
let cli_payload: Value = serde_json::from_slice(&cli_output.stdout).unwrap();
|
||||
|
||||
// SDK side: open repo, call plan_schema.
|
||||
let plan = tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
let db = Omnigraph::open(repo.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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue