Files
Yaojia Wang cbaa08daba feat(ios): W0 scaffold + day-1 spike + WireProtocol frozen contract + TestSupport doubles
T-iOS-1: ios/ XcodeGen project (iOS 17, Swift 6 strict concurrency, ATS per PLAN §5.2), 5 SPM package shells, CI skeleton
T-iOS-2: Origin spike vs real server — URLSessionWebSocketTask custom Origin CONFIRMED (no Starscream); 16MiB replay + EMSGSIZE(40) errno correction written back to plan
T-iOS-3: WireProtocol frozen contract, 59 tests, 100% line coverage, cross-impl vectors vs src/protocol.ts via tsx
T-iOS-4: FakeTransport/FakeClock/FakeHTTPTransport doubles
Verify: independent agent re-ran all acceptance — 6/6 PASS
2026-07-04 21:19:30 +02:00

127 lines
5.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import Testing
import WireProtocol
// T-iOS-3 · HostEndpoint originHeader/wsURL T-iOS-7 plan §7
// Origin plan §5.1 Origin
private func endpoint(_ urlString: String) -> HostEndpoint? {
guard let url = URL(string: urlString) else { return nil }
return HostEndpoint(baseURL: url)
}
// MARK: - originHeader
@Test("originHeaderhttp + IPv4 + 非默认端口 → 与 baseURL 同串")
func originHeaderKeepsNonDefaultPortIPv4() throws {
let sut = try #require(endpoint("http://192.168.1.5:3000"))
#expect(sut.originHeader == "http://192.168.1.5:3000")
}
@Test("originHeaderhttps + 非标端口保留端口")
func originHeaderKeepsNonStandardHTTPSPort() throws {
let sut = try #require(endpoint("https://mac.example:8443"))
#expect(sut.originHeader == "https://mac.example:8443")
}
@Test("originHeaderhttps + 443 → 无端口后缀(浏览器 Origin 序列化)")
func originHeaderOmitsDefaultHTTPSPort() throws {
let sut = try #require(endpoint("https://mac.example:443"))
#expect(sut.originHeader == "https://mac.example")
}
@Test("originHeaderhttp + 80 → 无端口后缀")
func originHeaderOmitsDefaultHTTPPort() throws {
let sut = try #require(endpoint("http://mac.example:80"))
#expect(sut.originHeader == "http://mac.example")
}
@Test("originHeader无端口 URL → 无端口后缀localhost 保留非默认端口")
func originHeaderWithoutExplicitPort() throws {
let bare = try #require(endpoint("https://mac.tailnet-1234.ts.net"))
#expect(bare.originHeader == "https://mac.tailnet-1234.ts.net")
let localhost = try #require(endpoint("http://localhost:3000"))
#expect(localhost.originHeader == "http://localhost:3000")
}
@Test("originHeaderscheme 与 host 小写规范化(服务器两侧 new URL() 规范化对称)")
func originHeaderLowercasesSchemeAndHost() throws {
let sut = try #require(endpoint("HTTP://Mac.Example:3000"))
#expect(sut.originHeader == "http://mac.example:3000")
}
@Test("originHeaderbaseURL 的 path/query 不进入 Origin")
func originHeaderIgnoresPathAndQuery() throws {
let sut = try #require(endpoint("http://192.168.1.5:3000/index.html?x=1"))
#expect(sut.originHeader == "http://192.168.1.5:3000")
}
@Test("originHeaderIPv6 host 保留方括号")
func originHeaderBracketsIPv6Host() throws {
let sut = try #require(endpoint("http://[fe80::1]:3000"))
#expect(sut.originHeader == "http://[fe80::1]:3000")
}
// MARK: - wsURL scheme httpws / httpswss + WireConstants.wsPath
@Test("wsURLhttp → ws 同 host 同 port + /term")
func wsURLDerivesWsFromHttp() throws {
let sut = try #require(endpoint("http://192.168.1.5:3000"))
#expect(sut.wsURL.absoluteString == "ws://192.168.1.5:3000/term")
}
@Test("wsURLhttps → wss + /term非标端口保留")
func wsURLDerivesWssFromHttps() throws {
let sut = try #require(endpoint("https://mac.example:8443"))
#expect(sut.wsURL.absoluteString == "wss://mac.example:8443/term")
}
@Test("wsURL无端口 https → wss 无端口path 恒为 WireConstants.wsPath")
func wsURLWithoutPort() throws {
let sut = try #require(endpoint("https://mac.tailnet-1234.ts.net"))
#expect(sut.wsURL.absoluteString == "wss://mac.tailnet-1234.ts.net/term")
#expect(sut.wsURL.path == WireConstants.wsPath)
}
@Test("wsURLbaseURL 带尾斜杠/path/query 时仍只保留 /term")
func wsURLReplacesPathAndDropsQuery() throws {
let sut = try #require(endpoint("http://192.168.1.5:3000/launcher?join=abc"))
#expect(sut.wsURL.absoluteString == "ws://192.168.1.5:3000/term")
}
// MARK: - http(s) / host plan §5
@Test("init非 http(s) scheme 或无 host → nil",
arguments: ["ftp://mac.example", "file:///tmp/x", "mailto:hi@example.com", "http://", "ws://mac.example:3000"])
func initRejectsNonHTTPBaseURLs(urlString: String) {
#expect(endpoint(urlString) == nil)
}
// MARK: - CodableHostRegistry Keychain
@Test("Codable roundtripencode→decode 恒等")
func codableRoundtripPreservesEquality() throws {
// Arrange
let original = try #require(endpoint("https://mac.example:8443"))
// Act
let data = try JSONEncoder().encode(original)
let decoded = try JSONDecoder().decode(HostEndpoint.self, from: data)
// Assert
#expect(decoded == original)
#expect(decoded.originHeader == original.originHeader)
#expect(decoded.wsURL == original.wsURL)
}
@Test("Codable持久化数据被篡改成非 http(s) URL → decode 显式 throw边界再验证")
func codableDecodeRevalidatesBaseURL() {
// Arrange
let tampered = Data("{\"baseURL\":\"ftp://mac.example\"}".utf8)
// Act / Assert
#expect(throws: DecodingError.self) {
_ = try JSONDecoder().decode(HostEndpoint.self, from: tampered)
}
}