From 100a9bb282dd4226368b4e5fac398726c7acf653 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 30 Apr 2026 11:28:21 +0300 Subject: task da: enforce media access and owner/admin role permissions Changes: - MediaService.ListMedia now accepts userID and filters by allowed sets for non-admins via AllowedSetIDs in repository.MediaFilter. - Handlers pass userID into ListMedia; API returns 403 for forbidden. - Added verifyModifyAccess and verifySetModifyAccess helpers so only owners/admins can upload, soft-delete, restore, and regenerate thumbnails/covers; viewers are blocked. - GetMediaDetail, ToggleFavorite, AssignTag, RemoveTag, notes, and shares now consistently verifyAccess before proceeding. - Handlers handle ErrForbidden with 403 for soft-delete and restore. - Added negative tests proving viewers cannot mutate and unauthorized users cannot access/detail/tag/note/favorite/share inaccessible media. --- internal/service/no_rows_test.go | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'internal/service/no_rows_test.go') diff --git a/internal/service/no_rows_test.go b/internal/service/no_rows_test.go index 4d512e5..3606b63 100644 --- a/internal/service/no_rows_test.go +++ b/internal/service/no_rows_test.go @@ -22,8 +22,8 @@ func TestService_NoRows_ReturnsNil(t *testing.T) { } svc := NewMediaService(store, newMockClock(), "/tmp/media") detail, err := svc.GetMediaDetail(ctx, 99, 1) - if err != nil { - t.Fatalf("expected no error, got %v", err) + if !errors.Is(err, ErrNotFound) { + t.Fatalf("expected ErrNotFound, got %v", err) } if detail != nil { t.Fatalf("expected nil detail, got %+v", detail) @@ -32,6 +32,21 @@ func TestService_NoRows_ReturnsNil(t *testing.T) { t.Run("GetNote nil", func(t *testing.T) { store := &repository.MockStore{ + MediaRepo: repository.MockMediaRepo{ + GetMediaByIDFunc: func(ctx context.Context, id int64) (*model.Media, error) { + return &model.Media{ID: 1, SetID: 1}, nil + }, + }, + UserRepo: repository.MockUserRepo{ + GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) { + return &model.User{ID: id, IsAdmin: true}, nil + }, + }, + SetRepo: repository.MockSetRepo{ + GetSetByIDFunc: func(ctx context.Context, id int64) (*model.Set, error) { + return &model.Set{ID: id}, nil + }, + }, NoteRepo: repository.MockNoteRepo{ GetNoteFunc: func(ctx context.Context, mediaID, userID int64) (*model.Note, error) { return nil, nil @@ -50,6 +65,21 @@ func TestService_NoRows_ReturnsNil(t *testing.T) { t.Run("AssignTag creates missing tag", func(t *testing.T) { store := &repository.MockStore{ + MediaRepo: repository.MockMediaRepo{ + GetMediaByIDFunc: func(ctx context.Context, id int64) (*model.Media, error) { + return &model.Media{ID: 1, SetID: 1}, nil + }, + }, + UserRepo: repository.MockUserRepo{ + GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) { + return &model.User{ID: id, IsAdmin: true}, nil + }, + }, + SetRepo: repository.MockSetRepo{ + GetSetByIDFunc: func(ctx context.Context, id int64) (*model.Set, error) { + return &model.Set{ID: id}, nil + }, + }, TagRepo: repository.MockTagRepo{ GetTagByNameFunc: func(ctx context.Context, name string) (*model.Tag, error) { return nil, nil -- cgit v1.2.3