import Foundation import MusicAssistantCore import XCTest final class LocalSongProjectStoreTests: XCTestCase { private var temporaryDirectoryURL: URL! override func setUpWithError() throws { temporaryDirectoryURL = FileManager.default.temporaryDirectory .appendingPathComponent("music-assistant-store-tests") .appendingPathComponent(UUID().uuidString) } override func tearDownWithError() throws { if let temporaryDirectoryURL { try? FileManager.default.removeItem(at: temporaryDirectoryURL) } temporaryDirectoryURL = nil } func testCreatesOpensSavesAndListsSongProjectsLocally() async throws { let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL) let createdAt = Date(timeIntervalSince1970: 100) let updatedAt = Date(timeIntervalSince1970: 200) var project = SongProject( id: "local-project", title: "Local Project", idea: "Persist this song locally", genres: [GenreStyle(id: "pop", name: "Pop")], createdAt: createdAt, updatedAt: updatedAt ) try await store.create(project) let createdProjectIDs = try await store.loadProjectIDs() let openedProject = try await store.open(id: project.id) XCTAssertEqual(createdProjectIDs, ["local-project"]) XCTAssertEqual(openedProject, project) project.title = "Updated Local Project" project.setBPMManually(100) try await store.save(project) let updatedProject = try await store.open(id: project.id) XCTAssertEqual(updatedProject, project) } func testPersistsSelectedInstrumentsOnSongProject() async throws { let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL) var project = SongProject( id: "instrument-selection-project", title: "Instrument Selection", idea: "Persist selected catalog instruments" ) project.setInstrumentSelected(id: "oud", isSelected: true, variant: "Oud") project.setInstrumentSelected(id: "violin", isSelected: true, variant: "Violin") project.setInstrumentSelected(id: "oud", isSelected: false) try await store.create(project) var openedProject = try await store.open(id: project.id) XCTAssertFalse(openedProject.isInstrumentSelected(id: "oud")) XCTAssertTrue(openedProject.isInstrumentSelected(id: "violin")) XCTAssertEqual(openedProject.instrumentTrack(for: "oud")?.variant, "Oud") XCTAssertEqual(openedProject.selectedInstrumentIDs, ["violin"]) openedProject.setInstrumentSelected(id: "oud", isSelected: true, variant: "Oud") try await store.save(openedProject) let savedProject = try await store.open(id: project.id) XCTAssertTrue(savedProject.isInstrumentSelected(id: "oud")) XCTAssertTrue(savedProject.isInstrumentSelected(id: "violin")) XCTAssertEqual(savedProject.selectedInstrumentIDs, ["oud", "violin"]) } func testLoadsProjectListSortedByMostRecentUpdate() async throws { let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL) let older = SongProject( id: "older-project", title: "Older", idea: "Created first", createdAt: Date(timeIntervalSince1970: 100), updatedAt: Date(timeIntervalSince1970: 150) ) let newer = SongProject( id: "newer-project", title: "Newer", idea: "Updated later", createdAt: Date(timeIntervalSince1970: 200), updatedAt: Date(timeIntervalSince1970: 300) ) try await store.create(older) try await store.create(newer) let projectList = try await store.loadProjectList() let projectIDs = try await store.loadProjectIDs() XCTAssertEqual( projectList, [ SongProjectListItem(project: newer), SongProjectListItem(project: older) ] ) XCTAssertEqual(projectIDs, ["newer-project", "older-project"]) } func testCreateDoesNotOverwriteExistingProject() async throws { let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL) let project = SongProject( id: "duplicate-project", title: "Duplicate", idea: "Do not overwrite" ) try await store.create(project) do { try await store.create(project) XCTFail("Expected duplicate project creation to fail.") } catch let error as LocalSongProjectStoreError { XCTAssertEqual(error, .projectAlreadyExists(project.id)) } } func testOpenMissingProjectThrowsNotFound() async throws { let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL) do { _ = try await store.open(id: "missing") XCTFail("Expected missing project open to fail.") } catch let error as LocalSongProjectStoreError { XCTAssertEqual(error, .projectNotFound("missing")) } } }