@@ -8,7 +8,8 @@ import FirebaseAuth
88import SwiftUI
99
1010// UI Test Runner keys
11- public let uiAuthEmulator = CommandLine . arguments. contains ( " --auth-emulator " )
11+ public let testRunner = CommandLine . arguments. contains ( " --auth-emulator " )
12+ let verifyEmail = CommandLine . arguments. contains ( " --verify-email " )
1213
1314public var testEmail : String ? {
1415 guard let emailIndex = CommandLine . arguments. firstIndex ( of: " --create-user " ) ,
@@ -21,7 +22,111 @@ func testCreateUser() async throws {
2122 if let email = testEmail {
2223 let password = " 123456 "
2324 let auth = Auth . auth ( )
24- try await auth. createUser ( withEmail: email, password: password)
25+ let result = try await auth. createUser ( withEmail: email, password: password)
26+ if verifyEmail {
27+ try await setEmailVerifiedInEmulator ( for: result. user)
28+ }
2529 try auth. signOut ( )
2630 }
2731}
32+
33+ /// Marks the given Firebase `user` as email-verified **in the Auth emulator**.
34+ /// Works in CI even if the email address doesn't exist.
35+ /// - Parameters:
36+ /// - user: The signed-in Firebase user you want to verify.
37+ /// - projectID: Your emulator project ID (e.g. "demo-project" or whatever you're using locally).
38+ /// - emulatorHost: Host:port for the Auth emulator. Defaults to localhost:9099.
39+ func setEmailVerifiedInEmulator( for user: User ,
40+ projectID: String = " flutterfire-e2e-tests " ,
41+ emulatorHost: String = " localhost:9099 " ) async throws {
42+
43+ guard let email = user. email else {
44+ throw NSError ( domain: " EmulatorError " , code: 1 ,
45+ userInfo: [
46+ NSLocalizedDescriptionKey: " User has no email; cannot look up OOB code in emulator " ,
47+ ] )
48+ }
49+
50+ // 1) Trigger a verification email -> creates an OOB code in the emulator.
51+ try await sendVerificationEmail ( user)
52+
53+ // 2) Read OOB codes from the emulator and find the VERIFY_EMAIL code for this user.
54+ let base = " http:// \( emulatorHost) "
55+ let oobURL = URL ( string: " \( base) /emulator/v1/projects/ \( projectID) /oobCodes " ) !
56+
57+ let ( oobData, oobResp) = try await URLSession . shared. data ( from: oobURL)
58+ guard ( oobResp as? HTTPURLResponse ) ? . statusCode == 200 else {
59+ let body = String ( data: oobData, encoding: . utf8) ?? " "
60+ throw NSError ( domain: " EmulatorError " , code: 2 ,
61+ userInfo: [
62+ NSLocalizedDescriptionKey: " Failed to fetch oobCodes. Response: \( body) " ,
63+ ] )
64+ }
65+
66+ struct OobEnvelope : Decodable { let oobCodes : [ OobItem ] }
67+ struct OobItem : Decodable {
68+ let oobCode : String
69+ let email : String
70+ let requestType : String
71+ let creationTime : String ? // RFC3339/ISO8601; optional for safety
72+ }
73+
74+ let envelope = try JSONDecoder ( ) . decode ( OobEnvelope . self, from: oobData)
75+
76+ // Pick the most recent VERIFY_EMAIL code for this email (in case there are multiple).
77+ let iso = ISO8601DateFormatter ( )
78+ let codeItem = envelope. oobCodes
79+ . filter {
80+ $0. email. caseInsensitiveCompare ( email) == . orderedSame && $0. requestType == " VERIFY_EMAIL "
81+ }
82+ . sorted {
83+ let d0 = $0. creationTime. flatMap { iso. date ( from: $0) } ?? . distantPast
84+ let d1 = $1. creationTime. flatMap { iso. date ( from: $0) } ?? . distantPast
85+ return d0 > d1
86+ }
87+ . first
88+
89+ guard let oobCode = codeItem? . oobCode else {
90+ throw NSError ( domain: " EmulatorError " , code: 3 ,
91+ userInfo: [
92+ NSLocalizedDescriptionKey: " No VERIFY_EMAIL oobCode found for \( email) in emulator " ,
93+ ] )
94+ }
95+
96+ // 3) Apply the OOB code via the emulator's identitytoolkit endpoint.
97+ // Note: API key value does not matter when talking to the emulator.
98+ var applyReq = URLRequest (
99+ url: URL ( string: " \( base) /identitytoolkit.googleapis.com/v1/accounts:update?key=anything " ) !
100+ )
101+ applyReq. httpMethod = " POST "
102+ applyReq. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
103+ applyReq. httpBody = try JSONSerialization . data ( withJSONObject: [ " oobCode " : oobCode] , options: [ ] )
104+
105+ let ( applyData, applyResp) = try await URLSession . shared. data ( for: applyReq)
106+ guard let http = applyResp as? HTTPURLResponse , http. statusCode == 200 else {
107+ let body = String ( data: applyData, encoding: . utf8) ?? " "
108+ throw NSError ( domain: " EmulatorError " , code: 4 ,
109+ userInfo: [
110+ NSLocalizedDescriptionKey: " Applying oobCode failed. Status \( ( applyResp as? HTTPURLResponse ) ? . statusCode ?? - 1 ) . Body: \( body) " ,
111+ ] )
112+ }
113+
114+ log ( " Applied oobCode successfully; reloading user... " )
115+
116+ // 4) Reload the user to reflect the new verification state.
117+ try await user. reload ( )
118+ log ( " User reloaded. emailVerified after reload: \( user. isEmailVerified) " )
119+ }
120+
121+ /// Small async helper to call FirebaseAuth's callback-based `sendEmailVerification` on iOS.
122+ private func sendVerificationEmail( _ user: User ) async throws {
123+ try await withCheckedThrowingContinuation { ( cont: CheckedContinuation < Void , Error > ) in
124+ user. sendEmailVerification { error in
125+ if let error = error {
126+ cont. resume ( throwing: error)
127+ } else {
128+ cont. resume ( )
129+ }
130+ }
131+ }
132+ }
0 commit comments