From e1d063d1f09177563f61ab39a85d7c1c718efdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 13 Mar 2026 17:54:45 +0100 Subject: [PATCH 01/10] First implementation --- .config/dotnet-tools.json | 13 ++++++++ .../.gitignore | 1 + src/Turnierplan.App.Test.Functional/Routes.cs | 29 ----------------- .../Scenarios.cs | 12 +++---- .../TestServer.cs | 32 +++++++++++-------- .../Turnierplan.App.Test.Functional.csproj | 6 ++++ src/Turnierplan.Core/PublicId/PublicId.cs | 2 ++ 7 files changed, 45 insertions(+), 50 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 src/Turnierplan.App.Test.Functional/.gitignore delete mode 100644 src/Turnierplan.App.Test.Functional/Routes.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000..5b4d81ba --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "microsoft.openapi.kiota": { + "version": "1.30.0", + "commands": [ + "kiota" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/src/Turnierplan.App.Test.Functional/.gitignore b/src/Turnierplan.App.Test.Functional/.gitignore new file mode 100644 index 00000000..299a75d6 --- /dev/null +++ b/src/Turnierplan.App.Test.Functional/.gitignore @@ -0,0 +1 @@ +Client diff --git a/src/Turnierplan.App.Test.Functional/Routes.cs b/src/Turnierplan.App.Test.Functional/Routes.cs deleted file mode 100644 index 2d00b710..00000000 --- a/src/Turnierplan.App.Test.Functional/Routes.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Turnierplan.Core.PublicId; - -namespace Turnierplan.App.Test.Functional; - -internal static class Routes -{ - public static class ApiKeys - { - public static string Delete(PublicId id) => $"/api/api-keys/{id}"; - } - - public static class Identity - { - public static string Login() => "/api/identity/login"; - } - - public static class Organizations - { - public static string Create() => "/api/organizations"; - } - - public static class Users - { - public static string List() => "/api/users"; - public static string Create() => "/api/users"; - public static string Delete(Guid id) => $"/api/users/{id}"; - public static string Update(Guid id) => $"/api/users/{id}"; - } -} diff --git a/src/Turnierplan.App.Test.Functional/Scenarios.cs b/src/Turnierplan.App.Test.Functional/Scenarios.cs index 0d63df35..ab19fa35 100644 --- a/src/Turnierplan.App.Test.Functional/Scenarios.cs +++ b/src/Turnierplan.App.Test.Functional/Scenarios.cs @@ -1,8 +1,5 @@ -using System.Net; -using System.Net.Http.Json; using FluentAssertions; using FluentAssertions.Extensions; -using Turnierplan.App.Models; using Turnierplan.Core.ApiKey; using Turnierplan.Core.Extensions; using Turnierplan.Core.Organization; @@ -46,19 +43,18 @@ 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.Api.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.Api.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); } + /* [Fact] public async Task New_User_Can_Not_Create_Organization_Unless_Explicitly_Granted_Permission() { @@ -96,5 +92,5 @@ public async Task New_User_Can_Not_Create_Organization_Unless_Explicitly_Granted new { Name = "test_org" }, TestContext.Current.CancellationToken); resp.EnsureSuccessStatusCode(); - } + }*/ } diff --git a/src/Turnierplan.App.Test.Functional/TestServer.cs b/src/Turnierplan.App.Test.Functional/TestServer.cs index bf0ec2e8..d593ac0d 100644 --- a/src/Turnierplan.App.Test.Functional/TestServer.cs +++ b/src/Turnierplan.App.Test.Functional/TestServer.cs @@ -1,9 +1,13 @@ -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.Models; using Turnierplan.Core.User; using Turnierplan.Dal; @@ -42,23 +46,25 @@ public TestServer() ctx.SaveChanges(); } - Client = CreateNewClientAndLogIn(username, password); + Client = CreateNewClientAndLogInAsync(username, password).GetAwaiter().GetResult(); } - public HttpClient Client { get; } + public TurnierplanClient Client { get; } - public HttpClient CreateNewClientAndLogIn(string username, string password) + public async Task CreateNewClientAndLogInAsync(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}) - }; - - var client = _application.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = true }); - var loginResponseTask = client.SendAsync(loginRequest); - loginResponseTask.Wait(); - var loginResponse = loginResponseTask.Result; - loginResponse.EnsureSuccessStatusCode(); + UserName = username, + Password = password + }); + + loginResponse!.Success.Should().BeTrue(); return client; } diff --git a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj index fe0ea8e1..5b1b5fa8 100644 --- a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj +++ b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj @@ -11,9 +11,15 @@ + + + + + + diff --git a/src/Turnierplan.Core/PublicId/PublicId.cs b/src/Turnierplan.Core/PublicId/PublicId.cs index 25d8f38f..400d9e52 100644 --- a/src/Turnierplan.Core/PublicId/PublicId.cs +++ b/src/Turnierplan.Core/PublicId/PublicId.cs @@ -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(); } From 842fc93c09ff31f0b155406c6f8850be840f8fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 13 Mar 2026 17:55:01 +0100 Subject: [PATCH 02/10] ADd comment in gitignore --- src/Turnierplan.App.Test.Functional/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Turnierplan.App.Test.Functional/.gitignore b/src/Turnierplan.App.Test.Functional/.gitignore index 299a75d6..c0febbdc 100644 --- a/src/Turnierplan.App.Test.Functional/.gitignore +++ b/src/Turnierplan.App.Test.Functional/.gitignore @@ -1 +1,2 @@ +# Generated HTTP clients Client From a2d511cf1a3aafa9f8fffb480cc8cf93d44a3264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 13 Mar 2026 17:56:57 +0100 Subject: [PATCH 03/10] Move config folder locally --- .../Turnierplan.App.Test.Functional/.config}/dotnet-tools.json | 0 .../Turnierplan.App.Test.Functional.csproj | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {.config => src/Turnierplan.App.Test.Functional/.config}/dotnet-tools.json (100%) diff --git a/.config/dotnet-tools.json b/src/Turnierplan.App.Test.Functional/.config/dotnet-tools.json similarity index 100% rename from .config/dotnet-tools.json rename to src/Turnierplan.App.Test.Functional/.config/dotnet-tools.json diff --git a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj index 5b1b5fa8..7a674058 100644 --- a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj +++ b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj @@ -17,7 +17,7 @@ - + From b7d923ca8769238e9cdc84de3702513d85132c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 13 Mar 2026 17:58:08 +0100 Subject: [PATCH 04/10] Warning log level --- .../Turnierplan.App.Test.Functional.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj index 7a674058..9fbd6a1b 100644 --- a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj +++ b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj @@ -20,6 +20,6 @@ - + From b98b4832c9fc97e04a8da6a76afe89995f7d8114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 13 Mar 2026 18:09:26 +0100 Subject: [PATCH 05/10] Rewrite other test --- .../Scenarios.cs | 90 ++++++++++++------- .../TestServer.cs | 9 +- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/Turnierplan.App.Test.Functional/Scenarios.cs b/src/Turnierplan.App.Test.Functional/Scenarios.cs index ab19fa35..b42089e4 100644 --- a/src/Turnierplan.App.Test.Functional/Scenarios.cs +++ b/src/Turnierplan.App.Test.Functional/Scenarios.cs @@ -1,12 +1,16 @@ +using System.Net; using FluentAssertions; using FluentAssertions.Extensions; +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; @@ -43,54 +47,74 @@ 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); - await _testServer.Client.Api.ApiKeys[apiKeyId].DeleteAsync(cancellationToken: TestContext.Current.CancellationToken); + 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); - await _testServer.Client.Api.Users[userId].DeleteAsync(cancellationToken: TestContext.Current.CancellationToken); + 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); } - /* [Fact] public async Task New_User_Can_Not_Create_Organization_Unless_Explicitly_Granted_Permission() { 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(); + await _testServer.Client.Users.PostAsync( + new CreateUserEndpointRequest { UserName = newUserName, Password = newUserPassword }, + cancellationToken: TestContext.Current.CancellationToken); - 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); + { + var userClient = await _testServer.CreateClientForUserAsync(newUserName, newUserPassword); + + var tryToCreateOrganization = () => userClient.Organizations.PostAsync( + new CreateOrganizationEndpointRequest { Name = "test_org" }, + cancellationToken: TestContext.Current.CancellationToken); + + await ExpectErrorAsync(tryToCreateOrganization, 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(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(); - }*/ + var allUsers = await _testServer.Client.Users.GetAsync(cancellationToken: TestContext.Current.CancellationToken); + var newUserId = allUsers!.Single(x => x.UserName!.Equals(newUserName)).Id; // TODO: UserName and Id should not be nullable? + + await _testServer.Client.Users[newUserId!.Value].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); + } + } + + // TODO: Make pretty + private async Task ExpectErrorAsync(Func func, HttpStatusCode code) + { + ApiException? ex = null; + + try + { + await func(); + } + catch (ApiException ex2) + { + ex = ex2; + } + + ex.Should().NotBeNull(); + ex.ResponseStatusCode.Should().Be((int)code); + } } diff --git a/src/Turnierplan.App.Test.Functional/TestServer.cs b/src/Turnierplan.App.Test.Functional/TestServer.cs index d593ac0d..bab42a07 100644 --- a/src/Turnierplan.App.Test.Functional/TestServer.cs +++ b/src/Turnierplan.App.Test.Functional/TestServer.cs @@ -7,6 +7,7 @@ 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; @@ -46,12 +47,12 @@ public TestServer() ctx.SaveChanges(); } - Client = CreateNewClientAndLogInAsync(username, password).GetAwaiter().GetResult(); + Client = CreateClientForUserAsync(username, password).GetAwaiter().GetResult(); } - public TurnierplanClient Client { get; } + public ApiRequestBuilder Client { get; } - public async Task CreateNewClientAndLogInAsync(string username, string password) + public async Task CreateClientForUserAsync(string username, string password) { var authenticationProvider = new AnonymousAuthenticationProvider(); var httpClient = _application.CreateClient(new WebApplicationFactoryClientOptions { HandleCookies = true }); @@ -66,7 +67,7 @@ public async Task CreateNewClientAndLogInAsync(string usernam loginResponse!.Success.Should().BeTrue(); - return client; + return client.Api; } public void ExecuteContextAction(Action action) From 539bbe1c44d50306bee0c857e43cb3762193432a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 14 Mar 2026 07:41:27 +0100 Subject: [PATCH 06/10] Prettify static method --- .../Scenarios.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Turnierplan.App.Test.Functional/Scenarios.cs b/src/Turnierplan.App.Test.Functional/Scenarios.cs index b42089e4..5c300f60 100644 --- a/src/Turnierplan.App.Test.Functional/Scenarios.cs +++ b/src/Turnierplan.App.Test.Functional/Scenarios.cs @@ -71,11 +71,9 @@ await _testServer.Client.Users.PostAsync( { var userClient = await _testServer.CreateClientForUserAsync(newUserName, newUserPassword); - var tryToCreateOrganization = () => userClient.Organizations.PostAsync( + await ExpectApiErrorAsync(() => userClient.Organizations.PostAsync( new CreateOrganizationEndpointRequest { Name = "test_org" }, - cancellationToken: TestContext.Current.CancellationToken); - - await ExpectErrorAsync(tryToCreateOrganization, HttpStatusCode.Forbidden); + cancellationToken: TestContext.Current.CancellationToken), HttpStatusCode.Forbidden); } // extra step required to get ID of new user @@ -100,21 +98,20 @@ await userClient.Organizations.PostAsync( } } - // TODO: Make pretty - private async Task ExpectErrorAsync(Func func, HttpStatusCode code) + private static async Task ExpectApiErrorAsync(Func func, HttpStatusCode code) { - ApiException? ex = null; + ApiException? exception = null; try { await func(); } - catch (ApiException ex2) + catch (ApiException ex) { - ex = ex2; + exception = ex; } - ex.Should().NotBeNull(); - ex.ResponseStatusCode.Should().Be((int)code); + exception.Should().NotBeNull(); + exception.ResponseStatusCode.Should().Be((int)code); } } From 34e43199c0e10311ce73ba47cb7a467206ab82e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 14 Mar 2026 07:50:29 +0100 Subject: [PATCH 07/10] Live with it --- src/Turnierplan.App.Test.Functional/Scenarios.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Turnierplan.App.Test.Functional/Scenarios.cs b/src/Turnierplan.App.Test.Functional/Scenarios.cs index 5c300f60..16ca2de7 100644 --- a/src/Turnierplan.App.Test.Functional/Scenarios.cs +++ b/src/Turnierplan.App.Test.Functional/Scenarios.cs @@ -76,11 +76,11 @@ await ExpectApiErrorAsync(() => userClient.Organizations.PostAsync( cancellationToken: TestContext.Current.CancellationToken), HttpStatusCode.Forbidden); } - // extra step required to get ID of new user + // 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; // TODO: UserName and Id should not be nullable? + var newUserId = allUsers!.Single(x => x.UserName!.Equals(newUserName)).Id!.Value; - await _testServer.Client.Users[newUserId!.Value].PutAsync(new UpdateUserEndpointRequest + await _testServer.Client.Users[newUserId].PutAsync(new UpdateUserEndpointRequest { UserName = newUserName, IsAdministrator = false, From f7cc1c1cb98df031f1d5767513b7f7acb5c4335e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 14 Mar 2026 08:37:08 +0100 Subject: [PATCH 08/10] Update build targets --- .../Turnierplan.App.Test.Functional.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj index 9fbd6a1b..ec6b1d77 100644 --- a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj +++ b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj @@ -18,7 +18,7 @@ - + From 929103b69bb4ba574075bc6e368822f2e4f96ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 14 Mar 2026 08:39:11 +0100 Subject: [PATCH 09/10] Add flag --- .../Turnierplan.App.Test.Functional.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj index ec6b1d77..05d79887 100644 --- a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj +++ b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj @@ -20,6 +20,6 @@ - + From ffed08ed3eae5146e74dc6341933237700b159c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 14 Mar 2026 08:43:33 +0100 Subject: [PATCH 10/10] Add compile items --- .../Turnierplan.App.Test.Functional.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj index 05d79887..9dca8dd3 100644 --- a/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj +++ b/src/Turnierplan.App.Test.Functional/Turnierplan.App.Test.Functional.csproj @@ -21,5 +21,10 @@ + + + + +