Skip to content

Commit a08df9e

Browse files
committed
Cleaning up the ListUsers() API
1 parent 75b86e1 commit a08df9e

File tree

9 files changed

+168
-83
lines changed

9 files changed

+168
-83
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,12 @@ public async Task ListUsersPaged()
324324
},
325325
};
326326
var userManager = this.CreateFirebaseUserManager(handler);
327-
328-
var usersPage = userManager.ListUsers(new ListUsersOptions());
329-
330327
var users = new List<ExportedUserRecord>();
331328
var tokens = new List<string>();
332329
var pageCounter = 0;
333-
for (Page<ExportedUserRecord> userPage; (userPage = await usersPage.ReadPageAsync(3)) != null;)
330+
331+
var pagedEnumerable = userManager.ListUsers(new ListUsersOptions());
332+
for (Page<ExportedUserRecord> userPage; (userPage = await pagedEnumerable.ReadPageAsync(3)) != null;)
334333
{
335334
pageCounter++;
336335
tokens.Add(userPage.NextPageToken);
@@ -351,6 +350,74 @@ public async Task ListUsersPaged()
351350
Assert.Equal("user4", users[3].Uid);
352351
Assert.Equal("user5", users[4].Uid);
353352
Assert.Equal("user6", users[5].Uid);
353+
354+
Assert.Equal(2, handler.Requests.Count);
355+
var req = handler.Requests[0];
356+
var query = req.Url.Query.Substring(1).Split('&').ToDictionary(
357+
entry => entry.Split('=')[0], entry => entry.Split('=')[1]);
358+
Assert.Equal("3", query["maxResults"]);
359+
Assert.Equal(string.Empty, query["nextPageToken"]);
360+
361+
req = handler.Requests[1];
362+
query = req.Url.Query.Substring(1).Split('&').ToDictionary(
363+
entry => entry.Split('=')[0], entry => entry.Split('=')[1]);
364+
Assert.Equal("3", query["maxResults"]);
365+
Assert.Equal("token", query["nextPageToken"]);
366+
}
367+
368+
[Fact]
369+
public void ListUsersByEntries()
370+
{
371+
var handler = new MockMessageHandler()
372+
{
373+
Response = new List<string>()
374+
{
375+
@"{
376+
""nextPageToken"": ""token"",
377+
""users"": [
378+
{""localId"": ""user1""},
379+
{""localId"": ""user2""},
380+
{""localId"": ""user3""}
381+
]
382+
}",
383+
@"{
384+
""users"": [
385+
{""localId"": ""user4""},
386+
{""localId"": ""user5""},
387+
{""localId"": ""user6""}
388+
]
389+
}",
390+
},
391+
};
392+
var userManager = this.CreateFirebaseUserManager(handler);
393+
394+
var pagedEnumerable = userManager.ListUsers(new ListUsersOptions());
395+
var users = new List<ExportedUserRecord>();
396+
foreach (var user in pagedEnumerable.ToEnumerable())
397+
{
398+
users.Add(user);
399+
}
400+
401+
Assert.Equal(6, users.Count);
402+
Assert.Equal("user1", users[0].Uid);
403+
Assert.Equal("user2", users[1].Uid);
404+
Assert.Equal("user3", users[2].Uid);
405+
Assert.Equal("user4", users[3].Uid);
406+
Assert.Equal("user5", users[4].Uid);
407+
Assert.Equal("user6", users[5].Uid);
408+
409+
Assert.Equal(2, handler.Requests.Count);
410+
var req = handler.Requests[0];
411+
var query = req.Url.Query.Substring(1).Split('&').ToDictionary(
412+
entry => entry.Split('=')[0], entry => entry.Split('=')[1]);
413+
Assert.Equal("1000", query["maxResults"]);
414+
Assert.Equal(string.Empty, query["nextPageToken"]);
415+
416+
req = handler.Requests[1];
417+
query = req.Url.Query.Substring(1).Split('&').ToDictionary(
418+
entry => entry.Split('=')[0], entry => entry.Split('=')[1]);
419+
Assert.Equal("1000", query["maxResults"]);
420+
Assert.Equal("token", query["nextPageToken"]);
354421
}
355422

356423
[Fact]
@@ -370,20 +437,61 @@ public async Task ListUsers()
370437
},
371438
},
372439
};
373-
374440
var userManager = this.CreateFirebaseUserManager(handler);
375441

376-
var usersPage = userManager.ListUsers(new ListUsersOptions());
377-
var listUsersRequest = await usersPage.ReadPageAsync(3);
378-
var userRecords = listUsersRequest.ToList();
379-
Assert.Equal(nextPageToken, listUsersRequest.NextPageToken);
442+
var pagedEnumerable = userManager.ListUsers(new ListUsersOptions());
443+
var usersPage = await pagedEnumerable.ReadPageAsync(3);
444+
445+
var userRecords = usersPage.ToList();
446+
Assert.Equal(nextPageToken, usersPage.NextPageToken);
380447
Assert.Equal(3, userRecords.Count);
381448
Assert.Equal("user1", userRecords[0].Uid);
382449
Assert.Equal("user2", userRecords[1].Uid);
383450
Assert.Equal("user3", userRecords[2].Uid);
451+
452+
Assert.Single(handler.Requests);
453+
var req = handler.Requests[0];
454+
var query = req.Url.Query.Substring(1).Split('&').ToDictionary(
455+
entry => entry.Split('=')[0], entry => entry.Split('=')[1]);
456+
Assert.Equal("3", query["maxResults"]);
457+
Assert.Equal(string.Empty, query["nextPageToken"]);
384458
}
385459

386460
[Fact]
461+
public void ListUsersMaxDefault()
462+
{
463+
var nextPageToken = Guid.NewGuid().ToString();
464+
var handler = new MockMessageHandler()
465+
{
466+
Response = new DownloadAccountResponse()
467+
{
468+
Users = new List<GetAccountInfoResponse.User>()
469+
{
470+
new GetAccountInfoResponse.User() { UserId = "user1" },
471+
new GetAccountInfoResponse.User() { UserId = "user2" },
472+
new GetAccountInfoResponse.User() { UserId = "user3" },
473+
},
474+
},
475+
};
476+
var userManager = this.CreateFirebaseUserManager(handler);
477+
478+
var pagedEnumerable = userManager.ListUsers(new ListUsersOptions());
479+
var userRecords = pagedEnumerable.ToEnumerable().ToList();
480+
481+
Assert.Equal(3, userRecords.Count);
482+
Assert.Equal("user1", userRecords[0].Uid);
483+
Assert.Equal("user2", userRecords[1].Uid);
484+
Assert.Equal("user3", userRecords[2].Uid);
485+
Assert.Single(handler.Requests);
486+
487+
var req = handler.Requests[0];
488+
var query = req.Url.Query.Substring(1).Split('&').ToDictionary(
489+
entry => entry.Split('=')[0], entry => entry.Split('=')[1]);
490+
Assert.Equal("1000", query["maxResults"]);
491+
Assert.Equal(string.Empty, query["nextPageToken"]);
492+
}
493+
494+
/* [Fact]
387495
public void ListUsersRequestOptionsAreSet()
388496
{
389497
var userManager = this.CreateFirebaseUserManager(new MockMessageHandler());
@@ -401,7 +509,7 @@ public void ListUsersRequestOptionsAreSet()
401509
listUsersRequest.SetPageToken("theNewNextPageToken");
402510
Assert.Equal(10, int.Parse(listUsersRequest.RequestParameters["maxResults"].DefaultValue));
403511
Assert.Equal("theNewNextPageToken", listUsersRequest.RequestParameters["nextPageToken"].DefaultValue);
404-
}
512+
} */
405513

406514
[Fact]
407515
public async Task CreateUser()

FirebaseAdmin/FirebaseAdmin/Auth/Internal/DownloadAccountResponse.cs renamed to FirebaseAdmin/FirebaseAdmin/Auth/DownloadAccountResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ internal class DownloadAccountResponse
3232
/// Gets or sets the users.
3333
/// </summary>
3434
[JsonProperty("users")]
35-
public List<GetAccountInfoResponse.User> Users { get; set; }
35+
public IEnumerable<GetAccountInfoResponse.User> Users { get; set; }
3636
}
3737
}

FirebaseAdmin/FirebaseAdmin/Auth/ExportedUserRecord.cs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,29 @@ namespace FirebaseAdmin.Auth
2020
/// </summary>
2121
public sealed class ExportedUserRecord : UserRecord
2222
{
23-
private readonly string passwordHash;
24-
private readonly string passwordSalt;
25-
2623
internal ExportedUserRecord(GetAccountInfoResponse.User user)
2724
: base(user)
2825
{
29-
this.passwordHash = user.PasswordHash;
30-
this.passwordSalt = user.PasswordSalt;
26+
this.PasswordHash = user.PasswordHash;
27+
this.PasswordSalt = user.PasswordSalt;
3128
}
3229

3330
/// <summary>
3431
/// Gets the user's password hash as a base64-encoded string.
3532
/// If the Firebase Auth hashing algorithm (SCRYPT) was used to create the user account,
36-
/// returns the base64-encoded password hash of the user.If a different hashing algorithm was
33+
/// returns the base64-encoded password hash of the user. If a different hashing algorithm was
3734
/// used to create this user, as is typical when migrating from another Auth system, returns
3835
/// an empty string. Returns null if no password is set.
3936
/// </summary>
40-
public string PasswordHash
41-
{
42-
get => this.passwordHash;
43-
}
37+
public string PasswordHash { get; }
4438

4539
/// <summary>
4640
/// Gets the user's password salt as a base64-encoded string.
4741
/// If the Firebase Auth hashing algorithm (SCRYPT) was used to create the user account,
48-
/// returns the base64-encoded password salt of the user.If a different hashing algorithm was
42+
/// returns the base64-encoded password salt of the user. If a different hashing algorithm was
4943
/// used to create this user, as is typical when migrating from another Auth system, returns
5044
/// an empty string. Returns null if no password is set.
5145
/// </summary>
52-
public string PasswordSalt
53-
{
54-
get => this.passwordSalt;
55-
}
46+
public string PasswordSalt { get; }
5647
}
5748
}

FirebaseAdmin/FirebaseAdmin/Auth/ExportedUserRecords.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ public sealed class ExportedUserRecords
2929
/// <summary>
3030
/// Gets or sets the users.
3131
/// </summary>
32-
public List<ExportedUserRecord> Users { get; set; }
32+
public IEnumerable<ExportedUserRecord> Users { get; set; }
3333
}
3434
}

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ namespace FirebaseAdmin.Auth
3434
/// </summary>
3535
internal class FirebaseUserManager : IDisposable
3636
{
37-
public const int MaxListUsersResults = 1000;
38-
3937
private const string IdTooklitUrl = "https://identitytoolkit.googleapis.com/v1/projects/{0}";
4038

4139
private readonly ConfigurableHttpClient httpClient;
@@ -141,13 +139,12 @@ internal async Task<UserRecord> GetUserByPhoneNumberAsync(
141139
return await this.GetUserAsync(query, cancellationToken);
142140
}
143141

144-
internal PagedAsyncEnumerable<ExportedUserRecords, ExportedUserRecord> ListUsers(ListUsersOptions requestOptions)
142+
internal PagedAsyncEnumerable<ExportedUserRecords, ExportedUserRecord> ListUsers(
143+
ListUsersOptions requestOptions)
145144
{
146-
var restPagedAsyncEnumerable = new RestPagedAsyncEnumerable<ListUsersRequest, ExportedUserRecords, ExportedUserRecord>(
147-
() => this.CreateListUserRequest(requestOptions),
148-
new ListUsersPageManager());
149-
150-
return restPagedAsyncEnumerable;
145+
var request = new ListUsersRequest(this.baseUrl, this.httpClient, requestOptions);
146+
return new RestPagedAsyncEnumerable<ListUsersRequest, ExportedUserRecords, ExportedUserRecord>(
147+
() => request, new ListUsersPageManager());
151148
}
152149

153150
/// <summary>
@@ -220,11 +217,6 @@ internal async Task DeleteUserAsync(
220217
}
221218
}
222219

223-
internal ListUsersRequest CreateListUserRequest(ListUsersOptions requestOptions)
224-
{
225-
return new ListUsersRequest(this.baseUrl, this.httpClient, requestOptions);
226-
}
227-
228220
private async Task<UserRecord> GetUserAsync(UserQuery query, CancellationToken cancellationToken)
229221
{
230222
var response = await this.PostAndDeserializeAsync<GetAccountInfoResponse>(

FirebaseAdmin/FirebaseAdmin/Auth/ListUsersOptions.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@
1717
namespace FirebaseAdmin.Auth
1818
{
1919
/// <summary>
20-
/// Options for <c>ListUsersOptions</c> operations.
20+
/// Options for <see cref="FirebaseAuth.ListUsersAsync(ListUsersOptions)"/> API.
2121
/// </summary>
2222
public sealed class ListUsersOptions
2323
{
2424
/// <summary>
25-
/// Gets or sets the number of results to return per page. (This modifies the per-request page size;
26-
/// it does not affect the total number of results returned.)
25+
/// Gets or sets the number of results to return per page. This modifies the per-request page size.
26+
/// It does not affect the total number of results returned.
2727
/// </summary>
2828
public int? PageSize { get; set; }
2929

3030
/// <summary>
31-
/// Gets or sets the page-token.
32-
/// If set, this token is used to indicate a continued list operation.
33-
/// The value should be taken from the <c>NextPageToken</c> property of either
34-
/// a <see cref="Page{TResource}"/> or a raw response from <see cref="PagedEnumerable{TResponse, TResource}.AsRawResponses"/>.
31+
/// Gets or sets the page-token. If set, this token is used to indicate a continued list operation.
32+
/// The value should be taken from the <c>NextPageToken</c> property of either a
33+
/// <see cref="Page{TResource}"/> or a raw response from
34+
/// <see cref="PagedEnumerable{TResponse, TResource}.AsRawResponses"/>.
3535
/// </summary>
3636
public string PageToken { get; set; }
3737
}

FirebaseAdmin/FirebaseAdmin/Auth/ListUsersPageManager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
namespace FirebaseAdmin.Auth
1919
{
20+
/// <summary>
21+
/// Utility for paging through user accounts in the Google API client.
22+
/// </summary>
2023
internal class ListUsersPageManager : IPageManager<ListUsersRequest, ExportedUserRecords, ExportedUserRecord>
2124
{
2225
public void SetPageSize(ListUsersRequest request, int pageSize)

0 commit comments

Comments
 (0)