feat(ios): W3 UI layer + integration CI + ntfy docs
T-iOS-11: TerminalScreen/KeyBar/TerminalViewModel + KeyByteMap (byte-for-byte keybar.ts, arrows excluded from UIKeyCommand to preserve DECCKM) T-iOS-12: PairingScreen/VM — confirm-before-network (zero-call assertions), §5.4 four-tier warnings, Host construction per contract ruling T-iOS-13: SessionListScreen/VM — Tunables-paced polling with leak-free teardown, optimistic kill+rollback, pending via overlay (LiveSessionInfo has no pending field) T-iOS-14: GateBanner/PlanGateSheet/AwayDigestView/GateViewModel — three-way mapping from SessionCore Affordance single source, tap-epoch guard, per-epoch haptics T-iOS-16: IntegrationTests vs real Node server (10 tests: origin guards, mirror, kill-close vs exit-frame differential, 16MiB+ESC/C0 replay) + ios.yml own-sources coverage gate (red-once demoed) T-iOS-17: ios/README.md ntfy chapter (read-only verification, file:line cites) Verified: 224 unit + 10 integration tests green; 5/5 semantic spot-checks; zero Owns violations
This commit is contained in:
67
ios/IntegrationTests/OriginGuardTests.swift
Normal file
67
ios/IntegrationTests/OriginGuardTests.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// OriginGuardTests.swift — T-iOS-16 测试 5:Origin 守卫回归(吸收 spike①④)
|
||||
//
|
||||
// plan §5.1 的自动化化身。安全注:任何"为了过 CI 放宽 Origin 断言"= CRITICAL——
|
||||
// 401/403 的精确断言不许软化成"连接失败即可"。
|
||||
// - WS 升级无 Origin → 401(src/server.ts:646-651,默认拒空 Origin)
|
||||
// - WS 升级端口失配 Origin → 401(src/http/origin.ts:47-51 端口精确比对;
|
||||
// 注意 :443/:80 这类默认端口被两侧 new URL() 对称规范化,不是失配案例)
|
||||
// - DELETE /live-sessions/:id 无 Origin → 403(G 守卫先于 id 查找,
|
||||
// src/server.ts:332-339 requireAllowedOrigin)
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
import WireProtocol
|
||||
|
||||
@Suite("T-iOS-16 Origin 守卫(真服务器)", .serialized, .timeLimit(.minutes(5)))
|
||||
struct OriginGuardTests {
|
||||
|
||||
@Test("无 Origin 的 WS 升级被 401 拒绝(服务器默认拒空 Origin)")
|
||||
func upgradeWithoutOriginIsRejectedWith401() async throws {
|
||||
let server = try await ServerHarness.shared.server()
|
||||
let client = WSTestClient(server: server, origin: nil)
|
||||
defer { client.close() }
|
||||
|
||||
let failure = await client.handshakeFailure(timeout: HarnessTunables.frameTimeout)
|
||||
|
||||
#expect(failure != nil, "无 Origin 升级必须失败")
|
||||
#expect(client.handshakeStatusCode == 401,
|
||||
"应收 401(src/server.ts:646-651),实际 status=\(String(describing: client.handshakeStatusCode)) err=\(failure.map(describeError) ?? "nil")")
|
||||
}
|
||||
|
||||
@Test("端口失配的 Origin → 401(端口精确比对;默认端口规范化不是失配案例)")
|
||||
func portMismatchedOriginIsRejectedWith401() async throws {
|
||||
let server = try await ServerHarness.shared.server()
|
||||
let mismatchPort = server.port == HarnessTunables.mismatchedOriginPort
|
||||
? HarnessTunables.mismatchedOriginPortFallback
|
||||
: HarnessTunables.mismatchedOriginPort
|
||||
let badOrigin = "http://127.0.0.1:\(mismatchPort)"
|
||||
let client = WSTestClient(server: server, origin: badOrigin)
|
||||
defer { client.close() }
|
||||
|
||||
let failure = await client.handshakeFailure(timeout: HarnessTunables.frameTimeout)
|
||||
|
||||
#expect(failure != nil, "端口失配 Origin 升级必须失败")
|
||||
#expect(client.handshakeStatusCode == 401,
|
||||
"应收 401(src/http/origin.ts:47-51),实际 status=\(String(describing: client.handshakeStatusCode)) err=\(failure.map(describeError) ?? "nil")")
|
||||
}
|
||||
|
||||
@Test("DELETE /live-sessions/:id 无 Origin → 403;带 Origin 的同样请求 → 404(差分证明 403 来自守卫)")
|
||||
func deleteWithoutOriginIsRejectedWith403() async throws {
|
||||
// Arrange: 用一个不存在的随机 id —— 守卫在 id 查找之前执行,无需真会话。
|
||||
let server = try await ServerHarness.shared.server()
|
||||
let bogusId = UUID().uuidString.lowercased()
|
||||
|
||||
// Act
|
||||
let statusWithoutOrigin = try await deleteLiveSession(
|
||||
server: server, id: bogusId, origin: nil)
|
||||
let statusWithOrigin = try await deleteLiveSession(
|
||||
server: server, id: bogusId, origin: server.origin)
|
||||
|
||||
// Assert: 无 Origin 走不到业务逻辑(403);带 Origin 走到了但 id 不存在(404)。
|
||||
#expect(statusWithoutOrigin == 403,
|
||||
"无 Origin 的 DELETE 应 403(src/server.ts:332-339),实际 \(statusWithoutOrigin)")
|
||||
#expect(statusWithOrigin == 404,
|
||||
"带 Origin 但 id 不存在应 404(src/server.ts:354-359),实际 \(statusWithOrigin)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user