Skip to content

Commit 8413f25

Browse files
committed
Added DeleteUserAsync() method to the public API; Added integration tests
1 parent 972754e commit 8413f25

File tree

8 files changed

+153
-188
lines changed

8 files changed

+153
-188
lines changed

FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,22 @@ await Assert.ThrowsAsync<FirebaseException>(
122122
async () => await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync("mock-uid", customClaims));
123123
}
124124

125+
[Fact]
126+
public async Task UserLifecycle()
127+
{
128+
var rand = new Random();
129+
var uid = $"user{rand.Next()}";
130+
var customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
131+
var idToken = await SignInWithCustomTokenAsync(customToken);
132+
133+
var user = await FirebaseAuth.DefaultInstance.GetUserAsync(uid);
134+
Assert.Equal(uid, user.Uid);
135+
136+
await FirebaseAuth.DefaultInstance.DeleteUserAsync(uid);
137+
await Assert.ThrowsAsync<FirebaseException>(
138+
async () => await FirebaseAuth.DefaultInstance.GetUserAsync(uid));
139+
}
140+
125141
private static async Task<string> SignInWithCustomTokenAsync(string customToken)
126142
{
127143
var rb = new Google.Apis.Requests.RequestBuilder()

FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs

Lines changed: 43 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System;
1616
using System.Collections.Generic;
1717
using System.Net;
18+
using System.Net.Http;
1819
using System.Threading.Tasks;
1920
using FirebaseAdmin.Tests;
2021
using Google.Apis.Auth.OAuth2;
@@ -47,6 +48,7 @@ public void ReservedClaims()
4748
{
4849
{ key, "value" },
4950
};
51+
5052
Assert.Throws<ArgumentException>(() => new UserRecord("user1") { CustomClaims = customClaims });
5153
}
5254
}
@@ -58,6 +60,7 @@ public void EmptyClaims()
5860
{
5961
{ string.Empty, "value" },
6062
};
63+
6164
Assert.Throws<ArgumentException>(() => new UserRecord("user1") { CustomClaims = emptyClaims });
6265
}
6366

@@ -86,15 +89,7 @@ public async Task GetUserById()
8689
},
8790
},
8891
};
89-
90-
var factory = new MockHttpClientFactory(handler);
91-
var userManager = new FirebaseUserManager(
92-
new FirebaseUserManagerArgs
93-
{
94-
Credential = MockCredential,
95-
ProjectId = MockProjectId,
96-
ClientFactory = factory,
97-
});
92+
var userManager = this.CreateFirebaseUserManager(handler);
9893

9994
var userRecord = await userManager.GetUserById("user1");
10095
Assert.Equal("user1", userRecord.Uid);
@@ -108,8 +103,8 @@ public async Task GetUserById()
108103
Assert.False(userRecord.Disabled);
109104
Assert.False(userRecord.EmailVerified);
110105
Assert.Equal(UserRecord.UnixEpoch, userRecord.TokensValidAfterTimestamp);
111-
Assert.Equal(0, userRecord.UserMetaData.CreationTimestamp);
112-
Assert.Equal(0, userRecord.UserMetaData.LastSignInTimestamp);
106+
Assert.Equal(DateTime.MinValue, userRecord.UserMetaData.CreationTimestamp);
107+
Assert.Equal(DateTime.MinValue, userRecord.UserMetaData.LastSignInTimestamp);
113108
}
114109

115110
[Fact]
@@ -147,21 +142,16 @@ public async Task GetUserByIdWithProperties()
147142
ProviderID = "other.com",
148143
UserId = "otheruid",
149144
DisplayName = "Other Name",
145+
Email = "[email protected]",
146+
PhotoUrl = "https://other.com/user.png",
147+
PhoneNumber = "+10987654321",
150148
},
151149
},
152150
},
153151
},
154152
},
155153
};
156-
157-
var factory = new MockHttpClientFactory(handler);
158-
var userManager = new FirebaseUserManager(
159-
new FirebaseUserManagerArgs
160-
{
161-
Credential = MockCredential,
162-
ProjectId = MockProjectId,
163-
ClientFactory = factory,
164-
});
154+
var userManager = this.CreateFirebaseUserManager(handler);
165155

166156
var userRecord = await userManager.GetUserById("user1");
167157
Assert.Equal("user1", userRecord.Uid);
@@ -191,15 +181,18 @@ public async Task GetUserByIdWithProperties()
191181
Assert.Equal("other.com", provider.ProviderId);
192182
Assert.Equal("otheruid", provider.Uid);
193183
Assert.Equal("Other Name", provider.DisplayName);
194-
Assert.Null(provider.Email);
195-
Assert.Null(provider.PhoneNumber);
196-
Assert.Null(provider.PhotoUrl);
184+
Assert.Equal("[email protected]", provider.Email);
185+
Assert.Equal("+10987654321", provider.PhoneNumber);
186+
Assert.Equal("https://other.com/user.png", provider.PhotoUrl);
197187

198188
Assert.True(userRecord.Disabled);
199189
Assert.True(userRecord.EmailVerified);
190+
200191
Assert.Equal(UserRecord.UnixEpoch.AddSeconds(3600), userRecord.TokensValidAfterTimestamp);
201-
Assert.Equal(100, userRecord.UserMetaData.CreationTimestamp);
202-
Assert.Equal(150, userRecord.UserMetaData.LastSignInTimestamp);
192+
var metadata = userRecord.UserMetaData;
193+
Assert.NotNull(metadata);
194+
Assert.Equal(UserRecord.UnixEpoch.AddMilliseconds(100), metadata.CreationTimestamp);
195+
Assert.Equal(UserRecord.UnixEpoch.AddMilliseconds(150), metadata.LastSignInTimestamp);
203196
}
204197

205198
[Fact]
@@ -209,14 +202,8 @@ public async Task GetUserByIdUserNotFound()
209202
{
210203
StatusCode = HttpStatusCode.NotFound,
211204
};
212-
var factory = new MockHttpClientFactory(handler);
213-
var userManager = new FirebaseUserManager(
214-
new FirebaseUserManagerArgs
215-
{
216-
Credential = MockCredential,
217-
ProjectId = MockProjectId,
218-
ClientFactory = factory,
219-
});
205+
var userManager = this.CreateFirebaseUserManager(handler);
206+
220207
await Assert.ThrowsAsync<FirebaseException>(
221208
async () => await userManager.GetUserById("user1"));
222209
}
@@ -235,14 +222,7 @@ public async Task UpdateUser()
235222
},
236223
},
237224
};
238-
var factory = new MockHttpClientFactory(handler);
239-
var userManager = new FirebaseUserManager(
240-
new FirebaseUserManagerArgs
241-
{
242-
Credential = MockCredential,
243-
ProjectId = MockProjectId,
244-
ClientFactory = factory,
245-
});
225+
var userManager = this.CreateFirebaseUserManager(handler);
246226
var customClaims = new Dictionary<string, object>()
247227
{
248228
{ "admin", true },
@@ -258,17 +238,10 @@ public async Task UpdateUserIncorrectResponseObject()
258238
{
259239
Response = new object(),
260240
};
261-
var factory = new MockHttpClientFactory(handler);
262-
var userManager = new FirebaseUserManager(
263-
new FirebaseUserManagerArgs
264-
{
265-
Credential = MockCredential,
266-
ProjectId = MockProjectId,
267-
ClientFactory = factory,
268-
});
241+
var userManager = this.CreateFirebaseUserManager(handler);
269242
var customClaims = new Dictionary<string, object>()
270243
{
271-
{ "admin", true },
244+
{ "admin", true },
272245
};
273246

274247
await Assert.ThrowsAsync<FirebaseException>(
@@ -282,17 +255,10 @@ public async Task UpdateUserIncorrectResponseUid()
282255
{
283256
Response = new UserRecord("testuser"),
284257
};
285-
var factory = new MockHttpClientFactory(handler);
286-
var userManager = new FirebaseUserManager(
287-
new FirebaseUserManagerArgs
288-
{
289-
Credential = MockCredential,
290-
ProjectId = MockProjectId,
291-
ClientFactory = factory,
292-
});
258+
var userManager = this.CreateFirebaseUserManager(handler);
293259
var customClaims = new Dictionary<string, object>()
294260
{
295-
{ "admin", true },
261+
{ "admin", true },
296262
};
297263

298264
await Assert.ThrowsAsync<FirebaseException>(
@@ -306,14 +272,7 @@ public async Task UpdateUserHttpError()
306272
{
307273
StatusCode = HttpStatusCode.InternalServerError,
308274
};
309-
var factory = new MockHttpClientFactory(handler);
310-
var userManager = new FirebaseUserManager(
311-
new FirebaseUserManagerArgs
312-
{
313-
Credential = MockCredential,
314-
ProjectId = MockProjectId,
315-
ClientFactory = factory,
316-
});
275+
var userManager = this.CreateFirebaseUserManager(handler);
317276
var customClaims = new Dictionary<string, object>()
318277
{
319278
{ "admin", true },
@@ -333,15 +292,9 @@ public async Task DeleteUser()
333292
{ "kind", "identitytoolkit#DeleteAccountResponse" },
334293
},
335294
};
336-
var factory = new MockHttpClientFactory(handler);
337-
var userManager = new FirebaseUserManager(
338-
new FirebaseUserManagerArgs
339-
{
340-
Credential = MockCredential,
341-
ProjectId = MockProjectId,
342-
ClientFactory = factory,
343-
});
344-
await userManager.DeleteUser("user1");
295+
var userManager = this.CreateFirebaseUserManager(handler);
296+
297+
await userManager.DeleteUserAsync("user1");
345298
}
346299

347300
[Fact]
@@ -351,16 +304,21 @@ public async Task DeleteUserNotFound()
351304
{
352305
StatusCode = HttpStatusCode.NotFound,
353306
};
354-
var factory = new MockHttpClientFactory(handler);
355-
var userManager = new FirebaseUserManager(
356-
new FirebaseUserManagerArgs
357-
{
358-
Credential = MockCredential,
359-
ProjectId = MockProjectId,
360-
ClientFactory = factory,
361-
});
307+
var userManager = this.CreateFirebaseUserManager(handler);
308+
362309
await Assert.ThrowsAsync<FirebaseException>(
363-
async () => await userManager.DeleteUser("user1"));
310+
async () => await userManager.DeleteUserAsync("user1"));
311+
}
312+
313+
private FirebaseUserManager CreateFirebaseUserManager(HttpMessageHandler handler)
314+
{
315+
var args = new FirebaseUserManagerArgs
316+
{
317+
Credential = MockCredential,
318+
ProjectId = MockProjectId,
319+
ClientFactory = new MockHttpClientFactory(handler),
320+
};
321+
return new FirebaseUserManager(args);
364322
}
365323
}
366324
}

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,34 @@ public async Task<UserRecord> GetUserAsync(
296296
return await userManager.GetUserById(uid, cancellationToken);
297297
}
298298

299+
/// <summary>
300+
/// Deletes the user identified by the specified <paramref name="uid"/>.
301+
/// </summary>
302+
/// <param name="uid">A user ID string.</param>
303+
/// <returns>A task that completes when the user account has been deleted.</returns>
304+
/// <exception cref="ArgumentException">If the user ID argument is null or empty.</exception>
305+
/// <exception cref="FirebaseException">If an error occurs while deleting the user.</exception>
306+
public async Task DeleteUserAsync(string uid)
307+
{
308+
await this.DeleteUserAsync(uid, default(CancellationToken));
309+
}
310+
311+
/// <summary>
312+
/// Deletes the user identified by the specified <paramref name="uid"/>.
313+
/// </summary>
314+
/// <param name="uid">A user ID string.</param>
315+
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
316+
/// operation.</param>
317+
/// <returns>A task that completes when the user account has been deleted.</returns>
318+
/// <exception cref="ArgumentException">If the user ID argument is null or empty.</exception>
319+
/// <exception cref="FirebaseException">If an error occurs while deleting the user.</exception>
320+
public async Task DeleteUserAsync(string uid, CancellationToken cancellationToken)
321+
{
322+
var userManager = this.IfNotDeleted(() => this.userManager.Value);
323+
324+
await userManager.DeleteUserAsync(uid, cancellationToken);
325+
}
326+
299327
/// <summary>
300328
/// Sets the specified custom claims on an existing user account. A null claims value
301329
/// removes any claims currently set on the user account. The claims must serialize into

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,19 @@ public async Task<UserRecord> GetUserById(
7676
throw new ArgumentException("User ID cannot be null or empty.");
7777
}
7878

79-
const string getUserPath = "accounts:lookup";
8079
var payload = new Dictionary<string, object>()
8180
{
8281
{ "localId", uid },
8382
};
8483

8584
var response = await this.PostAndDeserializeAsync<GetAccountInfoResponse>(
86-
getUserPath, payload, cancellationToken).ConfigureAwait(false);
85+
"accounts:lookup", payload, cancellationToken).ConfigureAwait(false);
8786
if (response == null || response.Users == null || response.Users.Count == 0)
8887
{
89-
throw new FirebaseException($"Failed to get user: {uid}");
88+
throw new FirebaseException($"Failed to get user by ID: {uid}");
9089
}
9190

92-
var user = response.Users[0];
93-
if (user == null || user.UserId != uid)
94-
{
95-
throw new FirebaseException($"Failed to get user: {uid}");
96-
}
97-
98-
return new UserRecord(user);
91+
return new UserRecord(response.Users[0]);
9992
}
10093

10194
/// <summary>
@@ -108,9 +101,8 @@ public async Task<UserRecord> GetUserById(
108101
public async Task UpdateUserAsync(
109102
UserRecord user, CancellationToken cancellationToken = default(CancellationToken))
110103
{
111-
const string updatePath = "accounts:update";
112104
var response = await this.PostAndDeserializeAsync<GetAccountInfoResponse>(
113-
updatePath, user, cancellationToken).ConfigureAwait(false);
105+
"accounts:update", user, cancellationToken).ConfigureAwait(false);
114106
if (response == null || response.Users == null || response.Users.Count == 0)
115107
{
116108
throw new FirebaseException($"Failed to get user: {user.Uid}");
@@ -129,21 +121,20 @@ public async Task UpdateUserAsync(
129121
/// <param name="uid">A user ID string.</param>
130122
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
131123
/// operation.</param>
132-
public async Task DeleteUser(
124+
public async Task DeleteUserAsync(
133125
string uid, CancellationToken cancellationToken = default(CancellationToken))
134126
{
135127
if (string.IsNullOrEmpty(uid))
136128
{
137129
throw new ArgumentException("User id cannot be null or empty.");
138130
}
139131

140-
const string getUserPath = "accounts:delete";
141132
var payload = new Dictionary<string, object>()
142133
{
143134
{ "localId", uid },
144135
};
145136
var response = await this.PostAndDeserializeAsync<JObject>(
146-
getUserPath, payload, cancellationToken).ConfigureAwait(false);
137+
"accounts:delete", payload, cancellationToken).ConfigureAwait(false);
147138
if (response == null || (string)response["kind"] == null)
148139
{
149140
throw new FirebaseException($"Failed to delete user: {uid}");

0 commit comments

Comments
 (0)