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
13 changes: 13 additions & 0 deletions src/Turnierplan.App.Test.Functional/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"microsoft.openapi.kiota": {
"version": "1.30.0",
"commands": [
"kiota"
],
"rollForward": false
}
}
}
2 changes: 2 additions & 0 deletions src/Turnierplan.App.Test.Functional/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generated HTTP clients
Client
29 changes: 0 additions & 29 deletions src/Turnierplan.App.Test.Functional/Routes.cs

This file was deleted.

93 changes: 55 additions & 38 deletions src/Turnierplan.App.Test.Functional/Scenarios.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using FluentAssertions.Extensions;
using Turnierplan.App.Models;
using Microsoft.Kiota.Abstractions;
using Turnierplan.App.Test.Functional.Client.Models;
using Turnierplan.Core.ApiKey;
using Turnierplan.Core.Extensions;
using Turnierplan.Core.Organization;
using Turnierplan.Core.RoleAssignment;
using Turnierplan.Core.Tournament;
using Turnierplan.Core.User;
using Xunit;
using Role = Turnierplan.Core.RoleAssignment.Role;
using Visibility = Turnierplan.Core.Tournament.Visibility;

namespace Turnierplan.App.Test.Functional;

Expand Down Expand Up @@ -46,14 +47,12 @@ public async Task When_ApiKey_And_User_Are_Deleted_The_Role_Assignments_Are_Also
_testServer.ExecuteContextAction(db => db.OrganizationRoleAssignments.Count()).Should().Be(1);
_testServer.ExecuteContextAction(db => db.TournamentRoleAssignments.Count()).Should().Be(2);

var resp = await _testServer.Client.DeleteAsync(Routes.ApiKeys.Delete(apiKeyId), TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();
await _testServer.Client.ApiKeys[apiKeyId].DeleteAsync(cancellationToken: TestContext.Current.CancellationToken);

_testServer.ExecuteContextAction(db => db.OrganizationRoleAssignments.Count()).Should().Be(1);
_testServer.ExecuteContextAction(db => db.TournamentRoleAssignments.Count()).Should().Be(1);

resp = await _testServer.Client.DeleteAsync(Routes.Users.Delete(userId), TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();
await _testServer.Client.Users[userId].DeleteAsync(cancellationToken: TestContext.Current.CancellationToken);

_testServer.ExecuteContextAction(db => db.OrganizationRoleAssignments.Count()).Should().Be(0);
_testServer.ExecuteContextAction(db => db.TournamentRoleAssignments.Count()).Should().Be(0);
Expand All @@ -65,36 +64,54 @@ public async Task New_User_Can_Not_Create_Organization_Unless_Explicitly_Granted
const string newUserName = "test_user";
const string newUserPassword = "test123";

var resp = await _testServer.Client.PostAsJsonAsync(
Routes.Users.Create(),
new { UserName = newUserName, Password = newUserPassword },
TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();

var userClient = _testServer.CreateNewClientAndLogIn(newUserName, newUserPassword);
resp = await userClient.PostAsJsonAsync(
Routes.Organizations.Create(),
new { Name = "test_org" },
TestContext.Current.CancellationToken);
resp.StatusCode.Should().Be(HttpStatusCode.Forbidden);

// extra step required to get ID of new user
resp = await _testServer.Client.GetAsync(Routes.Users.List(), TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();
var allUsers = await resp.Content.ReadFromJsonAsync<UserDto[]>(TestContext.Current.CancellationToken);
var newUserId = allUsers!.Single(x => x.UserName.Equals(newUserName)).Id;

resp = await _testServer.Client.PutAsJsonAsync(
Routes.Users.Update(newUserId),
new { UserName = newUserName, IsAdministrator = false, AllowCreateOrganization = true, UpdatePassword = false },
TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();

userClient = _testServer.CreateNewClientAndLogIn(newUserName, newUserPassword);
resp = await userClient.PostAsJsonAsync(
Routes.Organizations.Create(),
new { Name = "test_org" },
TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();
await _testServer.Client.Users.PostAsync(
new CreateUserEndpointRequest { UserName = newUserName, Password = newUserPassword },
cancellationToken: TestContext.Current.CancellationToken);

{
var userClient = await _testServer.CreateClientForUserAsync(newUserName, newUserPassword);

await ExpectApiErrorAsync(() => userClient.Organizations.PostAsync(
new CreateOrganizationEndpointRequest { Name = "test_org" },
cancellationToken: TestContext.Current.CancellationToken), HttpStatusCode.Forbidden);
}

// Extra step is required to get the ID of the created user
var allUsers = await _testServer.Client.Users.GetAsync(cancellationToken: TestContext.Current.CancellationToken);
var newUserId = allUsers!.Single(x => x.UserName!.Equals(newUserName)).Id!.Value;

await _testServer.Client.Users[newUserId].PutAsync(new UpdateUserEndpointRequest
{
UserName = newUserName,
IsAdministrator = false,
AllowCreateOrganization = true,
UpdatePassword = false
}, cancellationToken: TestContext.Current.CancellationToken);

{
// We need to create a new client because a fresh login is required to get the new claims in the token
var userClient = await _testServer.CreateClientForUserAsync(newUserName, newUserPassword);

await userClient.Organizations.PostAsync(
new CreateOrganizationEndpointRequest { Name = "test_org" },
cancellationToken: TestContext.Current.CancellationToken);
}
}

private static async Task ExpectApiErrorAsync(Func<Task> func, HttpStatusCode code)
{
ApiException? exception = null;

try
{
await func();
}
catch (ApiException ex)
{
exception = ex;
}

exception.Should().NotBeNull();
exception.ResponseStatusCode.Should().Be((int)code);
}
}
33 changes: 20 additions & 13 deletions src/Turnierplan.App.Test.Functional/TestServer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Http.HttpClientLibrary;
using Turnierplan.App.Test.Functional.Client;
using Turnierplan.App.Test.Functional.Client.Api;
using Turnierplan.App.Test.Functional.Client.Models;
using Turnierplan.Core.User;
using Turnierplan.Dal;

Expand Down Expand Up @@ -42,25 +47,27 @@ public TestServer()
ctx.SaveChanges();
}

Client = CreateNewClientAndLogIn(username, password);
Client = CreateClientForUserAsync(username, password).GetAwaiter().GetResult();
}

public HttpClient Client { get; }
public ApiRequestBuilder Client { get; }

public HttpClient CreateNewClientAndLogIn(string username, string password)
public async Task<ApiRequestBuilder> CreateClientForUserAsync(string username, string password)
{
var loginRequest = new HttpRequestMessage(HttpMethod.Post, Routes.Identity.Login())
var authenticationProvider = new AnonymousAuthenticationProvider();
var httpClient = _application.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = true });
var httpClientRequestAdapter = new HttpClientRequestAdapter(authenticationProvider, httpClient: httpClient);
var client = new TurnierplanClient(httpClientRequestAdapter);

var loginResponse = await client.Api.Identity.Login.PostAsync(new LoginEndpointRequest
{
Content = JsonContent.Create(new { UserName = username, Password = password})
};
UserName = username,
Password = password
});

var client = _application.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = true });
var loginResponseTask = client.SendAsync(loginRequest);
loginResponseTask.Wait();
var loginResponse = loginResponseTask.Result;
loginResponse.EnsureSuccessStatusCode();
loginResponse!.Success.Should().BeTrue();

return client;
return client.Api;
}

public void ExecuteContextAction(Action<TurnierplanContext> action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,20 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageReference Include="Microsoft.Kiota.Bundle" Version="1.22.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Turnierplan.App\Turnierplan.App.csproj" />
</ItemGroup>

<Target Name="GenerateKiotaClient" BeforeTargets="BeforeCompile">
<Exec Command="dotnet tool restore"/>
<Exec Command="dotnet tool run kiota generate -l CSharp -c TurnierplanClient -n Turnierplan.App.Test.Functional.Client -d ../Turnierplan.App/obj/Turnierplan.App_turnierplan.json -o ./Client --log-level error --exclude-backward-compatible"/>
<ItemGroup>
<!-- Required for the generated files to be discovered on the first build. -->
<Compile Remove="Client/**/*.cs" />
<Compile Include="Client/**/*.cs" />
</ItemGroup>
</Target>
</Project>
2 changes: 2 additions & 0 deletions src/Turnierplan.Core/PublicId/PublicId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,6 @@ private static ulong ConvertFromBytes(byte[] bytes)
}

public static implicit operator PublicId(ulong value) => new(value);

public static implicit operator string(PublicId value) => value.ToString();
}
Loading