161 lines
6.5 KiB
Swift
161 lines
6.5 KiB
Swift
// PairingTests.swift
|
|
// Unit tests for PairingService.parseQR — IC-1 QR URL parsing.
|
|
//
|
|
// Format: pi-remote://<host>:<port>?pair=<pairingToken>&fp=<fingerprint>&name=<sidecarName>
|
|
//
|
|
// No network calls are made; parseQR is a pure function.
|
|
|
|
import XCTest
|
|
@testable import piRemote
|
|
|
|
final class PairingTests: XCTestCase {
|
|
|
|
// =========================================================================
|
|
// MARK: 1. Happy-path parsing
|
|
// =========================================================================
|
|
|
|
func testParseQR_canonical_parsesAllFields() throws {
|
|
let url = "pi-remote://192.168.1.1:7777?pair=abc&fp=deadbeef&name=pi-remote"
|
|
let result = try PairingService.parseQR(url)
|
|
|
|
XCTAssertEqual(result.host, "192.168.1.1")
|
|
XCTAssertEqual(result.port, 7777)
|
|
XCTAssertEqual(result.pairingToken, "abc")
|
|
XCTAssertEqual(result.fingerprint, "deadbeef")
|
|
XCTAssertEqual(result.name, "pi-remote")
|
|
}
|
|
|
|
func testParseQR_hostOnly_differentPort() throws {
|
|
let url = "pi-remote://pi.local:9000?pair=tok123&fp=aabbcc&name=mypi"
|
|
let result = try PairingService.parseQR(url)
|
|
|
|
XCTAssertEqual(result.host, "pi.local")
|
|
XCTAssertEqual(result.port, 9000)
|
|
XCTAssertEqual(result.pairingToken, "tok123")
|
|
XCTAssertEqual(result.fingerprint, "aabbcc")
|
|
XCTAssertEqual(result.name, "mypi")
|
|
}
|
|
|
|
func testParseQR_longFingerprint_parsedCorrectly() throws {
|
|
let fp = String(repeating: "a1", count: 32) // 64-char SHA-256 hex
|
|
let url = "pi-remote://10.0.0.1:7777?pair=t&fp=\(fp)&name=n"
|
|
let result = try PairingService.parseQR(url)
|
|
XCTAssertEqual(result.fingerprint, fp)
|
|
}
|
|
|
|
func testParseQR_portBoundary_lowPort() throws {
|
|
let url = "pi-remote://localhost:1?pair=t&fp=f&name=n"
|
|
let result = try PairingService.parseQR(url)
|
|
XCTAssertEqual(result.port, 1)
|
|
}
|
|
|
|
func testParseQR_portBoundary_highPort() throws {
|
|
let url = "pi-remote://localhost:65535?pair=t&fp=f&name=n"
|
|
let result = try PairingService.parseQR(url)
|
|
XCTAssertEqual(result.port, 65535)
|
|
}
|
|
|
|
func testParseQR_nameWithSpaces_percentEncoded() throws {
|
|
// Spaces encoded as %20 in the QR URL
|
|
let url = "pi-remote://192.168.1.1:7777?pair=tok&fp=fp&name=my%20pi"
|
|
let result = try PairingService.parseQR(url)
|
|
XCTAssertEqual(result.name, "my pi")
|
|
}
|
|
|
|
// =========================================================================
|
|
// MARK: 2. Missing required parameters → throws
|
|
// =========================================================================
|
|
|
|
func testParseQR_missingPairParam_throws() {
|
|
let url = "pi-remote://192.168.1.1:7777?fp=deadbeef&name=pi-remote"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"Missing 'pair' must throw")
|
|
}
|
|
|
|
func testParseQR_missingFpParam_throws() {
|
|
let url = "pi-remote://192.168.1.1:7777?pair=abc&name=pi-remote"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"Missing 'fp' must throw")
|
|
}
|
|
|
|
func testParseQR_missingNameParam_throws() {
|
|
let url = "pi-remote://192.168.1.1:7777?pair=abc&fp=deadbeef"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"Missing 'name' must throw")
|
|
}
|
|
|
|
func testParseQR_noQueryParams_throws() {
|
|
let url = "pi-remote://192.168.1.1:7777"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"No query params must throw")
|
|
}
|
|
|
|
func testParseQR_emptyPairValue_throws() {
|
|
let url = "pi-remote://192.168.1.1:7777?pair=&fp=deadbeef&name=pi-remote"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"Empty 'pair' value must throw")
|
|
}
|
|
|
|
// =========================================================================
|
|
// MARK: 3. Wrong scheme → throws
|
|
// =========================================================================
|
|
|
|
func testParseQR_httpsScheme_throws() {
|
|
// Wrong scheme — sidecar only issues pi-remote:// URLs
|
|
let url = "https://192.168.1.1:7777?pair=abc&fp=deadbeef&name=pi-remote"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"https:// scheme must throw .invalidQR")
|
|
}
|
|
|
|
func testParseQR_httpScheme_throws() {
|
|
let url = "http://192.168.1.1:7777?pair=abc&fp=deadbeef&name=pi-remote"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"http:// scheme must throw .invalidQR")
|
|
}
|
|
|
|
func testParseQR_emptyString_throws() {
|
|
XCTAssertThrowsError(try PairingService.parseQR(""),
|
|
"Empty string must throw")
|
|
}
|
|
|
|
func testParseQR_randomString_throws() {
|
|
XCTAssertThrowsError(try PairingService.parseQR("not-a-url"),
|
|
"Garbage string must throw")
|
|
}
|
|
|
|
// =========================================================================
|
|
// MARK: 4. Missing port → throws
|
|
// =========================================================================
|
|
|
|
func testParseQR_missingPort_throws() {
|
|
// No port specified — should throw because port is required
|
|
let url = "pi-remote://192.168.1.1?pair=abc&fp=deadbeef&name=pi-remote"
|
|
XCTAssertThrowsError(try PairingService.parseQR(url),
|
|
"Missing port must throw .invalidQR")
|
|
}
|
|
|
|
// =========================================================================
|
|
// MARK: 5. Error type is PairingError.invalidQR
|
|
// =========================================================================
|
|
|
|
func testParseQR_wrongScheme_throwsInvalidQR() {
|
|
XCTAssertThrowsError(try PairingService.parseQR("https://host:7777?pair=x&fp=y&name=z")) { error in
|
|
guard let pairingError = error as? PairingError,
|
|
case .invalidQR = pairingError else {
|
|
XCTFail("Expected PairingError.invalidQR, got \(error)")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func testParseQR_missingParam_throwsInvalidQR() {
|
|
XCTAssertThrowsError(try PairingService.parseQR("pi-remote://host:7777?pair=x&fp=y")) { error in
|
|
guard let pairingError = error as? PairingError,
|
|
case .invalidQR = pairingError else {
|
|
XCTFail("Expected PairingError.invalidQR, got \(error)")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|