55 lines
3.4 KiB
Swift
55 lines
3.4 KiB
Swift
|
|
import XCTest
|
||
|
|
@testable import MusicBridge
|
||
|
|
|
||
|
|
@MainActor
|
||
|
|
final class SystemPlayerTests: XCTestCase {
|
||
|
|
private func state(_ changes: [String: Any] = [:]) throws -> MediaState {
|
||
|
|
let url = try XCTUnwrap(Bundle(for: Self.self).url(forResource: "state-v1", withExtension: "json"))
|
||
|
|
var json = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any])
|
||
|
|
changes.forEach { json[$0.key] = $0.value }
|
||
|
|
return try JSONDecoder().decode(MediaState.self, from: JSONSerialization.data(withJSONObject: json))
|
||
|
|
}
|
||
|
|
|
||
|
|
func testSeekConvertsRelativePositionAndClampsToAllowedRange() throws {
|
||
|
|
let state = try state(["startSeconds": 100, "endSeconds": 300, "minSeekSeconds": 110, "maxSeekSeconds": 280])
|
||
|
|
XCTAssertEqual(try SystemPlayerTransport.command(.seek, value: 30, state: state).value, 130)
|
||
|
|
XCTAssertEqual(try SystemPlayerTransport.command(.seek, value: -50, state: state).value, 110)
|
||
|
|
XCTAssertEqual(try SystemPlayerTransport.command(.seek, value: 500, state: state).value, 280)
|
||
|
|
XCTAssertThrowsError(try SystemPlayerTransport.command(.seek, value: .nan, state: state))
|
||
|
|
}
|
||
|
|
|
||
|
|
func testVolumeBoundsAndUnsupportedPlayback() throws {
|
||
|
|
let state = try state()
|
||
|
|
XCTAssertEqual(try SystemPlayerTransport.command(.volume, value: 2, state: state).value, 1)
|
||
|
|
XCTAssertEqual(try SystemPlayerTransport.command(.volume, value: -1, state: state).value, 0)
|
||
|
|
XCTAssertThrowsError(try SystemPlayerTransport.command(.volume, value: .infinity, state: state))
|
||
|
|
XCTAssertThrowsError(try SystemPlayerTransport.command(.play, value: nil, state: state))
|
||
|
|
XCTAssertThrowsError(try SystemPlayerTransport.command(.next, value: nil, state: self.state(["hasSession": false])))
|
||
|
|
}
|
||
|
|
|
||
|
|
func testRemoteSnapshotContainsOnlyPublicPlaybackMetadata() throws {
|
||
|
|
guard #available(iOS 27.0, *) else { throw XCTSkip("Requires NowPlaying") }
|
||
|
|
let snapshot = SystemPlayerAttributes(id: "session", state: try state(["positionSeconds": 130, "startSeconds": 100, "endSeconds": 300]))
|
||
|
|
XCTAssertEqual(snapshot.position, 30)
|
||
|
|
XCTAssertEqual(snapshot.duration, 200)
|
||
|
|
let json = try XCTUnwrap(JSONSerialization.jsonObject(with: JSONEncoder().encode(snapshot)) as? [String: Any])
|
||
|
|
XCTAssertEqual(Set(json.keys), Set(["id", "title", "artist", "source", "playing", "rate", "position", "duration", "timestamp", "capabilities", "volume", "hasSession"]))
|
||
|
|
}
|
||
|
|
|
||
|
|
func testRevokedSessionDoesNotSendCommandAndNextIsSentOnce() async throws {
|
||
|
|
let url = try XCTUnwrap(Bundle(for: Self.self).url(forResource: "tls-fixture", withExtension: "json"))
|
||
|
|
let fixtures = try JSONDecoder().decode([String: [String: String]].self, from: Data(contentsOf: url))
|
||
|
|
let f = try XCTUnwrap(fixtures["valid"])
|
||
|
|
let client = BridgeClient(try Connection(endpoint: XCTUnwrap(f["endpoint"]), fingerprint: XCTUnwrap(f["fingerprint"]), token: "live-system-next"))
|
||
|
|
defer { client.close() }
|
||
|
|
do {
|
||
|
|
_ = try await SystemPlayerTransport.execute(.next, client: client) { throw BridgeError.unauthorized }
|
||
|
|
XCTFail("Revoked session must be rejected")
|
||
|
|
} catch BridgeError.unauthorized { }
|
||
|
|
let unchanged = try await client.state()
|
||
|
|
XCTAssertEqual(unchanged.title, "Track 0")
|
||
|
|
let updated = try await SystemPlayerTransport.execute(.next, client: client)
|
||
|
|
XCTAssertEqual(updated.title, "Track 1")
|
||
|
|
}
|
||
|
|
}
|