Files
music-assistant/Tests/MusicAssistantCoreTests/LocalSongProjectStoreTests.swift
T
2026-09-15 12:38:56 +02:00

189 lines
7.3 KiB
Swift

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 testRestoresPersistedProjectVersionAndKeepsVersionHistory() async throws {
let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL)
let restoredAt = Date(timeIntervalSince1970: 900)
var project = SongProject(
id: "version-recovery-project",
title: "Original Direction",
idea: "Start with acoustic instruments",
genres: [GenreStyle(id: "folk", name: "Folk")],
lyrics: Lyrics(text: "Original lyrics"),
createdAt: Date(timeIntervalSince1970: 100),
updatedAt: Date(timeIntervalSince1970: 200)
)
_ = project.createVersion(
id: "original-version",
label: "Original direction",
createdAt: Date(timeIntervalSince1970: 300)
)
project.title = "Later Direction"
project.idea = "Add electronic elements"
project.selectInstrument(InstrumentTrack(instrumentId: "oud"))
project.lyrics = Lyrics(text: "Later lyrics")
_ = project.createVersion(
id: "later-version",
label: "Later direction",
createdAt: Date(timeIntervalSince1970: 400)
)
project.title = "Unsaved direction"
try await store.create(project)
var recoveredProject = try await store.open(id: project.id)
XCTAssertTrue(recoveredProject.restoreVersion(id: "original-version", restoredAt: restoredAt))
try await store.save(recoveredProject)
let restoredProject = try await store.open(id: project.id)
XCTAssertEqual(restoredProject.title, "Original Direction")
XCTAssertEqual(restoredProject.idea, "Start with acoustic instruments")
XCTAssertEqual(restoredProject.genres, [GenreStyle(id: "folk", name: "Folk")])
XCTAssertTrue(restoredProject.instruments.isEmpty)
XCTAssertEqual(restoredProject.lyrics, Lyrics(text: "Original lyrics"))
XCTAssertEqual(restoredProject.updatedAt, restoredAt)
XCTAssertEqual(restoredProject.versions.map(\.id), ["original-version", "later-version"])
XCTAssertEqual(restoredProject.version(id: "later-version")?.snapshot.title, "Later Direction")
}
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"))
}
}
}