Files

305 lines
9.2 KiB
Swift

import Testing
import Foundation
@testable import Mobile_Music_Assistant
// MARK: - MAPlayer Decoding
@Suite("MAPlayer Decoding")
struct MAPlayerDecodingTests {
private func decode(_ json: String) throws -> MAPlayer {
try JSONDecoder().decode(MAPlayer.self, from: Data(json.utf8))
}
@Test("Decodes all required fields correctly")
func decodesFullPlayer() throws {
let json = """
{
"player_id": "spotify://player/1",
"name": "Living Room",
"state": "playing",
"volume_level": 75,
"powered": true,
"available": true
}
"""
let player = try decode(json)
#expect(player.playerId == "spotify://player/1")
#expect(player.name == "Living Room")
#expect(player.state == .playing)
#expect(player.volume == 75)
#expect(player.powered == true)
#expect(player.available == true)
}
@Test("powered defaults to false when missing; available defaults to true when missing")
func defaultsMissingBooleans() throws {
let json = """
{"player_id": "x", "name": "Test", "state": "idle"}
"""
let player = try decode(json)
#expect(player.powered == false)
#expect(player.available == true)
}
@Test("Unknown state falls back to idle")
func unknownStateFallsBackToIdle() throws {
let json = """
{"player_id": "x", "name": "Test", "state": "banana"}
"""
let player = try decode(json)
#expect(player.state == .idle)
}
@Test("Parses all known PlayerState values")
func allPlayerStates() throws {
for (raw, expected): (String, PlayerState) in [
("idle", .idle), ("playing", .playing), ("paused", .paused)
] {
let json = """
{"player_id": "x", "name": "T", "state": "\(raw)"}
"""
let player = try decode(json)
#expect(player.state == expected)
}
}
@Test("Accepts integer or double for volume_level")
func volumeIntOrDouble() throws {
let jsonInt = #"{"player_id":"x","name":"T","state":"idle","volume_level":50}"#
let jsonDbl = #"{"player_id":"x","name":"T","state":"idle","volume_level":50.7}"#
let p1 = try decode(jsonInt)
let p2 = try decode(jsonDbl)
#expect(p1.volume == 50)
#expect(p2.volume == 50)
}
@Test("isGroupLeader is true when groupChilds is non-empty")
func isGroupLeader() throws {
let json = """
{"player_id":"x","name":"T","state":"idle","group_childs":["a","b"]}
"""
let player = try decode(json)
#expect(player.isGroupLeader == true)
}
@Test("isGroupLeader is false when groupChilds is empty")
func isNotGroupLeader() throws {
let json = #"{"player_id":"x","name":"T","state":"idle","group_childs":[]}"#
let player = try decode(json)
#expect(player.isGroupLeader == false)
}
@Test("isSyncMember is true when syncLeader is set")
func isSyncMember() throws {
let json = """
{"player_id":"x","name":"T","state":"idle","sync_leader":"leader_id"}
"""
let player = try decode(json)
#expect(player.isSyncMember == true)
}
@Test("isSyncMember is false when syncLeader is nil")
func isNotSyncMember() throws {
let json = #"{"player_id":"x","name":"T","state":"idle"}"#
let player = try decode(json)
#expect(player.isSyncMember == false)
}
}
// MARK: - MAMediaItem Decoding
@Suite("MAMediaItem Decoding")
struct MAMediaItemDecodingTests {
private func decode(_ json: String) throws -> MAMediaItem {
try JSONDecoder().decode(MAMediaItem.self, from: Data(json.utf8))
}
@Test("Extracts imageUrl from metadata.images")
func imageUrlFromMetadata() throws {
let json = """
{
"uri": "spotify://track/1",
"name": "Song",
"metadata": {
"images": [
{"type": "thumb", "path": "/img/thumb.jpg"}
]
}
}
"""
let item = try decode(json)
#expect(item.imageUrl == "/img/thumb.jpg")
}
@Test("Extracts imageUrl from direct image field")
func imageUrlFromDirectField() throws {
let json = """
{
"uri": "spotify://track/1",
"name": "Song",
"image": {"type": "thumb", "path": "/direct.jpg"}
}
"""
let item = try decode(json)
#expect(item.imageUrl == "/direct.jpg")
}
@Test("imageUrl is nil when no image data present")
func imageUrlNilWhenMissing() throws {
let json = #"{"uri":"x","name":"T"}"#
let item = try decode(json)
#expect(item.imageUrl == nil)
}
@Test("Prefers metadata over direct image field")
func prefersMetadataOverDirectImage() throws {
let json = """
{
"uri": "x",
"name": "T",
"metadata": {"images": [{"type": "thumb", "path": "/meta.jpg"}]},
"image": {"type": "thumb", "path": "/direct.jpg"}
}
"""
let item = try decode(json)
#expect(item.imageUrl == "/meta.jpg")
}
@Test("Decodes artist name")
func decodesArtistName() throws {
let json = """
{
"uri": "x",
"name": "Song",
"artists": [{"item_id": "1", "uri": "a://artist/1", "name": "Queen"}]
}
"""
let item = try decode(json)
#expect(item.artists?.first?.name == "Queen")
}
@Test("Accepts Int or Double for duration")
func durationIntOrDouble() throws {
let j1 = #"{"uri":"x","name":"T","duration":240}"#
let j2 = #"{"uri":"x","name":"T","duration":240.9}"#
let i1 = try decode(j1)
let i2 = try decode(j2)
#expect(i1.duration == 240)
#expect(i2.duration == 240)
}
@Test("favorite defaults to false when missing")
func favoriteDefaultsFalse() throws {
let json = #"{"uri":"x","name":"T"}"#
let item = try decode(json)
#expect(item.favorite == false)
}
}
// MARK: - MAQueueItem Decoding
@Suite("MAQueueItem Decoding")
struct MAQueueItemDecodingTests {
private func decode(_ json: String) throws -> MAQueueItem {
try JSONDecoder().decode(MAQueueItem.self, from: Data(json.utf8))
}
@Test("Decodes queue item with media item")
func decodesWithMediaItem() throws {
let json = """
{
"queue_item_id": "q1",
"name": "Bohemian Rhapsody",
"media_item": {"uri": "spotify://track/1", "name": "Bohemian Rhapsody"}
}
"""
let item = try decode(json)
#expect(item.queueItemId == "q1")
#expect(item.name == "Bohemian Rhapsody")
#expect(item.mediaItem?.name == "Bohemian Rhapsody")
}
@Test("mediaItem is nil when absent from JSON")
func mediaItemNilWhenAbsent() throws {
let json = #"{"queue_item_id":"q1","name":"Track"}"#
let item = try decode(json)
#expect(item.mediaItem == nil)
}
@Test("Tolerates malformed mediaItem without throwing")
func toleratesMalformedMediaItem() throws {
let json = #"{"queue_item_id":"q1","name":"T","media_item":"broken"}"#
// Should decode successfully, mediaItem == nil
let item = try decode(json)
#expect(item.mediaItem == nil)
}
}
// MARK: - PlayerState
@Suite("PlayerState")
struct PlayerStateTests {
@Test("All raw values round-trip correctly")
func rawValueRoundTrip() {
let states: [(String, PlayerState)] = [
("idle", .idle),
("playing", .playing),
("paused", .paused)
]
for (raw, state) in states {
#expect(PlayerState(rawValue: raw) == state)
#expect(state.rawValue == raw)
}
}
@Test("Unknown raw value returns nil")
func unknownRawValueIsNil() {
#expect(PlayerState(rawValue: "streaming") == nil)
#expect(PlayerState(rawValue: "") == nil)
}
}
// MARK: - MAPlayerQueue Decoding
@Suite("MAPlayerQueue Decoding")
struct MAPlayerQueueDecodingTests {
private func decode(_ json: String) throws -> MAPlayerQueue {
try JSONDecoder().decode(MAPlayerQueue.self, from: Data(json.utf8))
}
@Test("Decodes queue with currentItem")
func decodesWithCurrentItem() throws {
let json = """
{
"queue_id": "q://1",
"current_index": 2,
"elapsed_time": 45.5,
"current_item": {"queue_item_id": "qi1", "name": "Track"}
}
"""
let queue = try decode(json)
#expect(queue.queueId == "q://1")
#expect(queue.currentIndex == 2)
#expect(queue.currentItem?.name == "Track")
}
@Test("currentItem is nil when absent")
func currentItemNilWhenAbsent() throws {
let json = #"{"queue_id":"q://1"}"#
let queue = try decode(json)
#expect(queue.currentItem == nil)
}
@Test("Tolerates malformed currentItem gracefully")
func toleratesMalformedCurrentItem() throws {
let json = #"{"queue_id":"q://1","current_item":null}"#
let queue = try decode(json)
#expect(queue.currentItem == nil)
}
}