Files
web-terminal/ios/Packages/APIClient/Tests/APIClientTests/HostClassificationTests.swift
Yaojia Wang 4871e8ac3d feat(ios): P1-A — server touch-points (lastOutputAt, APNs sender+token endpoint) + APIClient P1 contract
T-iOS-37: LiveSessionInfo.lastOutputAt additive-optional field + manager.list() mapping (+4 tests; web consumers unaffected)
T-iOS-20: src/push/apns.ts — env-gated (all-or-disabled, no crash, zero key-material logging), hand-rolled ES256 JWT
(node:crypto ieee-p1363, zero new deps), NEEDS-INPUT/DONE payloads with structural minimization, token store per
subscription-store conventions, combineNotifyServices parallel to web-push, POST/DELETE /push/apns-token per frozen
wire shape (G-guard, 5/min/IP, 8kb); 65 tests incl. dual-channel e2e vs local fake APNs
T-iOS-38: APIClient builders — apns-token (client-side hex mirror, pre-network reject), projects/detail (lossy decode,
single-point percent-encoding), prefs (unknown-key byte-exact round-trip preservation), public four-tier HostNetworkTier
Coordination: webterminal:// CFBundleURLTypes pre-registered in project.yml for T-iOS-22
Verified: root 1470 tests + tsc clean; APIClient 76 tests, 95.26% coverage; wire-shape cross-check zero mismatches
2026-07-05 13:34:01 +02:00

84 lines
3.2 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
import APIClient
/// T-iOS-38 · host plan §5.4 API
/// loopback / privateLAN / tailscale / publicW3 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)")
}
}
}