feat(manager-core): admin is implicit app_admin; delete-script needs AppAdmin
Aligns the canonical capability rules with how the dashboard now shadows its UI. Instance admins become implicit app_admin on every app (only InstanceManageSettings stays owner-only), and the script-delete handler moves from AppWriteScript to AppAdmin so editors can save but not delete. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -293,7 +293,7 @@ async fn owner_access_matrix(pool: PgPool) {
|
||||
|
||||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||||
#[sqlx::test(migrations = "../manager-core/migrations")]
|
||||
async fn admin_can_manage_users_but_not_app_admin_settings(pool: PgPool) {
|
||||
async fn admin_is_implicit_app_admin_on_every_app(pool: PgPool) {
|
||||
let s = boot(pool.clone()).await;
|
||||
seed_user(&s.pool, "alice", "alice-pw", InstanceRole::Admin).await;
|
||||
let token = login_token(&s.server, "alice", "alice-pw").await;
|
||||
@@ -305,24 +305,32 @@ async fn admin_can_manage_users_but_not_app_admin_settings(pool: PgPool) {
|
||||
.await
|
||||
.assert_status_ok();
|
||||
|
||||
// Allowed: read default app (admin is implicit editor everywhere).
|
||||
// Allowed: read default app — admin is implicit app_admin
|
||||
// everywhere (per blueprint §11.6).
|
||||
s.server
|
||||
.get("/api/v1/admin/apps/default")
|
||||
.add_header("authorization", format!("Bearer {token}"))
|
||||
.await
|
||||
.assert_status_ok();
|
||||
|
||||
// Allowed: write scripts (implicit editor).
|
||||
// Allowed: write scripts.
|
||||
let script = create_script_via_api(&s.server, &token, s.default_app, "admin-write").await;
|
||||
assert!(script["id"].is_string());
|
||||
|
||||
// Denied: delete the default app (AppAdmin only).
|
||||
let denied = s
|
||||
.server
|
||||
// Allowed: list app members (AppAdmin gate). Pre-3.5.x this
|
||||
// 403'd; now it's the same allow as the owner sees.
|
||||
s.server
|
||||
.get("/api/v1/admin/apps/default/members")
|
||||
.add_header("authorization", format!("Bearer {token}"))
|
||||
.await
|
||||
.assert_status_ok();
|
||||
|
||||
// Allowed: delete the default app (AppAdmin).
|
||||
s.server
|
||||
.delete("/api/v1/admin/apps/default")
|
||||
.add_header("authorization", format!("Bearer {token}"))
|
||||
.await;
|
||||
assert_eq!(denied.status_code(), axum::http::StatusCode::FORBIDDEN);
|
||||
.await
|
||||
.assert_status(axum::http::StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
||||
@@ -735,7 +743,7 @@ async fn my_role_field_matches_caller_role(pool: PgPool) {
|
||||
"owner reports app_admin"
|
||||
);
|
||||
|
||||
// Admin → implicit editor everywhere.
|
||||
// Admin → implicit app_admin everywhere (post-§11.6 update).
|
||||
seed_user(&s.pool, "alice", "alice-pw", InstanceRole::Admin).await;
|
||||
let admin_token = login_token(&s.server, "alice", "alice-pw").await;
|
||||
let r = s
|
||||
@@ -746,8 +754,8 @@ async fn my_role_field_matches_caller_role(pool: PgPool) {
|
||||
r.assert_status_ok();
|
||||
assert_eq!(
|
||||
r.json::<Value>()["my_role"].as_str(),
|
||||
Some("editor"),
|
||||
"admin reports editor"
|
||||
Some("app_admin"),
|
||||
"admin reports app_admin"
|
||||
);
|
||||
|
||||
// Member with explicit `viewer` membership → viewer.
|
||||
|
||||
Reference in New Issue
Block a user