Files
ios/MusicBridgeTests/ProtocolTests.swift
T

68 lines
3.6 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 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"])
}
}