Files
ios/MusicBridgeTests/ProtocolTests.swift
T

86 lines
4.7 KiB
Swift
Raw Normal View History

2026-09-10 09:11:55 +03:00
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"))
}
2026-09-10 09:11:55 +03:00
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"])
}
}