🏡 index : quine-global/quintodrome.git

package subsonic

import (
	"context"
	"errors"
	"net/http/httptest"

	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/request"
	"github.com/navidrome/navidrome/server/subsonic/responses"
	"github.com/navidrome/navidrome/tests"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("LibraryScanning", func() {
	var api *Router
	var ms *tests.MockScanner

	BeforeEach(func() {
		ms = tests.NewMockScanner()
		api = &Router{scanner: ms}
	})

	Describe("StartScan", func() {
		It("triggers a full scan with no parameters", func() {
			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with no parameters
			r := httptest.NewRequest("GET", "/rest/startScan", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanAll was called (eventually, since it's in a goroutine)
			Eventually(func() int {
				return ms.GetScanAllCallCount()
			}).Should(BeNumerically(">", 0))
			calls := ms.GetScanAllCalls()
			Expect(calls).To(HaveLen(1))
			Expect(calls[0].FullScan).To(BeFalse())
		})

		It("triggers a full scan with fullScan=true", func() {
			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with fullScan parameter
			r := httptest.NewRequest("GET", "/rest/startScan?fullScan=true", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanAll was called with fullScan=true
			Eventually(func() int {
				return ms.GetScanAllCallCount()
			}).Should(BeNumerically(">", 0))
			calls := ms.GetScanAllCalls()
			Expect(calls).To(HaveLen(1))
			Expect(calls[0].FullScan).To(BeTrue())
		})

		It("triggers a selective scan with single target parameter", func() {
			// Setup mocks
			mockUserRepo := tests.CreateMockUserRepo()
			_ = mockUserRepo.SetUserLibraries(GinkgoT().Context(), "admin-id", []int{1, 2})
			mockDS := &tests.MockDataStore{MockedUser: mockUserRepo}
			api.ds = mockDS

			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with single target parameter
			r := httptest.NewRequest("GET", "/rest/startScan?target=1:Music/Rock", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanFolders was called with correct targets
			Eventually(func() int {
				return ms.GetScanFoldersCallCount()
			}).Should(BeNumerically(">", 0))
			calls := ms.GetScanFoldersCalls()
			Expect(calls).To(HaveLen(1))
			targets := calls[0].Targets
			Expect(targets).To(HaveLen(1))
			Expect(targets[0].LibraryID).To(Equal(1))
			Expect(targets[0].FolderPath).To(Equal("Music/Rock"))
		})

		It("triggers a selective scan with multiple target parameters", func() {
			// Setup mocks
			mockUserRepo := tests.CreateMockUserRepo()
			_ = mockUserRepo.SetUserLibraries(GinkgoT().Context(), "admin-id", []int{1, 2})
			mockDS := &tests.MockDataStore{MockedUser: mockUserRepo}
			api.ds = mockDS

			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with multiple target parameters
			r := httptest.NewRequest("GET", "/rest/startScan?target=1:Music/Reggae&target=2:Classical/Bach", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanFolders was called with correct targets
			Eventually(func() int {
				return ms.GetScanFoldersCallCount()
			}).Should(BeNumerically(">", 0))
			calls := ms.GetScanFoldersCalls()
			Expect(calls).To(HaveLen(1))
			targets := calls[0].Targets
			Expect(targets).To(HaveLen(2))
			Expect(targets[0].LibraryID).To(Equal(1))
			Expect(targets[0].FolderPath).To(Equal("Music/Reggae"))
			Expect(targets[1].LibraryID).To(Equal(2))
			Expect(targets[1].FolderPath).To(Equal("Classical/Bach"))
		})

		It("triggers a selective full scan with target and fullScan parameters", func() {
			// Setup mocks
			mockUserRepo := tests.CreateMockUserRepo()
			_ = mockUserRepo.SetUserLibraries(GinkgoT().Context(), "admin-id", []int{1})
			mockDS := &tests.MockDataStore{MockedUser: mockUserRepo}
			api.ds = mockDS

			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with target and fullScan parameters
			r := httptest.NewRequest("GET", "/rest/startScan?target=1:Music/Jazz&fullScan=true", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanFolders was called with fullScan=true
			Eventually(func() int {
				return ms.GetScanFoldersCallCount()
			}).Should(BeNumerically(">", 0))
			calls := ms.GetScanFoldersCalls()
			Expect(calls).To(HaveLen(1))
			Expect(calls[0].FullScan).To(BeTrue())
			targets := calls[0].Targets
			Expect(targets).To(HaveLen(1))
		})

		It("returns error for invalid target format", func() {
			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with invalid target format (missing colon)
			r := httptest.NewRequest("GET", "/rest/startScan?target=1MusicRock", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should return error
			Expect(err).To(HaveOccurred())
			Expect(response).To(BeNil())
			var subErr subError
			ok := errors.As(err, &subErr)
			Expect(ok).To(BeTrue())
			Expect(subErr.code).To(Equal(responses.ErrorGeneric))
		})

		It("returns error for invalid library ID in target", func() {
			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with invalid library ID
			r := httptest.NewRequest("GET", "/rest/startScan?target=0:Music/Rock", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should return error
			Expect(err).To(HaveOccurred())
			Expect(response).To(BeNil())
			var subErr subError
			ok := errors.As(err, &subErr)
			Expect(ok).To(BeTrue())
			Expect(subErr.code).To(Equal(responses.ErrorGeneric))
		})

		It("returns error when library does not exist", func() {
			// Setup mocks - user has access to library 1 and 2 only
			mockUserRepo := tests.CreateMockUserRepo()
			_ = mockUserRepo.SetUserLibraries(GinkgoT().Context(), "admin-id", []int{1, 2})
			mockDS := &tests.MockDataStore{MockedUser: mockUserRepo}
			api.ds = mockDS

			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with library ID that doesn't exist
			r := httptest.NewRequest("GET", "/rest/startScan?target=999:Music/Rock", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should return ErrorDataNotFound
			Expect(err).To(HaveOccurred())
			Expect(response).To(BeNil())
			var subErr subError
			ok := errors.As(err, &subErr)
			Expect(ok).To(BeTrue())
			Expect(subErr.code).To(Equal(responses.ErrorDataNotFound))
		})

		It("calls ScanAll when single library with empty path and only one library exists", func() {
			// Setup mocks - single library in DB
			mockUserRepo := tests.CreateMockUserRepo()
			_ = mockUserRepo.SetUserLibraries(GinkgoT().Context(), "admin-id", []int{1})
			mockLibraryRepo := &tests.MockLibraryRepo{}
			mockLibraryRepo.SetData(model.Libraries{
				{ID: 1, Name: "Music Library", Path: "/music"},
			})
			mockDS := &tests.MockDataStore{
				MockedUser:    mockUserRepo,
				MockedLibrary: mockLibraryRepo,
			}
			api.ds = mockDS

			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with single library and empty path
			r := httptest.NewRequest("GET", "/rest/startScan?target=1:", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanAll was called instead of ScanFolders
			Eventually(func() int {
				return ms.GetScanAllCallCount()
			}).Should(BeNumerically(">", 0))
			Expect(ms.GetScanFoldersCallCount()).To(Equal(0))
		})

		It("calls ScanFolders when single library with empty path but multiple libraries exist", func() {
			// Setup mocks - multiple libraries in DB
			mockUserRepo := tests.CreateMockUserRepo()
			_ = mockUserRepo.SetUserLibraries(GinkgoT().Context(), "admin-id", []int{1, 2})
			mockLibraryRepo := &tests.MockLibraryRepo{}
			mockLibraryRepo.SetData(model.Libraries{
				{ID: 1, Name: "Music Library", Path: "/music"},
				{ID: 2, Name: "Audiobooks", Path: "/audiobooks"},
			})
			mockDS := &tests.MockDataStore{
				MockedUser:    mockUserRepo,
				MockedLibrary: mockLibraryRepo,
			}
			api.ds = mockDS

			// Create admin user
			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			// Create request with single library and empty path
			r := httptest.NewRequest("GET", "/rest/startScan?target=1:", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.StartScan(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())

			// Verify ScanFolders was called (not ScanAll)
			Eventually(func() int {
				return ms.GetScanFoldersCallCount()
			}).Should(BeNumerically(">", 0))
			calls := ms.GetScanFoldersCalls()
			Expect(calls).To(HaveLen(1))
			targets := calls[0].Targets
			Expect(targets).To(HaveLen(1))
			Expect(targets[0].LibraryID).To(Equal(1))
			Expect(targets[0].FolderPath).To(Equal(""))
		})

		It("returns correct scanType in response when fullScan=false", func() {
			// Setup mock to update status when scan starts (simulating the real scanner)
			ms.SetScanStatusFunc(func(fullScan bool, targets []model.ScanTarget) *model.ScannerStatus {
				scanType := "quick"
				if fullScan {
					scanType = "full"
				}
				return &model.ScannerStatus{
					Scanning: true,
					ScanType: scanType,
				}
			})

			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			r := httptest.NewRequest("GET", "/rest/startScan", nil)
			r = r.WithContext(ctx)

			response, err := api.StartScan(r)

			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())
			Expect(response.ScanStatus).ToNot(BeNil())
			Expect(response.ScanStatus.Scanning).To(BeTrue())
			Expect(response.ScanStatus.ScanType).To(Equal("quick"))
		})

		It("returns correct scanType in response when fullScan=true", func() {
			// Setup mock to update status when scan starts (simulating the real scanner)
			ms.SetScanStatusFunc(func(fullScan bool, targets []model.ScanTarget) *model.ScannerStatus {
				scanType := "quick"
				if fullScan {
					scanType = "full"
				}
				return &model.ScannerStatus{
					Scanning: true,
					ScanType: scanType,
				}
			})

			ctx := request.WithUser(context.Background(), model.User{
				ID:      "admin-id",
				IsAdmin: true,
			})

			r := httptest.NewRequest("GET", "/rest/startScan?fullScan=true", nil)
			r = r.WithContext(ctx)

			response, err := api.StartScan(r)

			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())
			Expect(response.ScanStatus).ToNot(BeNil())
			Expect(response.ScanStatus.Scanning).To(BeTrue())
			Expect(response.ScanStatus.ScanType).To(Equal("full"))
		})
	})

	Describe("GetScanStatus", func() {
		It("returns scan status", func() {
			// Setup mock scanner status
			ms.SetStatusResponse(&model.ScannerStatus{
				Scanning:    false,
				Count:       100,
				FolderCount: 10,
			})

			// Create request
			ctx := context.Background()
			r := httptest.NewRequest("GET", "/rest/getScanStatus", nil)
			r = r.WithContext(ctx)

			// Call endpoint
			response, err := api.GetScanStatus(r)

			// Should succeed
			Expect(err).ToNot(HaveOccurred())
			Expect(response).ToNot(BeNil())
			Expect(response.ScanStatus).ToNot(BeNil())
			Expect(response.ScanStatus.Scanning).To(BeFalse())
			Expect(response.ScanStatus.Count).To(Equal(int64(100)))
			Expect(response.ScanStatus.FolderCount).To(Equal(int64(10)))
		})
	})
})