import MusicAssistantCore import XCTest final class SongProjectDiscussionDirectorTests: XCTestCase { func testDiscussBuildsARequestAndReturnsTrimmedQuestionsWithoutChangingProject() async throws { let project = SongProject( id: "discussion-project", title: "Discussion Project", idea: "Plan a cinematic chorus", conversationMode: .discuss, createdAt: Date(timeIntervalSince1970: 10), updatedAt: Date(timeIntervalSince1970: 20) ) let aiService = RecordingDiscussionAIService( result: SongProjectDiscussionResult( questions: [" Should the chorus use a choir? ", " "], notes: ["Awaiting the user's choice."] ) ) let director = SongProjectDiscussionDirector(aiService: aiService) let conversation = [AIConversationMessage(role: .assistant, content: "What mood should lead?")] let result = try await director.discuss( project: project, userMessage: " Keep the verses intimate. ", conversation: conversation, localeIdentifier: "en_US" ) let request = await aiService.recordedRequests.first XCTAssertEqual(request?.context.userInstruction, "Keep the verses intimate.") XCTAssertEqual(request?.context.conversation, conversation) XCTAssertEqual(request?.context.localeIdentifier, "en_US") XCTAssertEqual(request?.project, project) XCTAssertEqual(result.questions, ["Should the chorus use a choir?"]) XCTAssertEqual(result.notes, ["Awaiting the user's choice."]) } func testDiscussRejectsAutoModeAndBlankMessagesBeforeCallingAI() async throws { let aiService = RecordingDiscussionAIService( result: SongProjectDiscussionResult(questions: ["Unused"]) ) let director = SongProjectDiscussionDirector(aiService: aiService) do { _ = try await director.discuss( project: SongProject(title: "Auto", idea: "Auto", conversationMode: .auto), userMessage: "Ask a question." ) XCTFail("Expected discussion mode validation to fail.") } catch let error as SongProjectDiscussionDirectorError { XCTAssertEqual(error, .discussionModeNotEnabled) } do { _ = try await director.discuss( project: SongProject(title: "Discuss", idea: "Discuss", conversationMode: .discuss), userMessage: " \n " ) XCTFail("Expected blank message validation to fail.") } catch let error as SongProjectDiscussionDirectorError { XCTAssertEqual(error, .emptyUserMessage) } let requestCount = await aiService.recordedRequests.count XCTAssertEqual(requestCount, 0) } } private actor RecordingDiscussionAIService: AIService { private(set) var recordedRequests: [SongProjectDiscussionRequest] = [] private let result: SongProjectDiscussionResult init(result: SongProjectDiscussionResult) { self.result = result } func generateSongProject(from request: SongProjectGenerationRequest) async throws -> SongProjectGenerationResult { SongProjectGenerationResult(project: request.seedProject ?? SongProject(title: "Unused", idea: "Unused")) } func discussSongProject(from request: SongProjectDiscussionRequest) async throws -> SongProjectDiscussionResult { recordedRequests.append(request) return result } func reviseLyrics(from request: LyricsRevisionRequest) async throws -> LyricsRevisionResult { LyricsRevisionResult(lyrics: request.sourceLyrics) } func proposeProjectUpdate(from request: SongProjectUpdateRequest) async throws -> SongProjectUpdateResult { SongProjectUpdateResult(project: request.project) } }