Files
PiCloud/crates/picloud-cli/tests/vars.rs
MechaCat02 9ee85993d8 feat(vars): admin CRUD API + pic vars CLI
Completes the vars half of Phase 3 end-to-end:
- vars_repo: VarOwner{Group|App} upsert/delete/list (owner-kind-specific
  SQL; ON CONFLICT restates the partial-index predicate).
- vars_api: GET/PUT/DELETE under /apps/{id}/vars and /groups/{id}/vars,
  resolve-then-require gated on App/GroupVars{Read,Write}, secrets-style
  error mapping + key/env-scope validation.
- pic vars ls/set/rm (--group|--app, --env, --json, --tombstone).
- journey test: a group var is inherited by a descendant app's
  vars::get(), and an app-level value overrides it (proximity) — green.

386 manager-core lib tests + the vars journey pass; clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:11:46 +02:00

85 lines
2.9 KiB
Rust

//! Phase-3 config `vars`, end to end via `pic`: a group-owned var is
//! inherited by an app underneath it, and an app-owned var of the same key
//! overrides the inherited value (proximity wins, §3).
//!
//! Drives the real resolution path: a script does `vars::get("region")`
//! and returns it; we invoke it via `/api/v1/execute/{id}` and assert on
//! the body. First the group value flows down (inheritance), then an app
//! value shadows it (override).
use crate::common;
use crate::common::cleanup::{AppGuard, GroupGuard};
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn group_var_is_inherited_then_app_value_overrides() {
let Some(fx) = common::fixture_or_skip() else {
return;
};
let env = common::admin_env(fx);
let acme = common::unique_slug("v-acme");
let app = common::unique_slug("v-app");
// Group `acme`, then a `region` var on it (a JSON string "eu").
let _g_acme = GroupGuard::new(&env.url, &env.token, &acme);
common::pic_as(&env)
.args(["groups", "create", &acme])
.assert()
.success();
common::pic_as(&env)
.args(["vars", "set", "region", "eu", "--group", &acme])
.assert()
.success();
// App under acme with a script that reads + returns the resolved var.
let _app = AppGuard::new(&env.url, &env.token, &app);
common::pic_as(&env)
.args(["apps", "create", &app, "--group", &acme])
.assert()
.success();
let fixture = common::fixture_path("read-var.rhai");
common::pic_as(&env)
.args([
"scripts",
"deploy",
fixture.to_str().unwrap(),
"--app",
&app,
])
.assert()
.success();
// Resolve the deployed script's id.
let ls = common::pic_as(&env)
.args(["scripts", "ls", "--app", &app])
.output()
.expect("scripts ls");
let id = common::parse_first_id(std::str::from_utf8(&ls.stdout).unwrap())
.expect("scripts ls should produce one row");
// Inherited: the app has no own `region`, so the group's "eu" resolves.
assert_eq!(invoke_body(&env, &id), serde_json::json!("eu"), "inherited");
// Proximity override: an app-owned `region` shadows the group value.
common::pic_as(&env)
.args(["vars", "set", "region", "us", "--app", &app])
.assert()
.success();
assert_eq!(invoke_body(&env, &id), serde_json::json!("us"), "override");
}
/// Invoke a script via `pic scripts invoke <id>` (→ `/api/v1/execute/{id}`)
/// and parse its JSON body.
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")
}