From 85b8a92a0ea747bb8c687f008d29b2a777449ecf Mon Sep 17 00:00:00 2001 From: Minseong Choi Date: Wed, 8 Jul 2026 05:58:00 +0900 Subject: [PATCH] test(api): pin restore's owner gate against a superseded former owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleRestoreBackup's owner-or-admin gate was not pinned by any test: the former-owner gate backstopped every non-owner case the suite exercised, so a broken owner gate would not redden. Add the mirror of the former-owner test — a released former owner (still the backup's former_owner, no longer the current owner) must get 403 — the sole subtest that fails when the owner gate is disabled. Found by the round-2 backup/restore mutation audit; production code unchanged. --- internal/api/handlers_backups_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/api/handlers_backups_test.go b/internal/api/handlers_backups_test.go index 167db1b..6f99edf 100644 --- a/internal/api/handlers_backups_test.go +++ b/internal/api/handlers_backups_test.go @@ -190,6 +190,26 @@ func TestRestoreBackup(t *testing.T) { } }) + t.Run("former owner after release -> 403, no restore", func(t *testing.T) { + // Mirror of the guard above, pinning the owner gate rather than the former-owner + // gate (handler comment: "must re-claim first"). owner1 took this backup, then + // released survival to "newowner". owner1 is still the backup's former_owner — so + // the former-owner gate would wave them through — but is no longer the current + // owner. Only the owner gate stops them; without it a superseded owner could roll + // a live server back onto their old world. (Disable that gate and this is the one + // subtest that reddens — the former-owner gate does not backstop this case.) + api, repo, _, restorer := mk() + repo.byName["survival"].OwnerID = "newowner" + api.External = staticExternal{p: owner} // owner1: former_owner, not current owner + w := do(api.ExternalHandler(), "POST", path, "", nil) + if w.Code != http.StatusForbidden { + t.Fatalf("code = %d, want 403 (owner gate: former owner is no longer the current owner)", w.Code) + } + if restorer.calls != 0 { + t.Fatal("a released former owner must not restore onto the current owner's server") + } + }) + t.Run("unknown server -> 404", func(t *testing.T) { api, _, _, _ := mk() api.External = staticExternal{p: owner}