@@ -186,6 +186,175 @@ func TestBackupSummary(t *testing.T) {
186186 }
187187}
188188
189+ // --- RBAC tests for /api/backups/configs/{id} (PUT/DELETE) ---
190+
191+ // seedBackupConfigForApp creates an app + a backup config and returns the cfg ID.
192+ func seedBackupConfigForApp (t * testing.T , st * store.Store , slug string ) int64 {
193+ t .Helper ()
194+ if err := st .UpsertApp (& store.App {Name : slug , Slug : slug , ComposePath : "/tmp/" + slug + ".yml" , Status : "running" }, nil ); err != nil {
195+ t .Fatalf ("upsert app %s: %v" , slug , err )
196+ }
197+ app , err := st .GetAppBySlug (slug )
198+ if err != nil {
199+ t .Fatalf ("get app %s: %v" , slug , err )
200+ }
201+ cfg := & store.BackupConfig {
202+ AppID : app .ID ,
203+ Strategy : "postgres" ,
204+ Target : "local" ,
205+ ScheduleCron : "0 2 * * *" ,
206+ RetentionMode : "count" ,
207+ RetentionCount : 3 ,
208+ }
209+ if err := st .CreateBackupConfig (cfg ); err != nil {
210+ t .Fatalf ("create cfg: %v" , err )
211+ }
212+ return cfg .ID
213+ }
214+
215+ func TestUpdateBackupConfig_ManageWithGrant (t * testing.T ) {
216+ srv , st , _ := setupUserTestServer (t )
217+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
218+ app , _ := st .GetAppBySlug ("alpha" )
219+
220+ manageCookie := loginAs (t , srv , st , "mgr" , "managepass1" , "manage" )
221+ mgr , _ := st .GetUserByUsername ("mgr" )
222+ if err := st .GrantAppAccess (mgr .ID , app .ID ); err != nil {
223+ t .Fatalf ("grant: %v" , err )
224+ }
225+
226+ body := map [string ]any {
227+ "strategy" : "postgres" ,
228+ "target" : "local" ,
229+ "schedule_cron" : "0 3 * * *" ,
230+ "retention_mode" : "count" ,
231+ "retention_count" : 7 ,
232+ }
233+ req := authedRequest (t , http .MethodPut , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), body , manageCookie )
234+ w := httptest .NewRecorder ()
235+ srv .Handler ().ServeHTTP (w , req )
236+ if w .Code != http .StatusOK {
237+ t .Fatalf ("status = %d, want 200; body: %s" , w .Code , w .Body .String ())
238+ }
239+ }
240+
241+ func TestUpdateBackupConfig_ManageWithoutGrant (t * testing.T ) {
242+ srv , st , _ := setupUserTestServer (t )
243+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
244+
245+ manageCookie := loginAs (t , srv , st , "mgr" , "managepass1" , "manage" )
246+
247+ body := map [string ]any {"strategy" : "postgres" , "target" : "local" }
248+ req := authedRequest (t , http .MethodPut , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), body , manageCookie )
249+ w := httptest .NewRecorder ()
250+ srv .Handler ().ServeHTTP (w , req )
251+ if w .Code != http .StatusNotFound {
252+ t .Fatalf ("status = %d, want 404" , w .Code )
253+ }
254+ }
255+
256+ func TestUpdateBackupConfig_ViewerForbidden (t * testing.T ) {
257+ srv , st , _ := setupUserTestServer (t )
258+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
259+ app , _ := st .GetAppBySlug ("alpha" )
260+
261+ viewerCookie := loginAs (t , srv , st , "v1" , "viewerpass1" , "viewer" )
262+ v , _ := st .GetUserByUsername ("v1" )
263+ if err := st .GrantAppAccess (v .ID , app .ID ); err != nil {
264+ t .Fatalf ("grant: %v" , err )
265+ }
266+
267+ body := map [string ]any {"strategy" : "postgres" , "target" : "local" }
268+ req := authedRequest (t , http .MethodPut , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), body , viewerCookie )
269+ w := httptest .NewRecorder ()
270+ srv .Handler ().ServeHTTP (w , req )
271+ if w .Code != http .StatusForbidden {
272+ t .Fatalf ("status = %d, want 403" , w .Code )
273+ }
274+ }
275+
276+ func TestUpdateBackupConfig_SuperAdmin (t * testing.T ) {
277+ srv , st , adminCookie := setupUserTestServer (t )
278+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
279+
280+ body := map [string ]any {
281+ "strategy" : "postgres" ,
282+ "target" : "local" ,
283+ "schedule_cron" : "0 4 * * *" ,
284+ "retention_mode" : "count" ,
285+ "retention_count" : 9 ,
286+ }
287+ req := authedRequest (t , http .MethodPut , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), body , adminCookie )
288+ w := httptest .NewRecorder ()
289+ srv .Handler ().ServeHTTP (w , req )
290+ if w .Code != http .StatusOK {
291+ t .Fatalf ("status = %d, want 200; body: %s" , w .Code , w .Body .String ())
292+ }
293+ }
294+
295+ func TestDeleteBackupConfig_ManageWithGrant (t * testing.T ) {
296+ srv , st , _ := setupUserTestServer (t )
297+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
298+ app , _ := st .GetAppBySlug ("alpha" )
299+
300+ manageCookie := loginAs (t , srv , st , "mgr" , "managepass1" , "manage" )
301+ mgr , _ := st .GetUserByUsername ("mgr" )
302+ if err := st .GrantAppAccess (mgr .ID , app .ID ); err != nil {
303+ t .Fatalf ("grant: %v" , err )
304+ }
305+
306+ req := authedRequest (t , http .MethodDelete , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), nil , manageCookie )
307+ w := httptest .NewRecorder ()
308+ srv .Handler ().ServeHTTP (w , req )
309+ if w .Code != http .StatusNoContent {
310+ t .Fatalf ("status = %d, want 204; body: %s" , w .Code , w .Body .String ())
311+ }
312+ }
313+
314+ func TestDeleteBackupConfig_ManageWithoutGrant (t * testing.T ) {
315+ srv , st , _ := setupUserTestServer (t )
316+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
317+
318+ manageCookie := loginAs (t , srv , st , "mgr" , "managepass1" , "manage" )
319+
320+ req := authedRequest (t , http .MethodDelete , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), nil , manageCookie )
321+ w := httptest .NewRecorder ()
322+ srv .Handler ().ServeHTTP (w , req )
323+ if w .Code != http .StatusNotFound {
324+ t .Fatalf ("status = %d, want 404" , w .Code )
325+ }
326+ }
327+
328+ func TestDeleteBackupConfig_ViewerForbidden (t * testing.T ) {
329+ srv , st , _ := setupUserTestServer (t )
330+ cfgID := seedBackupConfigForApp (t , st , "alpha" )
331+ app , _ := st .GetAppBySlug ("alpha" )
332+
333+ viewerCookie := loginAs (t , srv , st , "v1" , "viewerpass1" , "viewer" )
334+ v , _ := st .GetUserByUsername ("v1" )
335+ if err := st .GrantAppAccess (v .ID , app .ID ); err != nil {
336+ t .Fatalf ("grant: %v" , err )
337+ }
338+
339+ req := authedRequest (t , http .MethodDelete , fmt .Sprintf ("/api/backups/configs/%d" , cfgID ), nil , viewerCookie )
340+ w := httptest .NewRecorder ()
341+ srv .Handler ().ServeHTTP (w , req )
342+ if w .Code != http .StatusForbidden {
343+ t .Fatalf ("status = %d, want 403" , w .Code )
344+ }
345+ }
346+
347+ func TestUpdateBackupConfig_NotFound (t * testing.T ) {
348+ srv , _ , adminCookie := setupUserTestServer (t )
349+ body := map [string ]any {"strategy" : "postgres" , "target" : "local" }
350+ req := authedRequest (t , http .MethodPut , "/api/backups/configs/99999" , body , adminCookie )
351+ w := httptest .NewRecorder ()
352+ srv .Handler ().ServeHTTP (w , req )
353+ if w .Code != http .StatusNotFound {
354+ t .Fatalf ("status = %d, want 404" , w .Code )
355+ }
356+ }
357+
189358func TestTriggerBackupConfig (t * testing.T ) {
190359 srv , s := newTestServer (t )
191360 s .UpsertApp (& store.App {Name : "myapp" , Slug : "myapp" , ComposePath : "/tmp/1.yml" , Status : "running" }, nil )
0 commit comments