Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kanpov committed Jun 15, 2024
1 parent 5ff66a5 commit afd14ac
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 12 deletions.
2 changes: 2 additions & 0 deletions src/Keycloak.AuthServices.Sdk/Admin/ApiUrls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}";
Expand Down
35 changes: 35 additions & 0 deletions src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,41 @@ async Task<IEnumerable<UserRepresentation>> GetUsersAsync(
?? Enumerable.Empty<UserRepresentation>();
}

/// <summary>
/// Get the integer amount of users on the realm that match the provided <see cref="GetUserCountRequestParameters"/>.
///
/// Note that the response is not JSON, but simply the integer value as a string.
/// </summary>
/// <param name="realm">Realm name (not ID).</param>
/// <param name="parameters">Optional query parameters.</param>
/// <param name="cancellationToken"></param>
/// <returns>An integer amount of users</returns>
Task<HttpResponseMessage> GetUserCountWithResponseAsync(
string realm,
GetUserCountRequestParameters? parameters = default,

Check failure on line 54 in src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

The type or namespace name 'GetUserCountRequestParameters' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 54 in src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

The type or namespace name 'GetUserCountRequestParameters' could not be found (are you missing a using directive or an assembly reference?)
CancellationToken cancellationToken = default
);

/// <summary>
/// Get the integer amount of users on the realm that match the provided <see cref="GetUserCountRequestParameters"/>.
/// </summary>
/// <param name="realm">Realm name (not ID).</param>
/// <param name="parameters">Optional query parameters.</param>
/// <param name="cancellationToken"></param>
/// <returns>An integer amount of users</returns>
async Task<int> GetUserCountAsync(
string realm,
GetUserCountRequestParameters? parameters = default,

Check failure on line 67 in src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

The type or namespace name 'GetUserCountRequestParameters' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 67 in src/Keycloak.AuthServices.Sdk/Admin/IKeycloakUserClient.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

The type or namespace name 'GetUserCountRequestParameters' could not be found (are you missing a using directive or an assembly reference?)
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
}

/// <summary>
/// Get representation of a user.
/// </summary>
Expand Down
39 changes: 37 additions & 2 deletions src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// <summary>
/// Represents a client for interacting with the Keycloak Admin API.
/// </summary>
public partial class KeycloakClient : IKeycloakClient
public class KeycloakClient : IKeycloakClient
{
private readonly HttpClient httpClient;

Expand Down Expand Up @@ -94,7 +94,42 @@ public async Task<HttpResponseMessage> GetUsersWithResponseAsync(

var responseMessage = await this.httpClient.GetAsync(path + query, cancellationToken);

return responseMessage!;
return responseMessage;
}

/// <inheritdoc/>
public async Task<HttpResponseMessage> GetUserCountWithResponseAsync(
string realm,
GetUserCountRequestParameters? parameters = default,

Check failure on line 103 in src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

The type or namespace name 'GetUserCountRequestParameters' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 103 in src/Keycloak.AuthServices.Sdk/Admin/KeycloakClient.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

The type or namespace name 'GetUserCountRequestParameters' could not be found (are you missing a using directive or an assembly reference?)
CancellationToken cancellationToken = default
)
{
var path = ApiUrls.GetUserCount.WithRealm(realm);

var query = string.Empty;

if (parameters is not null)
{
var queryParameters = new List<KeyValuePair<string, string?>>()
{
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;
}

/// <inheritdoc/>
Expand Down
83 changes: 73 additions & 10 deletions tests/Keycloak.AuthServices.Sdk.Tests/KeycloakUserClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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());

Expand All @@ -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()))
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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()
{
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -355,7 +418,7 @@ public async Task GetUserGroupsAsyncShouldCallCorrectEndpoint()
this.handler.Expect(HttpMethod.Get, expectedUrl)
.Respond(
HttpStatusCode.OK,
MediaType,
JsonMediaType,
JsonSerializer.Serialize(Array.Empty<GroupRepresentation>())
);

Expand Down

0 comments on commit afd14ac

Please sign in to comment.