import Foundation import XCTest @testable import VelodyDomain final class OfflineLibrarySearchTests: XCTestCase { func testSearchByTitleFiltersMatchingTracks() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: "trap") XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-light-trap"]) XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-light-trap"]) } func testSearchByArtistFiltersMatchingTracks() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: "unknown") XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-light-trap"]) XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-light-trap"]) } func testSearchIsCaseInsensitiveAndTrimsWhitespace() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: " TRAP ") XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-light-trap"]) XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-light-trap"]) } func testEmptySearchResultReturnsEmptySections() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: "nope") XCTAssertTrue(filtered.remoteTracks.isEmpty) XCTAssertTrue(filtered.availableTracks.isEmpty) } func testClearingSearchRestoresFullList() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: "") XCTAssertEqual(filtered.remoteTracks, snapshot.remoteTracks) XCTAssertEqual(filtered.availableTracks, snapshot.availableTracks) } func testOfflineSectionFilteringUsesAvailableOfflineTracks() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: "harbor") XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-harbor-lights"]) XCTAssertEqual(filtered.availableTracks.map(\.remoteTrackId), ["remote-harbor-lights"]) } func testRemoteSectionFilteringUsesRemoteLibraryTracks() { let snapshot = makeSnapshot() let filtered = snapshot.filtered(searchText: "shadow") XCTAssertEqual(filtered.remoteTracks.map(\.remoteTrack.trackId), ["remote-shadow-city"]) XCTAssertTrue(filtered.availableTracks.isEmpty) } func testFilteringDoesNotMutateUnderlyingSnapshotData() { let snapshot = makeSnapshot() let originalRemoteTracks = snapshot.remoteTracks let originalAvailableTracks = snapshot.availableTracks _ = snapshot.filtered(searchText: "light") XCTAssertEqual(snapshot.remoteTracks, originalRemoteTracks) XCTAssertEqual(snapshot.availableTracks, originalAvailableTracks) } private func makeSnapshot() -> OfflineLibrarySnapshot { let remoteTracks = [ makeRemoteLibraryTrack( trackId: "remote-light-trap", assetId: "asset-light-trap", title: "Light Trap", artist: "Unknown Artist", isFileAvailable: true ), makeRemoteLibraryTrack( trackId: "remote-shadow-city", assetId: "asset-shadow-city", title: "Shadow City", artist: "Night Runner", isFileAvailable: false ), makeRemoteLibraryTrack( trackId: "remote-harbor-lights", assetId: "asset-harbor-lights", title: "Harbor Lights", artist: "The Northline", isFileAvailable: true ), ] let availableTracks = [ makeOfflineTrack( trackId: "remote-light-trap", assetId: "asset-light-trap", title: "Light Trap", artist: "Unknown Artist" ), makeOfflineTrack( trackId: "remote-harbor-lights", assetId: "asset-harbor-lights", title: "Harbor Lights", artist: "The Northline" ), ] return OfflineLibrarySnapshot( remoteTracks: remoteTracks, availableTracks: availableTracks ) } private func makeRemoteLibraryTrack( trackId: String, assetId: String, title: String, artist: String, isFileAvailable: Bool ) -> OfflineLibraryRemoteTrack { OfflineLibraryRemoteTrack( remoteTrack: RemoteTrack( trackId: trackId, title: title, artist: artist, durationSeconds: 180, sha256: String(repeating: "a", count: 64), assetId: assetId, createdAt: "2026-05-30T10:00:00.000Z", updatedAt: "2026-05-30T10:00:00.000Z" ), localFilePath: isFileAvailable ? "/tmp/\(assetId).mp3" : "", downloadedAt: isFileAvailable ? Date(timeIntervalSince1970: 1_000) : nil, isFileAvailable: isFileAvailable, status: isFileAvailable ? .downloaded : .notDownloaded, lastDownloadError: nil ) } private func makeOfflineTrack( trackId: String, assetId: String, title: String, artist: String ) -> OfflineLibraryTrack { OfflineLibraryTrack( remoteTrackId: trackId, assetId: assetId, title: title, artist: artist, durationSeconds: 180, sha256: String(repeating: "b", count: 64), localFilePath: "/tmp/\(assetId).mp3", downloadedAt: Date(timeIntervalSince1970: 1_000), isFileAvailable: true ) } }