From 27dc04819f7fb87de9ba46c3e1fc8c17e56c7fc8 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Thu, 25 Jun 2026 08:06:44 +0200 Subject: [PATCH] test(cli): journey for declarative app-var reconciliation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `apply_reconciles_app_vars` drives the full cycle through `pic` against a real server: a manifest `[vars] region = "eu"` is applied (var created + injected — a script `vars::get("region")` returns "eu"), the value is changed and re-applied (Update, returns "us"), then the var is dropped and `apply --prune` removes it (`vars::get` resolves to `()` / JSON null). Exercises the Bundle.vars wire, diff_vars create/update/delete, the in-tx writes, and the runtime resolver end to end. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/picloud-cli/tests/apply.rs | 102 ++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/crates/picloud-cli/tests/apply.rs b/crates/picloud-cli/tests/apply.rs index 771b355..52b1d77 100644 --- a/crates/picloud-cli/tests/apply.rs +++ b/crates/picloud-cli/tests/apply.rs @@ -151,3 +151,105 @@ fn apply_rejects_bad_bundle_atomically() { "a failed apply must leave nothing behind:\n{s}" ); } + +#[ignore = "needs DATABASE_URL pointing at a running Postgres"] +#[test] +fn apply_reconciles_app_vars() { + let Some(fx) = common::fixture_or_skip() else { + return; + }; + let env = common::admin_env(fx); + let slug = common::unique_slug("apply-vars"); + common::pic_as(&env) + .args(["apps", "create", &slug]) + .assert() + .success(); + let _guard = AppGuard::new(&env.url, &env.token, &slug); + + let dir = manifest_dir(); + // The script returns the resolved `region` var verbatim, so an invoke + // reflects exactly what `apply` wrote. + fs::write( + dir.path().join("scripts/read.rhai"), + "vars::get(\"region\")", + ) + .unwrap(); + let manifest = |vars_block: &str| { + format!( + "[app]\nslug = \"{slug}\"\nname = \"Vars Test\"\n\n\ + [[scripts]]\nname = \"read\"\nfile = \"scripts/read.rhai\"\n\n\ + [[routes]]\nscript = \"read\"\nmethod = \"POST\"\n\ + host_kind = \"any\"\npath_kind = \"exact\"\npath = \"/read\"\n\n\ + {vars_block}" + ) + }; + let manifest_path = dir.path().join("picloud.toml"); + + // 1. Apply with `region = "eu"` → the var is created and injected. + fs::write(&manifest_path, manifest("[vars]\nregion = \"eu\"\n")).unwrap(); + let out = common::pic_as(&env) + .args(["apply", "--file"]) + .arg(&manifest_path) + .output() + .expect("apply"); + assert!( + out.status.success(), + "apply failed: {}", + String::from_utf8_lossy(&out.stderr) + ); + let id = script_id(&env, &slug); + assert_eq!( + invoke_body(&env, &id), + serde_json::json!("eu"), + "var applied" + ); + + // 2. Change the value → Update (not Create). + fs::write(&manifest_path, manifest("[vars]\nregion = \"us\"\n")).unwrap(); + common::pic_as(&env) + .args(["apply", "--file"]) + .arg(&manifest_path) + .assert() + .success(); + assert_eq!( + invoke_body(&env, &id), + serde_json::json!("us"), + "var updated" + ); + + // 3. Drop the var + `--prune` → Delete; `vars::get` now returns `()`. + fs::write(&manifest_path, manifest("")).unwrap(); + common::pic_as(&env) + .args(["apply", "--file"]) + .arg(&manifest_path) + .args(["--prune", "--yes"]) + .assert() + .success(); + assert_eq!( + invoke_body(&env, &id), + serde_json::Value::Null, + "var pruned → unresolved" + ); +} + +fn script_id(env: &common::TestEnv, slug: &str) -> String { + let ls = common::pic_as(env) + .args(["scripts", "ls", "--app", slug]) + .output() + .expect("scripts ls"); + common::parse_first_id(std::str::from_utf8(&ls.stdout).unwrap()) + .expect("scripts ls should produce one row") +} + +fn invoke_body(env: &common::TestEnv, id: &str) -> serde_json::Value { + let out = common::pic_as(env) + .args(["scripts", "invoke", id]) + .output() + .expect("scripts invoke"); + assert!( + out.status.success(), + "invoke failed: {}", + String::from_utf8_lossy(&out.stderr) + ); + serde_json::from_slice(&out.stdout).expect("invoke body is JSON") +}