feat(group-blobs): pic kv ls/get --group + journey + docs (M4.3+M4.4)
- `pic kv ls --group` / `pic kv get --group` (mutually exclusive with --app) hit the M4 admin API; client group_kv_list/group_kv_get. - collections journey: a script writes a shared KV value, the operator lists + fetches it via the admin API with no script. - docs: §11.6 + CLAUDE.md record M3 quotas + M4 read-only admin API. Completes M4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -556,3 +556,93 @@ fn invoke_body(env: &common::TestEnv, id: &str) -> serde_json::Value {
|
||||
);
|
||||
serde_json::from_slice(&out.stdout).expect("invoke body is JSON")
|
||||
}
|
||||
|
||||
/// §11.6 M4: the read-only operator admin API for a group's shared collections.
|
||||
/// A script writes to a shared KV collection; the operator then lists + fetches
|
||||
/// it through `pic kv ls --group` / `pic kv get --group` (no script).
|
||||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||||
#[test]
|
||||
fn operator_reads_shared_kv_via_admin_api() {
|
||||
let Some(fx) = common::fixture_or_skip() else {
|
||||
return;
|
||||
};
|
||||
let env = common::admin_env(fx);
|
||||
let group = common::unique_slug("m4-grp");
|
||||
let app = common::unique_slug("m4-app");
|
||||
|
||||
let _g = GroupGuard::new(&env.url, &env.token, &group);
|
||||
common::pic_as(&env)
|
||||
.args(["groups", "create", &group])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let dir = manifest_dir();
|
||||
let gmanifest =
|
||||
format!("[group]\nslug = \"{group}\"\nname = \"M4\"\n\ncollections = [\"catalog\"]\n");
|
||||
let gpath = dir.path().join("group.toml");
|
||||
fs::write(&gpath, &gmanifest).unwrap();
|
||||
common::pic_as(&env)
|
||||
.args(["apply", "--file"])
|
||||
.arg(&gpath)
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let _a = AppGuard::new(&env.url, &env.token, &app);
|
||||
common::pic_as(&env)
|
||||
.args(["apps", "create", &app, "--group", &group])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// A script writes a value to the shared collection.
|
||||
fs::write(
|
||||
dir.path().join("scripts/w.rhai"),
|
||||
r#"kv::shared_collection("catalog").set("op-key", #{ v: 42 }); "ok""#,
|
||||
)
|
||||
.unwrap();
|
||||
common::pic_as(&env)
|
||||
.args(["scripts", "deploy"])
|
||||
.arg(dir.path().join("scripts/w.rhai"))
|
||||
.args(["--app", &app, "--name", "w"])
|
||||
.assert()
|
||||
.success();
|
||||
common::pic_as(&env)
|
||||
.args(["scripts", "invoke", &app_script_id(&env, &app, "w")])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// Operator lists the shared collection's keys via the admin API — no script.
|
||||
let ls = String::from_utf8(
|
||||
common::pic_as(&env)
|
||||
.args(["kv", "ls", "--group", &group, "--collection", "catalog"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
ls.contains("op-key"),
|
||||
"kv ls --group must list the key:\n{ls}"
|
||||
);
|
||||
|
||||
// Operator fetches the value.
|
||||
let get = String::from_utf8(
|
||||
common::pic_as(&env)
|
||||
.args([
|
||||
"kv",
|
||||
"get",
|
||||
"--group",
|
||||
&group,
|
||||
"--collection",
|
||||
"catalog",
|
||||
"op-key",
|
||||
])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
get.contains("42"),
|
||||
"kv get --group must return the value:\n{get}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user