From afd14ac5b769ed95f5a3af91214c5b4720eeb11a Mon Sep 17 00:00:00 2001 From: kanpov Date: Sat, 15 Jun 2024 17:26:57 +0300 Subject: [PATCH] Add tests --- .../Admin/ApiUrls.cs | 2 + .../Admin/IKeycloakUserClient.cs | 35 ++++++++ .../Admin/KeycloakClient.cs | 39 ++++++++- .../KeycloakUserClientTests.cs | 83 ++++++++++++++++--- 4 files changed, 147 insertions(+), 12 deletions(-) diff --git a/src/Keycloak.AuthServices.Sdk/Admin/ApiUrls.cs b/src/Keycloak.AuthServices.Sdk/Admin/ApiUrls.cs index 9bbf1d5c..8d379d09 100644 --- a/src/Keycloak.AuthServices.Sdk/Admin/ApiUrls.cs +++ b/src/Keycloak.AuthServices.Sdk/Admin/ApiUrls.cs @@ -19,6 +19,8 @@ internal static class ApiUrls internal const string GetUser = $"{GetRealm}/users/{{id}}"; + internal const string GetUserCount = $"{GetRealm}/users/count"; + internal const string CreateUser = $"{GetRealm}/users"; internal const string UpdateUser = $"{GetRealm}/users/{{id}}"; diff --git a/src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs b/src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs index 3193842e..6f8dbfc6 100644 --- a/src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs +++ b/src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs @@ -40,6 +40,41 @@ async Task> GetUsersAsync( ?? Enumerable.Empty(); } + /// + /// Get the integer amount of users on the realm that match the provided . + /// + /// Note that the response is not JSON, but simply the integer value as a string. + /// + /// Realm name (not ID). + /// Optional query parameters. + /// + /// An integer amount of users + Task GetUserCountWithResponseAsync( + string realm, + GetUserCountRequestParameters? parameters = default, + CancellationToken cancellationToken = default + ); + + /// + /// Get the integer amount of users on the realm that match the provided . + /// + /// Realm name (not ID). + /// Optional query parameters. + /// + /// An integer amount of users + async Task GetUserCountAsync( + string realm, + GetUserCountRequestParameters? parameters = default, + CancellationToken cancellationToken = default + ) + { + var response = await this.GetUserCountWithResponseAsync(realm, parameters, cancellationToken); + var stringContent = await response.Content.ReadAsStringAsync(cancellationToken); +#pragma warning disable CA1305 // Specify IFormatProvider + return Convert.ToInt32(stringContent); +#pragma warning restore CA1305 // Specify IFormatProvider + } + /// /// Get representation of a user. /// diff --git a/src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs b/src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs index 245682c8..a60662f8 100644 --- a/src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs +++ b/src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs @@ -11,7 +11,7 @@ /// /// Represents a client for interacting with the Keycloak Admin API. /// -public partial class KeycloakClient : IKeycloakClient +public class KeycloakClient : IKeycloakClient { private readonly HttpClient httpClient; @@ -94,7 +94,42 @@ public async Task GetUsersWithResponseAsync( var responseMessage = await this.httpClient.GetAsync(path + query, cancellationToken); - return responseMessage!; + return responseMessage; + } + + /// + public async Task GetUserCountWithResponseAsync( + string realm, + GetUserCountRequestParameters? parameters = default, + CancellationToken cancellationToken = default + ) + { + var path = ApiUrls.GetUserCount.WithRealm(realm); + + var query = string.Empty; + + if (parameters is not null) + { + var queryParameters = new List>() + { + new("email", parameters.Email), + new("emailVerified", parameters.EmailVerified?.ToString()), + new("enabled", parameters.Enabled?.ToString()), + new("firstName", parameters.FirstName), + new("lastName", parameters.LastName), + new("q", parameters.Query), + new("search", parameters.Search), + new("username", parameters.Username) + }; + + query = new QueryBuilder(queryParameters.Where(q => q.Value is not null)!) + .ToQueryString() + .ToString(); + } + + var responseMessage = await this.httpClient.GetAsync(path + query, cancellationToken); + + return responseMessage; } /// diff --git a/tests/Keycloak.AuthServices.Sdk.Tests/KeycloakUserClientTests.cs b/tests/Keycloak.AuthServices.Sdk.Tests/KeycloakUserClientTests.cs index 362a95e1..9df59357 100644 --- a/tests/Keycloak.AuthServices.Sdk.Tests/KeycloakUserClientTests.cs +++ b/tests/Keycloak.AuthServices.Sdk.Tests/KeycloakUserClientTests.cs @@ -12,7 +12,8 @@ namespace Keycloak.AuthServices.Sdk.Tests; public class KeycloakUserClientTests { private const string BaseAddress = "http://localhost:8080"; - private const string MediaType = "application/json"; + private const string JsonMediaType = "application/json"; + private const string PlaintextMediaType = "text/plain"; private readonly MockHttpMessageHandler handler = new(); private readonly IKeycloakUserClient keycloakUserClient; @@ -30,7 +31,7 @@ public async Task GetUserShouldCallCorrectEndpoint(UserRepresentation userFixtur var userId = userFixture.Id; this.handler.Expect(HttpMethod.Get, $"/admin/realms/master/users/{userId}") - .Respond(HttpStatusCode.OK, MediaType, JsonSerializer.Serialize(userFixture)); + .Respond(HttpStatusCode.OK, JsonMediaType, JsonSerializer.Serialize(userFixture)); var user = await this.keycloakUserClient.GetUserAsync("master", userId!.ToString()); @@ -46,7 +47,7 @@ public async Task GetUserShouldShouldThrowNotFoundApiExceptionWhenUserDoesNotExi "{\"error\":\"User not found\"}"; this.handler.Expect(HttpMethod.Get, $"{BaseAddress}/admin/realms/master/users/{userId}") - .Respond(HttpStatusCode.NotFound, MediaType, errorMessage); + .Respond(HttpStatusCode.NotFound, JsonMediaType, errorMessage); var exception = await FluentActions .Invoking(() => this.keycloakUserClient.GetUserAsync("master", userId.ToString())) @@ -74,7 +75,7 @@ public async Task GetUsersShouldCallCorrectEndpoint() var response = $"[{string.Join(",", users.Select(u => u.Representation))}]"; this.handler.Expect(HttpMethod.Get, $"{BaseAddress}/admin/realms/master/users") - .Respond(HttpStatusCode.OK, MediaType, response); + .Respond(HttpStatusCode.OK, JsonMediaType, response); var result = await this.keycloakUserClient.GetUsersAsync("master"); @@ -125,13 +126,75 @@ public async Task GetUsersShouldCallCorrectEndpointWithOptionalQueryParameters() var response = $"[{GetUserRepresentation(Guid.NewGuid())}]"; this.handler.Expect(HttpMethod.Get, url + queryBuilder.ToQueryString()) - .Respond(HttpStatusCode.OK, MediaType, response); + .Respond(HttpStatusCode.OK, JsonMediaType, response); _ = await this.keycloakUserClient.GetUsersAsync("master", getUsersRequestParameters); this.handler.VerifyNoOutstandingExpectation(); } + [Fact] + public async Task GetUserCountShouldCallCorrectEndpoint() + { + const int userAmount = 5; + + for (var i = 0; i < userAmount; ++i) + { + var id = Guid.NewGuid(); + GetUserRepresentation(id); + } + +#pragma warning disable CA1305 // use locale provider + var response = userAmount.ToString(); +#pragma warning restore CA1305 // use locale provider + + this.handler.Expect(HttpMethod.Get, $"{BaseAddress}/admin/realms/master/users/count") + .Respond(HttpStatusCode.OK, PlaintextMediaType, response); + + var result = await this.keycloakUserClient.GetUserCountAsync("master"); + + result.Should().Be(userAmount); + this.handler.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task GetUserCountShouldCallCorrectEndpointWithOptionalQueryParameters() + { + var getUserCountRequestParameters = new GetUserCountRequestParameters + { + Email = "email", + EmailVerified = false, + Enabled = false, + FirstName = "firstName", + LastName = "lastName", + Query = "query", + Search = "search", + Username = "username" + }; + + const string url = "/admin/realms/master/users/count"; + var queryBuilder = new QueryBuilder + { + { "email", "email" }, + { "emailVerified", "False" }, + { "enabled", "False" }, + { "firstName", "firstName" }, + { "lastName", "lastName" }, + { "q", "query" }, + { "search", "search" }, + { "username", "username" } + }; + + const string response = "0"; + + this.handler.Expect(HttpMethod.Get, url + queryBuilder.ToQueryString()) + .Respond(HttpStatusCode.BadRequest, PlaintextMediaType, response); + + _ = await this.keycloakUserClient.GetUserCountAsync("master", getUserCountRequestParameters); + + this.handler.VerifyNoOutstandingExpectation(); + } + [Fact] public async Task CreateUserShouldCallCorrectEndpoint() { @@ -153,7 +216,7 @@ public async Task CreateUserShouldReturnBadRequestWhenRequestIsInvalid() "{\"errorMessage\":\"User name is missing\"}"; this.handler.Expect(HttpMethod.Post, $"/admin/realms/master/users") - .Respond(HttpStatusCode.BadRequest, MediaType, errorMessage); + .Respond(HttpStatusCode.BadRequest, JsonMediaType, errorMessage); var exception = await FluentActions .Invoking( @@ -195,7 +258,7 @@ public async Task UpdateUserShouldThrowNotFoundApiExceptionWhenUserDoesNotExist( "{\"errorMessage\":\"User name is missing\"}"; this.handler.Expect(HttpMethod.Put, $"/admin/realms/master/users/{userId}") - .Respond(HttpStatusCode.NotFound, MediaType, errorMessage); + .Respond(HttpStatusCode.NotFound, JsonMediaType, errorMessage); var exception = await FluentActions .Invoking( @@ -235,7 +298,7 @@ public async Task DeleteUserShouldThrowNotFoundApiExceptionWhenUserDoesNotExist( "{\"errorMessage\":\"User name is missing\"}"; this.handler.Expect(HttpMethod.Delete, $"/admin/realms/master/users/{userId}") - .Respond(HttpStatusCode.NotFound, MediaType, errorMessage); + .Respond(HttpStatusCode.NotFound, JsonMediaType, errorMessage); var exception = await FluentActions .Invoking(() => this.keycloakUserClient.DeleteUserAsync("master", userId)) @@ -303,7 +366,7 @@ public async Task SendVerifyEmailShouldThrowNotFoundApiExceptionWhenUserDoesNotE HttpMethod.Put, $"/admin/realms/master/users/{userId}/send-verify-email" ) - .Respond(HttpStatusCode.NotFound, MediaType, errorMessage); + .Respond(HttpStatusCode.NotFound, JsonMediaType, errorMessage); var exception = await FluentActions .Invoking( @@ -355,7 +418,7 @@ public async Task GetUserGroupsAsyncShouldCallCorrectEndpoint() this.handler.Expect(HttpMethod.Get, expectedUrl) .Respond( HttpStatusCode.OK, - MediaType, + JsonMediaType, JsonSerializer.Serialize(Array.Empty()) );