import XCTest @testable import MusicBridge final class ProtocolTests: XCTestCase { private let fingerprint = String(repeating: "A", count: 64) func testPrivateAddresses() throws { for address in ["10.0.0.1", "172.16.0.1", "172.31.255.254", "192.168.1.10"] { XCTAssertTrue(Connection.isPrivateIPv4(address)) } for address in ["127.0.0.1", "8.8.8.8", "172.32.0.1", "169.254.1.2", "192.168.1.999", "192.168.01.1", "localhost", "::1"] { XCTAssertFalse(Connection.isPrivateIPv4(address)) } } func testRejectsCredentialOrPathInjection() { for address in ["http://192.168.1.2", "https://user@192.168.1.2", "https://192.168.1.2/path", "https://192.168.1.2?token=secret", "https://192.168.1.2#fragment", "https://192.168.1.2:0"] { XCTAssertThrowsError(try Connection(endpoint: address, fingerprint: fingerprint)) } } func testFingerprintValidation() throws { XCTAssertThrowsError(try Connection(endpoint: "https://192.168.1.2", fingerprint: String(repeating: "Z", count: 64))) XCTAssertThrowsError(try Connection(endpoint: "https://192.168.1.2", fingerprint: "AA")) XCTAssertEqual(try Connection(endpoint: "https://192.168.1.2:8765", fingerprint: fingerprint.lowercased()).fingerprint, fingerprint) } func testInvitationAndExpiry() throws { let now = Date(timeIntervalSince1970: 1_800_000_000) let payload: [String: Any] = ["version": 1, "endpoint": "https://192.168.1.10:8765", "certificateSha256": fingerprint, "code": "01234567", "expiresAt": ISO8601DateFormatter().string(from: now.addingTimeInterval(60))] let data = try JSONSerialization.data(withJSONObject: payload).base64EncodedString() .replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") let text = "musicbridge://pair?data=\(data)" XCTAssertEqual(try Invitation.parse(text, now: now).code, "01234567") XCTAssertThrowsError(try Invitation.parse(text, now: now.addingTimeInterval(61))) XCTAssertThrowsError(try Invitation.parse(text + "&data=other", now: now)) } func testCodeRequiresEightASCIIDigits() { XCTAssertTrue(Invitation.validCode("01234567")) XCTAssertFalse(Invitation.validCode("1234567")) XCTAssertFalse(Invitation.validCode("12345678")) } func testWindowsInvitationTimestampAndMalformedJSON() throws { // System.Text.Json emits up to seven fractional digits and a numeric UTC offset. let json = """ {"version":1,"endpoint":"https://192.168.1.23:8765","certificateSha256":"\(fingerprint)","code":"01234567","expiresAt":"2026-09-12T15:30:00.1234567+03:00"} """ func payload(_ json: String) -> String { "musicbridge://pair?data=" + Data(json.utf8).base64EncodedString() .replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") } let before = try XCTUnwrap(ISO8601DateFormatter().date(from: "2026-09-12T12:29:00Z")) XCTAssertEqual(try Invitation.parse(payload(json), now: before).code, "01234567") XCTAssertThrowsError(try Invitation.parse(payload(json), now: before.addingTimeInterval(120))) XCTAssertThrowsError(try Invitation.parse(payload("{}"), now: before)) { error in XCTAssertTrue(error is BridgeError) } XCTAssertThrowsError(try Invitation.parse("https://example.com")) } func testWindowsSnapshotAndTimeline() throws { let url = try XCTUnwrap(Bundle(for: Self.self).url(forResource: "state-v1", withExtension: "json")) let state = try JSONDecoder().decode(MediaState.self, from: Data(contentsOf: url)) XCTAssertEqual(state.position(elapsed: 2), 44.5) XCTAssertEqual(state.position(elapsed: 9999), 180) XCTAssertTrue(state.capabilities.seek) XCTAssertEqual(state.volume, 0.5) XCTAssertNoThrow(try BridgeClient.validate(state)) } func testCommandsCarryUniqueIDsAndOptionalValue() throws { let command = Command(type: "seek", value: 90.5) let object = try XCTUnwrap(JSONSerialization.jsonObject(with: JSONEncoder().encode(command)) as? [String: Any]) XCTAssertEqual(object["type"] as? String, "seek") XCTAssertEqual(object["value"] as? Double, 90.5) XCTAssertNotEqual(command.id, Command(type: "seek", value: 90.5).id) let toggle = try XCTUnwrap(JSONSerialization.jsonObject(with: JSONEncoder().encode(Command(type: "toggle", value: nil))) as? [String: Any]) XCTAssertNil(toggle["value"]) } }