Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@
Task<ApiResult<UserProfileDto>> GetUserProfileByDemoAuthKey(string demoAuthKey, CancellationToken cancellationToken = default);
Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(string? filterString, UserProfileFilter? filter, int skipEntries, int takeEntries, UserProfilesOrder? order, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a paginated list of user profiles with optional filtering, game scoping and sorting.
/// </summary>
/// <param name="filterString">Optional filter string to search for user profiles.</param>
/// <param name="filter">Optional role-based filter to constrain results by claim type.</param>
/// <param name="gameType">
/// Optional game type used to constrain game-scoped role filters (<see cref="UserProfileFilter.HeadAdmins"/>,
/// <see cref="UserProfileFilter.GameAdmins"/>, <see cref="UserProfileFilter.Moderators"/> and
/// <see cref="UserProfileFilter.AnyAdmin"/>) to the claim matching that specific game. The role type and game
/// value must be present on the same claim. <see cref="GameType.Unknown"/> is treated as no game filter.
/// </param>
/// <param name="skipEntries">Number of entries to skip for pagination.</param>
/// <param name="takeEntries">Number of entries to take for pagination.</param>
/// <param name="order">Optional ordering criteria for results.</param>
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
/// <returns>An API result containing a paginated collection of user profiles.</returns>
Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(string? filterString, UserProfileFilter? filter, GameType? gameType, int skipEntries, int takeEntries, UserProfilesOrder? order, CancellationToken cancellationToken = default)
=> GetUserProfiles(filterString, filter, skipEntries, takeEntries, order, cancellationToken);

Check warning on line 33 in src/XtremeIdiots.Portal.Repository.Abstractions.V1/Interfaces/V1/IUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use block body for method

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd-9vlJpFVlskY79-G&open=AaBd-9vlJpFVlskY79-G&pullRequest=867

Task<ApiResult> CreateUserProfile(CreateUserProfileDto createUserProfileDto, CancellationToken cancellationToken = default);
Task<ApiResult> CreateUserProfiles(List<CreateUserProfileDto> createUserProfileDtos, CancellationToken cancellationToken = default);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System.Net;
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
using XtremeIdiots.Portal.Repository.Api.Client.Testing.Fakes;

namespace XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests;

[Trait("Category", "Unit")]
public class FakeUserProfileApiTests
{
[Fact]
public async Task GetUserProfiles_WithModeratorsFilterAndGameType_ReturnsOnlyMatchingGameModerators()

Check warning on line 11 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Missing XML comment for publicly visible type or member 'FakeUserProfileApiTests.GetUserProfiles_WithModeratorsFilterAndGameType_ReturnsOnlyMatchingGameModerators()'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaI&open=AaBd2dlEsTy0GAggAXaI&pullRequest=867
{
var fake = new FakeUserProfileApi();

var cod5Moderator = RepositoryDtoFactory.CreateUserProfile(displayName: "Cod5Moderator", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty5.ToString())
]);
var cod4Moderator = RepositoryDtoFactory.CreateUserProfile(displayName: "Cod4Moderator", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty4.ToString())
]);

fake.AddUserProfile(cod5Moderator).AddUserProfile(cod4Moderator);

Check warning on line 24 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expression value is never used

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaQ&open=AaBd2dlEsTy0GAggAXaQ&pullRequest=867

var result = await fake.GetUserProfiles(null, UserProfileFilter.Moderators, GameType.CallOfDuty5, 0, 50, null);

Assert.Equal(HttpStatusCode.OK, result.StatusCode);
var items = result.Result!.Data!.Items!.ToList();
Assert.Single(items);

Check warning on line 30 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expression value is never used

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaR&open=AaBd2dlEsTy0GAggAXaR&pullRequest=867
Assert.Equal(cod5Moderator.UserProfileId, items[0].UserProfileId);
}

[Fact]
public async Task GetUserProfiles_WithModeratorsFilterAndGameType_ExcludesUnrelatedGamePermissionOnSameProfile()

Check warning on line 35 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Missing XML comment for publicly visible type or member 'FakeUserProfileApiTests.GetUserProfiles_WithModeratorsFilterAndGameType_ExcludesUnrelatedGamePermissionOnSameProfile()'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaJ&open=AaBd2dlEsTy0GAggAXaJ&pullRequest=867
{
var fake = new FakeUserProfileApi();

var profile = RepositoryDtoFactory.CreateUserProfile(displayName: "Cod4ModeratorWithCod5Permission", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty4.ToString()),
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: false, claimType: AdditionalPermission.Maps_Read, claimValue: GameType.CallOfDuty5.ToString())
]);

fake.AddUserProfile(profile);

var result = await fake.GetUserProfiles(null, UserProfileFilter.Moderators, GameType.CallOfDuty5, 0, 50, null);

Assert.Equal(HttpStatusCode.OK, result.StatusCode);
Assert.Empty(result.Result!.Data!.Items!);
}

[Fact]
public async Task GetUserProfiles_WithoutGameType_RetainsExistingBehaviour()
{
var fake = new FakeUserProfileApi();

var cod5Moderator = RepositoryDtoFactory.CreateUserProfile(displayName: "Cod5Moderator", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty5.ToString())
]);
var cod4Moderator = RepositoryDtoFactory.CreateUserProfile(displayName: "Cod4Moderator", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty4.ToString())
]);

fake.AddUserProfile(cod5Moderator).AddUserProfile(cod4Moderator);

var viaOldOverload = await fake.GetUserProfiles(null, UserProfileFilter.Moderators, 0, 50, null);

Assert.Equal(HttpStatusCode.OK, viaOldOverload.StatusCode);
Assert.Equal(2, viaOldOverload.Result!.Data!.Items!.ToList().Count);
}

[Fact]
public async Task GetUserProfiles_SupportsSearchOrderingAndPaginationSemantics()

Check warning on line 76 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Missing XML comment for publicly visible type or member 'FakeUserProfileApiTests.GetUserProfiles_SupportsSearchOrderingAndPaginationSemantics()'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaL&open=AaBd2dlEsTy0GAggAXaL&pullRequest=867
{
var fake = new FakeUserProfileApi();

for (var i = 0; i < 3; i++)
{
fake.AddUserProfile(RepositoryDtoFactory.CreateUserProfile(displayName: $"Cod5Moderator{i}", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty5.ToString())
]));

Check warning on line 85 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expression value is never used

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaT&open=AaBd2dlEsTy0GAggAXaT&pullRequest=867
}

fake.AddUserProfile(RepositoryDtoFactory.CreateUserProfile(displayName: "Cod4Moderator", claims:
[
RepositoryDtoFactory.CreateUserProfileClaim(systemGenerated: true, claimType: UserProfileClaimType.Moderator, claimValue: GameType.CallOfDuty4.ToString())
]));

var result = await fake.GetUserProfiles(null, UserProfileFilter.Moderators, GameType.CallOfDuty5, 0, 2, UserProfilesOrder.DisplayNameAsc);

Assert.Equal(HttpStatusCode.OK, result.StatusCode);
Assert.Equal(4, result.Result!.Pagination!.TotalCount);
Assert.Equal(3, result.Result!.Pagination!.FilteredCount);
var items = result.Result!.Data!.Items!.ToList();
Assert.Equal(2, items.Count);
Assert.Equal("Cod5Moderator0", items[0].DisplayName);
Assert.Equal("Cod5Moderator1", items[1].DisplayName);
}

[Fact]
public async Task Reset_ClearsAllState()
{
var fake = new FakeUserProfileApi();
fake.AddUserProfile(RepositoryDtoFactory.CreateUserProfile());

fake.Reset();

Check warning on line 110 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing.Tests/FakeUserProfileApiTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expression value is never used

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dlEsTy0GAggAXaP&open=AaBd2dlEsTy0GAggAXaP&pullRequest=867

var result = await fake.GetUserProfiles(null, null, 0, 50, null);
Assert.Empty(result.Result!.Data!.Items!);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,84 @@
return Task.FromResult(new ApiResult<UserProfileDto>(HttpStatusCode.NotFound, new ApiResponse<UserProfileDto>(new ApiError("NOT_FOUND", "User profile not found"))));
}

public Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(string? filterString, UserProfileFilter? filter, int skipEntries, int takeEntries, UserProfilesOrder? order, CancellationToken cancellationToken = default)
=> GetUserProfiles(filterString, filter, null, skipEntries, takeEntries, order, cancellationToken);

Check warning on line 67 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use block body for method

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2djvsTy0GAggAXY3&open=AaBd2djvsTy0GAggAXY3&pullRequest=867

public Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(string? filterString, UserProfileFilter? filter, GameType? gameType, int skipEntries, int takeEntries, UserProfilesOrder? order, CancellationToken cancellationToken = default)

Check warning on line 69 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Missing XML comment for publicly visible type or member 'FakeUserProfileApi.GetUserProfiles(string?, UserProfileFilter?, GameType?, int, int, UserProfilesOrder?, CancellationToken)'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2djvsTy0GAggAXY2&open=AaBd2djvsTy0GAggAXY2&pullRequest=867
{
var items = _userProfiles.Values.Skip(skipEntries).Take(takeEntries).ToList();
IEnumerable<UserProfileDto> query = _userProfiles.Values;

var totalCount = query.Count();

if (!string.IsNullOrWhiteSpace(filterString))
{
var textFilter = filterString.Trim().ToLower();

Check warning on line 77 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Specify a culture or use an invariant version to avoid implicit dependency on current culture

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd-9zdJpFVlskY79-H&open=AaBd-9zdJpFVlskY79-H&pullRequest=867
query = query.Where(up => (up.IdentityOid != null && up.IdentityOid.ToLower().Contains(textFilter)) ||

Check warning on line 78 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer the string comparison method overload of 'string.Contains(string)' that takes a 'StringComparison' enum value to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2djvsTy0GAggAXY4&open=AaBd2djvsTy0GAggAXY4&pullRequest=867
(up.XtremeIdiotsForumId != null && up.XtremeIdiotsForumId.ToLower().Contains(textFilter)) ||

Check warning on line 79 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer the string comparison method overload of 'string.Contains(string)' that takes a 'StringComparison' enum value to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2djvsTy0GAggAXY5&open=AaBd2djvsTy0GAggAXY5&pullRequest=867
(up.DemoAuthKey != null && up.DemoAuthKey.ToLower().Contains(textFilter)) ||

Check warning on line 80 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The behavior of 'string.ToLower()' could vary based on the current user's locale settings. Replace this call in 'FakeUserProfileApi.GetUserProfiles(string, UserProfileFilter?, GameType?, int, int, UserProfilesOrder?, [CancellationToken])' with a call to 'string.ToLower(CultureInfo)'.

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd-9zdJpFVlskY79-Q&open=AaBd-9zdJpFVlskY79-Q&pullRequest=867
(up.DisplayName != null && up.DisplayName.ToLower().Contains(textFilter)) ||

Check warning on line 81 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The behavior of 'string.ToLower()' could vary based on the current user's locale settings. Replace this call in 'FakeUserProfileApi.GetUserProfiles(string, UserProfileFilter?, GameType?, int, int, UserProfilesOrder?, [CancellationToken])' with a call to 'string.ToLower(CultureInfo)'.

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd-9zdJpFVlskY79-R&open=AaBd-9zdJpFVlskY79-R&pullRequest=867
(up.Email != null && up.Email.ToLower().Contains(textFilter)));

Check warning on line 82 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer the string comparison method overload of 'string.Contains(string)' that takes a 'StringComparison' enum value to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2djvsTy0GAggAXY8&open=AaBd2djvsTy0GAggAXY8&pullRequest=867
}

// GameType.Unknown is treated the same as no game filter being supplied.
var gameTypeFilter = gameType.HasValue && gameType.Value != GameType.Unknown ? gameType : null;

if (filter.HasValue)
{
query = filter.Value switch
{
UserProfileFilter.Webmasters => query.Where(up => up.UserProfileClaims.Any(c => c.ClaimType == UserProfileClaimType.Webmaster)),
UserProfileFilter.SeniorAdmins => query.Where(up => up.UserProfileClaims.Any(c => c.ClaimType == UserProfileClaimType.SeniorAdmin)),
UserProfileFilter.HeadAdmins => ApplyGameScopedRoleFilter(query, UserProfileClaimType.HeadAdmin, gameTypeFilter),
UserProfileFilter.GameAdmins => ApplyGameScopedRoleFilter(query, UserProfileClaimType.GameAdmin, gameTypeFilter),
UserProfileFilter.Moderators => ApplyGameScopedRoleFilter(query, UserProfileClaimType.Moderator, gameTypeFilter),
UserProfileFilter.AnyAdmin => ApplyAnyAdminFilter(query, gameTypeFilter),
UserProfileFilter.HasAdditionalPermissions => query.Where(up => up.UserProfileClaims.Any(c => !c.SystemGenerated)),
_ => query
};
}

var filtered = query.ToList();
var filteredCount = filtered.Count;

var ordered = order switch

Check warning on line 106 in src/XtremeIdiots.Portal.Repository.Api.Client.Testing/Fakes/FakeUserProfileApi.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Populate switch

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2djvsTy0GAggAXY9&open=AaBd2djvsTy0GAggAXY9&pullRequest=867
{
UserProfilesOrder.DisplayNameDesc => filtered.OrderByDescending(up => up.DisplayName),
_ => filtered.OrderBy(up => up.DisplayName)
};

var items = ordered.Skip(skipEntries).Take(takeEntries).ToList();
var collection = new CollectionModel<UserProfileDto> { Items = items };
return Task.FromResult(new ApiResult<CollectionModel<UserProfileDto>>(HttpStatusCode.OK, new ApiResponse<CollectionModel<UserProfileDto>>(collection)));

return Task.FromResult(new ApiResult<CollectionModel<UserProfileDto>>(HttpStatusCode.OK, new ApiResponse<CollectionModel<UserProfileDto>>(collection)
{
Pagination = new ApiPagination(totalCount, filteredCount, skipEntries, takeEntries)
}));
}

private static IEnumerable<UserProfileDto> ApplyGameScopedRoleFilter(IEnumerable<UserProfileDto> query, string claimType, GameType? gameType)
{
if (gameType.HasValue)
{
var gameTypeString = gameType.Value.ToString();
return query.Where(up => up.UserProfileClaims.Any(c => c.ClaimType == claimType && c.ClaimValue == gameTypeString));
}

return query.Where(up => up.UserProfileClaims.Any(c => c.ClaimType == claimType));
}

private static IEnumerable<UserProfileDto> ApplyAnyAdminFilter(IEnumerable<UserProfileDto> query, GameType? gameType)
{
if (gameType.HasValue)
{
var gameTypeString = gameType.Value.ToString();
return query.Where(up => up.UserProfileClaims.Any(c =>
c.ClaimType == UserProfileClaimType.Webmaster ||
c.ClaimType == UserProfileClaimType.SeniorAdmin ||
((c.ClaimType == UserProfileClaimType.HeadAdmin || c.ClaimType == UserProfileClaimType.GameAdmin || c.ClaimType == UserProfileClaimType.Moderator) && c.ClaimValue == gameTypeString)));
}

return query.Where(up => up.UserProfileClaims.Any(c => c.ClaimType == UserProfileClaimType.Webmaster || c.ClaimType == UserProfileClaimType.SeniorAdmin || c.ClaimType == UserProfileClaimType.HeadAdmin || c.ClaimType == UserProfileClaimType.GameAdmin || c.ClaimType == UserProfileClaimType.Moderator));
}

public Task<ApiResult> CreateUserProfile(CreateUserProfileDto createUserProfileDto, CancellationToken cancellationToken = default) => Task.FromResult(new ApiResult(HttpStatusCode.OK, new ApiResponse()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System.Net;
using System.Security.Authentication;
using Microsoft.Extensions.Logging.Abstractions;

using MX.Api.Client;
using MX.Api.Client.Auth;

using RestSharp;

using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
using XtremeIdiots.Portal.Repository.Api.Client.V1;

namespace XtremeIdiots.Portal.Repository.Api.Client.Tests.V1;

public class UserProfileApiQuerySerializationTests
{
[Fact]
public async Task GetUserProfiles_WithGameType_EmitsGameTypeQueryParameter()

Check warning on line 18 in src/XtremeIdiots.Portal.Repository.Api.Client.Tests.V1/UserProfileApiQuerySerializationTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Missing XML comment for publicly visible type or member 'UserProfileApiQuerySerializationTests.GetUserProfiles_WithGameType_EmitsGameTypeQueryParameter()'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dk7sTy0GAggAXaB&open=AaBd2dk7sTy0GAggAXaB&pullRequest=867
{
var rest = new CapturingRestClientService();
var api = new UserProfileApi(
NullLogger<BaseApi<RepositoryApiClientOptions>>.Instance,
new FakeTokenProvider(),
rest,
CreateOptions());

await api.GetUserProfiles(null, UserProfileFilter.Moderators, GameType.CallOfDuty5, 0, 50, null, CancellationToken.None);

Assert.Equal("v1/user-profiles", rest.LastResource);
Assert.Equal("Moderators", rest.Query["filter"]);
Assert.Equal("CallOfDuty5", rest.Query["gameType"]);
}

[Fact]
public async Task GetUserProfiles_WithoutGameType_DoesNotEmitGameTypeQueryParameter()
{
var rest = new CapturingRestClientService();
var api = new UserProfileApi(
NullLogger<BaseApi<RepositoryApiClientOptions>>.Instance,
new FakeTokenProvider(),
rest,
CreateOptions());

await api.GetUserProfiles(null, UserProfileFilter.Moderators, null, 0, 50, null, CancellationToken.None);

Assert.Equal("v1/user-profiles", rest.LastResource);
Assert.False(rest.Query.ContainsKey("gameType"));
}

[Fact]
public async Task GetUserProfiles_OldOverload_RemainsFunctionalAndDoesNotEmitGameTypeQueryParameter()

Check warning on line 51 in src/XtremeIdiots.Portal.Repository.Api.Client.Tests.V1/UserProfileApiQuerySerializationTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Missing XML comment for publicly visible type or member 'UserProfileApiQuerySerializationTests.GetUserProfiles_OldOverload_RemainsFunctionalAndDoesNotEmitGameTypeQueryParameter()'

See more on https://sonarcloud.io/project/issues?id=frasermolyneux_portal-repository&issues=AaBd2dk7sTy0GAggAXaD&open=AaBd2dk7sTy0GAggAXaD&pullRequest=867
{
var rest = new CapturingRestClientService();
var api = new UserProfileApi(
NullLogger<BaseApi<RepositoryApiClientOptions>>.Instance,
new FakeTokenProvider(),
rest,
CreateOptions());

var result = await api.GetUserProfiles(null, UserProfileFilter.Moderators, 0, 50, null, CancellationToken.None);

Assert.Equal(HttpStatusCode.OK, result.StatusCode);
Assert.Equal("v1/user-profiles", rest.LastResource);
Assert.Equal("Moderators", rest.Query["filter"]);
Assert.False(rest.Query.ContainsKey("gameType"));
}

private static RepositoryApiClientOptions CreateOptions()
{
return new RepositoryApiClientOptions
{
BaseUrl = "https://example.test",
MaxRetryCount = 1
};
}

private sealed class CapturingRestClientService : IRestClientService
{
public string LastResource { get; private set; } = string.Empty;

public Dictionary<string, string> Query { get; private set; } = [];

public Task<RestResponse> ExecuteAsync(string baseUrl, RestRequest request, CancellationToken cancellationToken = default)
{
LastResource = request.Resource;
Query = request.Parameters
.Where(p => p.Type == ParameterType.QueryString)
.ToDictionary(p => p.Name ?? string.Empty, p => p.Value?.ToString() ?? string.Empty);

return Task.FromResult(new RestResponse
{
StatusCode = HttpStatusCode.OK,
Content = "{}"
});
}

public Task<RestResponse> ExecuteWithNamedOptionsAsync(string optionsName, RestRequest request, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}

public void Dispose()
{
}
}

private sealed class FakeTokenProvider : IApiTokenProvider
{
public Task<string> GetAccessTokenAsync(string audience, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(audience))
{
throw new AuthenticationException("Invalid audience");
}

return Task.FromResult("token");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public async Task<ApiResult<UserProfileDto>> GetUserProfileByDemoAuthKey(string
}

public async Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(string? filterString, UserProfileFilter? filter, int skipEntries, int takeEntries, UserProfilesOrder? order, CancellationToken cancellationToken = default)
=> await GetUserProfiles(filterString, filter, null, skipEntries, takeEntries, order, cancellationToken).ConfigureAwait(false);

public async Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(string? filterString, UserProfileFilter? filter, GameType? gameType, int skipEntries, int takeEntries, UserProfilesOrder? order, CancellationToken cancellationToken = default)
{
var request = await CreateRequestAsync("v1/user-profiles", Method.Get).ConfigureAwait(false);

Expand All @@ -67,6 +70,11 @@ public async Task<ApiResult<CollectionModel<UserProfileDto>>> GetUserProfiles(st
request.AddQueryParameter("filter", filter.ToString());
}

if (gameType.HasValue)
{
request.AddQueryParameter("gameType", gameType.Value.ToString());
}

request.AddQueryParameter("skipEntries", skipEntries.ToString());
request.AddQueryParameter("takeEntries", takeEntries.ToString());

Expand Down
Loading
Loading