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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user