From 907187b16ebf44a883f28358c2f3728865f9b8d8 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 7 May 2026 15:26:17 +0300 Subject: j1 fix admin media lifecycle QA defects --- internal/service/access.go | 29 +++++++++++++++++++++++++++++ internal/service/browse.go | 3 +++ internal/service/media_test.go | 34 ++++++++++++++++++++++++++++++++++ internal/service/write.go | 2 +- internal/service/write_test.go | 24 +++++++++++++++++++----- 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/internal/service/access.go b/internal/service/access.go index 4d630a1..e8546d6 100644 --- a/internal/service/access.go +++ b/internal/service/access.go @@ -82,6 +82,35 @@ func (h *accessHelper) verifyModifyAccess(ctx context.Context, mediaID, userID i return media, nil } +// verifyRestoreAccess checks owner/admin access for active or soft-deleted media. +func (h *accessHelper) verifyRestoreAccess(ctx context.Context, mediaID, userID int64) (*model.Media, error) { + media, err := h.store.GetMediaByID(ctx, mediaID) + if err != nil { + return nil, fmt.Errorf("get media: %w", err) + } + if media == nil { + deleted, err := h.store.ListDeletedMedia(ctx) + if err != nil { + return nil, fmt.Errorf("list deleted media: %w", err) + } + for i := range deleted { + if deleted[i].ID == mediaID { + media = &deleted[i] + break + } + } + } + if media == nil { + return nil, ErrNotFound + } + + if err := h.checkSetPermission(ctx, media.SetID, userID, model.RoleOwner); err != nil { + return nil, err + } + + return media, nil +} + // verifySetModifyAccess checks that the user is an owner or admin for a set. func (h *accessHelper) verifySetModifyAccess(ctx context.Context, setID, userID int64) error { return h.checkSetPermission(ctx, setID, userID, model.RoleOwner) diff --git a/internal/service/browse.go b/internal/service/browse.go index 7425d35..9e23fa9 100644 --- a/internal/service/browse.go +++ b/internal/service/browse.go @@ -146,6 +146,9 @@ func (s *browseService) ListMedia(ctx context.Context, userID int64, filter Medi for _, p := range perms { allowed = append(allowed, p.SetID) } + if len(allowed) == 0 { + return []model.Media{}, nil + } repoFilter.AllowedSetIDs = allowed repoFilter.UserID = userID return s.store.ListMedia(ctx, repoFilter) diff --git a/internal/service/media_test.go b/internal/service/media_test.go index d9cbebd..3d550ce 100644 --- a/internal/service/media_test.go +++ b/internal/service/media_test.go @@ -1614,6 +1614,40 @@ func TestMediaService_ListMedia_AdminAndUserFiltering(t *testing.T) { t.Fatalf("unexpected result: %+v", res) } }) + + t.Run("user with no permissions sees no media", func(t *testing.T) { + listMediaCalled := false + store := &repository.MockStore{ + UserRepo: repository.MockUserRepo{ + GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) { + return &model.User{ID: id, IsAdmin: false}, nil + }, + }, + SetPermissionRepo: repository.MockSetPermissionRepo{ + ListPermissionsByUserFunc: func(ctx context.Context, userID int64) ([]model.SetPermission, error) { + return nil, nil + }, + }, + MediaRepo: repository.MockMediaRepo{ + ListMediaFunc: func(ctx context.Context, filter repository.MediaFilter) ([]model.Media, error) { + listMediaCalled = true + return []model.Media{{ID: 5}}, nil + }, + }, + } + svc := NewMediaService(store, newMockClock(), "/tmp/media", nil, nil) + setID := int64(1) + res, err := svc.ListMedia(ctx, 2, MediaQueryFilter{SetID: &setID}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(res) != 0 { + t.Fatalf("expected no media, got %+v", res) + } + if listMediaCalled { + t.Fatal("expected repository ListMedia not to be called") + } + }) } func TestMediaService_RegenerateThumbnail(t *testing.T) { diff --git a/internal/service/write.go b/internal/service/write.go index b67e644..b5cd181 100644 --- a/internal/service/write.go +++ b/internal/service/write.go @@ -49,7 +49,7 @@ func (s *writeService) SoftDeleteMedia(ctx context.Context, mediaID, userID int6 } func (s *writeService) RestoreMedia(ctx context.Context, mediaID, userID int64) error { - _, err := s.helper.verifyModifyAccess(ctx, mediaID, userID) + _, err := s.helper.verifyRestoreAccess(ctx, mediaID, userID) if err != nil { return err } diff --git a/internal/service/write_test.go b/internal/service/write_test.go index d303431..122ca78 100644 --- a/internal/service/write_test.go +++ b/internal/service/write_test.go @@ -14,16 +14,27 @@ func TestWriteService_RestoreMedia(t *testing.T) { ctx := context.Background() tests := []struct { - name string - media *model.Media - storeErr error - wantErr bool - wantCode error + name string + media *model.Media + deletedMedia []model.Media + deletedErr error + storeErr error + wantErr bool + wantCode error }{ { name: "ok", media: &model.Media{ID: 1, SetID: 1}, }, + { + name: "soft deleted ok", + deletedMedia: []model.Media{{ID: 1, SetID: 1}}, + }, + { + name: "deleted lookup error", + deletedErr: errors.New("deleted lookup failed"), + wantErr: true, + }, { name: "store error", media: &model.Media{ID: 1, SetID: 1}, @@ -45,6 +56,9 @@ func TestWriteService_RestoreMedia(t *testing.T) { GetMediaByIDFunc: func(ctx context.Context, id int64) (*model.Media, error) { return tt.media, nil }, + ListDeletedMediaFunc: func(ctx context.Context) ([]model.Media, error) { + return tt.deletedMedia, tt.deletedErr + }, RestoreMediaFunc: func(ctx context.Context, id int64) error { return tt.storeErr }, -- cgit v1.2.3