import Foundation import Testing import WireProtocol import APIClient /// T-iOS-38 · 四层 host 分级(plan §5.4 提示分层表),公开 API: /// loopback / privateLAN / tailscale / public。W3 时 PairingViewModel 因 /// `isPrivateOrLocalHost` 是 internal 单桶谓词而自行重写了分类——本类型 /// 是给 VM 换用的单一出处(本任务不改 VM,见日志条目)。 /// 注意本文件**不用 @testable**:证明 API 确实 public。 struct HostClassificationTests { @Test("loopback 层:localhost/127.0.0.0\u{2044}8/IPv6 ::1(裸与带括号)") func loopbackTierMatchesLocalTargets() { // Arrange let hosts = ["localhost", "LOCALHOST", "127.0.0.1", "127.8.8.8", "::1", "[::1]"] // Act + Assert for host in hosts { #expect(HostClassifier.classify(host: host) == .loopback, "\(host) 应为 loopback") } } @Test("privateLAN 层:RFC1918 三段 + link-local 169.254\u{2044}16 + mDNS .local") func privateLANTierMatchesRFC1918AndLinkLocal() { // Arrange let hosts = [ "10.0.0.5", "192.168.0.9", "172.16.0.1", "172.31.255.255", "169.254.1.1", "mac-mini.local", "Mac-Mini.LOCAL", ] // Act + Assert for host in hosts { #expect(HostClassifier.classify(host: host) == .privateLAN, "\(host) 应为 privateLAN") } } @Test("tailscale 层:CGNAT 100.64.0.0\u{2044}10 与 MagicDNS *.ts.net") func tailscaleTierMatchesCGNATAndMagicDNS() { // Arrange let hosts = [ "100.64.0.1", "100.100.1.1", "100.127.255.255", "mac.tailnet.ts.net", "foo.TS.NET", ] // Act + Assert for host in hosts { #expect(HostClassifier.classify(host: host) == .tailscale, "\(host) 应为 tailscale") } } @Test("public 层:公网 IP/域名/越界 CIDR/畸形 IPv4 一律最强警告层") func publicTierIsTheFailSafeDefault() { // Arrange — 越界:172.32 超出 172.16/12;100.128 与 100.63 超出 100.64/10 let hosts = [ "8.8.8.8", "203.0.113.7", "example.com", "tsnet.example.com", "172.32.0.1", "100.128.0.1", "100.63.255.255", "256.1.1.1", "1.2.3", "", ] // Act + Assert — 分不清 → public(fail-safe:宁可多警告,plan §5.4) for host in hosts { #expect(HostClassifier.classify(host: host) == .public, "\(host) 应为 public") } } @Test("classify(endpoint:) 便捷入口与 host 字符串版一致(VM 只有 HostEndpoint 可用)") func endpointConvenienceMatchesHostClassification() throws { // Arrange let vectors: [(url: String, tier: HostNetworkTier)] = [ ("http://127.0.0.1:3000", .loopback), ("http://192.168.1.5:3000", .privateLAN), ("http://100.100.1.1:3000", .tailscale), ("https://mac.tailnet.ts.net", .tailscale), ("https://example.com", .public), ] // Act + Assert for vector in vectors { let url = try #require(URL(string: vector.url)) let endpoint = try #require(HostEndpoint(baseURL: url)) #expect(HostClassifier.classify(endpoint: endpoint) == vector.tier, "\(vector.url)") } } }