@@ -8,7 +8,8 @@ import FirebaseAuth
8
8
import SwiftUI
9
9
10
10
// 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 " )
12
13
13
14
public var testEmail : String ? {
14
15
guard let emailIndex = CommandLine . arguments. firstIndex ( of: " --create-user " ) ,
@@ -21,7 +22,111 @@ func testCreateUser() async throws {
21
22
if let email = testEmail {
22
23
let password = " 123456 "
23
24
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
+ }
25
29
try auth. signOut ( )
26
30
}
27
31
}
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