test(cli): shared-docs journey + docs (§11.6 docs C5)

End-to-end docs journey: a group declares a kv AND a docs collection via the
string-or-table manifest; an authenticated app A docs::shared_collection(
"articles").create(#{...}), app B finds it back across the subtree (exercising
the shared find DSL); a sibling-subtree app gets CollectionNotShared;
collections ls shows both kinds; re-apply NoOp. Full journey suite 116/116.

Docs: groups-and-project-tool §11.6 (KV+docs slices shipped; files/topics/queue
deferred), sdk-shape.md (docs::shared_collection + string-or-table), CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-30 19:09:41 +02:00
parent 5efb068b9f
commit a00454e5de
4 changed files with 164 additions and 17 deletions

View File

@@ -263,6 +263,137 @@ fn nearest_declaring_group_wins() {
);
}
/// §11.6 docs slice: a group declares a `docs`-kind shared collection (via the
/// string-or-table manifest, mixed with a `kv` one); an authenticated app A
/// `create`s a document and app B `find`s it back across the subtree; a
/// sibling-subtree app gets `CollectionNotShared`; `collections ls` shows both
/// kinds.
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
#[test]
fn shared_docs_collection_is_read_write_across_the_subtree() {
let Some(fx) = common::fixture_or_skip() else {
return;
};
let env = common::admin_env(fx);
let group = common::unique_slug("dcoll-grp");
let sibling = common::unique_slug("dcoll-sib");
let app_a = common::unique_slug("dcoll-a");
let app_b = common::unique_slug("dcoll-b");
let foreign = common::unique_slug("dcoll-f");
let _g = GroupGuard::new(&env.url, &env.token, &group);
let _gs = GroupGuard::new(&env.url, &env.token, &sibling);
common::pic_as(&env)
.args(["groups", "create", &group])
.assert()
.success();
common::pic_as(&env)
.args(["groups", "create", &sibling])
.assert()
.success();
// The group declares a kv AND a docs collection (string-or-table form).
let dir = manifest_dir();
let gmanifest = format!(
"[group]\nslug = \"{group}\"\nname = \"DColl\"\n\n\
collections = [\"catalog\", {{ name = \"articles\", kind = \"docs\" }}]\n"
);
let gpath = dir.path().join("group.toml");
fs::write(&gpath, &gmanifest).unwrap();
common::pic_as(&env)
.args(["apply", "--file"])
.arg(&gpath)
.assert()
.success();
// `collections ls` shows both names with their kinds.
let ls = String::from_utf8(
common::pic_as(&env)
.args(["collections", "ls", "--group", &group])
.output()
.unwrap()
.stdout,
)
.unwrap();
assert!(
ls.contains("articles") && ls.contains("docs") && ls.contains("catalog"),
"ls should list both kinds:\n{ls}"
);
// Re-apply is a no-op (both markers already exist).
common::pic_as(&env)
.args(["apply", "--file"])
.arg(&gpath)
.assert()
.success();
let _a = AppGuard::new(&env.url, &env.token, &app_a);
let _b = AppGuard::new(&env.url, &env.token, &app_b);
let _f = AppGuard::new(&env.url, &env.token, &foreign);
for (app, grp) in [(&app_a, &group), (&app_b, &group), (&foreign, &sibling)] {
common::pic_as(&env)
.args(["apps", "create", app, "--group", grp])
.assert()
.success();
}
// App A creates a document in the shared docs collection (authenticated).
fs::write(
dir.path().join("scripts/dwriter.rhai"),
r#"docs::shared_collection("articles").create(#{ t: "hi" })"#,
)
.unwrap();
common::pic_as(&env)
.args(["scripts", "deploy"])
.arg(dir.path().join("scripts/dwriter.rhai"))
.args(["--app", &app_a, "--name", "dwriter"])
.assert()
.success();
common::pic_as(&env)
.args(["scripts", "invoke", &app_script_id(&env, &app_a, "dwriter")])
.assert()
.success();
// App B finds the SAME doc back — cross-app docs sharing + the find DSL.
fs::write(
dir.path().join("scripts/dreader.rhai"),
r#"docs::shared_collection("articles").find(#{ t: "hi" })[0].data.t"#,
)
.unwrap();
common::pic_as(&env)
.args(["scripts", "deploy"])
.arg(dir.path().join("scripts/dreader.rhai"))
.args(["--app", &app_b, "--name", "dreader"])
.assert()
.success();
assert_eq!(
invoke_body(&env, &app_script_id(&env, &app_b, "dreader")),
serde_json::json!("hi"),
"app B finds the document app A created in the shared docs collection"
);
// The foreign app (sibling subtree) → CollectionNotShared.
fs::write(
dir.path().join("scripts/dpeek.rhai"),
r#"docs::shared_collection("articles").find(#{})"#,
)
.unwrap();
common::pic_as(&env)
.args(["scripts", "deploy"])
.arg(dir.path().join("scripts/dpeek.rhai"))
.args(["--app", &foreign, "--name", "dpeek"])
.assert()
.success();
let out = common::pic_as(&env)
.args(["scripts", "invoke", &app_script_id(&env, &foreign, "dpeek")])
.output()
.expect("invoke");
assert!(
!out.status.success(),
"a foreign app must not resolve another subtree's shared docs collection"
);
}
/// Id of the named script in an app (`pic scripts ls --app <a>`).
fn app_script_id(env: &common::TestEnv, app: &str, name: &str) -> String {
let ls = common::pic_as(env)