211 lines
8.1 KiB
Swift
211 lines
8.1 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 testDeletesSongProjectAndRemovesItFromTheProjectList() async throws {
|
|
let store = LocalSongProjectStore(directoryURL: temporaryDirectoryURL)
|
|
let project = SongProject(
|
|
id: "deletable-project",
|
|
title: "Delete Me",
|
|
idea: "Remove this local project"
|
|
)
|
|
|
|
try await store.create(project)
|
|
try await store.delete(id: project.id)
|
|
|
|
let remainingProjects = try await store.loadProjectList()
|
|
XCTAssertTrue(remainingProjects.isEmpty)
|
|
|
|
do {
|
|
_ = try await store.open(id: project.id)
|
|
XCTFail("Expected deleted project open to fail.")
|
|
} catch let error as LocalSongProjectStoreError {
|
|
XCTAssertEqual(error, .projectNotFound(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"))
|
|
}
|
|
}
|
|
}
|