import Foundation import XCTest @testable import VelodyPersistence final class FavoriteTrackStoreTests: XCTestCase { func testFavoriteTrackStoreUpsertsAndRemovesByRemoteTrackID() async throws { let store = InMemoryFavoriteTrackStore() let originalFavorite = FavoriteTrackRecord( remoteTrackId: "track-123", favoritedAt: Date(timeIntervalSince1970: 1_000) ) let updatedFavorite = FavoriteTrackRecord( remoteTrackId: "track-123", favoritedAt: Date(timeIntervalSince1970: 2_000) ) let secondFavorite = FavoriteTrackRecord( remoteTrackId: "track-456", favoritedAt: Date(timeIntervalSince1970: 3_000) ) try await store.saveFavoriteTrack(originalFavorite) try await store.saveFavoriteTrack(updatedFavorite) try await store.saveFavoriteTrack(secondFavorite) let savedFavorites = try await store.loadFavoriteTracks() XCTAssertEqual(savedFavorites.count, 2) XCTAssertTrue(savedFavorites.contains(updatedFavorite)) XCTAssertTrue(savedFavorites.contains(secondFavorite)) try await store.removeFavoriteTrack(remoteTrackId: "track-123") let remainingFavorites = try await store.loadFavoriteTracks() XCTAssertEqual(remainingFavorites, [secondFavorite]) } func testFileFavoriteTrackStorePersistsAcrossInstances() async throws { let fileManager = FileManager.default let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent( UUID().uuidString, isDirectory: true ) let fileURL = tempDirectory.appendingPathComponent("favorite-tracks.json") let favorites = [ FavoriteTrackRecord( remoteTrackId: "track-123", favoritedAt: Date(timeIntervalSince1970: 1_000) ), FavoriteTrackRecord( remoteTrackId: "track-456", favoritedAt: Date(timeIntervalSince1970: 2_000) ), ] defer { try? fileManager.removeItem(at: tempDirectory) } let firstStore = try FileFavoriteTrackStore(fileURL: fileURL) try await firstStore.saveFavoriteTracks(favorites) let secondStore = try FileFavoriteTrackStore(fileURL: fileURL) let restoredFavorites = try await secondStore.loadFavoriteTracks() XCTAssertEqual(restoredFavorites, favorites) } func testFileFavoriteTrackStoreWritesAtomicallyAndOverwritesOldContents() async throws { let fileManager = FileManager.default let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent( UUID().uuidString, isDirectory: true ) let fileURL = tempDirectory.appendingPathComponent("favorite-tracks.json") let firstFavorites = [ FavoriteTrackRecord( remoteTrackId: "track-b", favoritedAt: Date(timeIntervalSince1970: 2_000) ), FavoriteTrackRecord( remoteTrackId: "track-a", favoritedAt: Date(timeIntervalSince1970: 1_000) ), ] let secondFavorites = [ FavoriteTrackRecord( remoteTrackId: "track-c", favoritedAt: Date(timeIntervalSince1970: 3_000) ), ] let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 defer { try? fileManager.removeItem(at: tempDirectory) } let store = try FileFavoriteTrackStore(fileURL: fileURL) try await store.saveFavoriteTracks(firstFavorites) try await store.saveFavoriteTracks(secondFavorites) let rawData = try Data(contentsOf: fileURL) let restoredFavorites = try decoder.decode([FavoriteTrackRecord].self, from: rawData) let directoryContents = try fileManager.contentsOfDirectory(atPath: tempDirectory.path) let json = try XCTUnwrap( JSONSerialization.jsonObject(with: rawData) as? [[String: Any]] ) XCTAssertEqual(restoredFavorites, secondFavorites) XCTAssertEqual(directoryContents.sorted(), ["favorite-tracks.json"]) XCTAssertEqual(Set(json[0].keys), ["favoritedAt", "remoteTrackId"]) } }