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("originHeader:http + 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("originHeader:https + 非标端口保留端口") func originHeaderKeepsNonStandardHTTPSPort() throws { let sut = try #require(endpoint("https://mac.example:8443")) #expect(sut.originHeader == "https://mac.example:8443") } @Test("originHeader:https + 443 → 无端口后缀(浏览器 Origin 序列化)") func originHeaderOmitsDefaultHTTPSPort() throws { let sut = try #require(endpoint("https://mac.example:443")) #expect(sut.originHeader == "https://mac.example") } @Test("originHeader:http + 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("originHeader:scheme 与 host 小写规范化(服务器两侧 new URL() 规范化对称)") func originHeaderLowercasesSchemeAndHost() throws { let sut = try #require(endpoint("HTTP://Mac.Example:3000")) #expect(sut.originHeader == "http://mac.example:3000") } @Test("originHeader:baseURL 的 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("originHeader:IPv6 host 保留方括号") func originHeaderBracketsIPv6Host() throws { let sut = try #require(endpoint("http://[fe80::1]:3000")) #expect(sut.originHeader == "http://[fe80::1]:3000") } // MARK: - wsURL 派生向量(scheme http→ws / https→wss + WireConstants.wsPath) @Test("wsURL:http → 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("wsURL:https → 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("wsURL:baseURL 带尾斜杠/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: - Codable(HostRegistry Keychain 持久化经由它) @Test("Codable roundtrip:encode→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) } }