diff --git a/core/archiver_test.go b/core/archiver_test.go new file mode 100644 index 00000000..4e0f91b2 --- /dev/null +++ b/core/archiver_test.go @@ -0,0 +1,211 @@ +package core_test + +import ( + "archive/zip" + "bytes" + "context" + "io" + "strings" + + "github.com/Masterminds/squirrel" + "github.com/navidrome/navidrome/core" + "github.com/navidrome/navidrome/model" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" +) + +var _ = Describe("Archiver", func() { + var ( + arch core.Archiver + ms *mockMediaStreamer + ds *mockDataStore + sh *mockShare + ) + + BeforeEach(func() { + ms = &mockMediaStreamer{} + ds = &mockDataStore{} + sh = &mockShare{} + arch = core.NewArchiver(ms, ds, sh) + }) + + Context("ZipAlbum", func() { + It("zips an album correctly", func() { + mfs := model.MediaFiles{ + {Path: "test_data/01 - track1.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album 1", DiscNumber: 1}, + {Path: "test_data/02 - track2.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album 1", DiscNumber: 1}, + } + + mfRepo := &mockMediaFileRepository{} + mfRepo.On("GetAll", []model.QueryOptions{{ + Filters: squirrel.Eq{"album_id": "1"}, + Sort: "album", + }}).Return(mfs, nil) + + ds.On("MediaFile", mock.Anything).Return(mfRepo) + ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + + out := new(bytes.Buffer) + err := arch.ZipAlbum(context.Background(), "1", "mp3", 128, out) + Expect(err).To(BeNil()) + + zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len())) + Expect(err).To(BeNil()) + + Expect(len(zr.File)).To(Equal(2)) + Expect(zr.File[0].Name).To(Equal("Album 1/01 - track1.mp3")) + Expect(zr.File[1].Name).To(Equal("Album 1/02 - track2.mp3")) + }) + }) + + Context("ZipArtist", func() { + It("zips an artist's albums correctly", func() { + mfs := model.MediaFiles{ + {Path: "test_data/01 - track1.mp3", Suffix: "mp3", AlbumArtistID: "1", AlbumID: "1", Album: "Album 1", DiscNumber: 1}, + {Path: "test_data/02 - track2.mp3", Suffix: "mp3", AlbumArtistID: "1", AlbumID: "1", Album: "Album 1", DiscNumber: 1}, + } + + mfRepo := &mockMediaFileRepository{} + mfRepo.On("GetAll", []model.QueryOptions{{ + Filters: squirrel.Eq{"album_artist_id": "1"}, + Sort: "album", + }}).Return(mfs, nil) + + ds.On("MediaFile", mock.Anything).Return(mfRepo) + ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + + out := new(bytes.Buffer) + err := arch.ZipArtist(context.Background(), "1", "mp3", 128, out) + Expect(err).To(BeNil()) + + zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len())) + Expect(err).To(BeNil()) + + Expect(len(zr.File)).To(Equal(2)) + Expect(zr.File[0].Name).To(Equal("Album 1/01 - track1.mp3")) + Expect(zr.File[1].Name).To(Equal("Album 1/02 - track2.mp3")) + }) + }) + + Context("ZipShare", func() { + It("zips a share correctly", func() { + mfs := model.MediaFiles{ + {ID: "1", Path: "test_data/01 - track1.mp3", Suffix: "mp3", Artist: "Artist 1", Title: "track1"}, + {ID: "2", Path: "test_data/02 - track2.mp3", Suffix: "mp3", Artist: "Artist 2", Title: "track2"}, + } + + share := &model.Share{ + ID: "1", + Downloadable: true, + Format: "mp3", + MaxBitRate: 128, + Tracks: mfs, + } + + sh.On("Load", mock.Anything, "1").Return(share, nil) + ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + + out := new(bytes.Buffer) + err := arch.ZipShare(context.Background(), "1", out) + Expect(err).To(BeNil()) + + zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len())) + Expect(err).To(BeNil()) + + Expect(len(zr.File)).To(Equal(2)) + Expect(zr.File[0].Name).To(Equal("01 - Artist 1 - track1.mp3")) + Expect(zr.File[1].Name).To(Equal("02 - Artist 2 - track2.mp3")) + + }) + }) + + Context("ZipPlaylist", func() { + It("zips a playlist correctly", func() { + tracks := []model.PlaylistTrack{ + {MediaFile: model.MediaFile{Path: "test_data/01 - track1.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album 1", DiscNumber: 1, Artist: "Artist 1", Title: "track1"}}, + {MediaFile: model.MediaFile{Path: "test_data/02 - track2.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album 1", DiscNumber: 1, Artist: "Artist 2", Title: "track2"}}, + } + + pls := &model.Playlist{ + ID: "1", + Name: "Test Playlist", + Tracks: tracks, + } + + plRepo := &mockPlaylistRepository{} + plRepo.On("GetWithTracks", "1", true).Return(pls, nil) + ds.On("Playlist", mock.Anything).Return(plRepo) + ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + + out := new(bytes.Buffer) + err := arch.ZipPlaylist(context.Background(), "1", "mp3", 128, out) + Expect(err).To(BeNil()) + + zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len())) + Expect(err).To(BeNil()) + + Expect(len(zr.File)).To(Equal(2)) + Expect(zr.File[0].Name).To(Equal("01 - Artist 1 - track1.mp3")) + Expect(zr.File[1].Name).To(Equal("02 - Artist 2 - track2.mp3")) + }) + }) +}) + +type mockDataStore struct { + mock.Mock + model.DataStore +} + +func (m *mockDataStore) MediaFile(ctx context.Context) model.MediaFileRepository { + args := m.Called(ctx) + return args.Get(0).(model.MediaFileRepository) +} + +func (m *mockDataStore) Playlist(ctx context.Context) model.PlaylistRepository { + args := m.Called(ctx) + return args.Get(0).(model.PlaylistRepository) +} + +type mockMediaFileRepository struct { + mock.Mock + model.MediaFileRepository +} + +func (m *mockMediaFileRepository) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) { + args := m.Called(options) + return args.Get(0).(model.MediaFiles), args.Error(1) +} + +type mockPlaylistRepository struct { + mock.Mock + model.PlaylistRepository +} + +func (m *mockPlaylistRepository) GetWithTracks(id string, includeTracks bool) (*model.Playlist, error) { + args := m.Called(id, includeTracks) + return args.Get(0).(*model.Playlist), args.Error(1) +} + +type mockMediaStreamer struct { + mock.Mock + core.MediaStreamer +} + +func (m *mockMediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, format string, bitrate int) (*core.Stream, error) { + args := m.Called(ctx, mf, format, bitrate) + if args.Error(1) != nil { + return nil, args.Error(1) + } + return &core.Stream{ReadCloser: args.Get(0).(io.ReadCloser)}, nil +} + +type mockShare struct { + mock.Mock + core.Share +} + +func (m *mockShare) Load(ctx context.Context, id string) (*model.Share, error) { + args := m.Called(ctx, id) + return args.Get(0).(*model.Share), args.Error(1) +}