import Foundation import Testing @testable import ClientTLS // C-iOS-1 · The responder truth table: 3 challenge types × identity present / // absent. Exercised through the pure method-keyed core AND through a real // `URLAuthenticationChallenge` (proving the method is extracted correctly), // with no live socket. private let responder = MutualTLSChallengeResponder() private func makeIdentity() throws -> ClientIdentity { try PKCS12Importer.importIdentity( data: ClientTLSFixtures.deviceP12Data, passphrase: ClientTLSFixtures.passphrase ) } // MARK: - ClientCertificate @Test("ClientCertificate + identity → .useCredential with a credential") func clientCertWithIdentityUsesCredential() throws { // Arrange let identity = try makeIdentity() // Act let resolution = responder.resolve( authenticationMethod: NSURLAuthenticationMethodClientCertificate, identity: identity ) // Assert #expect(resolution.disposition == .useCredential) #expect(resolution.credential != nil) #expect(resolution.credential?.identity != nil) } @Test("ClientCertificate + no identity → .cancelAuthenticationChallenge, no credential") func clientCertWithoutIdentityCancels() { // Act let resolution = responder.resolve( authenticationMethod: NSURLAuthenticationMethodClientCertificate, identity: nil ) // Assert #expect(resolution.disposition == .cancelAuthenticationChallenge) #expect(resolution.credential == nil) } // MARK: - ServerTrust @Test("ServerTrust → .performDefaultHandling regardless of identity") func serverTrustDefaultHandling() throws { let identity = try makeIdentity() for id in [identity, nil] as [ClientIdentity?] { let resolution = responder.resolve( authenticationMethod: NSURLAuthenticationMethodServerTrust, identity: id ) #expect(resolution.disposition == .performDefaultHandling) #expect(resolution.credential == nil) } } // MARK: - Other method (e.g. Basic) @Test("an unrelated method → .performDefaultHandling regardless of identity") func otherMethodDefaultHandling() throws { let identity = try makeIdentity() for id in [identity, nil] as [ClientIdentity?] { let resolution = responder.resolve( authenticationMethod: NSURLAuthenticationMethodHTTPBasic, identity: id ) #expect(resolution.disposition == .performDefaultHandling) #expect(resolution.credential == nil) } } // MARK: - Full URLAuthenticationChallenge extraction @Test("resolve(challenge:) extracts the method from the protection space") func resolveFromRealChallenge() throws { // Arrange — a real challenge for the ClientCertificate method. let identity = try makeIdentity() let challenge = makeChallenge(method: NSURLAuthenticationMethodClientCertificate) // Act let withIdentity = responder.resolve(challenge, identity: identity) let withoutIdentity = responder.resolve(challenge, identity: nil) // Assert #expect(withIdentity.disposition == .useCredential) #expect(withIdentity.credential != nil) #expect(withoutIdentity.disposition == .cancelAuthenticationChallenge) } // MARK: - Helpers /// Minimal sender so `URLAuthenticationChallenge` can be constructed in-process /// (its designated init requires a non-optional sender). The responder never /// calls back into the sender — it only reads `protectionSpace`. private final class NoopChallengeSender: NSObject, URLAuthenticationChallengeSender { func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {} func continueWithoutCredential(for challenge: URLAuthenticationChallenge) {} func cancel(_ challenge: URLAuthenticationChallenge) {} } private func makeChallenge(method: String) -> URLAuthenticationChallenge { let space = URLProtectionSpace( host: "t1.terminal.yaojia.wang", port: 443, protocol: "https", realm: nil, authenticationMethod: method ) return URLAuthenticationChallenge( protectionSpace: space, proposedCredential: nil, previousFailureCount: 0, failureResponse: nil, error: nil, sender: NoopChallengeSender() ) }