import MusicAssistantCore import XCTest final class StructuredAIResponseSchemaTests: XCTestCase { func testStructuredSongProjectUpdateResponseDecodesFromJSON() throws { let json = """ { "project": { "title": "Updated Song", "idea": "A brighter chorus", "conversationMode": "auto", "languages": [{"id": "english", "name": "English"}], "dialects": [], "arabicPronunciationSettings": { "isEnabled": false, "diacritizationPolicy": "unspecified", "tanweenPolicy": "unspecified", "preservedSpellings": [] }, "genres": [{"id": "pop", "name": "Pop", "aliases": []}], "moods": [{"id": "hopeful", "name": "Hopeful"}], "emotionalArc": [], "sections": [ { "id": "chorus", "type": "chorus", "title": "Chorus", "order": 0, "lyrics": "Lift the hook higher" } ], "instruments": [ { "instrumentId": "piano", "selected": true, "autoArrangementEnabled": true, "placements": [] } ], "vocalists": [], "lyrics": { "text": "Lift the hook higher", "sectionTexts": {"chorus": "Lift the hook higher"} }, "productionDirections": [], "sunoOutput": { "lyricsText": "Lift the hook higher", "stylePrompt": "Hopeful pop with piano", "additionalFields": {}, "generatedAt": "2026-09-13T18:00:00Z" } }, "changedScopes": ["lyrics", "structure", "sunoOutput"], "notes": ["Updated requested creative fields."], "followUpQuestions": ["Should the final chorus repeat?"] } """.data(using: .utf8)! let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 let response = try decoder.decode(StructuredSongProjectUpdateResponse.self, from: json) XCTAssertEqual(StructuredSongProjectUpdateResponse.schemaName, "song_project_update_response") XCTAssertEqual(response.project.title, "Updated Song") XCTAssertEqual(response.project.sections.first?.type, .chorus) XCTAssertEqual(response.project.instruments.first?.instrumentId, "piano") XCTAssertEqual(response.changedScopes, [.lyrics, .structure, .sunoOutput]) XCTAssertEqual(response.notes, ["Updated requested creative fields."]) XCTAssertEqual(response.followUpQuestions, ["Should the final chorus repeat?"]) } func testStructuredSongProjectUpdateResponseCreatesUpdateResultWhilePreservingProjectIdentityAndVersions() { let createdAt = Date(timeIntervalSince1970: 100) let updatedAt = Date(timeIntervalSince1970: 200) var existingProject = SongProject( id: "project-id", title: "Original", idea: "Original idea", createdAt: createdAt, updatedAt: createdAt ) let version = existingProject.createVersion( id: "version-id", label: "Before AI update", createdAt: createdAt ) let response = StructuredSongProjectUpdateResponse( project: SongProjectSnapshot( title: "Updated", idea: "Updated idea", genres: [GenreStyle(id: "cinematic", name: "Cinematic")], lyrics: Lyrics(text: "Updated lyrics") ), changedScopes: [.genres, .lyrics], notes: ["Proposed focused update."], followUpQuestions: ["Keep it minimal?"] ) let result = response.makeUpdateResult( updating: existingProject, updatedAt: updatedAt ) XCTAssertEqual(result.project.id, "project-id") XCTAssertEqual(result.project.createdAt, createdAt) XCTAssertEqual(result.project.updatedAt, updatedAt) XCTAssertEqual(result.project.versions, [version]) XCTAssertEqual(result.project.title, "Updated") XCTAssertEqual(result.project.lyrics.text, "Updated lyrics") XCTAssertEqual(result.changedScopes, [.genres, .lyrics]) XCTAssertEqual(result.notes, ["Proposed focused update."]) XCTAssertEqual(result.followUpQuestions, ["Keep it minimal?"]) } }