Files
music-assistant/Tests/MusicAssistantCoreTests/ExistingLyricsRevisionDirectorTests.swift
T

163 lines
6.7 KiB
Swift

import MusicAssistantCore
import XCTest
final class ExistingLyricsRevisionDirectorTests: XCTestCase {
func testReviseExistingLyricsBuildsRevisionRequestAndUpdatesProjectLyrics() async throws {
let revisedAt = Date(timeIntervalSince1970: 500)
let project = SongProject(
id: "lyric-project",
title: "Lyric Project",
idea: "Make the hook stronger",
lyrics: Lyrics(text: "Original lines"),
createdAt: Date(timeIntervalSince1970: 100),
updatedAt: Date(timeIntervalSince1970: 200)
)
let aiService = RecordingLyricsAIService(
revisionResult: LyricsRevisionResult(
lyrics: Lyrics(text: "Revised lines"),
notes: ["Improved hook phrasing."]
)
)
let director = ExistingLyricsRevisionDirector(aiService: aiService)
let conversation = [
AIConversationMessage(role: .user, content: "Keep it direct.")
]
let result = try await director.reviseExistingLyrics(
in: project,
instruction: " Improve the chorus. ",
mode: .improve,
conversation: conversation,
localeIdentifier: "en_US",
revisedAt: revisedAt
)
let recordedRequest = await aiService.recordedRevisionRequests.first
XCTAssertEqual(recordedRequest?.context.userInstruction, "Improve the chorus.")
XCTAssertEqual(recordedRequest?.context.conversation, conversation)
XCTAssertEqual(recordedRequest?.context.localeIdentifier, "en_US")
XCTAssertEqual(recordedRequest?.project, project)
XCTAssertEqual(recordedRequest?.sourceLyrics, Lyrics(text: "Original lines"))
XCTAssertEqual(recordedRequest?.mode, .improve)
XCTAssertEqual(result.project.id, project.id)
XCTAssertEqual(result.project.createdAt, project.createdAt)
XCTAssertEqual(result.project.lyrics, Lyrics(text: "Revised lines"))
XCTAssertEqual(result.project.updatedAt, revisedAt)
XCTAssertEqual(result.changedScopes, [.lyrics])
XCTAssertEqual(result.notes, ["Improved hook phrasing."])
}
func testReviseExistingLyricsCanUseExplicitSourceLyricsWithoutMutatingOtherProjectFields() async throws {
let project = SongProject(
title: "Existing Project",
idea: "Complete second verse",
genres: [GenreStyle(id: "pop", name: "Pop")],
lyrics: Lyrics(text: "Stored project lyrics")
)
let sourceLyrics = Lyrics(text: "External draft lyrics")
let aiService = RecordingLyricsAIService(
revisionResult: LyricsRevisionResult(
lyrics: Lyrics(text: "Completed external draft")
)
)
let director = ExistingLyricsRevisionDirector(aiService: aiService)
let result = try await director.reviseExistingLyrics(
in: project,
sourceLyrics: sourceLyrics,
instruction: "Complete this draft.",
mode: .complete,
revisedAt: Date(timeIntervalSince1970: 900)
)
let recordedRequest = await aiService.recordedRevisionRequests.first
XCTAssertEqual(recordedRequest?.sourceLyrics, sourceLyrics)
XCTAssertEqual(result.project.title, project.title)
XCTAssertEqual(result.project.idea, project.idea)
XCTAssertEqual(result.project.genres, project.genres)
XCTAssertEqual(result.project.lyrics.text, "Completed external draft")
}
func testReviseExistingLyricsRejectsBlankSourceBeforeCallingAI() async {
let aiService = RecordingLyricsAIService(
revisionResult: LyricsRevisionResult(lyrics: Lyrics(text: "Unused"))
)
let director = ExistingLyricsRevisionDirector(aiService: aiService)
do {
_ = try await director.reviseExistingLyrics(
in: SongProject(title: "Blank", idea: "Blank", lyrics: Lyrics(text: " \n ")),
instruction: "Improve.",
mode: .improve
)
XCTFail("Expected blank source lyrics to throw.")
} catch let error as ExistingLyricsRevisionDirectorError {
XCTAssertEqual(error, .emptySourceLyrics)
} catch {
XCTFail("Expected ExistingLyricsRevisionDirectorError.")
}
let requestCount = await aiService.recordedRevisionRequests.count
XCTAssertEqual(requestCount, 0)
}
func testReviseExistingLyricsRejectsBlankInstructionAndWriteFromIdeaMode() async {
let aiService = RecordingLyricsAIService(
revisionResult: LyricsRevisionResult(lyrics: Lyrics(text: "Unused"))
)
let director = ExistingLyricsRevisionDirector(aiService: aiService)
let project = SongProject(title: "Song", idea: "Idea", lyrics: Lyrics(text: "Draft"))
do {
_ = try await director.reviseExistingLyrics(
in: project,
instruction: " ",
mode: .correct
)
XCTFail("Expected blank instruction to throw.")
} catch let error as ExistingLyricsRevisionDirectorError {
XCTAssertEqual(error, .emptyInstruction)
} catch {
XCTFail("Expected ExistingLyricsRevisionDirectorError.")
}
do {
_ = try await director.reviseExistingLyrics(
in: project,
instruction: "Write from idea.",
mode: .writeFromIdea
)
XCTFail("Expected unsupported mode to throw.")
} catch let error as ExistingLyricsRevisionDirectorError {
XCTAssertEqual(error, .unsupportedMode(.writeFromIdea))
} catch {
XCTFail("Expected ExistingLyricsRevisionDirectorError.")
}
let requestCount = await aiService.recordedRevisionRequests.count
XCTAssertEqual(requestCount, 0)
}
}
private actor RecordingLyricsAIService: AIService {
private(set) var recordedRevisionRequests: [LyricsRevisionRequest] = []
private let revisionResult: LyricsRevisionResult
init(revisionResult: LyricsRevisionResult) {
self.revisionResult = revisionResult
}
func generateSongProject(from request: SongProjectGenerationRequest) async throws -> SongProjectGenerationResult {
SongProjectGenerationResult(project: request.seedProject ?? SongProject(title: "Unused", idea: "Unused"))
}
func reviseLyrics(from request: LyricsRevisionRequest) async throws -> LyricsRevisionResult {
recordedRevisionRequests.append(request)
return revisionResult
}
func proposeProjectUpdate(from request: SongProjectUpdateRequest) async throws -> SongProjectUpdateResult {
SongProjectUpdateResult(project: request.project)
}
}