import Foundation import Testing @testable import ManaCore @Suite("AuthClient 2FA-Enrollment") @MainActor struct AuthClientTwoFactorEnrollmentTests { /// Bringt den AuthClient in den `.signedIn`-Status mit einem /// Session-Token für authenticated Calls. private func signedInAuth() async -> MockedAuth { let mocked = makeMockedAuth() let access = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1MSIsImV4cCI6MjAwMDAwMDAwMH0.sig" mocked.setHandler { _ in (200, Data(#"{"accessToken":"\#(access)","refreshToken":"session-tok"}"#.utf8)) } await mocked.auth.signIn(email: "u@x.de", password: "pw") return mocked } @Test("enrollTotp liefert URI + Backup-Codes") func enrollSuccess() async throws { let mocked = await signedInAuth() let captured = MockURLProtocol.Capture() mocked.setHandler { request in captured.store(request) return (200, Data(#""" {"totpURI":"otpauth://totp/Mana:u@x.de?secret=ABC&issuer=Mana","backupCodes":["a-b-c","d-e-f","g-h-i"]} """#.utf8)) } let enrollment = try await mocked.auth.enrollTotp(password: "pw") #expect(enrollment.totpURI.hasPrefix("otpauth://totp/")) #expect(enrollment.backupCodes == ["a-b-c", "d-e-f", "g-h-i"]) let request = try #require(captured.request) #expect(request.url?.path == "/api/v1/auth/two-factor/enable") // Bearer-Header trägt den Session-Token (nicht JWT) #expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer session-tok") } @Test("enrollTotp mit leerem Passwort → validation, kein Server-Call") func enrollEmptyPassword() async { let mocked = await signedInAuth() mocked.setHandler { _ in Issue.record("Server darf nicht aufgerufen werden") return (500, Data()) } await #expect(throws: AuthError.validation(message: "Passwort ist erforderlich")) { try await mocked.auth.enrollTotp(password: "") } } @Test("enrollTotp ohne Session → notSignedIn") func enrollNoSession() async { let mocked = makeMockedAuth() await #expect(throws: AuthError.notSignedIn) { try await mocked.auth.enrollTotp(password: "pw") } } @Test("disableTotp success") func disableSuccess() async throws { let mocked = await signedInAuth() let captured = MockURLProtocol.Capture() mocked.setHandler { request in captured.store(request) return (200, Data(#"{"success":true}"#.utf8)) } try await mocked.auth.disableTotp(password: "pw") let request = try #require(captured.request) #expect(request.url?.path == "/api/v1/auth/two-factor/disable") } @Test("disableTotp mit falschem Passwort → invalidCredentials") func disableWrongPassword() async { let mocked = await signedInAuth() mocked.setHandler { _ in (401, Data(#"{"error":"INVALID_CREDENTIALS","status":401}"#.utf8)) } await #expect(throws: AuthError.invalidCredentials) { try await mocked.auth.disableTotp(password: "wrong") } } @Test("getTotpUri liefert URI ohne Backup-Codes") func getUriSuccess() async throws { let mocked = await signedInAuth() mocked.setHandler { _ in (200, Data(#""" {"totpURI":"otpauth://totp/Mana:u@x.de?secret=XYZ&issuer=Mana"} """#.utf8)) } let uri = try await mocked.auth.getTotpUri(password: "pw") #expect(uri.hasPrefix("otpauth://totp/")) #expect(uri.contains("secret=XYZ")) } @Test("regenerateBackupCodes liefert neue Codes") func regenerateSuccess() async throws { let mocked = await signedInAuth() let captured = MockURLProtocol.Capture() mocked.setHandler { request in captured.store(request) return (200, Data(#""" {"backupCodes":["new-1","new-2","new-3","new-4","new-5"]} """#.utf8)) } let codes = try await mocked.auth.regenerateBackupCodes(password: "pw") #expect(codes.count == 5) #expect(codes.first == "new-1") let request = try #require(captured.request) #expect(request.url?.path == "/api/v1/auth/two-factor/generate-backup-codes") } }