// // 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)") } }