From 64f4b6f36ed599e53a5648771fb0213e4f6591ef Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 20:43:48 +0200 Subject: [PATCH 01/87] Start Mission and Signup design --- .../Missions/DTOs/MissionCreateRequestDto.cs | 38 +++++++++++ .../Features/Missions/DTOs/MissionDto.cs | 13 ++++ .../Features/Missions/MissionsController.cs | 62 ++++++++++++++++++ .../Features/Missions/Models/Mission.cs | 17 +++++ .../Features/Signups/Models/Signup.cs | 19 ++++++ .../Features/Signups/Models/Slot.cs | 9 +++ .../Features/Signups/Models/Team.cs | 11 ++++ .../Signups/SignupCreateRequestDto.cs | 42 +++++++++++++ .../Features/Signups/SignupsController.cs | 63 +++++++++++++++++++ 9 files changed, 274 insertions(+) create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs new file mode 100644 index 0000000..937d983 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs @@ -0,0 +1,38 @@ +using System; +using Newtonsoft.Json; + +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs +{ + public class MissionCreateRequestDto + { + /// + /// Mission title. + /// + [JsonProperty(Required = Required.Always)] + public string Title { get; set; } = string.Empty; + + /// + /// Mission description. + /// + [JsonProperty(Required = Required.Always)] + public string Description { get; set; } = string.Empty; + + /// + /// Mission start time. + /// + [JsonProperty(Required = Required.DisallowNull)] + public DateTime? MissionTime { get; set; } + + /// + /// Name of the modset. + /// + [JsonProperty(Required = Required.DisallowNull)] + public string? ModsetName { get; set; } + + /// + /// Owner of the mission. + /// + [JsonProperty(Required = Required.Always)] + public string Owner { get; set; } = string.Empty; + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs new file mode 100644 index 0000000..c01e241 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs +{ + public class MissionDto + { + public int MissionId { get; set; } + + public string Title { get; set; } = string.Empty; + + public DateTime MissionDate { get; set; } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs new file mode 100644 index 0000000..cb48ed9 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using Microsoft.AspNetCore.Mvc; + +namespace ArmaForces.Boderator.BotService.Features.Missions +{ + public class MissionsController : Controller + { + /// + /// + /// + /// + /// + [HttpPost] + public ActionResult CreateMission([FromBody] MissionCreateRequestDto request) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// Updated mission data. + [HttpPatch("{missionId:int}")] + public ActionResult UpdateMission(int missionId) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// Deleted mission data. + [HttpDelete("{missionId:int}")] + public ActionResult DeleteMission(int missionId) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + [HttpGet] + public ActionResult> GetMissions() + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + [HttpGet("{missionId:int}")] + public ActionResult GetMission(int missionId) + { + throw new NotImplementedException(); + } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs new file mode 100644 index 0000000..5331060 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs @@ -0,0 +1,17 @@ +using System; + +namespace ArmaForces.Boderator.BotService.Features.Missions.Models +{ + public record Mission + { + public int MissionId { get; init; } + + public string Title { get; init; } = string.Empty; + + public DateTime? MissionTime { get; init; } + + public string? ModsetName { get; set; } = string.Empty; + + public string Owner { get; set; } = string.Empty; + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs b/ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs new file mode 100644 index 0000000..8c1a705 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.BotService.Features.Missions.Models; + +namespace ArmaForces.Boderator.BotService.Features.Signups.Models +{ + public record Signup + { + public int SignupsId { get; init; } + + public DateTime StartDate { get; init; } + + public DateTime CloseDate { get; init; } + + public Mission Mission { get; init; } = new Mission(); + + public IReadOnlyList Teams { get; init; } = new List(); + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs b/ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs new file mode 100644 index 0000000..8b40bb8 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs @@ -0,0 +1,9 @@ +namespace ArmaForces.Boderator.BotService.Features.Signups.Models +{ + public record Slot + { + public string Name { get; init; } = string.Empty; + + public string? Occupant { get; init; } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs b/ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs new file mode 100644 index 0000000..8e5a784 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace ArmaForces.Boderator.BotService.Features.Signups.Models +{ + public record Team + { + public string Name { get; init; } = string.Empty; + + public IReadOnlyList Slots { get; init; } = new List(); + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs new file mode 100644 index 0000000..ad7b210 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Features.Signups.Models; +using Newtonsoft.Json; + +namespace ArmaForces.Boderator.BotService.Features.Signups +{ + public class SignupCreateRequestDto + { + /// + /// Id of the mission for which the signup will be created. + /// + [JsonProperty(Required = Required.DisallowNull)] + public int? MissionId { get; set; } + + /// + /// Mission for which the signups will be created. + /// Mission will be created. + /// + [JsonProperty(Required = Required.DisallowNull)] + public MissionCreateRequestDto? Mission { get; set; } + + /// + /// Starting date of signup. + /// + [JsonProperty(Required = Required.DisallowNull)] + public DateTime? StartDate { get; set; } + + /// + /// Closing date of signup. + /// + [JsonProperty(Required = Required.DisallowNull)] + public DateTime? CloseDate { get; set; } + + /// + /// Teams available in signup. + /// + [JsonProperty(Required = Required.Always)] + public List Teams { get; set; } = new List(); + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs new file mode 100644 index 0000000..ca75e8a --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.BotService.Features.Signups.Models; +using Microsoft.AspNetCore.Mvc; + +namespace ArmaForces.Boderator.BotService.Features.Signups +{ + public class SignupsController : ControllerBase + { + /// + /// + /// + /// + [HttpPost] + public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + [HttpPatch("{signupId:int}")] + public ActionResult ModifySignup(int signupId) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + [HttpDelete("{signupId:int}")] + public ActionResult DeleteSignup(int signupId) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + [HttpGet("{signupId:int}")] + public ActionResult GetSignup(int signupId) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + [HttpGet] + public ActionResult> GetSignups() + { + throw new NotImplementedException(); + } + } +} From eaaf8c183582037fecdc4a23fb99a5fafcd5a5f9 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:02:55 +0200 Subject: [PATCH 02/87] Add ReDoc documentation --- .../ArmaForces.Boderator.BotService.csproj | 1 + .../Documentation/DocumentationExtensions.cs | 38 +++++++++++++++++++ ArmaForces.Boderator.BotService/Startup.cs | 23 ++++++++--- ArmaForces.Boderator.sln.DotSettings | 4 +- 4 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index d950674..9a51906 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -27,6 +27,7 @@ + diff --git a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs new file mode 100644 index 0000000..3d424c3 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +namespace ArmaForces.Boderator.BotService.Documentation +{ + internal static class DocumentationExtensions + { + private const string DefaultSwaggerJsonUrl = "/swagger/v3/swagger.json"; + + public static IServiceCollection AddDocumentation(this IServiceCollection services, OpenApiInfo openApiConfig) + { + return services.AddSwaggerGen( + options => + { + options.SwaggerDoc(openApiConfig.Version, openApiConfig); + }); + } + + public static IApplicationBuilder AddDocumentation( + this IApplicationBuilder app, + OpenApiInfo openApiConfig, + string url = DefaultSwaggerJsonUrl) + { + return app.UseSwagger() + .UseSwaggerUI( + options => options.SwaggerEndpoint( + url, + openApiConfig.Title)) + .UseReDoc( + options => + { + options.DocumentTitle = "Boderator API Documentation"; + options.SpecUrl = url; + }); + } + } +} diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 0322ed3..675858c 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,4 +1,6 @@ +using System; using ArmaForces.Boderator.BotService.Discord; +using ArmaForces.Boderator.BotService.Documentation; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -18,15 +20,25 @@ public Startup(IConfiguration configuration) public IConfiguration Configuration { get; } + private OpenApiInfo OpenApiConfiguration { get; } = new() + { + Title = "ArmaForces Boderator API", + Description = "Does nothing.", + Version = "v0.1", + Contact = new OpenApiContact + { + Name = "ArmaForces", + Url = new Uri("https://armaforces.com") + } + }; + // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMediatR(typeof(Startup)); services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v3", new OpenApiInfo { Title = "ArmaForces.Boderator.WebService", Version = "v3" }); - }); + services.AddDocumentation(OpenApiConfiguration); + services.AddDiscordService(Helpers.Configuration.DiscordToken); } @@ -36,8 +48,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v3/swagger.json", "ArmaForces.Boderator.WebService v3")); + app.AddDocumentation(OpenApiConfiguration); } app.UseHttpsRedirection(); diff --git a/ArmaForces.Boderator.sln.DotSettings b/ArmaForces.Boderator.sln.DotSettings index fc986b0..f4b9f27 100644 --- a/ArmaForces.Boderator.sln.DotSettings +++ b/ArmaForces.Boderator.sln.DotSettings @@ -1,3 +1,5 @@ - + + True + True True True \ No newline at end of file From b58a66430313faec2e7954258e47436f72d4d0e2 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:08:33 +0200 Subject: [PATCH 03/87] Fix version --- ArmaForces.Boderator.BotService/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 675858c..fd88501 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -24,7 +24,7 @@ public Startup(IConfiguration configuration) { Title = "ArmaForces Boderator API", Description = "Does nothing.", - Version = "v0.1", + Version = "v3", Contact = new OpenApiContact { Name = "ArmaForces", From dfd6204b13e7af0892fcbc8efeda03424884c000 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:10:05 +0200 Subject: [PATCH 04/87] Set ReDoc as default start page --- ArmaForces.Boderator.BotService/Properties/launchSettings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ArmaForces.Boderator.BotService/Properties/launchSettings.json b/ArmaForces.Boderator.BotService/Properties/launchSettings.json index afa3bd8..b86381a 100644 --- a/ArmaForces.Boderator.BotService/Properties/launchSettings.json +++ b/ArmaForces.Boderator.BotService/Properties/launchSettings.json @@ -18,6 +18,7 @@ "ArmaForces.Boderator.BotService": { "commandName": "Project", "launchBrowser": true, + "launchUrl": "api-docs/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, From 9d88111a8f6e2226fb2982f5cabfd0ee9bf44c4a Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:14:02 +0200 Subject: [PATCH 05/87] Fix ReDoc DocumentTitle --- .../Documentation/DocumentationExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs index 3d424c3..1f1c59b 100644 --- a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs +++ b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs @@ -30,7 +30,7 @@ public static IApplicationBuilder AddDocumentation( .UseReDoc( options => { - options.DocumentTitle = "Boderator API Documentation"; + options.DocumentTitle = openApiConfig.Title; options.SpecUrl = url; }); } From 18395cc1ad50e693037b41d1b9a5ce3a690ddda8 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:30:35 +0200 Subject: [PATCH 06/87] Enable Nullable feature --- .../ArmaForces.Boderator.BotService.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index d950674..6f61541 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -4,6 +4,7 @@ net5.0 a9e33227-0f21-4863-9830-8aa69ac1e928 Linux + enable From 5a209d93c4bb7c987823eced41fe2f34da08642c Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:31:30 +0200 Subject: [PATCH 07/87] Fix Discord LogSeverity mapping switch not having default --- ArmaForces.Boderator.BotService/Discord/DiscordService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Discord/DiscordService.cs b/ArmaForces.Boderator.BotService/Discord/DiscordService.cs index 7b2cc80..6d25064 100644 --- a/ArmaForces.Boderator.BotService/Discord/DiscordService.cs +++ b/ArmaForces.Boderator.BotService/Discord/DiscordService.cs @@ -61,7 +61,11 @@ private static LogLevel MapSeverity(LogSeverity severity) => LogSeverity.Info => LogLevel.Information, LogSeverity.Warning => LogLevel.Warning, LogSeverity.Error => LogLevel.Error, - LogSeverity.Critical => LogLevel.Critical + LogSeverity.Critical => LogLevel.Critical, + _ => throw new ArgumentOutOfRangeException( + nameof(severity), + severity, + null) }; } } From 9d32450a300f653e7662119ddf83050de4a00462 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:32:04 +0200 Subject: [PATCH 08/87] Set Configuration in Startup to private --- ArmaForces.Boderator.BotService/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 0322ed3..776343b 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -16,7 +16,7 @@ public Startup(IConfiguration configuration) Configuration = configuration; } - public IConfiguration Configuration { get; } + private IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) From 8db36d825560254ddd51717bbca67e5175e465cc Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:33:27 +0200 Subject: [PATCH 09/87] Rename test project to BotService.Tests --- .../ArmaForces.Boderator.BotService.Tests.csproj | 0 .../BasicTest.cs | 2 +- .../TestBase.cs | 2 +- ArmaForces.Boderator.sln | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename ArmaForces.Boderator.Tests/ArmaForces.Boderator.Tests.csproj => ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj (100%) rename {ArmaForces.Boderator.Tests => ArmaForces.Boderator.BotService.Tests}/BasicTest.cs (91%) rename {ArmaForces.Boderator.Tests => ArmaForces.Boderator.BotService.Tests}/TestBase.cs (90%) diff --git a/ArmaForces.Boderator.Tests/ArmaForces.Boderator.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj similarity index 100% rename from ArmaForces.Boderator.Tests/ArmaForces.Boderator.Tests.csproj rename to ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj diff --git a/ArmaForces.Boderator.Tests/BasicTest.cs b/ArmaForces.Boderator.BotService.Tests/BasicTest.cs similarity index 91% rename from ArmaForces.Boderator.Tests/BasicTest.cs rename to ArmaForces.Boderator.BotService.Tests/BasicTest.cs index d53e5a5..02354df 100644 --- a/ArmaForces.Boderator.Tests/BasicTest.cs +++ b/ArmaForces.Boderator.BotService.Tests/BasicTest.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace ArmaForces.Boderator.Tests +namespace ArmaForces.Boderator.BotService.Tests { public class BasicTest : TestBase { diff --git a/ArmaForces.Boderator.Tests/TestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestBase.cs similarity index 90% rename from ArmaForces.Boderator.Tests/TestBase.cs rename to ArmaForces.Boderator.BotService.Tests/TestBase.cs index ed53eea..194a8f7 100644 --- a/ArmaForces.Boderator.Tests/TestBase.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestBase.cs @@ -3,7 +3,7 @@ using MediatR; using Microsoft.Extensions.DependencyInjection; -namespace ArmaForces.Boderator.Tests +namespace ArmaForces.Boderator.BotService.Tests { public class TestBase { diff --git a/ArmaForces.Boderator.sln b/ArmaForces.Boderator.sln index 60328a7..237e23b 100644 --- a/ArmaForces.Boderator.sln +++ b/ArmaForces.Boderator.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.31205.134 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.DAO", "ArmaForces.Boderator.DAO\ArmaForces.Boderator.DAO.csproj", "{A2849677-3590-45E2-BD3C-2C21B2C59BC6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.Tests", "ArmaForces.Boderator.Tests\ArmaForces.Boderator.Tests.csproj", "{85762E30-4C0D-4735-B40F-C8C40F73F4C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.BotService.Tests", "ArmaForces.Boderator.BotService.Tests\ArmaForces.Boderator.BotService.Tests.csproj", "{85762E30-4C0D-4735-B40F-C8C40F73F4C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.BotService", "ArmaForces.Boderator.BotService\ArmaForces.Boderator.BotService.csproj", "{250BF5C2-0750-4BE0-BFEA-35EAA9AB24F1}" EndProject From 34dda11445dd4a0c8cd08d4ba43135f621de0aba Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:35:03 +0200 Subject: [PATCH 10/87] Rename DAO project to Core --- .../ArmaForces.Boderator.Core.csproj | 4 ---- .../BoderatorContext.cs | 2 +- ArmaForces.Boderator.sln | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) rename ArmaForces.Boderator.DAO/ArmaForces.Boderator.DAO.csproj => ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj (89%) rename {ArmaForces.Boderator.DAO => ArmaForces.Boderator.Core}/BoderatorContext.cs (82%) diff --git a/ArmaForces.Boderator.DAO/ArmaForces.Boderator.DAO.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj similarity index 89% rename from ArmaForces.Boderator.DAO/ArmaForces.Boderator.DAO.csproj rename to ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index ae64065..289f24f 100644 --- a/ArmaForces.Boderator.DAO/ArmaForces.Boderator.DAO.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -13,8 +13,4 @@ - - - - diff --git a/ArmaForces.Boderator.DAO/BoderatorContext.cs b/ArmaForces.Boderator.Core/BoderatorContext.cs similarity index 82% rename from ArmaForces.Boderator.DAO/BoderatorContext.cs rename to ArmaForces.Boderator.Core/BoderatorContext.cs index 3dc23f1..50fda77 100644 --- a/ArmaForces.Boderator.DAO/BoderatorContext.cs +++ b/ArmaForces.Boderator.Core/BoderatorContext.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.DAO +namespace ArmaForces.Boderator.Core { public class BoderatorContext : DbContext { diff --git a/ArmaForces.Boderator.sln b/ArmaForces.Boderator.sln index 237e23b..995faf7 100644 --- a/ArmaForces.Boderator.sln +++ b/ArmaForces.Boderator.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31205.134 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.DAO", "ArmaForces.Boderator.DAO\ArmaForces.Boderator.DAO.csproj", "{A2849677-3590-45E2-BD3C-2C21B2C59BC6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.Core", "ArmaForces.Boderator.Core\ArmaForces.Boderator.Core.csproj", "{A2849677-3590-45E2-BD3C-2C21B2C59BC6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.BotService.Tests", "ArmaForces.Boderator.BotService.Tests\ArmaForces.Boderator.BotService.Tests.csproj", "{85762E30-4C0D-4735-B40F-C8C40F73F4C3}" EndProject From 631681169e774610c581c00148128130d76c1b73 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:36:27 +0200 Subject: [PATCH 11/87] Add Boderator.Core.Tests project --- .../ArmaForces.Boderator.Core.Tests.csproj | 22 +++++++++++++++++++ ArmaForces.Boderator.sln | 6 +++++ 2 files changed, 28 insertions(+) create mode 100644 ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj new file mode 100644 index 0000000..f1ea4d6 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -0,0 +1,22 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/ArmaForces.Boderator.sln b/ArmaForces.Boderator.sln index 995faf7..02791b5 100644 --- a/ArmaForces.Boderator.sln +++ b/ArmaForces.Boderator.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.BotSer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArmaForces.Boderator.BotService", "ArmaForces.Boderator.BotService\ArmaForces.Boderator.BotService.csproj", "{250BF5C2-0750-4BE0-BFEA-35EAA9AB24F1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArmaForces.Boderator.Core.Tests", "ArmaForces.Boderator.Core.Tests\ArmaForces.Boderator.Core.Tests.csproj", "{7DE1D733-EBE7-4800-A5E2-6F0425EC93E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {250BF5C2-0750-4BE0-BFEA-35EAA9AB24F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {250BF5C2-0750-4BE0-BFEA-35EAA9AB24F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {250BF5C2-0750-4BE0-BFEA-35EAA9AB24F1}.Release|Any CPU.Build.0 = Release|Any CPU + {7DE1D733-EBE7-4800-A5E2-6F0425EC93E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DE1D733-EBE7-4800-A5E2-6F0425EC93E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DE1D733-EBE7-4800-A5E2-6F0425EC93E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DE1D733-EBE7-4800-A5E2-6F0425EC93E9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From a4862a613c93a41543c3fe92ff5dac0434444fb9 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:42:51 +0200 Subject: [PATCH 12/87] Add InternalsVisibleTo attributes --- .../ArmaForces.Boderator.BotService.csproj | 6 ++++++ ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index 6f61541..f945722 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -29,5 +29,11 @@ + + + + <_Parameter1>$(MSBuildProjectName).Tests + + diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 289f24f..432e398 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -12,5 +12,11 @@ + + + + <_Parameter1>$(MSBuildProjectName).Tests + + From e4b545f2ff610a90dab603d5f6ffa0bf1710d60f Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:45:45 +0200 Subject: [PATCH 13/87] Reference Core project by BotService --- .../ArmaForces.Boderator.BotService.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index f945722..493e549 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -30,6 +30,10 @@ + + + + <_Parameter1>$(MSBuildProjectName).Tests From 9d04bb87fad9035858eab5c1ff229fa74f0aec2a Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:50:33 +0200 Subject: [PATCH 14/87] Remove clutter from BotService csproj --- .../ArmaForces.Boderator.BotService.csproj | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index 493e549..5e3982e 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -7,13 +7,6 @@ enable - - - - - - - From 4b76b1b11b2836523a7949fbc097fec950fbca67 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:51:20 +0200 Subject: [PATCH 15/87] Add Core reference for Core.Tests --- .../ArmaForces.Boderator.Core.Tests.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index f1ea4d6..ece3d0e 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -19,4 +19,8 @@ + + + + From 7a254b80a3410b26363f37293483a1f4fcbcf7bb Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:54:02 +0200 Subject: [PATCH 16/87] Reorder references in csprojs --- .../ArmaForces.Boderator.BotService.Tests.csproj | 10 +++++----- .../ArmaForces.Boderator.BotService.csproj | 10 +++++----- .../ArmaForces.Boderator.Core.Tests.csproj | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index ab67797..78a4ed1 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -5,7 +5,11 @@ false - + + + + + @@ -19,8 +23,4 @@ - - - - diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index 5e3982e..765fb3b 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -6,7 +6,11 @@ Linux enable - + + + + + @@ -23,10 +27,6 @@ - - - - <_Parameter1>$(MSBuildProjectName).Tests diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index ece3d0e..39b546f 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -5,7 +5,11 @@ false - + + + + + @@ -19,8 +23,4 @@ - - - - From e2feb2cf696e3494a73f40d1ad4a87b8eb6e4031 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:55:10 +0200 Subject: [PATCH 17/87] Change indent to 2 spaces in csprojs --- .../ArmaForces.Boderator.Core.Tests.csproj | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 39b546f..427d693 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -1,26 +1,26 @@ - - net5.0 + + net5.0 - false - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + false + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + From b0fce08a47a85fbeebc99b38578f9cb693bef36d Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 9 Sep 2021 21:58:33 +0200 Subject: [PATCH 18/87] Add .editorconfig --- .editorconfig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..362c2da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +end_of_line = crlf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.csproj] +indent_size = 2 From b402e46996e7eab4a699eac1b0d3200e954c582e Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 00:19:13 +0200 Subject: [PATCH 19/87] Add AutoFixture, Moq and FluentAssertions to test projects --- .../ArmaForces.Boderator.BotService.Tests.csproj | 3 +++ .../ArmaForces.Boderator.Core.Tests.csproj | 3 +++ 2 files changed, 6 insertions(+) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 78a4ed1..7c0b27c 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -11,7 +11,10 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 427d693..5cb2d64 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -11,7 +11,10 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive From aadcec5b9195038c8c1b48699ed6f6b007e9227e Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 00:19:30 +0200 Subject: [PATCH 20/87] Fixes after merge --- .../ArmaForces.Boderator.BotService.csproj | 4 ---- ArmaForces.Boderator.BotService/Discord/DiscordService.cs | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index 19c1231..ef07942 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -26,10 +26,6 @@ - - - - <_Parameter1>$(MSBuildProjectName).Tests diff --git a/ArmaForces.Boderator.BotService/Discord/DiscordService.cs b/ArmaForces.Boderator.BotService/Discord/DiscordService.cs index 6b8bb7f..6d25064 100644 --- a/ArmaForces.Boderator.BotService/Discord/DiscordService.cs +++ b/ArmaForces.Boderator.BotService/Discord/DiscordService.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.DTOs; using Discord; From c96658cd661fa254e4089490705f7bd3bc2f369a Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 01:58:12 +0200 Subject: [PATCH 21/87] Add CSharpFunctionalExtensions --- .../ArmaForces.Boderator.BotService.Tests.csproj | 1 + .../ArmaForces.Boderator.BotService.csproj | 1 + .../ArmaForces.Boderator.Core.Tests.csproj | 1 + ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj | 1 + 4 files changed, 4 insertions(+) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 7c0b27c..0ef897b 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index ef07942..11b24c3 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -12,6 +12,7 @@ + diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 5cb2d64..9cd3b8f 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 432e398..0f13f71 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -5,6 +5,7 @@ + all From 057884e14e5fd9e45bb6a1dc9cace2c964d41c62 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 03:12:02 +0200 Subject: [PATCH 22/87] Start work on TestApiServer --- .../ApiTestBase.cs | 60 +++++++++++++++++++ .../TestApiServiceFixture.cs | 45 ++++++++++++++ .../TestBase.cs | 16 ----- .../TestCollections/Collections.cs | 7 +++ .../Definitions/TestApiCollection.cs | 11 ++++ 5 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs create mode 100644 ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs delete mode 100644 ArmaForces.Boderator.BotService.Tests/TestBase.cs create mode 100644 ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs create mode 100644 ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs diff --git a/ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs b/ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs new file mode 100644 index 0000000..062984f --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs @@ -0,0 +1,60 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Tests.TestCollections; +using CSharpFunctionalExtensions; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests +{ + [Collection(Collections.ApiTest)] + public abstract class ApiTestBase + { + private readonly HttpClient _httpClient; + + protected IServiceProvider Provider { get; } + + protected ApiTestBase(TestApiServiceFixture testApi) + { + _httpClient = testApi.HttpClient; + + Provider = new ServiceCollection() + .BuildServiceProvider(); + } + + protected async Task> HttpGetAsync(string path) + { + var httpResponseMessage = await _httpClient.GetAsync(path); + if (httpResponseMessage.IsSuccessStatusCode) + { + var content = await httpResponseMessage.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content); + } + + var responseBody = await httpResponseMessage.Content.ReadAsStringAsync(); + var error = string.IsNullOrWhiteSpace(responseBody) + ? httpResponseMessage.ReasonPhrase + : responseBody; + + return Result.Failure(error); + } + + protected async Task HttpPostAsync(string path, T body) + { + var httpResponseMessage = await _httpClient.PostAsync(path, new StringContent(JsonConvert.SerializeObject(body))); + if (httpResponseMessage.IsSuccessStatusCode) + { + return Result.Success(); + } + + var responseBody = await httpResponseMessage.Content.ReadAsStringAsync(); + var error = string.IsNullOrWhiteSpace(responseBody) + ? httpResponseMessage.ReasonPhrase + : responseBody; + + return Result.Failure(error); + } + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs new file mode 100644 index 0000000..ffa4eaf --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs @@ -0,0 +1,45 @@ +using System; +using System.Net.Http; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace ArmaForces.Boderator.BotService.Tests +{ + public class TestApiServiceFixture : IDisposable + { + private const int Port = 43421; + private readonly SocketsHttpHandler _socketsHttpHandler; + private readonly IHost _host; + + public HttpClient HttpClient { get; } + + public TestApiServiceFixture() + { + _socketsHttpHandler = new SocketsHttpHandler(); + HttpClient = new HttpClient(_socketsHttpHandler) + { + BaseAddress = new Uri($"http://localhost:{Port}") + }; + + _host = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults(ConfigureWebBuilder()) + .Build(); + + _host.Start(); + } + + public void Dispose() + { + HttpClient.Dispose(); + _socketsHttpHandler.Dispose(); + _host.Dispose(); + GC.SuppressFinalize(this); + } + + private static Action ConfigureWebBuilder() => webBuilder => + { + webBuilder.UseStartup(); + webBuilder.UseKestrel(x => x.ListenLocalhost(Port)); + }; + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestBase.cs deleted file mode 100644 index d94ca3d..0000000 --- a/ArmaForces.Boderator.BotService.Tests/TestBase.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; - -namespace ArmaForces.Boderator.BotService.Tests -{ - public class TestBase - { - protected IServiceProvider Provider { get; } - - public TestBase() - { - Provider = new ServiceCollection() - .BuildServiceProvider(); - } - } -} diff --git a/ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs b/ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs new file mode 100644 index 0000000..3159e7a --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs @@ -0,0 +1,7 @@ +namespace ArmaForces.Boderator.BotService.Tests.TestCollections +{ + internal static class Collections + { + public const string ApiTest = "TestAPI"; + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs b/ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs new file mode 100644 index 0000000..9c5bebe --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs @@ -0,0 +1,11 @@ +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests.TestCollections.Definitions +{ + public class TestApiCollection : ICollectionFixture + { + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. + } +} From 4d27128e838fb2a69f74873c211c3b71cd0422f5 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 03:12:11 +0200 Subject: [PATCH 23/87] Remove DiscordService from startup --- ArmaForces.Boderator.BotService/Startup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index ccf0f77..840f982 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -36,8 +36,6 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); - - services.AddDiscordService(Helpers.Configuration.DiscordToken); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From f6bdde855215f451fd46c29db090e74b326b070e Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 11:16:08 +0200 Subject: [PATCH 24/87] Add Result extensions for easier assertions --- .../ResultAssertionsExtensions.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs new file mode 100644 index 0000000..cee12c8 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -0,0 +1,24 @@ +using CSharpFunctionalExtensions; +using FluentAssertions; +using FluentAssertions.Execution; + +namespace ArmaForces.Boderator.Core.Tests.TestUtilities +{ + public static class ResultAssertionsExtensions + { + public static void ShouldBeSuccess(this Result result, T expectedValue) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + result.Value.Should().BeEquivalentTo(expectedValue); + } + else + { + result.IsSuccess.Should().BeTrue(); + result.Error.Should().BeNull(); + } + } + } +} From 18d0a39d061c85dedeffa4f63a86add0300385be Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 11:16:22 +0200 Subject: [PATCH 25/87] Fix controllers mapping --- ArmaForces.Boderator.BotService/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 840f982..d272bb0 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -55,7 +55,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { - endpoints.MapControllerRoute("default", "api/{controller}/{action}"); + endpoints.MapControllers(); }); } } From 92108ee3841884553c34d6617a8ec6c63e99fbac Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 11:16:50 +0200 Subject: [PATCH 26/87] Replace Debug controller with Health controller --- .../Controllers/DebugController.cs | 35 ------------------- .../Features/Health/HealthController.cs | 11 ++++++ 2 files changed, 11 insertions(+), 35 deletions(-) delete mode 100644 ArmaForces.Boderator.BotService/Controllers/DebugController.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Health/HealthController.cs diff --git a/ArmaForces.Boderator.BotService/Controllers/DebugController.cs b/ArmaForces.Boderator.BotService/Controllers/DebugController.cs deleted file mode 100644 index cd7e1b1..0000000 --- a/ArmaForces.Boderator.BotService/Controllers/DebugController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using ArmaForces.Boderator.BotService.Discord; -using Discord; -using Microsoft.AspNetCore.Mvc; - -namespace ArmaForces.Boderator.BotService.Controllers -{ - [Route("api/[controller]")] - public class DebugController : Controller - { - private readonly IDiscordService _discordService; - - public DebugController(IDiscordService discordService) - { - _discordService = discordService; - } - - [HttpGet("Test")] - public async Task Test(CancellationToken cToken) - { - return Ok(); - } - - [HttpGet("GetDiscordStatus")] - public ActionResult GetDiscordStatus() => Ok(_discordService.GetDiscordClientStatus()); - - [HttpPut("SetBotStatus/{status}")] - public async Task SetBotStatus(string status, ActivityType type = ActivityType.Playing) - { - await _discordService.SetBotStatus(status, type); - return Ok(); - } - } -} diff --git a/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs b/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs new file mode 100644 index 0000000..427913d --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace ArmaForces.Boderator.BotService.Features.Health +{ + [Route("api/[controller]")] + public class HealthController : Controller + { + [HttpGet("ping")] + public IActionResult Ping() => Ok("pong"); + } +} From f6c5bc53c7b0953a0086513fb29f149d44f32edd Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 11:17:13 +0200 Subject: [PATCH 27/87] Add integration test for ping endpoint --- ...maForces.Boderator.BotService.Tests.csproj | 1 + .../Features/Health/HealthControllerTests.cs | 23 +++++++++++++++++ .../TestCollections/Collections.cs | 7 ------ .../Collections/CollectionsNames.cs | 7 ++++++ .../Definitions/TestApiCollection.cs | 4 ++- .../TestBases}/ApiTestBase.cs | 25 ++++++++++++++----- .../TestFixtures}/TestApiServiceFixture.cs | 2 +- 7 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 ArmaForces.Boderator.BotService.Tests/Features/Health/HealthControllerTests.cs delete mode 100644 ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs create mode 100644 ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/CollectionsNames.cs rename ArmaForces.Boderator.BotService.Tests/{TestCollections => TestUtilities/Collections}/Definitions/TestApiCollection.cs (58%) rename ArmaForces.Boderator.BotService.Tests/{ => TestUtilities/TestBases}/ApiTestBase.cs (66%) rename ArmaForces.Boderator.BotService.Tests/{ => TestUtilities/TestFixtures}/TestApiServiceFixture.cs (94%) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 0ef897b..800ca20 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Health/HealthControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Health/HealthControllerTests.cs new file mode 100644 index 0000000..350f069 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/Features/Health/HealthControllerTests.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests.Features.Health +{ + [Trait("Category", "Integration")] + public class HealthControllerTests : ApiTestBase + { + public HealthControllerTests(TestApiServiceFixture testApi) + : base(testApi) { } + + [Fact] + public async Task Ping_AllOk_ReturnsPong() + { + var result = await HttpGetAsync("api/health/ping"); + + result.ShouldBeSuccess("pong"); + } + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs b/ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs deleted file mode 100644 index 3159e7a..0000000 --- a/ArmaForces.Boderator.BotService.Tests/TestCollections/Collections.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ArmaForces.Boderator.BotService.Tests.TestCollections -{ - internal static class Collections - { - public const string ApiTest = "TestAPI"; - } -} diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/CollectionsNames.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/CollectionsNames.cs new file mode 100644 index 0000000..33c1494 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/CollectionsNames.cs @@ -0,0 +1,7 @@ +namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.Collections +{ + internal static class CollectionsNames + { + public const string ApiTest = "TestAPI"; + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/Definitions/TestApiCollection.cs similarity index 58% rename from ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs rename to ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/Definitions/TestApiCollection.cs index 9c5bebe..d65920c 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestCollections/Definitions/TestApiCollection.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/Collections/Definitions/TestApiCollection.cs @@ -1,7 +1,9 @@ +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; using Xunit; -namespace ArmaForces.Boderator.BotService.Tests.TestCollections.Definitions +namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.Collections.Definitions { + [CollectionDefinition(CollectionsNames.ApiTest)] public class TestApiCollection : ICollectionFixture { // This class has no code, and is never created. Its purpose is simply diff --git a/ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs similarity index 66% rename from ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs rename to ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs index 062984f..2ff23ea 100644 --- a/ArmaForces.Boderator.BotService.Tests/ApiTestBase.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs @@ -1,15 +1,20 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using ArmaForces.Boderator.BotService.Tests.TestCollections; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.Collections; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; using CSharpFunctionalExtensions; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Xunit; -namespace ArmaForces.Boderator.BotService.Tests +namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases { - [Collection(Collections.ApiTest)] + /// + /// Base class for integration tests involving API. + /// Provider test server and methods to invoke endpoints. + /// + [Collection(CollectionsNames.ApiTest)] public abstract class ApiTestBase { private readonly HttpClient _httpClient; @@ -25,12 +30,17 @@ protected ApiTestBase(TestApiServiceFixture testApi) } protected async Task> HttpGetAsync(string path) + { + return await HttpGetAsync(path) + .Bind(DeserializeContent); + } + + protected async Task> HttpGetAsync(string path) { var httpResponseMessage = await _httpClient.GetAsync(path); if (httpResponseMessage.IsSuccessStatusCode) { - var content = await httpResponseMessage.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(content); + return await httpResponseMessage.Content.ReadAsStringAsync(); } var responseBody = await httpResponseMessage.Content.ReadAsStringAsync(); @@ -38,7 +48,7 @@ protected async Task> HttpGetAsync(string path) ? httpResponseMessage.ReasonPhrase : responseBody; - return Result.Failure(error); + return Result.Failure(error); } protected async Task HttpPostAsync(string path, T body) @@ -56,5 +66,8 @@ protected async Task HttpPostAsync(string path, T body) return Result.Failure(error); } + + private static Result DeserializeContent(string content) + => JsonConvert.DeserializeObject(content); } } diff --git a/ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs similarity index 94% rename from ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs rename to ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs index ffa4eaf..2cfcf8f 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestApiServiceFixture.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace ArmaForces.Boderator.BotService.Tests +namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures { public class TestApiServiceFixture : IDisposable { From a2b910dfb0b5a962654c0bfb2ab4c34aeaa9503f Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 11:30:58 +0200 Subject: [PATCH 28/87] Add documentation for HealthController --- .../Features/Health/HealthController.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs b/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs index 427913d..4e0c1d2 100644 --- a/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs +++ b/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs @@ -1,11 +1,19 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Health { + /// + /// Allows obtaining application status. + /// [Route("api/[controller]")] public class HealthController : Controller { + /// + /// Responds to a ping. + /// [HttpGet("ping")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Ping() => Ok("pong"); } } From 40b36b72103bba058f47bbba5d84ba53d8a8be67 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 12:17:26 +0200 Subject: [PATCH 29/87] Change log settings --- .gitignore | 2 +- .../appsettings.Development.json | 13 ++++++++----- ArmaForces.Boderator.BotService/appsettings.json | 11 +++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index eeff256..a915aee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .vs/* .idea/* +Logs/* */obj/* */bin/* -*/Logs/* *.user *.dat *.mp3 diff --git a/ArmaForces.Boderator.BotService/appsettings.Development.json b/ArmaForces.Boderator.BotService/appsettings.Development.json index 5ed56cc..d3c3868 100644 --- a/ArmaForces.Boderator.BotService/appsettings.Development.json +++ b/ArmaForces.Boderator.BotService/appsettings.Development.json @@ -5,24 +5,27 @@ "Name": "Console", "Args": { "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", - "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {Message:lj}{NewLine}{Exception}" + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {SourceContext} {Message:lj} {Properties}{NewLine}{Exception}" } }, { "Name": "Debug", - "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {Message:lj}{NewLine}{Exception}" + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {SourceContext} {Message:lj} {Properties}{NewLine}{Exception}" }, { "Name": "File", "Args": { - "path": "Logs/log.txt", + "path": "../Logs/log.txt", "rollingInterval": "Day", "retainedFileCountLimit": 7, - "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {Message:lj}{NewLine}{Exception}" + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {SourceContext} {Message:lj} {Properties}{NewLine}{Exception}" } } ], - "Enrich": [ "WithThreadId" ] + "Enrich": [ + "FromLogContext", + "WithThreadId" + ] }, "Logging": { "LogLevel": { diff --git a/ArmaForces.Boderator.BotService/appsettings.json b/ArmaForces.Boderator.BotService/appsettings.json index 35581c1..1c337f1 100644 --- a/ArmaForces.Boderator.BotService/appsettings.json +++ b/ArmaForces.Boderator.BotService/appsettings.json @@ -5,20 +5,23 @@ "Name": "Console", "Args": { "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", - "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {Message:lj} {Properties}{NewLine}{Exception}" + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {SourceContext} {Message:lj} {Properties}{NewLine}{Exception}" } }, { "Name": "File", "Args": { - "path": "log.txt", + "path": "Logs/log.txt", "rollingInterval": "Day", "retainedFileCountLimit": 7, - "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {Message:lj} {Properties}{NewLine}{Exception}" + "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{ThreadId}] {SourceContext} {Message:lj} {Properties}{NewLine}{Exception}" } } ], - "Enrich": [ "WithThreadId" ] + "Enrich": [ + "FromLogContext", + "WithThreadId" + ] }, "Logging": { "LogLevel": { From 2bfc93854201df2d8e51104eede90145c782a127 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 12:21:08 +0200 Subject: [PATCH 30/87] Extract Serilog initialization to separate method --- .../Logging/SerilogConfigurationExtensions.cs | 15 +++++++++++++++ ArmaForces.Boderator.BotService/Program.cs | 8 ++++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Logging/SerilogConfigurationExtensions.cs diff --git a/ArmaForces.Boderator.BotService/Logging/SerilogConfigurationExtensions.cs b/ArmaForces.Boderator.BotService/Logging/SerilogConfigurationExtensions.cs new file mode 100644 index 0000000..4bb2f8b --- /dev/null +++ b/ArmaForces.Boderator.BotService/Logging/SerilogConfigurationExtensions.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Extensions.Hosting; +using Serilog; + +namespace ArmaForces.Boderator.BotService.Logging +{ + internal static class SerilogConfigurationExtensions + { + public static IHostBuilder AddSerilog(this IHostBuilder hostBuilder) + => hostBuilder.UseSerilog(ConfigureLogging()); + + private static Action ConfigureLogging() => (context, configuration) => configuration + .ReadFrom.Configuration(context.Configuration); + } +} diff --git a/ArmaForces.Boderator.BotService/Program.cs b/ArmaForces.Boderator.BotService/Program.cs index 635f4ee..281be83 100644 --- a/ArmaForces.Boderator.BotService/Program.cs +++ b/ArmaForces.Boderator.BotService/Program.cs @@ -1,19 +1,19 @@ +using ArmaForces.Boderator.BotService.Logging; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -using Serilog; namespace ArmaForces.Boderator.BotService { - public class Program + public static class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } - public static IHostBuilder CreateHostBuilder(string[] args) => + private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .UseSerilog((hbc, lc) => lc.ReadFrom.Configuration(hbc.Configuration)) + .AddSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); From ed21c1b280be7a0c2b16c6831f806ecc80fa0ffb Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 13:55:43 +0200 Subject: [PATCH 31/87] Add log levels config --- ArmaForces.Boderator.BotService/appsettings.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ArmaForces.Boderator.BotService/appsettings.json b/ArmaForces.Boderator.BotService/appsettings.json index 1c337f1..d76d42c 100644 --- a/ArmaForces.Boderator.BotService/appsettings.json +++ b/ArmaForces.Boderator.BotService/appsettings.json @@ -1,5 +1,14 @@ { "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "ArmaForces": "Verbose", + "Microsoft": "Warning", + "Microsoft.AspNetCore.Hosting.Diagnostics" : "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, "WriteTo": [ { "Name": "Console", From ee3d4a86690666b875f52b5553609bb1a09807a6 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 13:55:51 +0200 Subject: [PATCH 32/87] Set indent to 2 for json files --- .editorconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.editorconfig b/.editorconfig index 362c2da..4ba1ecd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,7 @@ trim_trailing_whitespace = false [*.csproj] indent_size = 2 + +[*.json] +indent_size += 2 \ No newline at end of file From 0c6c98014e9dfdb0efe3c3797fdb4d2b481931c3 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 14:06:10 +0200 Subject: [PATCH 33/87] Move DiscordService to DiscordClient feature --- .../DTOs/DiscordServiceStatus.cs | 9 --- .../DiscordServiceBuilder.cs | 11 --- .../IDiscordServiceBuilder.cs | 6 -- .../Discord/DiscordService.cs | 71 ----------------- .../DiscordServiceCollectionExtentions.cs | 16 ---- .../Discord/Interfaces/IDiscordService.cs | 12 --- .../DTOs/DiscordServiceStatusDto.cs | 11 +++ .../Features/DiscordClient/DiscordService.cs | 64 +++++++++++++++ .../Features/DiscordClient/IDiscordService.cs | 12 +++ .../DiscordServiceCollectionExtensions.cs | 15 ++++ .../DiscordSocketClientFactory.cs | 40 ++++++++++ .../Logging/LogMessageExtensions.cs | 78 +++++++++++++++++++ ArmaForces.Boderator.BotService/Startup.cs | 1 - 13 files changed, 220 insertions(+), 126 deletions(-) delete mode 100644 ArmaForces.Boderator.BotService/DTOs/DiscordServiceStatus.cs delete mode 100644 ArmaForces.Boderator.BotService/Discord/DependencyInjection/DiscordServiceBuilder.cs delete mode 100644 ArmaForces.Boderator.BotService/Discord/DependencyInjection/IDiscordServiceBuilder.cs delete mode 100644 ArmaForces.Boderator.BotService/Discord/DiscordService.cs delete mode 100644 ArmaForces.Boderator.BotService/Discord/DiscordServiceCollectionExtentions.cs delete mode 100644 ArmaForces.Boderator.BotService/Discord/Interfaces/IDiscordService.cs create mode 100644 ArmaForces.Boderator.BotService/Features/DiscordClient/DTOs/DiscordServiceStatusDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs create mode 100644 ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs create mode 100644 ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs create mode 100644 ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordSocketClientFactory.cs create mode 100644 ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/Logging/LogMessageExtensions.cs diff --git a/ArmaForces.Boderator.BotService/DTOs/DiscordServiceStatus.cs b/ArmaForces.Boderator.BotService/DTOs/DiscordServiceStatus.cs deleted file mode 100644 index b808323..0000000 --- a/ArmaForces.Boderator.BotService/DTOs/DiscordServiceStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace ArmaForces.Boderator.BotService.DTOs -{ - public class DiscordServiceStatus - { - public string ConnectionState { get; init; } - public string LoginState { get; init; } - public string ClientState { get; init; } - } -} diff --git a/ArmaForces.Boderator.BotService/Discord/DependencyInjection/DiscordServiceBuilder.cs b/ArmaForces.Boderator.BotService/Discord/DependencyInjection/DiscordServiceBuilder.cs deleted file mode 100644 index 8fd4325..0000000 --- a/ArmaForces.Boderator.BotService/Discord/DependencyInjection/DiscordServiceBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace ArmaForces.Boderator.BotService.Discord.DependencyInjection -{ - internal class DiscordServiceBuilder : IDiscordServiceBuilder - { - private IServiceCollection Services { get; } - - public DiscordServiceBuilder(IServiceCollection services) => Services = services; - } -} diff --git a/ArmaForces.Boderator.BotService/Discord/DependencyInjection/IDiscordServiceBuilder.cs b/ArmaForces.Boderator.BotService/Discord/DependencyInjection/IDiscordServiceBuilder.cs deleted file mode 100644 index c45d93e..0000000 --- a/ArmaForces.Boderator.BotService/Discord/DependencyInjection/IDiscordServiceBuilder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ArmaForces.Boderator.BotService.Discord.DependencyInjection -{ - internal interface IDiscordServiceBuilder - { - } -} diff --git a/ArmaForces.Boderator.BotService/Discord/DiscordService.cs b/ArmaForces.Boderator.BotService/Discord/DiscordService.cs deleted file mode 100644 index 6d25064..0000000 --- a/ArmaForces.Boderator.BotService/Discord/DiscordService.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using ArmaForces.Boderator.BotService.DTOs; -using Discord; -using Discord.WebSocket; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ArmaForces.Boderator.BotService.Discord -{ - public sealed class DiscordService : IDiscordService, IHostedService - { - private readonly ILogger _log; - private readonly DiscordSocketClient _discordClient; - private string _token; - - public DiscordService(ILogger logger, string token) - { - _log = logger; - _discordClient = new DiscordSocketClient(); - _discordClient.Log += message => Task.Run(() => _log.Log(MapSeverity(message.Severity), "[Discord.NET Log] " + message.Message)); - _discordClient.Connected += () => Task.Run(() => _log.LogInformation("Discord connected")); - _token = token; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - _log.LogInformation("Discord Service started"); - await _discordClient.LoginAsync(TokenType.Bot, _token, true); - await _discordClient.StartAsync(); - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.Run(() => - { - _discordClient.Dispose(); - _log.LogInformation("Discord Service stopped"); - }, cancellationToken); - - public DiscordServiceStatus GetDiscordClientStatus() - { - _log.LogInformation($"Current Discord Bot status: Login: {_discordClient.LoginState} | " + - $"Connection: {_discordClient.ConnectionState} | " + - $"Status: {_discordClient.Status}"); - return new DiscordServiceStatus - { - ConnectionState = _discordClient.ConnectionState.ToString(), - LoginState = _discordClient.LoginState.ToString(), - ClientState = _discordClient.Status.ToString() - }; - } - - public async Task SetBotStatus(string newStatus, ActivityType statusType) => - await _discordClient.SetGameAsync(newStatus, type: statusType); - - private static LogLevel MapSeverity(LogSeverity severity) => - severity switch - { - LogSeverity.Verbose => LogLevel.Trace, - LogSeverity.Debug => LogLevel.Debug, - LogSeverity.Info => LogLevel.Information, - LogSeverity.Warning => LogLevel.Warning, - LogSeverity.Error => LogLevel.Error, - LogSeverity.Critical => LogLevel.Critical, - _ => throw new ArgumentOutOfRangeException( - nameof(severity), - severity, - null) - }; - } -} diff --git a/ArmaForces.Boderator.BotService/Discord/DiscordServiceCollectionExtentions.cs b/ArmaForces.Boderator.BotService/Discord/DiscordServiceCollectionExtentions.cs deleted file mode 100644 index 3d62dc2..0000000 --- a/ArmaForces.Boderator.BotService/Discord/DiscordServiceCollectionExtentions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ArmaForces.Boderator.BotService.Discord.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace ArmaForces.Boderator.BotService.Discord -{ - internal static class DiscordServiceCollectionExtentions - { - public static IDiscordServiceBuilder AddDiscordService(this IServiceCollection serviceDescriptors, string token) - { - serviceDescriptors.AddSingleton(sP => new DiscordService(sP.GetService>(), token)); - serviceDescriptors.AddHostedService(sP => sP.GetRequiredService() as DiscordService); - return new DiscordServiceBuilder(serviceDescriptors); - } - } -} diff --git a/ArmaForces.Boderator.BotService/Discord/Interfaces/IDiscordService.cs b/ArmaForces.Boderator.BotService/Discord/Interfaces/IDiscordService.cs deleted file mode 100644 index 1f64ba1..0000000 --- a/ArmaForces.Boderator.BotService/Discord/Interfaces/IDiscordService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; -using ArmaForces.Boderator.BotService.DTOs; -using Discord; - -namespace ArmaForces.Boderator.BotService.Discord -{ - public interface IDiscordService - { - DiscordServiceStatus GetDiscordClientStatus(); - Task SetBotStatus(string newStatus, ActivityType statusType); - } -} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/DTOs/DiscordServiceStatusDto.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/DTOs/DiscordServiceStatusDto.cs new file mode 100644 index 0000000..def56b2 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/DTOs/DiscordServiceStatusDto.cs @@ -0,0 +1,11 @@ +using Discord; + +namespace ArmaForces.Boderator.BotService.Features.DiscordClient.DTOs +{ + public class DiscordServiceStatusDto + { + public ConnectionState ConnectionState { get; init; } + public LoginState LoginState { get; init; } + public UserStatus ClientState { get; init; } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs new file mode 100644 index 0000000..a20dfd8 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs @@ -0,0 +1,64 @@ +using System.Threading; +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.DiscordClient.DTOs; +using Discord; +using Discord.WebSocket; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ArmaForces.Boderator.BotService.Features.DiscordClient +{ + internal sealed class DiscordService : IDiscordService, IHostedService + { + private readonly DiscordSocketClient _discordClient; + private readonly ILogger _logger; + + private readonly string _token; + + public DiscordService( + DiscordSocketClient discordClient, + // TODO: Change the configuration to proper interface + IConfiguration configuration, + ILogger logger) + { + _logger = logger; + _discordClient = discordClient; + _token = configuration["token"]; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Discord Service started"); + await _discordClient.LoginAsync(TokenType.Bot, _token); + await _discordClient.StartAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.Run(() => + { + _discordClient.Dispose(); + _logger.LogInformation("Discord Service stopped"); + }, cancellationToken); + + public DiscordServiceStatusDto GetDiscordClientStatus() + { + _logger.LogInformation( + "Current Discord Bot status: Login: {BotStatus} | " + + "Connection: {ConnectionStatus} | " + + "Status: {ClientStatus}", + _discordClient.LoginState, + _discordClient.ConnectionState, + _discordClient.Status); + + return new DiscordServiceStatusDto + { + ConnectionState = _discordClient.ConnectionState, + LoginState = _discordClient.LoginState, + ClientState = _discordClient.Status + }; + } + + public async Task SetBotStatus(string newStatus, ActivityType statusType) => + await _discordClient.SetGameAsync(newStatus, type: statusType); + } +} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs new file mode 100644 index 0000000..8894f38 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.DiscordClient.DTOs; +using Discord; + +namespace ArmaForces.Boderator.BotService.Features.DiscordClient +{ + internal interface IDiscordService + { + DiscordServiceStatusDto GetDiscordClientStatus(); + Task SetBotStatus(string newStatus, ActivityType statusType); + } +} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs new file mode 100644 index 0000000..8bc339a --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection +{ + internal static class DiscordServiceCollectionExtensions + { + public static IServiceCollection AddDiscordClient(this IServiceCollection services, string token) + { + return services + .AddSingleton(DiscordSocketClientFactory.CreateDiscordClient) + .AddSingleton() + .AddHostedService(); + } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordSocketClientFactory.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordSocketClientFactory.cs new file mode 100644 index 0000000..a694db1 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordSocketClientFactory.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.Logging; +using Discord.WebSocket; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection +{ + internal static class DiscordSocketClientFactory + { + private static bool _isClientCreated; + + public static DiscordSocketClient CreateDiscordClient(IServiceProvider serviceProvider) + { + if (_isClientCreated) + throw new InvalidOperationException("Only one Discord client can be created!"); + + _isClientCreated = true; + + var discordSocketConfig = new DiscordSocketConfig + { + AlwaysDownloadUsers = true, + MessageCacheSize = 100000 + }; + var client = new DiscordSocketClient(discordSocketConfig); + + ConfigureLogging(client, serviceProvider); + + return client; + } + + private static void ConfigureLogging(DiscordSocketClient client, IServiceProvider serviceProvider) + { + var logger = serviceProvider.GetRequiredService>(); + client.Log += message => Task.Run(() => message.LogWithLoggerAsync(logger)); + client.Connected += () => Task.Run(() => logger.LogInformation("Discord connected")); + } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/Logging/LogMessageExtensions.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/Logging/LogMessageExtensions.cs new file mode 100644 index 0000000..dc33aa1 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/Logging/LogMessageExtensions.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Microsoft.Extensions.Logging; + +// Disabled warning as messages are coming from Discord logger +// ReSharper disable TemplateIsNotCompileTimeConstantProblem +namespace ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.Logging +{ + internal static class LogMessageExtensions + { + public static Task LogWithLoggerAsync(this LogMessage logMessage, ILogger logger) + { + LogWithLogger(logMessage, logger); + return Task.CompletedTask; + } + + private static void LogWithLogger(this LogMessage logMessage, ILogger logger) + { + if (logMessage.Exception != null) + { + LogExceptionWithLogger(logMessage, logger); + return; + } + + switch (logMessage.Severity) + { + case LogSeverity.Critical: + logger.LogCritical(logMessage.Message); + break; + case LogSeverity.Error: + logger.LogError(logMessage.Message); + break; + case LogSeverity.Warning: + logger.LogWarning(logMessage.Message); + break; + case LogSeverity.Info: + logger.LogInformation(logMessage.Message); + break; + case LogSeverity.Verbose: + logger.LogDebug(logMessage.Message); + break; + case LogSeverity.Debug: + logger.LogTrace(logMessage.Message); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static void LogExceptionWithLogger(this LogMessage logMessage, ILogger logger) + { + switch (logMessage.Severity) + { + case LogSeverity.Critical: + logger.LogCritical(logMessage.Exception, logMessage.Message); + break; + case LogSeverity.Error: + logger.LogError(logMessage.Exception, logMessage.Message); + break; + case LogSeverity.Warning: + logger.LogWarning(logMessage.Exception, logMessage.Message); + break; + case LogSeverity.Info: + logger.LogInformation(logMessage.Exception, logMessage.Message); + break; + case LogSeverity.Verbose: + logger.LogDebug(logMessage.Exception, logMessage.Message); + break; + case LogSeverity.Debug: + logger.LogTrace(logMessage.Exception, logMessage.Message); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index d272bb0..c0fdae4 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,5 +1,4 @@ using System; -using ArmaForces.Boderator.BotService.Discord; using ArmaForces.Boderator.BotService.Documentation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; From 3a4c3379d84deb4725db769a84263ae9d23987b2 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 11 Sep 2021 14:50:28 +0200 Subject: [PATCH 34/87] Add basic Configuration --- .../Configuration/BoderatorConfiguration.cs | 7 ++++ .../BoderatorConfigurationFactory.cs | 32 +++++++++++++++++++ .../Helpers/Configuration.cs | 19 ----------- ArmaForces.Boderator.BotService/Startup.cs | 2 ++ 4 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs create mode 100644 ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs delete mode 100644 ArmaForces.Boderator.BotService/Helpers/Configuration.cs diff --git a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs new file mode 100644 index 0000000..31cefa7 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs @@ -0,0 +1,7 @@ +namespace ArmaForces.Boderator.BotService.Configuration +{ + internal record BoderatorConfiguration + { + public string DiscordToken { get; init; } = string.Empty; + } +} diff --git a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs new file mode 100644 index 0000000..302c26f --- /dev/null +++ b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections; +using System.Configuration; + +namespace ArmaForces.Boderator.BotService.Configuration +{ + internal class BoderatorConfigurationFactory + { + private readonly IDictionary _environmentVariables; + + public BoderatorConfigurationFactory() + { + _environmentVariables = Environment.GetEnvironmentVariables(); + } + + // TODO: Consider making this a bit more automatic so configuration is easily extensible + public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration + { + DiscordToken = GetStringValue(nameof(BoderatorConfiguration.DiscordToken)) + }; + + private string GetStringValue(string variableName) + { + var fullVariableName = $"AF_Boderator_{variableName}"; + var value = _environmentVariables[fullVariableName]; + + return value is not null + ? (string) value + : throw new ConfigurationErrorsException($"Variable {fullVariableName} does not exist."); + } + } +} diff --git a/ArmaForces.Boderator.BotService/Helpers/Configuration.cs b/ArmaForces.Boderator.BotService/Helpers/Configuration.cs deleted file mode 100644 index 744cff1..0000000 --- a/ArmaForces.Boderator.BotService/Helpers/Configuration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections; - -namespace ArmaForces.Boderator.BotService.Helpers -{ - public static class Configuration - { - public static string DiscordToken => GetParameter("DISCORD_TOKEN"); - - private static IDictionary Parameters { get; } - - static Configuration() - { - Parameters = Environment.GetEnvironmentVariables(); - } - - public static string GetParameter(string key) => (string)Parameters[key]; - } -} diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index c0fdae4..2ee7a91 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,4 +1,5 @@ using System; +using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Documentation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -35,6 +36,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); + services.AddSingleton(new BoderatorConfigurationFactory().CreateConfiguration()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 0bde373759cf9b1cc78fc37b830eb03b40af567a Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 13 Nov 2021 11:13:31 +0100 Subject: [PATCH 35/87] Add AutoAddInterfacesAsScoped() --- ArmaForces.Boderator.BotService/Startup.cs | 2 + .../ServiceCollectionExtensionsTests.cs | 59 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 40 +++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs create mode 100644 ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 2ee7a91..23860a3 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,6 +1,7 @@ using System; using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Documentation; +using ArmaForces.Boderator.Core.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -36,6 +37,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); + services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); services.AddSingleton(new BoderatorConfigurationFactory().CreateConfiguration()); } diff --git a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..232902c --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,59 @@ +using System.Linq; +using ArmaForces.Boderator.Core.DependencyInjection; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.DependencyInjection +{ + public class ServiceCollectionExtensionsTests + { + [Fact] + [Trait("Category", "Unit")] + public void AutoAddInterfacesAsScoped_EmptyCollection_RegistersOnlyInterfacesWithOneImplementation() + { + var serviceProvider = new ServiceCollection() + .AutoAddInterfacesAsScoped(typeof(ServiceCollectionExtensionsTests).Assembly) + .BuildServiceProvider(); + + using (new AssertionScope()) + { + serviceProvider.GetService().Should().BeNull(); + serviceProvider.GetService().Should().NotBeNull(); + serviceProvider.GetService().Should().BeNull(); + } + } + + [Fact] + [Trait("Category", "Unit")] + public void AutoAddInterfacesAsScoped_ImplementationRegisteredAsSingleton_DoesNotReplaceExistingRegistration() + { + var serviceCollection = new ServiceCollection() + .AddSingleton() + .AutoAddInterfacesAsScoped(typeof(ServiceCollectionExtensionsTests).Assembly); + + AssertServiceRegisteredCorrectly(serviceCollection, ServiceLifetime.Singleton); + } + + private static void AssertServiceRegisteredCorrectly(IServiceCollection serviceCollection, ServiceLifetime expectedLifetime) + { + var expectedServiceDescriptor = new ServiceDescriptor(typeof(TService), typeof(TExpectedImplementation), expectedLifetime); + + serviceCollection.Should() + .ContainSingle(descriptor => descriptor.ServiceType == typeof(TService)) + .Which.Should() + .BeEquivalentTo(expectedServiceDescriptor, $"the service {nameof(TService)} was registered as {expectedLifetime} and should not be replaced"); + } + + private interface ITest1 { } + + private interface ITest2 { } + + private interface ITest3 { } + + private class Test1 : ITest1 { } + + private class Test2 : ITest1, ITest2 { } + } +} diff --git a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..b529814 --- /dev/null +++ b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace ArmaForces.Boderator.Core.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + /// + /// Adds all interfaces from with single implementation type as scoped service. + /// Does not replace existing registrations. + /// + /// Service collection where services will be registered + /// Assembly which will be searched for interfaces and implementations. + /// Service collection for method chaining. + public static IServiceCollection AutoAddInterfacesAsScoped(this IServiceCollection services, Assembly assembly) + { + assembly.DefinedTypes + .Where(x => x.ImplementedInterfaces.Any()) + .SelectMany( + implementingClass => implementingClass.ImplementedInterfaces, + (implementingClass, implementedInterface) => new {implementedInterface, implementingClass}) + .GroupBy(x => x.implementedInterface) + .Where(x => x.Count() == 1) + .Select(x => x.Single()) + .Where(x => services.IsNotServiceRegistered(x.implementedInterface)) + .ToList() + .ForEach(x => services.AddScoped(x.implementedInterface, x.implementingClass)); + + return services; + } + + private static bool IsNotServiceRegistered(this IServiceCollection services, Type serviceType) + => !IsServiceRegistered(services, serviceType); + + private static bool IsServiceRegistered(this IServiceCollection services, Type serviceType) + => services.Any(descriptor => descriptor.ServiceType == serviceType); + } +} From 9766f988c95fc282125b9ebff966209d23ea40a8 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 13 Nov 2021 11:44:51 +0100 Subject: [PATCH 36/87] Enable nullable in all projects --- .../ArmaForces.Boderator.BotService.Tests.csproj | 2 +- .../ArmaForces.Boderator.Core.Tests.csproj | 2 +- ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 800ca20..9c574e7 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -2,7 +2,7 @@ net5.0 - + enable false diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 9cd3b8f..b86a6c5 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -2,7 +2,7 @@ net5.0 - + enable false diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 0f13f71..9caffab 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -2,6 +2,7 @@ net5.0 + enable From 69ea576c055595310b4ac7bfc04d5b0f14c56580 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 13 Nov 2021 11:49:42 +0100 Subject: [PATCH 37/87] Move models to Core project --- .../Features/Signups/{ => DTOs}/SignupCreateRequestDto.cs | 4 ++-- .../Features/Signups/SignupsController.cs | 3 ++- .../ArmaForces.Boderator.Core.csproj.DotSettings | 2 ++ .../Features/Missions/Models/Mission.cs | 2 +- .../Features/Signups/Models/Signup.cs | 4 ++-- .../Features/Signups/Models/Slot.cs | 2 +- .../Features/Signups/Models/Team.cs | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) rename ArmaForces.Boderator.BotService/Features/Signups/{ => DTOs}/SignupCreateRequestDto.cs (91%) create mode 100644 ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings rename {ArmaForces.Boderator.BotService => ArmaForces.Boderator.Core}/Features/Missions/Models/Mission.cs (83%) rename {ArmaForces.Boderator.BotService => ArmaForces.Boderator.Core}/Features/Signups/Models/Signup.cs (75%) rename {ArmaForces.Boderator.BotService => ArmaForces.Boderator.Core}/Features/Signups/Models/Slot.cs (69%) rename {ArmaForces.Boderator.BotService => ArmaForces.Boderator.Core}/Features/Signups/Models/Team.cs (76%) diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs similarity index 91% rename from ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs rename to ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs index ad7b210..6401c3c 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; -using ArmaForces.Boderator.BotService.Features.Signups.Models; +using ArmaForces.Boderator.Core.Signups.Models; using Newtonsoft.Json; -namespace ArmaForces.Boderator.BotService.Features.Signups +namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs { public class SignupCreateRequestDto { diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs index ca75e8a..9710df2 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using ArmaForces.Boderator.BotService.Features.Signups.Models; +using ArmaForces.Boderator.BotService.Features.Signups.DTOs; +using ArmaForces.Boderator.Core.Signups.Models; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Signups diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings new file mode 100644 index 0000000..3a36d17 --- /dev/null +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs similarity index 83% rename from ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs rename to ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs index 5331060..f865a62 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Models/Mission.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs @@ -1,6 +1,6 @@ using System; -namespace ArmaForces.Boderator.BotService.Features.Missions.Models +namespace ArmaForces.Boderator.Core.Missions.Models { public record Mission { diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs similarity index 75% rename from ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs rename to ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs index 8c1a705..8cf552f 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/Models/Signup.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using ArmaForces.Boderator.BotService.Features.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Models; -namespace ArmaForces.Boderator.BotService.Features.Signups.Models +namespace ArmaForces.Boderator.Core.Signups.Models { public record Signup { diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs similarity index 69% rename from ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs rename to ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs index 8b40bb8..4c797b8 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/Models/Slot.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs @@ -1,4 +1,4 @@ -namespace ArmaForces.Boderator.BotService.Features.Signups.Models +namespace ArmaForces.Boderator.Core.Signups.Models { public record Slot { diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs similarity index 76% rename from ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs rename to ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs index 8e5a784..7b9d9b2 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace ArmaForces.Boderator.BotService.Features.Signups.Models +namespace ArmaForces.Boderator.Core.Signups.Models { public record Team { From d2e98c3054bb73abb88c1463e7d131b033a4febf Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 13 Nov 2021 20:26:31 +0100 Subject: [PATCH 38/87] Add basic Mission logic --- .../Missions/MissionsControllerTests.cs | 33 ++++++++++++ .../TestUtilities/TestBases/ApiTestBase.cs | 6 ++- .../TestUtilities/TestConfigurationFactory.cs | 12 +++++ .../TestFixtures/TestApiServiceFixture.cs | 4 ++ .../BoderatorConfigurationFactory.cs | 7 ++- .../Features/DiscordClient/DiscordService.cs | 2 +- .../DiscordServiceCollectionExtensions.cs | 4 +- .../Missions/DTOs/MissionCreateRequestDto.cs | 4 +- .../Features/Missions/DTOs/MissionDto.cs | 8 +-- .../Missions/Mappers/MissionMapper.cs | 31 ++++++++++++ .../Features/Missions/MissionsController.cs | 50 ++++++++++++++----- ArmaForces.Boderator.BotService/Startup.cs | 5 +- .../ArmaForces.Boderator.Core.Tests.csproj | 4 ++ .../ResultAssertionsExtensions.cs | 11 ++++ .../ArmaForces.Boderator.Core.csproj | 1 + .../BoderatorCoreServiceExtensions.cs | 14 ++++++ .../ServiceCollectionExtensions.cs | 8 +++ .../Features/Missions/IMissionService.cs | 16 ++++++ .../Features/Missions/MissionService.cs | 35 +++++++++++++ .../Missions/Models/MissionCreateRequest.cs | 17 +++++++ .../Persistence/IMissionRepository.cs | 15 ++++++ .../Missions/Persistence/MissionContext.cs | 13 +++++ .../Missions/Persistence/MissionRepository.cs | 34 +++++++++++++ 23 files changed, 310 insertions(+), 24 deletions(-) create mode 100644 ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs create mode 100644 ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs create mode 100644 ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/MissionService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs new file mode 100644 index 0000000..029bcf8 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using AutoFixture; +using FluentAssertions; +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests.Features.Missions +{ + [Trait("Category", "Integration")] + public class MissionsControllerTests : ApiTestBase + { + public MissionsControllerTests(TestApiServiceFixture testApi) + : base(testApi) { } + + [Fact] + public async Task CreateMission_ValidRequest_MissionCreated() + { + var missionCreateRequest = new MissionCreateRequestDto + { + Title = Fixture.Create(), + Owner = Fixture.Create(), + Description = Fixture.Create() + }; + + var result = await HttpPostAsync("api/missions", missionCreateRequest); + + result.ShouldBeSuccess(); + } + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs index 2ff23ea..d2aed9e 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs @@ -1,8 +1,10 @@ using System; using System.Net.Http; +using System.Text; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Tests.TestUtilities.Collections; using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using AutoFixture; using CSharpFunctionalExtensions; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; @@ -19,6 +21,7 @@ public abstract class ApiTestBase { private readonly HttpClient _httpClient; + protected Fixture Fixture { get; } = new Fixture(); protected IServiceProvider Provider { get; } protected ApiTestBase(TestApiServiceFixture testApi) @@ -53,7 +56,8 @@ protected async Task> HttpGetAsync(string path) protected async Task HttpPostAsync(string path, T body) { - var httpResponseMessage = await _httpClient.PostAsync(path, new StringContent(JsonConvert.SerializeObject(body))); + var stringContent = new StringContent(JsonConvert.SerializeObject(body), Encoding.Default, "application/json"); + var httpResponseMessage = await _httpClient.PostAsync(path, stringContent); if (httpResponseMessage.IsSuccessStatusCode) { return Result.Success(); diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs new file mode 100644 index 0000000..2b658a6 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs @@ -0,0 +1,12 @@ +using ArmaForces.Boderator.BotService.Configuration; + +namespace ArmaForces.Boderator.BotService.Tests.TestUtilities +{ + internal class TestConfigurationFactory : IBoderatorConfigurationFactory + { + public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration + { + DiscordToken = string.Empty + }; + } +} diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs index 2cfcf8f..82a2d58 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs @@ -1,5 +1,7 @@ using System; using System.Net.Http; +using ArmaForces.Boderator.BotService.Configuration; +using ArmaForces.Boderator.Core.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; @@ -39,6 +41,8 @@ public void Dispose() private static Action ConfigureWebBuilder() => webBuilder => { webBuilder.UseStartup(); + webBuilder.ConfigureServices( + x => x.AddOrReplaceSingleton()); webBuilder.UseKestrel(x => x.ListenLocalhost(Port)); }; } diff --git a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs index 302c26f..1b3c5ba 100644 --- a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs +++ b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs @@ -4,7 +4,7 @@ namespace ArmaForces.Boderator.BotService.Configuration { - internal class BoderatorConfigurationFactory + internal class BoderatorConfigurationFactory : IBoderatorConfigurationFactory { private readonly IDictionary _environmentVariables; @@ -29,4 +29,9 @@ private string GetStringValue(string variableName) : throw new ConfigurationErrorsException($"Variable {fullVariableName} does not exist."); } } + + internal interface IBoderatorConfigurationFactory + { + BoderatorConfiguration CreateConfiguration(); + } } diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs index a20dfd8..641099c 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs @@ -9,7 +9,7 @@ namespace ArmaForces.Boderator.BotService.Features.DiscordClient { - internal sealed class DiscordService : IDiscordService, IHostedService + internal sealed class DiscordService : IDiscordService//, IHostedService { private readonly DiscordSocketClient _discordClient; private readonly ILogger _logger; diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs index 8bc339a..8c2c3b7 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs @@ -8,8 +8,8 @@ public static IServiceCollection AddDiscordClient(this IServiceCollection servic { return services .AddSingleton(DiscordSocketClientFactory.CreateDiscordClient) - .AddSingleton() - .AddHostedService(); + .AddSingleton(); + //.AddHostedService(); } } } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs index 937d983..774ea4f 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs @@ -20,13 +20,13 @@ public class MissionCreateRequestDto /// /// Mission start time. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] public DateTime? MissionTime { get; set; } /// /// Name of the modset. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] public string? ModsetName { get; set; } /// diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index c01e241..f8f2a38 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -2,12 +2,12 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs { - public class MissionDto + public record MissionDto { - public int MissionId { get; set; } + public int MissionId { get; init; } - public string Title { get; set; } = string.Empty; + public string Title { get; init; } = string.Empty; - public DateTime MissionDate { get; set; } + public DateTime? MissionDate { get; init; } } } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs new file mode 100644 index 0000000..c19c685 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers +{ + public static class MissionMapper + { + public static MissionDto Map(Mission mission) + => new() + { + Title = mission.Title, + MissionDate = mission.MissionTime, + MissionId = mission.MissionId + }; + + public static List Map(List missions) + => missions.Select(Map).ToList(); + + public static MissionCreateRequest Map(MissionCreateRequestDto request) + => new MissionCreateRequest + { + Title = request.Title, + Description = request.Description, + Owner = request.Owner, + ModsetName = request.ModsetName, + MissionTime = request.MissionTime + }; + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index cb48ed9..edecca1 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -1,22 +1,39 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Features.Missions.Mappers; +using ArmaForces.Boderator.Core.Missions; +using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Missions { + /// + /// Allows missions data retrieval and creation. + /// + [Route("api/[controller]")] public class MissionsController : Controller { + private readonly IMissionService _missionService; + + public MissionsController(IMissionService missionService) + { + _missionService = missionService; + } + /// /// /// /// /// [HttpPost] - public ActionResult CreateMission([FromBody] MissionCreateRequestDto request) - { - throw new NotImplementedException(); - } + public async Task> CreateMission([FromBody] MissionCreateRequestDto request) + => await _missionService.CreateMission(MissionMapper.Map(request)) + .Map(MissionMapper.Map) + .Match, MissionDto>( + onSuccess: mission => Created(mission.MissionId.ToString(), mission), + onFailure: error => BadRequest(error)); /// /// @@ -43,10 +60,12 @@ public ActionResult DeleteMission(int missionId) /// /// [HttpGet] - public ActionResult> GetMissions() - { - throw new NotImplementedException(); - } + public async Task>> GetMissions() + => await _missionService.GetMissions() + .Map(MissionMapper.Map) + .Match>, List>( + onSuccess: missions => Ok(missions), + onFailure: error => BadRequest(error)); /// /// @@ -54,9 +73,16 @@ public ActionResult> GetMissions() /// /// [HttpGet("{missionId:int}")] - public ActionResult GetMission(int missionId) - { - throw new NotImplementedException(); - } + public async Task> GetMission(int missionId) + => await _missionService.GetMission(missionId) + .Map(MissionMapper.Map) + .Match, MissionDto>( + onSuccess: mission => Ok(mission), + onFailure: error => NotFound(error)); + + private ActionResult ReturnSomething(Result result) + => result.Match( + onSuccess: x => Ok(x), + onFailure: error => (ActionResult) BadRequest(error)); } } diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 23860a3..899d283 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,6 +1,7 @@ using System; using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Documentation; +using ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection; using ArmaForces.Boderator.Core.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -37,8 +38,10 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); + services.AddDiscordClient(string.Empty); + services.AddBoderatorCore(); services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); - services.AddSingleton(new BoderatorConfigurationFactory().CreateConfiguration()); + services.AddSingleton(x => x.GetRequiredService().CreateConfiguration()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index b86a6c5..d3e11f4 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -26,5 +26,9 @@ all + + + + diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs index cee12c8..180733c 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -6,6 +6,17 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities { public static class ResultAssertionsExtensions { + public static void ShouldBeSuccess(this Result result) + { + using var scope = new AssertionScope(); + + if (result.IsFailure) + { + result.IsSuccess.Should().BeTrue(); + result.Error.Should().BeNull(); + } + } + public static void ShouldBeSuccess(this Result result, T expectedValue) { using var scope = new AssertionScope(); diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 9caffab..75a4edb 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -12,6 +12,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs new file mode 100644 index 0000000..2e30406 --- /dev/null +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -0,0 +1,14 @@ +using ArmaForces.Boderator.Core.Missions.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace ArmaForces.Boderator.Core.DependencyInjection +{ + public static class BoderatorCoreServiceExtensions + { + public static IServiceCollection AddBoderatorCore(this IServiceCollection services) + => services + .AddDbContext(options => options.UseSqlite()) + .AutoAddInterfacesAsScoped(typeof(BoderatorCoreServiceExtensions).Assembly); + } +} diff --git a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs index b529814..6be3b20 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -2,11 +2,19 @@ using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace ArmaForces.Boderator.Core.DependencyInjection { public static class ServiceCollectionExtensions { + public static IServiceCollection AddOrReplaceSingleton(this IServiceCollection services) + where TImplementation : class + { + return services.RemoveAll(typeof(TService)) + .AddSingleton(); + } + /// /// Adds all interfaces from with single implementation type as scoped service. /// Does not replace existing registrations. diff --git a/ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs b/ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs new file mode 100644 index 0000000..55ab850 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions +{ + public interface IMissionService + { + Task> CreateMission(MissionCreateRequest missionCreateRequest); + + Task> GetMission(int missionId); + + Task>> GetMissions(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs b/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs new file mode 100644 index 0000000..4d0c19c --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Persistence; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions +{ + internal class MissionService : IMissionService + { + private readonly IMissionRepository _missionRepository; + + public MissionService(IMissionRepository missionRepository) + { + _missionRepository = missionRepository; + } + + public async Task> CreateMission(MissionCreateRequest missionCreateRequest) + => await _missionRepository.CreateMission( + new Mission + { + Title = missionCreateRequest.Title, + Owner = missionCreateRequest.Owner, + MissionTime = missionCreateRequest.MissionTime, + ModsetName = missionCreateRequest.ModsetName + }); + + public async Task> GetMission(int missionId) + => await _missionRepository.GetMission(missionId) + ?? Result.Failure("Mission does not exist."); + + public async Task>> GetMissions() + => await _missionRepository.GetMissions(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs new file mode 100644 index 0000000..6c50761 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs @@ -0,0 +1,17 @@ +using System; + +namespace ArmaForces.Boderator.Core.Missions.Models +{ + public record MissionCreateRequest + { + public string Title { get; init; } = string.Empty; + + public string? Description { get; init; } + + public DateTime? MissionTime { get; init; } + + public string? ModsetName { get; init; } + + public string Owner { get; init; } = string.Empty; + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs new file mode 100644 index 0000000..6f8bb4f --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Persistence +{ + internal interface IMissionRepository + { + Task GetMission(int missionId); + + Task> GetMissions(); + Task> CreateMission(Mission missionToCreate); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs new file mode 100644 index 0000000..241f307 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs @@ -0,0 +1,13 @@ +using ArmaForces.Boderator.Core.Missions.Models; +using Microsoft.EntityFrameworkCore; + +namespace ArmaForces.Boderator.Core.Missions.Persistence +{ + internal class MissionContext : DbContext + { + public MissionContext(DbContextOptions options) + : base(options) { } + + public DbSet Missions { get; set; } + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs new file mode 100644 index 0000000..d6f2eb7 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; +using Microsoft.EntityFrameworkCore; + +namespace ArmaForces.Boderator.Core.Missions.Persistence +{ + internal class MissionRepository : IMissionRepository + { + private readonly MissionContext _context; + + public MissionRepository(MissionContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task GetMission(int missionId) + => await _context.Missions.FindAsync(missionId); + + public async Task> GetMissions() + => await _context.Missions.ToListAsync(); + + public async Task> CreateMission(Mission missionToCreate) + { + var ddd = await _context.Missions.AddAsync(missionToCreate); + + return ddd is not null + ? ddd.Entity + : Result.Failure("Failure creating mission."); + } + } +} From 807f33d8ec983ea062e0d184094a7bd3a3de8bd2 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 13 Nov 2021 23:37:00 +0100 Subject: [PATCH 39/87] Add test for GetMission --- .../Missions/MissionsControllerTests.cs | 24 +++++++++++++++++++ .../TestUtilities/TestBases/ApiTestBase.cs | 17 +++++++++++++ ArmaForces.Boderator.BotService/Startup.cs | 2 +- .../appsettings.json | 3 +++ .../ArmaForces.Boderator.Core.csproj | 2 +- .../BoderatorCoreServiceExtensions.cs | 4 ++-- .../Features/Missions/MissionService.cs | 2 +- .../Missions/Persistence/MissionContext.cs | 7 ++++-- .../Missions/Persistence/MissionRepository.cs | 7 +++--- 9 files changed, 58 insertions(+), 10 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs index 029bcf8..c572c60 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -29,5 +29,29 @@ public async Task CreateMission_ValidRequest_MissionCreated() result.ShouldBeSuccess(); } + + [Fact] + public async Task GetMission_MissionExists_ReturnsExistingMission() + { + var missionCreateRequest = new MissionCreateRequestDto + { + Title = Fixture.Create(), + Owner = Fixture.Create(), + Description = Fixture.Create() + }; + + var missionCreateResult = await HttpPostAsync("api/missions", missionCreateRequest); + + var expectedMission = new MissionDto + { + Title = missionCreateResult.Value.Title, + MissionDate = missionCreateResult.Value.MissionDate, + MissionId = missionCreateResult.Value.MissionId + }; + + var result = await HttpGetAsync($"api/missions/{expectedMission.MissionId}"); + + result.ShouldBeSuccess(expectedMission); + } } } diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs index d2aed9e..36554fb 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs @@ -71,6 +71,23 @@ protected async Task HttpPostAsync(string path, T body) return Result.Failure(error); } + protected async Task> HttpPostAsync(string path, TRequest body) + { + var stringContent = new StringContent(JsonConvert.SerializeObject(body), Encoding.Default, "application/json"); + var httpResponseMessage = await _httpClient.PostAsync(path, stringContent); + if (httpResponseMessage.IsSuccessStatusCode) + { + return DeserializeContent(await httpResponseMessage.Content.ReadAsStringAsync()); + } + + var responseBody = await httpResponseMessage.Content.ReadAsStringAsync(); + var error = string.IsNullOrWhiteSpace(responseBody) + ? httpResponseMessage.ReasonPhrase + : responseBody; + + return Result.Failure(error); + } + private static Result DeserializeContent(string content) => JsonConvert.DeserializeObject(content); } diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 899d283..1155f09 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -39,7 +39,7 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); services.AddDiscordClient(string.Empty); - services.AddBoderatorCore(); + services.AddBoderatorCore(Configuration.GetConnectionString("DefaultConnection")); services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); services.AddSingleton(x => x.GetRequiredService().CreateConfiguration()); } diff --git a/ArmaForces.Boderator.BotService/appsettings.json b/ArmaForces.Boderator.BotService/appsettings.json index d76d42c..a46cc8c 100644 --- a/ArmaForces.Boderator.BotService/appsettings.json +++ b/ArmaForces.Boderator.BotService/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BoderatorTestDB;Trusted_Connection=True;MultipleActiveResultSets=true" + }, "Serilog": { "MinimumLevel": { "Default": "Information", diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 75a4edb..3939c05 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -12,7 +12,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs index 2e30406..0940e5c 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -6,9 +6,9 @@ namespace ArmaForces.Boderator.Core.DependencyInjection { public static class BoderatorCoreServiceExtensions { - public static IServiceCollection AddBoderatorCore(this IServiceCollection services) + public static IServiceCollection AddBoderatorCore(this IServiceCollection services, string connectionString) => services - .AddDbContext(options => options.UseSqlite()) + .AddDbContext(options => options.UseSqlServer(connectionString)) .AutoAddInterfacesAsScoped(typeof(BoderatorCoreServiceExtensions).Assembly); } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs b/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs index 4d0c19c..2164020 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs @@ -27,7 +27,7 @@ public async Task> CreateMission(MissionCreateRequest missionCre public async Task> GetMission(int missionId) => await _missionRepository.GetMission(missionId) - ?? Result.Failure("Mission does not exist."); + ?? Result.Failure("Mission with given ID does not exist."); public async Task>> GetMissions() => await _missionRepository.GetMissions(); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs index 241f307..6e9c4a7 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs @@ -3,10 +3,13 @@ namespace ArmaForces.Boderator.Core.Missions.Persistence { - internal class MissionContext : DbContext + internal sealed class MissionContext : DbContext { public MissionContext(DbContextOptions options) - : base(options) { } + : base(options) + { + Database.EnsureCreated(); + } public DbSet Missions { get; set; } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs index d6f2eb7..fb4074b 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs @@ -25,10 +25,11 @@ public async Task> GetMissions() public async Task> CreateMission(Mission missionToCreate) { var ddd = await _context.Missions.AddAsync(missionToCreate); + + if (ddd is null) return Result.Failure("Failure creating mission."); - return ddd is not null - ? ddd.Entity - : Result.Failure("Failure creating mission."); + await _context.SaveChangesAsync(); + return ddd.Entity; } } } From 12010888df40fcf1818f46f30c4c70164ae5815f Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 19 Nov 2021 16:07:50 +0100 Subject: [PATCH 40/87] Start basic validation in models --- .../Missions/MissionsControllerTests.cs | 14 +++++++++++++ .../ResultAssertionsExtensions.cs | 14 +++++++++++++ .../Missions/Models/MissionCreateRequest.cs | 20 +++++++++++++++++-- .../Missions/Persistence/MissionRepository.cs | 6 +++--- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs index c572c60..176eb56 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -15,6 +15,20 @@ public class MissionsControllerTests : ApiTestBase public MissionsControllerTests(TestApiServiceFixture testApi) : base(testApi) { } + [Fact] + public async Task CreateMission_InvalidRequest_ReturnsBadRequest() + { + var missionCreateRequestWithoutOwner = new MissionCreateRequestDto + { + Title = Fixture.Create(), + Description = Fixture.Create() + }; + + var result = await HttpPostAsync("api/missions", missionCreateRequestWithoutOwner); + + result.ShouldBeFailure("Bad Request"); + } + [Fact] public async Task CreateMission_ValidRequest_MissionCreated() { diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs index 180733c..529d545 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -6,6 +6,20 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities { public static class ResultAssertionsExtensions { + public static void ShouldBeFailure(this Result result, string expectedError) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + result.IsSuccess.Should().BeFalse(); + } + else + { + result.Error.Should().Be(expectedError); + } + } + public static void ShouldBeSuccess(this Result result) { using var scope = new AssertionScope(); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs index 6c50761..9ccdddf 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs @@ -4,7 +4,14 @@ namespace ArmaForces.Boderator.Core.Missions.Models { public record MissionCreateRequest { - public string Title { get; init; } = string.Empty; + private readonly string _title = null!; + private readonly string _owner = null!; + + public string Title + { + get => _title; + init => _title = ValidateStringNotEmpty(value, nameof(Title)); + } public string? Description { get; init; } @@ -12,6 +19,15 @@ public record MissionCreateRequest public string? ModsetName { get; init; } - public string Owner { get; init; } = string.Empty; + public string Owner + { + get => _owner; + init => _owner = ValidateStringNotEmpty(value, nameof(Owner)); + } + + private static string ValidateStringNotEmpty(string? value, string propertyName) + => !string.IsNullOrWhiteSpace(value) + ? value + : throw new ArgumentNullException(propertyName); } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs index fb4074b..f48eaa4 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs @@ -24,12 +24,12 @@ public async Task> GetMissions() public async Task> CreateMission(Mission missionToCreate) { - var ddd = await _context.Missions.AddAsync(missionToCreate); + var missionEntityEntry = await _context.Missions.AddAsync(missionToCreate); - if (ddd is null) return Result.Failure("Failure creating mission."); + if (missionEntityEntry is null) return Result.Failure("Failure creating mission."); await _context.SaveChangesAsync(); - return ddd.Entity; + return missionEntityEntry.Entity; } } } From b59647adefcce9618729ea0e9cddf5ffa8d0f8e3 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 15:00:23 +0100 Subject: [PATCH 41/87] Some SignupsQuery work --- ArmaForces.Boderator.Core/BoderatorContext.cs | 12 -------- .../Missions/Persistence/MissionContext.cs | 2 +- .../Signups/Query/ISignupsQueryRepository.cs | 13 +++++++++ .../Signups/Query/ISignupsQueryService.cs | 11 +++++++ .../Signups/Query/SignupsQueryRepository.cs | 29 +++++++++++++++++++ .../Signups/Query/SignupsQueryService.cs | 19 ++++++++++++ .../Features/Signups/SignupsContext.cs | 16 ++++++++++ 7 files changed, 89 insertions(+), 13 deletions(-) delete mode 100644 ArmaForces.Boderator.Core/BoderatorContext.cs create mode 100644 ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs diff --git a/ArmaForces.Boderator.Core/BoderatorContext.cs b/ArmaForces.Boderator.Core/BoderatorContext.cs deleted file mode 100644 index 50fda77..0000000 --- a/ArmaForces.Boderator.Core/BoderatorContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace ArmaForces.Boderator.Core -{ - public class BoderatorContext : DbContext - { - public BoderatorContext() : base() - { - - } - } -} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs index 6e9c4a7..228c8b2 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs @@ -11,6 +11,6 @@ public MissionContext(DbContextOptions options) Database.EnsureCreated(); } - public DbSet Missions { get; set; } + public DbSet Missions { get; set; } } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs new file mode 100644 index 0000000..f682931 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Signups.Models; + +namespace ArmaForces.Boderator.Core.Signups.Query +{ + internal interface ISignupsQueryRepository + { + public Task> GetAllSignups(); + + public Task> GetOpenSignups(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs new file mode 100644 index 0000000..4f1722a --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Signups.Models; + +namespace ArmaForces.Boderator.Core.Signups.Query +{ + internal interface ISignupsQueryService + { + Task> GetOpenSignups(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs new file mode 100644 index 0000000..47855be --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Signups.Models; +using Microsoft.EntityFrameworkCore; + +namespace ArmaForces.Boderator.Core.Signups.Query +{ + internal class SignupsQueryRepository : ISignupsQueryRepository + { + private readonly SignupsContext _context; + + public SignupsQueryRepository(SignupsContext context) + { + _context = context; + } + + public async Task> GetAllSignups() + { + return await _context.Signups.ToListAsync(); + } + + public async Task> GetOpenSignups() + => await _context.Signups + .Where(x => x.CloseDate > DateTime.Now) + .ToListAsync(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs new file mode 100644 index 0000000..7e563d4 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Signups.Models; + +namespace ArmaForces.Boderator.Core.Signups.Query +{ + internal class SignupsQueryService : ISignupsQueryService + { + private readonly ISignupsQueryRepository _signupsQueryRepository; + + public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) + { + _signupsQueryRepository = signupsQueryRepository; + } + + public async Task> GetOpenSignups() + => await _signupsQueryRepository.GetOpenSignups(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs b/ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs new file mode 100644 index 0000000..695c79a --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs @@ -0,0 +1,16 @@ +using ArmaForces.Boderator.Core.Signups.Models; +using Microsoft.EntityFrameworkCore; + +namespace ArmaForces.Boderator.Core.Signups +{ + internal sealed class SignupsContext : DbContext + { + public SignupsContext(DbContextOptions options) + : base(options) + { + Database.EnsureCreated(); + } + + public DbSet Signups { get; set; } + } +} From 0ef0d0df5cdbd823fc23de5204bea2044ec2a374 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 15:01:06 +0100 Subject: [PATCH 42/87] Update to .NET 6 --- .../ArmaForces.Boderator.BotService.Tests.csproj | 2 +- .../ArmaForces.Boderator.BotService.csproj | 2 +- .../ArmaForces.Boderator.Core.Tests.csproj | 2 +- ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 9c574e7..e65a12e 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 enable false diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index 11b24c3..f18146a 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 a9e33227-0f21-4863-9830-8aa69ac1e928 Linux enable diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index b86a6c5..1d7332a 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 enable false diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 9caffab..9e5605b 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 enable From b4e6068a7e9200412b0b63d8bf6773e1014ca6db Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 15:16:28 +0100 Subject: [PATCH 43/87] Add GetSignup by id --- .../Features/Signups/SignupsController.cs | 17 ++++++++++++----- .../ArmaForces.Boderator.Core.csproj | 3 ++- .../BoderatorCoreServiceExtensions.cs | 4 +++- .../Signups/Query/ISignupsQueryRepository.cs | 2 ++ .../Signups/Query/ISignupsQueryService.cs | 4 +++- .../Signups/Query/SignupsQueryRepository.cs | 5 +++++ .../Signups/Query/SignupsQueryService.cs | 3 +++ 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs index 9710df2..d566652 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs @@ -1,13 +1,22 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Signups.DTOs; using ArmaForces.Boderator.Core.Signups.Models; +using ArmaForces.Boderator.Core.Signups.Query; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Signups { public class SignupsController : ControllerBase { + private readonly ISignupsQueryService _signupsQueryService; + + public SignupsController(ISignupsQueryService signupsQueryService) + { + _signupsQueryService = signupsQueryService; + } + /// /// /// @@ -46,11 +55,9 @@ public ActionResult DeleteSignup(int signupId) /// /// [HttpGet("{signupId:int}")] - public ActionResult GetSignup(int signupId) - { - throw new NotImplementedException(); - } - + public async Task> GetSignup(int signupId) + => await _signupsQueryService.GetSignup(signupId); + /// /// /// diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 600a3a1..078bcf9 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -7,11 +7,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs index 0940e5c..06687c7 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -1,4 +1,5 @@ using ArmaForces.Boderator.Core.Missions.Persistence; +using ArmaForces.Boderator.Core.Signups; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -8,7 +9,8 @@ public static class BoderatorCoreServiceExtensions { public static IServiceCollection AddBoderatorCore(this IServiceCollection services, string connectionString) => services - .AddDbContext(options => options.UseSqlServer(connectionString)) + .AddDbContext(options => options.UseSqlite(connectionString)) + .AddDbContext(options => options.UseSqlite(connectionString)) .AutoAddInterfacesAsScoped(typeof(BoderatorCoreServiceExtensions).Assembly); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs index f682931..c00a391 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs @@ -9,5 +9,7 @@ internal interface ISignupsQueryRepository public Task> GetAllSignups(); public Task> GetOpenSignups(); + + public Task GetSignup(int signupId); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs index 4f1722a..c1005dc 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs @@ -4,8 +4,10 @@ namespace ArmaForces.Boderator.Core.Signups.Query { - internal interface ISignupsQueryService + public interface ISignupsQueryService { + Task GetSignup(int signupId); + Task> GetOpenSignups(); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs index 47855be..97a4763 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs @@ -25,5 +25,10 @@ public async Task> GetOpenSignups() => await _context.Signups .Where(x => x.CloseDate > DateTime.Now) .ToListAsync(); + + public async Task GetSignup(int signupId) + => await _context.Signups + .FindAsync(signupId) + .AsTask(); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs index 7e563d4..c864731 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs @@ -13,6 +13,9 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) _signupsQueryRepository = signupsQueryRepository; } + public Task GetSignup(int signupId) + => _signupsQueryRepository.GetSignup(signupId); + public async Task> GetOpenSignups() => await _signupsQueryRepository.GetOpenSignups(); } From 0f9f5e96e614003f36292d01795e0249a0ead97b Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 16:07:13 +0100 Subject: [PATCH 44/87] Update packages --- ...maForces.Boderator.BotService.Tests.csproj | 10 +++++----- .../ArmaForces.Boderator.BotService.csproj | 20 +++++++++---------- .../ArmaForces.Boderator.Core.Tests.csproj | 10 +++++----- .../ArmaForces.Boderator.Core.csproj | 8 ++++---- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index e65a12e..79f7a73 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -13,16 +13,16 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index f18146a..6a4c23b 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -12,19 +12,19 @@ - - - + + + - + - - - + + + - - - + + + diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 1d7332a..34d60e5 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -12,16 +12,16 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 9e5605b..5a71804 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -6,13 +6,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 26c9d818e7d6bfbfd19e98bf606dfc98e646bf00 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 18:56:45 +0100 Subject: [PATCH 45/87] Update structure & add endpoint names --- .../Features/Health/HealthController.cs | 2 +- .../Features/Missions/MissionsController.cs | 33 +++++++++-------- .../Features/Signups/SignupsController.cs | 16 +++++---- .../BoderatorCoreServiceExtensions.cs | 3 +- .../Missions/IMissionCommandService.cs | 11 ++++++ .../Features/Missions/IMissionQueryService.cs | 13 +++++++ .../Features/Missions/IMissionService.cs | 16 --------- .../Implementation/MissionCommandService.cs | 26 ++++++++++++++ .../Implementation/MissionQueryService.cs | 24 +++++++++++++ .../Persistence/IMissionRepository.cs | 2 +- .../Persistence/MissionContext.cs | 2 +- .../Persistence/MissionRepository.cs | 2 +- .../Features/Missions/MissionService.cs | 35 ------------------- .../{Query => }/ISignupsQueryService.cs | 2 +- .../Query/ISignupsQueryRepository.cs | 2 +- .../Query/SignupsQueryRepository.cs | 2 +- .../Query/SignupsQueryService.cs | 2 +- .../{ => Implementation}/SignupsContext.cs | 2 +- 18 files changed, 114 insertions(+), 81 deletions(-) create mode 100644 ArmaForces.Boderator.Core/Features/Missions/IMissionCommandService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs rename ArmaForces.Boderator.Core/Features/Missions/{ => Implementation}/Persistence/IMissionRepository.cs (84%) rename ArmaForces.Boderator.Core/Features/Missions/{ => Implementation}/Persistence/MissionContext.cs (83%) rename ArmaForces.Boderator.Core/Features/Missions/{ => Implementation}/Persistence/MissionRepository.cs (94%) delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/MissionService.cs rename ArmaForces.Boderator.Core/Features/Signups/{Query => }/ISignupsQueryService.cs (81%) rename ArmaForces.Boderator.Core/Features/Signups/{ => Implementation}/Query/ISignupsQueryRepository.cs (81%) rename ArmaForces.Boderator.Core/Features/Signups/{ => Implementation}/Query/SignupsQueryRepository.cs (90%) rename ArmaForces.Boderator.Core/Features/Signups/{ => Implementation}/Query/SignupsQueryService.cs (88%) rename ArmaForces.Boderator.Core/Features/Signups/{ => Implementation}/SignupsContext.cs (83%) diff --git a/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs b/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs index 4e0c1d2..fae740b 100644 --- a/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs +++ b/ArmaForces.Boderator.BotService/Features/Health/HealthController.cs @@ -12,7 +12,7 @@ public class HealthController : Controller /// /// Responds to a ping. /// - [HttpGet("ping")] + [HttpGet("ping", Name = "Ping")] [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Ping() => Ok("pong"); } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index edecca1..9f4044d 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -5,6 +5,7 @@ using ArmaForces.Boderator.BotService.Features.Missions.Mappers; using ArmaForces.Boderator.Core.Missions; using CSharpFunctionalExtensions; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Missions @@ -15,11 +16,13 @@ namespace ArmaForces.Boderator.BotService.Features.Missions [Route("api/[controller]")] public class MissionsController : Controller { - private readonly IMissionService _missionService; - - public MissionsController(IMissionService missionService) + private readonly IMissionCommandService _missionCommandService; + private readonly IMissionQueryService _missionQueryService; + + public MissionsController(IMissionCommandService missionCommandService, IMissionQueryService missionQueryService) { - _missionService = missionService; + _missionCommandService = missionCommandService; + _missionQueryService = missionQueryService; } /// @@ -27,9 +30,9 @@ public MissionsController(IMissionService missionService) /// /// /// - [HttpPost] + [HttpPost(Name = "Create Mission")] public async Task> CreateMission([FromBody] MissionCreateRequestDto request) - => await _missionService.CreateMission(MissionMapper.Map(request)) + => await _missionCommandService.CreateMission(MissionMapper.Map(request)) .Map(MissionMapper.Map) .Match, MissionDto>( onSuccess: mission => Created(mission.MissionId.ToString(), mission), @@ -39,7 +42,7 @@ public async Task> CreateMission([FromBody] MissionCrea /// /// /// Updated mission data. - [HttpPatch("{missionId:int}")] + [HttpPatch("{missionId:int}", Name = "Update Mission")] public ActionResult UpdateMission(int missionId) { throw new NotImplementedException(); @@ -49,7 +52,7 @@ public ActionResult UpdateMission(int missionId) /// /// /// Deleted mission data. - [HttpDelete("{missionId:int}")] + [HttpDelete("{missionId:int}", Name = "Delete Mission")] public ActionResult DeleteMission(int missionId) { throw new NotImplementedException(); @@ -59,22 +62,24 @@ public ActionResult DeleteMission(int missionId) /// /// /// - [HttpGet] + [HttpGet(Name = "Get Missions")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetMissions() - => await _missionService.GetMissions() + => await _missionQueryService.GetMissions() .Map(MissionMapper.Map) .Match>, List>( onSuccess: missions => Ok(missions), onFailure: error => BadRequest(error)); /// - /// + /// Retrieves mission with given . /// - /// + /// Unique identifier of a mission /// - [HttpGet("{missionId:int}")] + [HttpGet("{missionId:int}", Name = "Get Mission")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetMission(int missionId) - => await _missionService.GetMission(missionId) + => await _missionQueryService.GetMission(missionId) .Map(MissionMapper.Map) .Match, MissionDto>( onSuccess: mission => Ok(mission), diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs index d566652..5226078 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs @@ -2,12 +2,16 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Signups.DTOs; +using ArmaForces.Boderator.Core.Signups; using ArmaForces.Boderator.Core.Signups.Models; -using ArmaForces.Boderator.Core.Signups.Query; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Signups { + /// + /// Allows signups data retrieval and creation. + /// + [Route("api/[controller]")] public class SignupsController : ControllerBase { private readonly ISignupsQueryService _signupsQueryService; @@ -21,7 +25,7 @@ public SignupsController(ISignupsQueryService signupsQueryService) /// /// /// - [HttpPost] + [HttpPost(Name = "Create Signup")] public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) { throw new NotImplementedException(); @@ -32,7 +36,7 @@ public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) /// /// /// - [HttpPatch("{signupId:int}")] + [HttpPatch("{signupId:int}", Name = "Update Signup")] public ActionResult ModifySignup(int signupId) { throw new NotImplementedException(); @@ -43,7 +47,7 @@ public ActionResult ModifySignup(int signupId) /// /// /// - [HttpDelete("{signupId:int}")] + [HttpDelete("{signupId:int}", Name = "Delete Signup")] public ActionResult DeleteSignup(int signupId) { throw new NotImplementedException(); @@ -54,7 +58,7 @@ public ActionResult DeleteSignup(int signupId) /// /// /// - [HttpGet("{signupId:int}")] + [HttpGet("{signupId:int}", Name = "Get Signup")] public async Task> GetSignup(int signupId) => await _signupsQueryService.GetSignup(signupId); @@ -62,7 +66,7 @@ public async Task> GetSignup(int signupId) /// /// /// - [HttpGet] + [HttpGet(Name = "Get Signups")] public ActionResult> GetSignups() { throw new NotImplementedException(); diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs index 06687c7..a07cd4d 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -1,5 +1,6 @@ -using ArmaForces.Boderator.Core.Missions.Persistence; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Signups; +using ArmaForces.Boderator.Core.Signups.Implementation; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/ArmaForces.Boderator.Core/Features/Missions/IMissionCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/IMissionCommandService.cs new file mode 100644 index 0000000..69727b1 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/IMissionCommandService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions; + +public interface IMissionCommandService +{ + Task> CreateMission(MissionCreateRequest missionCreateRequest); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs new file mode 100644 index 0000000..aa100a6 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions; + +public interface IMissionQueryService +{ + Task> GetMission(int missionId); + + Task>> GetMissions(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs b/ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs deleted file mode 100644 index 55ab850..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/IMissionService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Missions.Models; -using CSharpFunctionalExtensions; - -namespace ArmaForces.Boderator.Core.Missions -{ - public interface IMissionService - { - Task> CreateMission(MissionCreateRequest missionCreateRequest); - - Task> GetMission(int missionId); - - Task>> GetMissions(); - } -} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs new file mode 100644 index 0000000..f343394 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Implementation; + +internal class MissionCommandService : IMissionCommandService +{ + private readonly IMissionRepository _missionRepository; + + public MissionCommandService(IMissionRepository missionRepository) + { + _missionRepository = missionRepository; + } + + public async Task> CreateMission(MissionCreateRequest missionCreateRequest) + => await _missionRepository.CreateMission( + new Mission + { + Title = missionCreateRequest.Title, + Owner = missionCreateRequest.Owner, + MissionTime = missionCreateRequest.MissionTime, + ModsetName = missionCreateRequest.ModsetName + }); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs new file mode 100644 index 0000000..2fb8a4b --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Implementation; + +internal class MissionQueryService : IMissionQueryService +{ + private readonly IMissionRepository _missionRepository; + + public MissionQueryService(IMissionRepository missionRepository) + { + _missionRepository = missionRepository; + } + + public async Task> GetMission(int missionId) + => await _missionRepository.GetMission(missionId) + ?? Result.Failure("Mission with given ID does not exist."); + + public async Task>> GetMissions() + => await _missionRepository.GetMissions(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionRepository.cs similarity index 84% rename from ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionRepository.cs index 6f8bb4f..f875794 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/IMissionRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionRepository.cs @@ -3,7 +3,7 @@ using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; -namespace ArmaForces.Boderator.Core.Missions.Persistence +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence { internal interface IMissionRepository { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs similarity index 83% rename from ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs index 228c8b2..80531ed 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionContext.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs @@ -1,7 +1,7 @@ using ArmaForces.Boderator.Core.Missions.Models; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Missions.Persistence +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence { internal sealed class MissionContext : DbContext { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs similarity index 94% rename from ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs index f48eaa4..8ef5c5a 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Persistence/MissionRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs @@ -5,7 +5,7 @@ using CSharpFunctionalExtensions; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Missions.Persistence +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence { internal class MissionRepository : IMissionRepository { diff --git a/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs b/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs deleted file mode 100644 index 2164020..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/MissionService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Missions.Models; -using ArmaForces.Boderator.Core.Missions.Persistence; -using CSharpFunctionalExtensions; - -namespace ArmaForces.Boderator.Core.Missions -{ - internal class MissionService : IMissionService - { - private readonly IMissionRepository _missionRepository; - - public MissionService(IMissionRepository missionRepository) - { - _missionRepository = missionRepository; - } - - public async Task> CreateMission(MissionCreateRequest missionCreateRequest) - => await _missionRepository.CreateMission( - new Mission - { - Title = missionCreateRequest.Title, - Owner = missionCreateRequest.Owner, - MissionTime = missionCreateRequest.MissionTime, - ModsetName = missionCreateRequest.ModsetName - }); - - public async Task> GetMission(int missionId) - => await _missionRepository.GetMission(missionId) - ?? Result.Failure("Mission with given ID does not exist."); - - public async Task>> GetMissions() - => await _missionRepository.GetMissions(); - } -} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs similarity index 81% rename from ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs rename to ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs index c1005dc..0b758a9 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Signups.Models; -namespace ArmaForces.Boderator.Core.Signups.Query +namespace ArmaForces.Boderator.Core.Signups { public interface ISignupsQueryService { diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs similarity index 81% rename from ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs rename to ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs index c00a391..d147dc0 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/ISignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Signups.Models; -namespace ArmaForces.Boderator.Core.Signups.Query +namespace ArmaForces.Boderator.Core.Signups.Implementation.Query { internal interface ISignupsQueryRepository { diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs similarity index 90% rename from ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs rename to ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs index 97a4763..08e9954 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs @@ -5,7 +5,7 @@ using ArmaForces.Boderator.Core.Signups.Models; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Signups.Query +namespace ArmaForces.Boderator.Core.Signups.Implementation.Query { internal class SignupsQueryRepository : ISignupsQueryRepository { diff --git a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs similarity index 88% rename from ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs rename to ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs index c864731..b554e5d 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Query/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Signups.Models; -namespace ArmaForces.Boderator.Core.Signups.Query +namespace ArmaForces.Boderator.Core.Signups.Implementation.Query { internal class SignupsQueryService : ISignupsQueryService { diff --git a/ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs similarity index 83% rename from ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs rename to ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs index 695c79a..2725eb9 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/SignupsContext.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs @@ -1,7 +1,7 @@ using ArmaForces.Boderator.Core.Signups.Models; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Signups +namespace ArmaForces.Boderator.Core.Signups.Implementation { internal sealed class SignupsContext : DbContext { From e359f13a253c84bc23c20ad830d6ab04e8f0f2da Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 18:58:04 +0100 Subject: [PATCH 46/87] Fix mission model to be readonly --- ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs index f865a62..07e20d7 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs @@ -10,8 +10,8 @@ public record Mission public DateTime? MissionTime { get; init; } - public string? ModsetName { get; set; } = string.Empty; + public string? ModsetName { get; init; } = string.Empty; - public string Owner { get; set; } = string.Empty; + public string Owner { get; init; } = string.Empty; } } From dc20582c459e8efbcca576a39cb6744918bf2a17 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 19:14:26 +0100 Subject: [PATCH 47/87] Use Result & mapping in Signups --- .../Features/Signups/DTOs/SignupDto.cs | 18 +++++++++ .../Features/Signups/DTOs/SlotDto.cs | 8 ++++ .../Features/Signups/DTOs/TeamDto.cs | 10 +++++ .../Features/Signups/Mappers/SignupsMapper.cs | 40 +++++++++++++++++++ .../Features/Signups/SignupsController.cs | 10 ++++- .../Features/Signups/ISignupsQueryService.cs | 5 ++- .../Query/SignupsQueryService.cs | 8 ++-- 7 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs new file mode 100644 index 0000000..162705b --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; + +namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; + +public record SignupDto +{ + public int SignupId { get; init; } + + public DateTime? StartDate { get; init; } + + public DateTime? CloseDate { get; init; } + + public MissionDto Mission { get; init; } = new(); + + public List Teams { get; init; } = new(); +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs new file mode 100644 index 0000000..f52fc5b --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs @@ -0,0 +1,8 @@ +namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; + +public record SlotDto +{ + public string Name { get; init; } = string.Empty; + + public string? Occupant { get; init; } +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs new file mode 100644 index 0000000..837de51 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; + +public record TeamDto +{ + public string Name { get; init; } = string.Empty; + + public List Slots { get; init; } = new(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs new file mode 100644 index 0000000..c6fa529 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using ArmaForces.Boderator.BotService.Features.Missions.Mappers; +using ArmaForces.Boderator.BotService.Features.Signups.DTOs; +using ArmaForces.Boderator.Core.Signups.Models; + +namespace ArmaForces.Boderator.BotService.Features.Signups.Mappers; + +public static class SignupsMapper +{ + public static SignupDto Map(Signup signup) + => new() + { + SignupId = signup.SignupsId, + StartDate = signup.StartDate, + CloseDate = signup.CloseDate, + Mission = MissionMapper.Map(signup.Mission), + Teams = Map(signup.Teams) + }; + + public static TeamDto Map(Team team) + => new() + { + Name = team.Name, + Slots = Map(team.Slots) + }; + + public static List Map(IEnumerable teams) + => teams.Select(Map).ToList(); + + public static SlotDto Map(Slot slot) + => new() + { + Name = slot.Name, + Occupant = slot.Occupant + }; + + public static List Map(IEnumerable slots) + => slots.Select(Map).ToList(); +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs index 5226078..aabcd27 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Signups.DTOs; +using ArmaForces.Boderator.BotService.Features.Signups.Mappers; using ArmaForces.Boderator.Core.Signups; using ArmaForces.Boderator.Core.Signups.Models; +using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Signups @@ -59,8 +61,12 @@ public ActionResult DeleteSignup(int signupId) /// /// [HttpGet("{signupId:int}", Name = "Get Signup")] - public async Task> GetSignup(int signupId) - => await _signupsQueryService.GetSignup(signupId); + public async Task> GetSignup(int signupId) + => await _signupsQueryService.GetSignup(signupId) + .Map(SignupsMapper.Map) + .Match, SignupDto>( + onSuccess: signup => Ok(signup), + onFailure: error => BadRequest(error)); /// /// diff --git a/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs index 0b758a9..afc70ef 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Signups.Models; +using CSharpFunctionalExtensions; namespace ArmaForces.Boderator.Core.Signups { public interface ISignupsQueryService { - Task GetSignup(int signupId); + Task> GetSignup(int signupId); - Task> GetOpenSignups(); + Task>> GetOpenSignups(); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs index b554e5d..c04a4e1 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Signups.Models; +using CSharpFunctionalExtensions; namespace ArmaForces.Boderator.Core.Signups.Implementation.Query { @@ -13,10 +14,11 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) _signupsQueryRepository = signupsQueryRepository; } - public Task GetSignup(int signupId) - => _signupsQueryRepository.GetSignup(signupId); + public async Task> GetSignup(int signupId) + => await _signupsQueryRepository.GetSignup(signupId) + ?? Result.Failure($"Signup with ID {signupId} not found"); - public async Task> GetOpenSignups() + public async Task>> GetOpenSignups() => await _signupsQueryRepository.GetOpenSignups(); } } From f2ca1e26a615c63ca7ed939fb47a908e0e1703ee Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 20:28:25 +0100 Subject: [PATCH 48/87] Add basic unit & integration tests for missions query --- .../ArmaForces.Boderator.Core.Tests.csproj | 4 - .../ServiceCollectionExtensionsTests.cs | 1 - .../MissionQueryServiceIntegrationTests.cs | 80 +++++++++++++++++++ .../Missions/MissionQueryServiceUnitTests.cs | 49 ++++++++++++ .../TestUtilities/DatabaseTestBase.cs | 22 +++++ .../ResultAssertionsExtensions.cs | 14 ++++ .../ArmaForces.Boderator.Core.csproj | 3 + .../Implementation/MissionQueryService.cs | 2 +- 8 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs create mode 100644 ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 8247a94..34d60e5 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -26,9 +26,5 @@ all - - - - diff --git a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs index 232902c..1817948 100644 --- a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs +++ b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -1,4 +1,3 @@ -using System.Linq; using ArmaForces.Boderator.Core.DependencyInjection; using FluentAssertions; using FluentAssertions.Execution; diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs new file mode 100644 index 0000000..08dc636 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions; + +public class MissionQueryServiceIntegrationTests : DatabaseTestBase +{ + private readonly MissionContext _missionContext; + private readonly IMissionQueryService _missionQueryService; + + public MissionQueryServiceIntegrationTests() + { + _missionContext = ServiceProvider.GetRequiredService(); + _missionQueryService = ServiceProvider.GetRequiredService(); + + DbContextTransaction = _missionContext.Database.BeginTransaction(); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetMissions_NoMissionsInDatabase_ReturnsEmptyList() + { + var result = await _missionQueryService.GetMissions(); + result.ShouldBeSuccess(new List()); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetMissions_OneMissionInDatabase_ReturnsOneMission() + { + var mission = CreateTestMission(); + + var addedEntry = _missionContext.Missions.Add(mission); + await _missionContext.SaveChangesAsync(); + + var result = await _missionQueryService.GetMissions(); + + result.ShouldBeSuccess(new List{addedEntry.Entity}); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetMission_MissionIdInDatabase_ReturnsMission() + { + var mission = CreateTestMission(); + + var createdMission = _missionContext.Missions.Add(mission).Entity; + await _missionContext.SaveChangesAsync(); + + var result = await _missionQueryService.GetMission(createdMission.MissionId); + + result.ShouldBeSuccess(createdMission); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetMission_MissionIdNotInDatabase_ReturnsFailure() + { + var mission = CreateTestMission(); + + var createdMission = _missionContext.Missions.Add(mission).Entity; + await _missionContext.SaveChangesAsync(); + var notExistingMissionId = createdMission.MissionId + 1; + + var result = await _missionQueryService.GetMission(notExistingMissionId); + + result.ShouldBeFailure($"Mission with ID {notExistingMissionId} does not exist."); + } + + private static Mission CreateTestMission() + { + return new Mission + { + Title = "Test mission", + Owner = "Tester", + }; + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs new file mode 100644 index 0000000..0fec531 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Implementation; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using AutoFixture; +using Moq; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions; + +public class MissionQueryServiceUnitTests +{ + private readonly Fixture _fixture = new(); + + [Fact, Trait("Category", "Unit")] + public async Task GetMissions_RepositoryEmpty_ReturnsEmptyList() + { + var missionQueryRepositoryMock = new Mock(); + missionQueryRepositoryMock + .Setup(x => x.GetMissions()) + .Returns(Task.FromResult(new List())); + + var missionQueryService = new MissionQueryService(missionQueryRepositoryMock.Object); + + var result = await missionQueryService.GetMissions(); + + result.ShouldBeSuccess(new List()); + } + + [Fact, Trait("Category", "Unit")] + public async Task GetMissions_RepositoryNotEmpty_ReturnsExpectedMissions() + { + var missionsInRepository = _fixture.CreateMany(5).ToList(); + + var missionQueryRepositoryMock = new Mock(); + missionQueryRepositoryMock + .Setup(x => x.GetMissions()) + .Returns(Task.FromResult(missionsInRepository)); + + var missionQueryService = new MissionQueryService(missionQueryRepositoryMock.Object); + + var result = await missionQueryService.GetMissions(); + + result.ShouldBeSuccess(missionsInRepository); + } +} diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs new file mode 100644 index 0000000..d768290 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using ArmaForces.Boderator.Core.DependencyInjection; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace ArmaForces.Boderator.Core.Tests.TestUtilities; + +public class DatabaseTestBase : IDisposable +{ + private static readonly string TestConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); + + protected readonly IServiceProvider ServiceProvider = new ServiceCollection() + .AddBoderatorCore(TestConnectionString) + .BuildServiceProvider(); + + protected IDbContextTransaction? DbContextTransaction { get; init; } + + protected DatabaseTestBase() { } + + public void Dispose() => DbContextTransaction?.Dispose(); +} diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs index 529d545..3157851 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -19,6 +19,20 @@ public static void ShouldBeFailure(this Result result, string expectedError) result.Error.Should().Be(expectedError); } } + + public static void ShouldBeFailure(this Result result, string expectedError) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + result.IsSuccess.Should().BeFalse(); + } + else + { + result.Error.Should().Be(expectedError); + } + } public static void ShouldBeSuccess(this Result result) { diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 4ff1083..b5ccd1e 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -20,6 +20,9 @@ <_Parameter1>$(MSBuildProjectName).Tests + + <_Parameter1>DynamicProxyGenAssembly2 + diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs index 2fb8a4b..98c6ad2 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs @@ -17,7 +17,7 @@ public MissionQueryService(IMissionRepository missionRepository) public async Task> GetMission(int missionId) => await _missionRepository.GetMission(missionId) - ?? Result.Failure("Mission with given ID does not exist."); + ?? Result.Failure($"Mission with ID {missionId} does not exist."); public async Task>> GetMissions() => await _missionRepository.GetMissions(); From a0653f3d69a8c8e23fa645d7de4f801f80bad933 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 20:49:58 +0100 Subject: [PATCH 49/87] Split MissionRepository --- .../Missions/MissionsControllerTests.cs | 8 ++--- .../Missions/MissionQueryServiceUnitTests.cs | 28 ++++++++------- .../Implementation/MissionCommandService.cs | 8 ++--- .../Implementation/MissionQueryService.cs | 10 +++--- ...sitory.cs => IMissionCommandRepository.cs} | 6 +--- .../Persistence/IMissionQueryRepository.cs | 12 +++++++ .../Persistence/MissionCommandRepository.cs | 26 ++++++++++++++ .../Persistence/MissionQueryRepository.cs | 23 ++++++++++++ .../Persistence/MissionRepository.cs | 35 ------------------- 9 files changed, 90 insertions(+), 66 deletions(-) rename ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/{IMissionRepository.cs => IMissionCommandRepository.cs} (60%) create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs index 176eb56..51a0353 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -15,7 +15,7 @@ public class MissionsControllerTests : ApiTestBase public MissionsControllerTests(TestApiServiceFixture testApi) : base(testApi) { } - [Fact] + [Fact, Trait("Category", "Integration")] public async Task CreateMission_InvalidRequest_ReturnsBadRequest() { var missionCreateRequestWithoutOwner = new MissionCreateRequestDto @@ -27,9 +27,9 @@ public async Task CreateMission_InvalidRequest_ReturnsBadRequest() var result = await HttpPostAsync("api/missions", missionCreateRequestWithoutOwner); result.ShouldBeFailure("Bad Request"); - } + } - [Fact] + [Fact, Trait("Category", "Integration")] public async Task CreateMission_ValidRequest_MissionCreated() { var missionCreateRequest = new MissionCreateRequestDto @@ -44,7 +44,7 @@ public async Task CreateMission_ValidRequest_MissionCreated() result.ShouldBeSuccess(); } - [Fact] + [Fact, Trait("Category", "Integration")] public async Task GetMission_MissionExists_ReturnsExistingMission() { var missionCreateRequest = new MissionCreateRequestDto diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs index 0fec531..9b6e72a 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions; using ArmaForces.Boderator.Core.Missions.Implementation; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Models; @@ -18,12 +19,8 @@ public class MissionQueryServiceUnitTests [Fact, Trait("Category", "Unit")] public async Task GetMissions_RepositoryEmpty_ReturnsEmptyList() { - var missionQueryRepositoryMock = new Mock(); - missionQueryRepositoryMock - .Setup(x => x.GetMissions()) - .Returns(Task.FromResult(new List())); - - var missionQueryService = new MissionQueryService(missionQueryRepositoryMock.Object); + var missionQueryRepository = CreateRepositoryMock(new List()); + var missionQueryService = new MissionQueryService(missionQueryRepository); var result = await missionQueryService.GetMissions(); @@ -34,16 +31,21 @@ public async Task GetMissions_RepositoryEmpty_ReturnsEmptyList() public async Task GetMissions_RepositoryNotEmpty_ReturnsExpectedMissions() { var missionsInRepository = _fixture.CreateMany(5).ToList(); - - var missionQueryRepositoryMock = new Mock(); - missionQueryRepositoryMock - .Setup(x => x.GetMissions()) - .Returns(Task.FromResult(missionsInRepository)); - - var missionQueryService = new MissionQueryService(missionQueryRepositoryMock.Object); + var missionQueryRepository = CreateRepositoryMock(missionsInRepository); + var missionQueryService = new MissionQueryService(missionQueryRepository); var result = await missionQueryService.GetMissions(); result.ShouldBeSuccess(missionsInRepository); } + + private static IMissionQueryRepository CreateRepositoryMock(IEnumerable missions) + { + var missionQueryRepositoryMock = new Mock(); + missionQueryRepositoryMock + .Setup(x => x.GetMissions()) + .Returns(Task.FromResult(missions.ToList())); + + return missionQueryRepositoryMock.Object; + } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs index f343394..e99798f 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs @@ -7,15 +7,15 @@ namespace ArmaForces.Boderator.Core.Missions.Implementation; internal class MissionCommandService : IMissionCommandService { - private readonly IMissionRepository _missionRepository; + private readonly IMissionCommandRepository _missionCommandRepository; - public MissionCommandService(IMissionRepository missionRepository) + public MissionCommandService(IMissionCommandRepository missionCommandRepository) { - _missionRepository = missionRepository; + _missionCommandRepository = missionCommandRepository; } public async Task> CreateMission(MissionCreateRequest missionCreateRequest) - => await _missionRepository.CreateMission( + => await _missionCommandRepository.CreateMission( new Mission { Title = missionCreateRequest.Title, diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs index 98c6ad2..f353559 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs @@ -8,17 +8,17 @@ namespace ArmaForces.Boderator.Core.Missions.Implementation; internal class MissionQueryService : IMissionQueryService { - private readonly IMissionRepository _missionRepository; + private readonly IMissionQueryRepository _missionQueryRepository; - public MissionQueryService(IMissionRepository missionRepository) + public MissionQueryService(IMissionQueryRepository missionQueryRepository) { - _missionRepository = missionRepository; + _missionQueryRepository = missionQueryRepository; } public async Task> GetMission(int missionId) - => await _missionRepository.GetMission(missionId) + => await _missionQueryRepository.GetMission(missionId) ?? Result.Failure($"Mission with ID {missionId} does not exist."); public async Task>> GetMissions() - => await _missionRepository.GetMissions(); + => await _missionQueryRepository.GetMissions(); } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionCommandRepository.cs similarity index 60% rename from ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionCommandRepository.cs index f875794..573d0e8 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionCommandRepository.cs @@ -1,15 +1,11 @@ -using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence { - internal interface IMissionRepository + internal interface IMissionCommandRepository { - Task GetMission(int missionId); - - Task> GetMissions(); Task> CreateMission(Mission missionToCreate); } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs new file mode 100644 index 0000000..cda27a1 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; + +internal interface IMissionQueryRepository +{ + Task GetMission(int missionId); + + Task> GetMissions(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs new file mode 100644 index 0000000..d340095 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; + +internal class MissionCommandRepository : IMissionCommandRepository +{ + private readonly MissionContext _context; + + public MissionCommandRepository(MissionContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task> CreateMission(Mission missionToCreate) + { + var missionEntityEntry = await _context.Missions.AddAsync(missionToCreate); + + if (missionEntityEntry is null) return Result.Failure("Failure creating mission."); + + await _context.SaveChangesAsync(); + return missionEntityEntry.Entity; + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs new file mode 100644 index 0000000..dbb68a4 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using Microsoft.EntityFrameworkCore; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; + +internal class MissionQueryRepository : IMissionQueryRepository +{ + private readonly MissionContext _context; + + public MissionQueryRepository(MissionContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task GetMission(int missionId) + => await _context.Missions.FindAsync(missionId); + + public async Task> GetMissions() + => await _context.Missions.ToListAsync(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs deleted file mode 100644 index 8ef5c5a..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionRepository.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Missions.Models; -using CSharpFunctionalExtensions; -using Microsoft.EntityFrameworkCore; - -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence -{ - internal class MissionRepository : IMissionRepository - { - private readonly MissionContext _context; - - public MissionRepository(MissionContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - public async Task GetMission(int missionId) - => await _context.Missions.FindAsync(missionId); - - public async Task> GetMissions() - => await _context.Missions.ToListAsync(); - - public async Task> CreateMission(Mission missionToCreate) - { - var missionEntityEntry = await _context.Missions.AddAsync(missionToCreate); - - if (missionEntityEntry is null) return Result.Failure("Failure creating mission."); - - await _context.SaveChangesAsync(); - return missionEntityEntry.Entity; - } - } -} From 40e7741c663470cf7ecce6c1282e8cac6ebee6b8 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 21:38:17 +0100 Subject: [PATCH 50/87] Merge remote-tracking branch 'origin/Boderator3.0' into Boderator30/SignupsDesign --- .../TestFixtures/TestApiServiceFixture.cs | 2 +- .../Features/DiscordClient/DiscordService.cs | 9 ++- .../Features/DiscordClient/IDiscordService.cs | 25 +++++---- .../DiscordServiceCollectionExtensions.cs | 4 +- .../Properties/launchSettings.json | 3 +- ArmaForces.Boderator.BotService/Startup.cs | 7 ++- .../ServiceCollectionExtensionsTests.cs | 1 + .../ArmaForces.Boderator.Core.csproj | 56 +++++++++---------- .../ServiceCollectionExtensions.cs | 7 +++ 9 files changed, 62 insertions(+), 52 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs index 82a2d58..b0c5a66 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs @@ -42,7 +42,7 @@ private static Action ConfigureWebBuilder() => webBuilder => { webBuilder.UseStartup(); webBuilder.ConfigureServices( - x => x.AddOrReplaceSingleton()); + x => x.AddOrReplaceSingleton(new TestConfigurationFactory().CreateConfiguration())); webBuilder.UseKestrel(x => x.ListenLocalhost(Port)); }; } diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs index 641099c..8d0a6c1 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs @@ -1,15 +1,15 @@ using System.Threading; using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Features.DiscordClient.DTOs; using Discord; using Discord.WebSocket; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace ArmaForces.Boderator.BotService.Features.DiscordClient { - internal sealed class DiscordService : IDiscordService//, IHostedService + internal sealed class DiscordService : IDiscordService, IHostedService { private readonly DiscordSocketClient _discordClient; private readonly ILogger _logger; @@ -18,13 +18,12 @@ internal sealed class DiscordService : IDiscordService//, IHostedService public DiscordService( DiscordSocketClient discordClient, - // TODO: Change the configuration to proper interface - IConfiguration configuration, + BoderatorConfiguration configuration, ILogger logger) { _logger = logger; _discordClient = discordClient; - _token = configuration["token"]; + _token = configuration.DiscordToken; } public async Task StartAsync(CancellationToken cancellationToken) diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs index 8894f38..3cb4f27 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/IDiscordService.cs @@ -1,12 +1,13 @@ -using System.Threading.Tasks; -using ArmaForces.Boderator.BotService.Features.DiscordClient.DTOs; -using Discord; - -namespace ArmaForces.Boderator.BotService.Features.DiscordClient -{ - internal interface IDiscordService - { - DiscordServiceStatusDto GetDiscordClientStatus(); - Task SetBotStatus(string newStatus, ActivityType statusType); - } -} +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.DiscordClient.DTOs; +using Discord; + +namespace ArmaForces.Boderator.BotService.Features.DiscordClient +{ + internal interface IDiscordService + { + DiscordServiceStatusDto GetDiscordClientStatus(); + + Task SetBotStatus(string newStatus, ActivityType statusType); + } +} diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs index 8c2c3b7..37260c0 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs @@ -4,12 +4,12 @@ namespace ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure. { internal static class DiscordServiceCollectionExtensions { - public static IServiceCollection AddDiscordClient(this IServiceCollection services, string token) + public static IServiceCollection AddDiscordClient(this IServiceCollection services) { return services .AddSingleton(DiscordSocketClientFactory.CreateDiscordClient) .AddSingleton(); - //.AddHostedService(); + // .AddHostedService(); } } } diff --git a/ArmaForces.Boderator.BotService/Properties/launchSettings.json b/ArmaForces.Boderator.BotService/Properties/launchSettings.json index b86381a..fc9df5e 100644 --- a/ArmaForces.Boderator.BotService/Properties/launchSettings.json +++ b/ArmaForces.Boderator.BotService/Properties/launchSettings.json @@ -20,7 +20,8 @@ "launchBrowser": true, "launchUrl": "api-docs/", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "AF_Boderator_DiscordToken": "" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" } diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 1155f09..e4283c9 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -3,6 +3,7 @@ using ArmaForces.Boderator.BotService.Documentation; using ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection; using ArmaForces.Boderator.Core.DependencyInjection; +using Discord.WebSocket; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -38,10 +39,10 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); - services.AddDiscordClient(string.Empty); - services.AddBoderatorCore(Configuration.GetConnectionString("DefaultConnection")); + services.AddBoderatorCore(serviceProvider => serviceProvider.GetRequiredService().ConnectionString); + services.AddSingleton(_ => new BoderatorConfigurationFactory().CreateConfiguration()); + services.AddDiscordClient(); services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); - services.AddSingleton(x => x.GetRequiredService().CreateConfiguration()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs index 1817948..232902c 100644 --- a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs +++ b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -1,3 +1,4 @@ +using System.Linq; using ArmaForces.Boderator.Core.DependencyInjection; using FluentAssertions; using FluentAssertions.Execution; diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index b5ccd1e..546333a 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -1,28 +1,28 @@ - - - - net6.0 - enable - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - <_Parameter1>$(MSBuildProjectName).Tests - - - <_Parameter1>DynamicProxyGenAssembly2 - - - - + + + + net6.0 + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + <_Parameter1>DynamicProxyGenAssembly2 + + + + diff --git a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs index 6be3b20..cfe0478 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -14,6 +14,13 @@ public static IServiceCollection AddOrReplaceSingleton(); } + + public static IServiceCollection AddOrReplaceSingleton(this IServiceCollection services, TService implementation) + where TService : class + { + return services.RemoveAll(typeof(TService)) + .AddSingleton(implementation); + } /// /// Adds all interfaces from with single implementation type as scoped service. From d2908ba3a0c272b0e24d5abc0907c6c3e12aba8e Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 21:38:29 +0100 Subject: [PATCH 51/87] Fixes for ConnectionString configuration --- .../TestUtilities/TestConfigurationFactory.cs | 2 ++ .../TestFixtures/TestApiServiceFixture.cs | 3 +++ .../Configuration/BoderatorConfiguration.cs | 2 ++ .../Features/DiscordClient/DiscordService.cs | 4 ++-- .../DiscordServiceCollectionExtensions.cs | 4 ++-- ArmaForces.Boderator.BotService/Startup.cs | 7 ------- .../TestUtilities/DatabaseTestBase.cs | 5 +---- .../TestUtilities/TestDatabaseConstants.cs | 8 ++++++++ .../BoderatorCoreServiceExtensions.cs | 15 +++++++++++---- 9 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs index 2b658a6..67e090d 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs @@ -1,4 +1,5 @@ using ArmaForces.Boderator.BotService.Configuration; +using ArmaForces.Boderator.Core.Tests.TestUtilities; namespace ArmaForces.Boderator.BotService.Tests.TestUtilities { @@ -6,6 +7,7 @@ internal class TestConfigurationFactory : IBoderatorConfigurationFactory { public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration { + ConnectionString = TestDatabaseConstants.TestConnectionString, DiscordToken = string.Empty }; } diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs index b0c5a66..4d4657e 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs @@ -2,8 +2,11 @@ using System.Net.Http; using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.Core.DependencyInjection; +using ArmaForces.Boderator.Core.Tests.TestUtilities; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Moq; namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures { diff --git a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs index 31cefa7..430adf4 100644 --- a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs +++ b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfiguration.cs @@ -2,6 +2,8 @@ namespace ArmaForces.Boderator.BotService.Configuration { internal record BoderatorConfiguration { + public string ConnectionString { get; init; } = string.Empty; + public string DiscordToken { get; init; } = string.Empty; } } diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs index 8d0a6c1..510e992 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/DiscordService.cs @@ -29,8 +29,8 @@ public DiscordService( public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Discord Service started"); - await _discordClient.LoginAsync(TokenType.Bot, _token); - await _discordClient.StartAsync(); + // await _discordClient.LoginAsync(TokenType.Bot, _token); + // await _discordClient.StartAsync(); } public Task StopAsync(CancellationToken cancellationToken) => Task.Run(() => diff --git a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs index 37260c0..3c1c233 100644 --- a/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.BotService/Features/DiscordClient/Infrastructure/DependencyInjection/DiscordServiceCollectionExtensions.cs @@ -8,8 +8,8 @@ public static IServiceCollection AddDiscordClient(this IServiceCollection servic { return services .AddSingleton(DiscordSocketClientFactory.CreateDiscordClient) - .AddSingleton(); - // .AddHostedService(); + .AddSingleton() + .AddHostedService(); } } } diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index e4283c9..3734ed7 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -15,13 +15,6 @@ namespace ArmaForces.Boderator.BotService { public class Startup { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - private IConfiguration Configuration { get; } - private OpenApiInfo OpenApiConfiguration { get; } = new() { Title = "ArmaForces Boderator API", diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs index d768290..f3f97a3 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using ArmaForces.Boderator.Core.DependencyInjection; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; @@ -8,10 +7,8 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities; public class DatabaseTestBase : IDisposable { - private static readonly string TestConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); - protected readonly IServiceProvider ServiceProvider = new ServiceCollection() - .AddBoderatorCore(TestConnectionString) + .AddBoderatorCore(_ => TestDatabaseConstants.TestConnectionString) .BuildServiceProvider(); protected IDbContextTransaction? DbContextTransaction { get; init; } diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs new file mode 100644 index 0000000..ebe2c46 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace ArmaForces.Boderator.Core.Tests.TestUtilities; + +public static class TestDatabaseConstants +{ + public static readonly string TestConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); +} diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs index a07cd4d..955c69e 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -1,5 +1,5 @@ +using System; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; -using ArmaForces.Boderator.Core.Signups; using ArmaForces.Boderator.Core.Signups.Implementation; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -8,10 +8,17 @@ namespace ArmaForces.Boderator.Core.DependencyInjection { public static class BoderatorCoreServiceExtensions { - public static IServiceCollection AddBoderatorCore(this IServiceCollection services, string connectionString) + public static IServiceCollection AddBoderatorCore(this IServiceCollection services, + Func connectionStringFactory) => services - .AddDbContext(options => options.UseSqlite(connectionString)) - .AddDbContext(options => options.UseSqlite(connectionString)) + .AddDbContext(connectionStringFactory) + .AddDbContext(connectionStringFactory) .AutoAddInterfacesAsScoped(typeof(BoderatorCoreServiceExtensions).Assembly); + + private static IServiceCollection AddDbContext(this IServiceCollection services, + Func connectionStringFactory) + where T : DbContext + => services.AddDbContext((serviceProvider, options) => + options.UseSqlite(connectionStringFactory(serviceProvider))); } } From 991305b003fcc0c31c3d35cee020084ebf98ad08 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 21:39:10 +0100 Subject: [PATCH 52/87] Add ConnectionString to factory --- .../Configuration/BoderatorConfigurationFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs index 1b3c5ba..a0eb5cc 100644 --- a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs +++ b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs @@ -16,6 +16,7 @@ public BoderatorConfigurationFactory() // TODO: Consider making this a bit more automatic so configuration is easily extensible public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration { + ConnectionString = GetStringValue(nameof(BoderatorConfiguration.ConnectionString)), DiscordToken = GetStringValue(nameof(BoderatorConfiguration.DiscordToken)) }; From 9c00a76825c78937c66e0960516f619ce545f24c Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Mon, 21 Mar 2022 22:03:13 +0100 Subject: [PATCH 53/87] Fix last test + add AF_Boderator_ConnectionString to launchSettings.json --- .../Missions/MissionsControllerTests.cs | 4 +- .../Properties/launchSettings.json | 3 +- ArmaForces.Boderator.BotService/Startup.cs | 38 +++++++++++++++++++ .../ResultAssertionsExtensions.cs | 10 +++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs index 51a0353..91ccd3b 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -4,7 +4,7 @@ using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; using ArmaForces.Boderator.Core.Tests.TestUtilities; using AutoFixture; -using FluentAssertions; +using Newtonsoft.Json; using Xunit; namespace ArmaForces.Boderator.BotService.Tests.Features.Missions @@ -26,7 +26,7 @@ public async Task CreateMission_InvalidRequest_ReturnsBadRequest() var result = await HttpPostAsync("api/missions", missionCreateRequestWithoutOwner); - result.ShouldBeFailure("Bad Request"); + result.ShouldBeFailure(); } [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.BotService/Properties/launchSettings.json b/ArmaForces.Boderator.BotService/Properties/launchSettings.json index fc9df5e..b4178bd 100644 --- a/ArmaForces.Boderator.BotService/Properties/launchSettings.json +++ b/ArmaForces.Boderator.BotService/Properties/launchSettings.json @@ -21,7 +21,8 @@ "launchUrl": "api-docs/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "AF_Boderator_DiscordToken": "" + "AF_Boderator_DiscordToken": "", + "AF_Boderator_ConnectionString": "Data Source=" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" } diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 3734ed7..5e16740 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,4 +1,7 @@ using System; +using System.ComponentModel; +using System.Text.Json.Serialization; +using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Documentation; using ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection; @@ -6,6 +9,9 @@ using Discord.WebSocket; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -36,6 +42,15 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(_ => new BoderatorConfigurationFactory().CreateConfiguration()); services.AddDiscordClient(); services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); + + services.AddMvc(options => options + .Filters.Add(new ExceptionFilter())) + .AddJsonOptions(opt => + { + opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + opt.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + opt.JsonSerializerOptions.WriteIndented = true; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -59,4 +74,27 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) }); } } + + public class ExceptionFilter : IExceptionFilter, IAsyncExceptionFilter + { + public Task OnExceptionAsync(ExceptionContext context) + { + OnException(context); + return Task.CompletedTask; + } + + public void OnException(ExceptionContext context) + { + if (context.Exception is not ArgumentNullException) return; + + var error = new + { + Message = "Validation error", + Details = context.Exception.Message + }; + + context.Result = new BadRequestObjectResult(error); + context.ExceptionHandled = true; + } + } } diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs index 3157851..fde4120 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -6,6 +6,16 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities { public static class ResultAssertionsExtensions { + public static void ShouldBeFailure(this Result result) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + result.IsSuccess.Should().BeFalse(); + } + } + public static void ShouldBeFailure(this Result result, string expectedError) { using var scope = new AssertionScope(); From fe3dd2a0b7efc8fcc51e7f8fe88a77208355e901 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 26 Mar 2022 23:26:22 +0100 Subject: [PATCH 54/87] More signups design --- .../Signups/DTOs/PlayerSignupRequestDto.cs | 8 +++ .../Signups/DTOs/SignupCreateRequestDto.cs | 6 +++ .../Features/Signups/DTOs/SignupDto.cs | 5 +- .../Features/Signups/DTOs/SlotDto.cs | 10 +++- .../Features/Signups/DTOs/TeamDto.cs | 4 ++ .../Features/Signups/Mappers/SignupsMapper.cs | 9 +++- .../Features/Signups/SignupsController.cs | 50 +++++++++++-------- .../Features/Signups/ISignupsQueryService.cs | 2 +- .../Query/ISignupsQueryRepository.cs | 2 +- .../Query/SignupsQueryRepository.cs | 2 +- .../Query/SignupsQueryService.cs | 2 +- .../Features/Signups/Models/Signup.cs | 2 + .../Features/Signups/Models/SignupStatus.cs | 10 ++++ .../Features/Signups/Models/Slot.cs | 8 +++ .../Features/Signups/Models/Team.cs | 4 ++ 15 files changed, 96 insertions(+), 28 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs create mode 100644 ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs new file mode 100644 index 0000000..8cf9f72 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs @@ -0,0 +1,8 @@ +namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; + +public record PlayerSignupRequestDto +{ + public long SlotId { get; set; } + + public string Player { get; set; } = string.Empty; +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs index 6401c3c..1b91fef 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs @@ -20,6 +20,12 @@ public class SignupCreateRequestDto /// [JsonProperty(Required = Required.DisallowNull)] public MissionCreateRequestDto? Mission { get; set; } + + /// + /// Desired status of signups. + /// + [JsonProperty(Required = Required.DisallowNull)] + public SignupStatus? SignupStatus { get; set; } /// /// Starting date of signup. diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs index 162705b..a635390 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.Core.Signups.Models; namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; @@ -8,6 +9,8 @@ public record SignupDto { public int SignupId { get; init; } + public SignupStatus SignupStatus { get; init; } + public DateTime? StartDate { get; init; } public DateTime? CloseDate { get; init; } @@ -15,4 +18,4 @@ public record SignupDto public MissionDto Mission { get; init; } = new(); public List Teams { get; init; } = new(); -} +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs index f52fc5b..08fbc04 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs @@ -1,8 +1,16 @@ -namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; +using System.Collections.Generic; + +namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; public record SlotDto { + public long? SlotId { get; init; } + public string Name { get; init; } = string.Empty; + + public List RequiredDlcs { get; init; } = new(); + + public string Vehicle { get; init; } = string.Empty; public string? Occupant { get; init; } } diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs index 837de51..237d66e 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs @@ -5,6 +5,10 @@ namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; public record TeamDto { public string Name { get; init; } = string.Empty; + + public List RequiredDlcs { get; init; } = new(); + + public string Vehicle { get; init; } = string.Empty; public List Slots { get; init; } = new(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs index c6fa529..cab6467 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs @@ -22,7 +22,9 @@ public static TeamDto Map(Team team) => new() { Name = team.Name, - Slots = Map(team.Slots) + Slots = Map(team.Slots), + Vehicle = team.Vehicle, + RequiredDlcs = team.RequiredDlcs }; public static List Map(IEnumerable teams) @@ -31,8 +33,11 @@ public static List Map(IEnumerable teams) public static SlotDto Map(Slot slot) => new() { + SlotId = slot.SlotId, Name = slot.Name, - Occupant = slot.Occupant + Occupant = slot.Occupant, + Vehicle = slot.Vehicle, + RequiredDlcs = slot.RequiredDlcs }; public static List Map(IEnumerable slots) diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs index aabcd27..60859b5 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs @@ -28,40 +28,34 @@ public SignupsController(ISignupsQueryService signupsQueryService) /// /// [HttpPost(Name = "Create Signup")] - public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) - { - throw new NotImplementedException(); - } + public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) + => throw new NotImplementedException(); /// /// /// /// /// - [HttpPatch("{signupId:int}", Name = "Update Signup")] - public ActionResult ModifySignup(int signupId) - { - throw new NotImplementedException(); - } + [HttpPatch("{signupId:long}", Name = "Update Signup")] + public ActionResult UpdateSignup(long signupId) + => throw new NotImplementedException(); /// /// /// /// /// - [HttpDelete("{signupId:int}", Name = "Delete Signup")] - public ActionResult DeleteSignup(int signupId) - { - throw new NotImplementedException(); - } + [HttpDelete("{signupId:long}", Name = "Delete Signup")] + public ActionResult DeleteSignup(long signupId) + => throw new NotImplementedException(); /// /// /// /// /// - [HttpGet("{signupId:int}", Name = "Get Signup")] - public async Task> GetSignup(int signupId) + [HttpGet("{signupId:long}", Name = "Get Signup")] + public async Task> GetSignup(long signupId) => await _signupsQueryService.GetSignup(signupId) .Map(SignupsMapper.Map) .Match, SignupDto>( @@ -69,13 +63,29 @@ public async Task> GetSignup(int signupId) onFailure: error => BadRequest(error)); /// - /// + /// Returns all signups satisfying query parameters. /// /// [HttpGet(Name = "Get Signups")] public ActionResult> GetSignups() - { - throw new NotImplementedException(); - } + => throw new NotImplementedException(); + + /// + /// Signs up a player for a given slot. + /// + [HttpPost("{signupId:long}/sign", Name = "Sign Player")] + public ActionResult SignPlayer(long signupId, [FromBody] PlayerSignupRequestDto request) + => throw new NotImplementedException(); + + [HttpPost("{signupId:long}/open", Name = "Open Signup")] + public ActionResult OpenSignup(long signupId) + => throw new NotImplementedException(); + + /// + /// Immediately closes given signups. + /// + [HttpPost("{signupId:long}/close", Name = "Close Signup")] + public ActionResult CloseSignup(long signupId) + => throw new NotImplementedException(); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs index afc70ef..8be6ec3 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs @@ -7,7 +7,7 @@ namespace ArmaForces.Boderator.Core.Signups { public interface ISignupsQueryService { - Task> GetSignup(int signupId); + Task> GetSignup(long signupId); Task>> GetOpenSignups(); } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs index d147dc0..a77ef37 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs @@ -10,6 +10,6 @@ internal interface ISignupsQueryRepository public Task> GetOpenSignups(); - public Task GetSignup(int signupId); + public Task GetSignup(long signupId); } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs index 08e9954..0938dc7 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs @@ -26,7 +26,7 @@ public async Task> GetOpenSignups() .Where(x => x.CloseDate > DateTime.Now) .ToListAsync(); - public async Task GetSignup(int signupId) + public async Task GetSignup(long signupId) => await _context.Signups .FindAsync(signupId) .AsTask(); diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs index c04a4e1..c7c2fd8 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs @@ -14,7 +14,7 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) _signupsQueryRepository = signupsQueryRepository; } - public async Task> GetSignup(int signupId) + public async Task> GetSignup(long signupId) => await _signupsQueryRepository.GetSignup(signupId) ?? Result.Failure($"Signup with ID {signupId} not found"); diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs index 8cf552f..2abe39b 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs @@ -7,6 +7,8 @@ namespace ArmaForces.Boderator.Core.Signups.Models public record Signup { public int SignupsId { get; init; } + + public SignupStatus SignupStatus { get; init; } public DateTime StartDate { get; init; } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs new file mode 100644 index 0000000..784b2e4 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs @@ -0,0 +1,10 @@ +namespace ArmaForces.Boderator.Core.Signups.Models; + +public enum SignupStatus +{ + Prebeton, + + Open, + + Closed +} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs index 4c797b8..c00736c 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs @@ -1,8 +1,16 @@ +using System.Collections.Generic; + namespace ArmaForces.Boderator.Core.Signups.Models { public record Slot { + public long? SlotId { get; init; } + public string Name { get; init; } = string.Empty; + + public List RequiredDlcs { get; init; } = new(); + + public string Vehicle { get; init; } = string.Empty; public string? Occupant { get; init; } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs index 7b9d9b2..4f81442 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs @@ -6,6 +6,10 @@ public record Team { public string Name { get; init; } = string.Empty; + public List RequiredDlcs { get; init; } = new(); + + public string Vehicle { get; init; } = string.Empty; + public IReadOnlyList Slots { get; init; } = new List(); } } From 2483b41440056bb3bd5b94efa721176e6e61c33d Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 19:15:25 +0200 Subject: [PATCH 55/87] Merge Signups into Missions feature Having them separate is a pain --- .../Features/Missions/DTOs/MissionDto.cs | 2 + .../DTOs/PlayerSignupRequestDto.cs | 2 +- .../DTOs/SignupCreateRequestDto.cs | 5 +-- .../{Signups => Missions}/DTOs/SignupDto.cs | 9 ++-- .../{Signups => Missions}/DTOs/SlotDto.cs | 5 ++- .../{Signups => Missions}/DTOs/TeamDto.cs | 5 ++- .../Missions/Mappers/SignupsMapper.cs | 42 +++++++++++++++++ .../SignupsController.cs | 11 +++-- .../Features/Signups/Mappers/SignupsMapper.cs | 45 ------------------- .../Missions/Helpers/MissionsDbHelper.cs | 25 +++++++++++ .../Missions/Helpers/MissionsFixture.cs | 15 +++++++ .../Missions/Helpers/SignupsDbHelper.cs | 42 +++++++++++++++++ .../MissionQueryServiceIntegrationTests.cs | 36 ++++----------- .../Missions/MissionQueryServiceUnitTests.cs | 1 + .../SignupsQueryServiceIntegrationTests.cs | 42 +++++++++++++++++ .../TestUtilities/DatabaseTestBase.cs | 3 ++ .../BoderatorCoreServiceExtensions.cs | 2 - .../Features/Dlcs/Models/Dlc.cs | 8 ++++ .../Features/Missions/ISignupsQueryService.cs | 13 ++++++ .../Implementation/MissionCommandService.cs | 1 + .../Implementation/MissionQueryService.cs | 1 + .../IMissionCommandRepository.cs | 2 +- .../{ => Command}/MissionCommandRepository.cs | 2 +- .../Persistence/MissionContext.cs | 7 +++ .../{ => Query}/IMissionQueryRepository.cs | 2 +- .../Query/ISignupsQueryRepository.cs | 14 ++++++ .../{ => Query}/MissionQueryRepository.cs | 2 +- .../Query/SignupsQueryRepository.cs | 13 +++--- .../Implementation}/SignupsQueryService.cs | 11 ++--- .../Features/Missions/Models/Mission.cs | 2 + .../Models/SignupStatus.cs | 2 +- .../Signup.cs => Missions/Models/Signups.cs} | 9 ++-- .../{Signups => Missions}/Models/Slot.cs | 6 +-- .../{Signups => Missions}/Models/Team.cs | 6 +-- .../Features/Signups/ISignupsQueryService.cs | 14 ------ .../Query/ISignupsQueryRepository.cs | 15 ------- .../Signups/Implementation/SignupsContext.cs | 16 ------- 37 files changed, 270 insertions(+), 168 deletions(-) rename ArmaForces.Boderator.BotService/Features/{Signups => Missions}/DTOs/PlayerSignupRequestDto.cs (63%) rename ArmaForces.Boderator.BotService/Features/{Signups => Missions}/DTOs/SignupCreateRequestDto.cs (88%) rename ArmaForces.Boderator.BotService/Features/{Signups => Missions}/DTOs/SignupDto.cs (57%) rename ArmaForces.Boderator.BotService/Features/{Signups => Missions}/DTOs/SlotDto.cs (59%) rename ArmaForces.Boderator.BotService/Features/{Signups => Missions}/DTOs/TeamDto.cs (56%) create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs rename ArmaForces.Boderator.BotService/Features/{Signups => Missions}/SignupsController.cs (90%) delete mode 100644 ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs create mode 100644 ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs rename ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/{ => Command}/IMissionCommandRepository.cs (95%) rename ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/{ => Command}/MissionCommandRepository.cs (98%) rename ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/{ => Query}/IMissionQueryRepository.cs (92%) create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs rename ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/{ => Query}/MissionQueryRepository.cs (95%) rename ArmaForces.Boderator.Core/Features/{Signups/Implementation => Missions/Implementation/Persistence}/Query/SignupsQueryRepository.cs (57%) rename ArmaForces.Boderator.Core/Features/{Signups/Implementation/Query => Missions/Implementation}/SignupsQueryService.cs (54%) rename ArmaForces.Boderator.Core/Features/{Signups => Missions}/Models/SignupStatus.cs (53%) rename ArmaForces.Boderator.Core/Features/{Signups/Models/Signup.cs => Missions/Models/Signups.cs} (65%) rename ArmaForces.Boderator.Core/Features/{Signups => Missions}/Models/Slot.cs (64%) rename ArmaForces.Boderator.Core/Features/{Signups => Missions}/Models/Team.cs (69%) delete mode 100644 ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index f8f2a38..d329056 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -9,5 +9,7 @@ public record MissionDto public string Title { get; init; } = string.Empty; public DateTime? MissionDate { get; init; } + + public SignupDto? Signups { get; init; } } } diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs similarity index 63% rename from ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs index 8cf9f72..c6dccc4 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/PlayerSignupRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs @@ -1,4 +1,4 @@ -namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record PlayerSignupRequestDto { diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs similarity index 88% rename from ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs index 1b91fef..010b346 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using ArmaForces.Boderator.BotService.Features.Missions.DTOs; -using ArmaForces.Boderator.Core.Signups.Models; +using ArmaForces.Boderator.Core.Missions.Models; using Newtonsoft.Json; -namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs { public class SignupCreateRequestDto { diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs similarity index 57% rename from ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs index a635390..cd6a855 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SignupDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using ArmaForces.Boderator.BotService.Features.Missions.DTOs; -using ArmaForces.Boderator.Core.Signups.Models; +using ArmaForces.Boderator.Core.Missions.Models; -namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record SignupDto { @@ -15,7 +14,7 @@ public record SignupDto public DateTime? CloseDate { get; init; } - public MissionDto Mission { get; init; } = new(); - public List Teams { get; init; } = new(); + + public int MissionId { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs similarity index 59% rename from ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs index 08fbc04..e7ae746 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/SlotDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using ArmaForces.Boderator.Core.Dlcs.Models; -namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record SlotDto { @@ -8,7 +9,7 @@ public record SlotDto public string Name { get; init; } = string.Empty; - public List RequiredDlcs { get; init; } = new(); + public List RequiredDlcs { get; init; } = new(); public string Vehicle { get; init; } = string.Empty; diff --git a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs similarity index 56% rename from ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs index 237d66e..555b7e9 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/DTOs/TeamDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; +using ArmaForces.Boderator.Core.Dlcs.Models; -namespace ArmaForces.Boderator.BotService.Features.Signups.DTOs; +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record TeamDto { public string Name { get; init; } = string.Empty; - public List RequiredDlcs { get; init; } = new(); + public List RequiredDlcs { get; init; } = new(); public string Vehicle { get; init; } = string.Empty; diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs new file mode 100644 index 0000000..526b39d --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers; + +public static class SignupsMapper +{ + public static SignupDto Map(Core.Missions.Models.Signups signups) + => new() + { + SignupId = signups.SignupsId, + StartDate = signups.StartDate, + CloseDate = signups.CloseDate, + MissionId = signups.MissionId, + Teams = Map(signups.Teams) + }; + + public static TeamDto Map(Team team) + => new() + { + Name = team.Name, + Slots = Map(team.Slots), + Vehicle = team.Vehicle + }; + + public static List Map(IEnumerable teams) + => teams.Select(Map).ToList(); + + public static SlotDto Map(Slot slot) + => new() + { + SlotId = slot.SlotId, + Name = slot.Name, + Occupant = slot.Occupant, + Vehicle = slot.Vehicle + }; + + public static List Map(IEnumerable slots) + => slots.Select(Map).ToList(); +} diff --git a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs similarity index 90% rename from ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs rename to ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index 60859b5..baab162 100644 --- a/ArmaForces.Boderator.BotService/Features/Signups/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using ArmaForces.Boderator.BotService.Features.Signups.DTOs; -using ArmaForces.Boderator.BotService.Features.Signups.Mappers; -using ArmaForces.Boderator.Core.Signups; -using ArmaForces.Boderator.Core.Signups.Models; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Features.Missions.Mappers; +using ArmaForces.Boderator.Core.Missions; using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Mvc; -namespace ArmaForces.Boderator.BotService.Features.Signups +namespace ArmaForces.Boderator.BotService.Features.Missions { /// /// Allows signups data retrieval and creation. @@ -67,7 +66,7 @@ public async Task> GetSignup(long signupId) /// /// [HttpGet(Name = "Get Signups")] - public ActionResult> GetSignups() + public ActionResult> GetSignups() => throw new NotImplementedException(); /// diff --git a/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs deleted file mode 100644 index cab6467..0000000 --- a/ArmaForces.Boderator.BotService/Features/Signups/Mappers/SignupsMapper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using ArmaForces.Boderator.BotService.Features.Missions.Mappers; -using ArmaForces.Boderator.BotService.Features.Signups.DTOs; -using ArmaForces.Boderator.Core.Signups.Models; - -namespace ArmaForces.Boderator.BotService.Features.Signups.Mappers; - -public static class SignupsMapper -{ - public static SignupDto Map(Signup signup) - => new() - { - SignupId = signup.SignupsId, - StartDate = signup.StartDate, - CloseDate = signup.CloseDate, - Mission = MissionMapper.Map(signup.Mission), - Teams = Map(signup.Teams) - }; - - public static TeamDto Map(Team team) - => new() - { - Name = team.Name, - Slots = Map(team.Slots), - Vehicle = team.Vehicle, - RequiredDlcs = team.RequiredDlcs - }; - - public static List Map(IEnumerable teams) - => teams.Select(Map).ToList(); - - public static SlotDto Map(Slot slot) - => new() - { - SlotId = slot.SlotId, - Name = slot.Name, - Occupant = slot.Occupant, - Vehicle = slot.Vehicle, - RequiredDlcs = slot.RequiredDlcs - }; - - public static List Map(IEnumerable slots) - => slots.Select(Map).ToList(); -} diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs new file mode 100644 index 0000000..bf414dd --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; + +internal class MissionsDbHelper +{ + private readonly MissionContext _missionContext; + + public MissionsDbHelper(MissionContext missionContext) + { + _missionContext = missionContext; + } + + public async Task CreateTestMission() + { + var mission = MissionsFixture.CreateTestMission(); + + var addedEntry = _missionContext.Missions.Add(mission); + await _missionContext.SaveChangesAsync(); + + return addedEntry.Entity; + } +} diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs new file mode 100644 index 0000000..656cee7 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs @@ -0,0 +1,15 @@ +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; + +internal static class MissionsFixture +{ + public static Mission CreateTestMission() + { + return new Mission + { + Title = "Test mission", + Owner = "Tester", + }; + } +} diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs new file mode 100644 index 0000000..dd354fe --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; + +internal class SignupsDbHelper +{ + private readonly MissionContext _missionContext; + + public SignupsDbHelper(MissionContext missionContext) + { + _missionContext = missionContext; + } + + public async Task CreateTestSignup(int missionId) + { + var signup = SignupsFixture.CreateTestSignup(missionId); + + var addedEntry = _missionContext.Signups.Add(signup); + await _missionContext.SaveChangesAsync(); + + return addedEntry.Entity; + } +} + +internal static class SignupsFixture +{ + public static Core.Missions.Models.Signups CreateTestSignup(int missionId) + { + return new Core.Missions.Models.Signups + { + MissionId = missionId, + SignupStatus = SignupStatus.Open, + StartDate = DateTime.Now, + CloseDate = DateTime.Now.AddHours(1), + Teams = new List() + }; + } +} diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs index 08dc636..8444edb 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions; -using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; using ArmaForces.Boderator.Core.Tests.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -11,15 +11,15 @@ namespace ArmaForces.Boderator.Core.Tests.Features.Missions; public class MissionQueryServiceIntegrationTests : DatabaseTestBase { - private readonly MissionContext _missionContext; + private readonly MissionsDbHelper _missionsDbHelper; private readonly IMissionQueryService _missionQueryService; - + public MissionQueryServiceIntegrationTests() { - _missionContext = ServiceProvider.GetRequiredService(); + _missionsDbHelper = ServiceProvider.GetRequiredService(); _missionQueryService = ServiceProvider.GetRequiredService(); - DbContextTransaction = _missionContext.Database.BeginTransaction(); + // DbContextTransaction = _missionContext.Database.BeginTransaction(); } [Fact, Trait("Category", "Integration")] @@ -32,23 +32,17 @@ public async Task GetMissions_NoMissionsInDatabase_ReturnsEmptyList() [Fact, Trait("Category", "Integration")] public async Task GetMissions_OneMissionInDatabase_ReturnsOneMission() { - var mission = CreateTestMission(); - - var addedEntry = _missionContext.Missions.Add(mission); - await _missionContext.SaveChangesAsync(); + var createdMission = await _missionsDbHelper.CreateTestMission(); var result = await _missionQueryService.GetMissions(); - result.ShouldBeSuccess(new List{addedEntry.Entity}); + result.ShouldBeSuccess(new List{createdMission}); } [Fact, Trait("Category", "Integration")] public async Task GetMission_MissionIdInDatabase_ReturnsMission() { - var mission = CreateTestMission(); - - var createdMission = _missionContext.Missions.Add(mission).Entity; - await _missionContext.SaveChangesAsync(); + var createdMission = await _missionsDbHelper.CreateTestMission(); var result = await _missionQueryService.GetMission(createdMission.MissionId); @@ -58,23 +52,11 @@ public async Task GetMission_MissionIdInDatabase_ReturnsMission() [Fact, Trait("Category", "Integration")] public async Task GetMission_MissionIdNotInDatabase_ReturnsFailure() { - var mission = CreateTestMission(); - - var createdMission = _missionContext.Missions.Add(mission).Entity; - await _missionContext.SaveChangesAsync(); + var createdMission = await _missionsDbHelper.CreateTestMission(); var notExistingMissionId = createdMission.MissionId + 1; var result = await _missionQueryService.GetMission(notExistingMissionId); result.ShouldBeFailure($"Mission with ID {notExistingMissionId} does not exist."); } - - private static Mission CreateTestMission() - { - return new Mission - { - Title = "Test mission", - Owner = "Tester", - }; - } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs index 9b6e72a..e85066d 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs @@ -4,6 +4,7 @@ using ArmaForces.Boderator.Core.Missions; using ArmaForces.Boderator.Core.Missions.Implementation; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using ArmaForces.Boderator.Core.Missions.Models; using ArmaForces.Boderator.Core.Tests.TestUtilities; using AutoFixture; diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs new file mode 100644 index 0000000..3b7b848 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions; + +public class SignupsQueryServiceIntegrationTests : DatabaseTestBase +{ + private readonly MissionsDbHelper _missionsDbHelper; + private readonly SignupsDbHelper _signupsDbHelper; + private readonly ISignupsQueryService _signupsQueryService; + + public SignupsQueryServiceIntegrationTests() + { + _missionsDbHelper = ServiceProvider.GetRequiredService(); + _signupsDbHelper = ServiceProvider.GetRequiredService(); + _signupsQueryService = ServiceProvider.GetRequiredService(); + + // DbContextTransaction = _missionContext.Database.BeginTransaction(); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetOpenSignups_NoSignupsInDatabase_ReturnsEmptyList() + { + var result = await _signupsQueryService.GetOpenSignups(); + result.ShouldBeSuccess(new List()); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetOpenSignups_NoOpenSignupsInDatabase_ReturnsEmptyList() + { + var testMission = await _missionsDbHelper.CreateTestMission(); + var signup = await _signupsDbHelper.CreateTestSignup(testMission.MissionId); + + var result = await _signupsQueryService.GetOpenSignups(); + result.ShouldBeSuccess(new List{signup}); + } +} diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs index f3f97a3..756b8d7 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs @@ -1,5 +1,6 @@ using System; using ArmaForces.Boderator.Core.DependencyInjection; +using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; @@ -9,6 +10,8 @@ public class DatabaseTestBase : IDisposable { protected readonly IServiceProvider ServiceProvider = new ServiceCollection() .AddBoderatorCore(_ => TestDatabaseConstants.TestConnectionString) + .AddScoped() + .AddScoped() .BuildServiceProvider(); protected IDbContextTransaction? DbContextTransaction { get; init; } diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs index 955c69e..34621a9 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -1,6 +1,5 @@ using System; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; -using ArmaForces.Boderator.Core.Signups.Implementation; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -12,7 +11,6 @@ public static IServiceCollection AddBoderatorCore(this IServiceCollection servic Func connectionStringFactory) => services .AddDbContext(connectionStringFactory) - .AddDbContext(connectionStringFactory) .AutoAddInterfacesAsScoped(typeof(BoderatorCoreServiceExtensions).Assembly); private static IServiceCollection AddDbContext(this IServiceCollection services, diff --git a/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs b/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs new file mode 100644 index 0000000..e19d121 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs @@ -0,0 +1,8 @@ +namespace ArmaForces.Boderator.Core.Dlcs.Models; + +public class Dlc +{ + public int DlcId { get; init; } + + public string Name { get; init; } = string.Empty; +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs new file mode 100644 index 0000000..a78e8a5 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions; + +public interface ISignupsQueryService +{ + Task> GetSignup(long signupId); + + Task>> GetOpenSignups(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs index e99798f..10ea298 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs index f353559..f4e31fe 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs similarity index 95% rename from ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionCommandRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs index 573d0e8..ef02863 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionCommandRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs @@ -2,7 +2,7 @@ using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command { internal interface IMissionCommandRepository { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/MissionCommandRepository.cs similarity index 98% rename from ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/MissionCommandRepository.cs index d340095..3994240 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionCommandRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/MissionCommandRepository.cs @@ -3,7 +3,7 @@ using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; internal class MissionCommandRepository : IMissionCommandRepository { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs index 80531ed..027496a 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs @@ -12,5 +12,12 @@ public MissionContext(DbContextOptions options) } public DbSet Missions { get; set; } + public DbSet Signups { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(x => new {x.SignupsId, x.Name}); + } } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs similarity index 92% rename from ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs index cda27a1..ecc5482 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/IMissionQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Models; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; internal interface IMissionQueryRepository { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs new file mode 100644 index 0000000..0978e41 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query +{ + internal interface ISignupsQueryRepository + { + public Task> GetAllSignups(); + + public Task> GetOpenSignups(); + + public Task GetSignup(long signupId); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs similarity index 95% rename from ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs index dbb68a4..bfee158 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs @@ -4,7 +4,7 @@ using ArmaForces.Boderator.Core.Missions.Models; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; internal class MissionQueryRepository : IMissionQueryRepository { diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs similarity index 57% rename from ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs index 0938dc7..dd40739 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs @@ -2,31 +2,30 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Signups.Models; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Signups.Implementation.Query +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query { internal class SignupsQueryRepository : ISignupsQueryRepository { - private readonly SignupsContext _context; + private readonly MissionContext _context; - public SignupsQueryRepository(SignupsContext context) + public SignupsQueryRepository(MissionContext context) { _context = context; } - public async Task> GetAllSignups() + public async Task> GetAllSignups() { return await _context.Signups.ToListAsync(); } - public async Task> GetOpenSignups() + public async Task> GetOpenSignups() => await _context.Signups .Where(x => x.CloseDate > DateTime.Now) .ToListAsync(); - public async Task GetSignup(long signupId) + public async Task GetSignup(long signupId) => await _context.Signups .FindAsync(signupId) .AsTask(); diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs similarity index 54% rename from ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs rename to ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs index c7c2fd8..fe8b645 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Signups.Models; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using CSharpFunctionalExtensions; -namespace ArmaForces.Boderator.Core.Signups.Implementation.Query +namespace ArmaForces.Boderator.Core.Missions.Implementation { internal class SignupsQueryService : ISignupsQueryService { @@ -14,11 +15,11 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) _signupsQueryRepository = signupsQueryRepository; } - public async Task> GetSignup(long signupId) + public async Task> GetSignup(long signupId) => await _signupsQueryRepository.GetSignup(signupId) - ?? Result.Failure($"Signup with ID {signupId} not found"); + ?? Result.Failure($"Signup with ID {signupId} not found"); - public async Task>> GetOpenSignups() + public async Task>> GetOpenSignups() => await _signupsQueryRepository.GetOpenSignups(); } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs index 07e20d7..8100b3d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs @@ -13,5 +13,7 @@ public record Mission public string? ModsetName { get; init; } = string.Empty; public string Owner { get; init; } = string.Empty; + + public Signups? Signups { get; init; } } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupStatus.cs similarity index 53% rename from ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs rename to ArmaForces.Boderator.Core/Features/Missions/Models/SignupStatus.cs index 784b2e4..9d1bb79 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/SignupStatus.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupStatus.cs @@ -1,4 +1,4 @@ -namespace ArmaForces.Boderator.Core.Signups.Models; +namespace ArmaForces.Boderator.Core.Missions.Models; public enum SignupStatus { diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs similarity index 65% rename from ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs rename to ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index 2abe39b..d8ba229 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/Signup.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using ArmaForces.Boderator.Core.Missions.Models; -namespace ArmaForces.Boderator.Core.Signups.Models +namespace ArmaForces.Boderator.Core.Missions.Models { - public record Signup + public record Signups { public int SignupsId { get; init; } @@ -13,8 +12,8 @@ public record Signup public DateTime StartDate { get; init; } public DateTime CloseDate { get; init; } - - public Mission Mission { get; init; } = new Mission(); + + public int MissionId { get; init; } public IReadOnlyList Teams { get; init; } = new List(); } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs similarity index 64% rename from ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs rename to ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs index c00736c..aebb064 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/Slot.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace ArmaForces.Boderator.Core.Signups.Models +namespace ArmaForces.Boderator.Core.Missions.Models { public record Slot { @@ -8,8 +6,6 @@ public record Slot public string Name { get; init; } = string.Empty; - public List RequiredDlcs { get; init; } = new(); - public string Vehicle { get; init; } = string.Empty; public string? Occupant { get; init; } diff --git a/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs similarity index 69% rename from ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs rename to ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs index 4f81442..ff1f471 100644 --- a/ArmaForces.Boderator.Core/Features/Signups/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs @@ -1,15 +1,15 @@ using System.Collections.Generic; -namespace ArmaForces.Boderator.Core.Signups.Models +namespace ArmaForces.Boderator.Core.Missions.Models { public record Team { public string Name { get; init; } = string.Empty; - public List RequiredDlcs { get; init; } = new(); - public string Vehicle { get; init; } = string.Empty; public IReadOnlyList Slots { get; init; } = new List(); + + public int SignupsId { get; init; } } } diff --git a/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs deleted file mode 100644 index 8be6ec3..0000000 --- a/ArmaForces.Boderator.Core/Features/Signups/ISignupsQueryService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Signups.Models; -using CSharpFunctionalExtensions; - -namespace ArmaForces.Boderator.Core.Signups -{ - public interface ISignupsQueryService - { - Task> GetSignup(long signupId); - - Task>> GetOpenSignups(); - } -} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs deleted file mode 100644 index a77ef37..0000000 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/Query/ISignupsQueryRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Signups.Models; - -namespace ArmaForces.Boderator.Core.Signups.Implementation.Query -{ - internal interface ISignupsQueryRepository - { - public Task> GetAllSignups(); - - public Task> GetOpenSignups(); - - public Task GetSignup(long signupId); - } -} diff --git a/ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs b/ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs deleted file mode 100644 index 2725eb9..0000000 --- a/ArmaForces.Boderator.Core/Features/Signups/Implementation/SignupsContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using ArmaForces.Boderator.Core.Signups.Models; -using Microsoft.EntityFrameworkCore; - -namespace ArmaForces.Boderator.Core.Signups.Implementation -{ - internal sealed class SignupsContext : DbContext - { - public SignupsContext(DbContextOptions options) - : base(options) - { - Database.EnsureCreated(); - } - - public DbSet Signups { get; set; } - } -} From 639a768ff6710d245b089bddf02d5b7d47b38f85 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 19:17:43 +0200 Subject: [PATCH 56/87] Change to file-scoped namespace --- .../Missions/DTOs/MissionCreateRequestDto.cs | 59 ++++--- .../Features/Missions/DTOs/MissionDto.cs | 17 +- .../Missions/DTOs/SignupCreateRequestDto.cs | 71 ++++----- .../Missions/Mappers/MissionMapper.cs | 45 +++--- .../Features/Missions/MissionsController.cs | 149 +++++++++--------- .../Features/Missions/SignupsController.cs | 141 ++++++++--------- .../Properties/launchSettings.json | 2 +- .../Missions/Helpers/SignupsDbHelper.cs | 23 +-- .../Missions/Helpers/SignupsFixture.cs | 20 +++ .../Command/IMissionCommandRepository.cs | 11 +- .../Persistence/MissionContext.cs | 29 ++-- .../Query/ISignupsQueryRepository.cs | 15 +- .../Query/SignupsQueryRepository.cs | 43 +++-- .../Implementation/SignupsQueryService.cs | 29 ++-- .../Features/Missions/Models/Mission.cs | 21 ++- .../Missions/Models/MissionCreateRequest.cs | 47 +++--- .../Features/Missions/Models/Signups.cs | 21 ++- .../Features/Missions/Models/Slot.cs | 17 +- .../Features/Missions/Models/Team.cs | 17 +- 19 files changed, 382 insertions(+), 395 deletions(-) create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs index 774ea4f..266f0da 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs @@ -1,38 +1,37 @@ using System; using Newtonsoft.Json; -namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; + +public class MissionCreateRequestDto { - public class MissionCreateRequestDto - { - /// - /// Mission title. - /// - [JsonProperty(Required = Required.Always)] - public string Title { get; set; } = string.Empty; + /// + /// Mission title. + /// + [JsonProperty(Required = Required.Always)] + public string Title { get; set; } = string.Empty; - /// - /// Mission description. - /// - [JsonProperty(Required = Required.Always)] - public string Description { get; set; } = string.Empty; + /// + /// Mission description. + /// + [JsonProperty(Required = Required.Always)] + public string Description { get; set; } = string.Empty; - /// - /// Mission start time. - /// - [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] - public DateTime? MissionTime { get; set; } + /// + /// Mission start time. + /// + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public DateTime? MissionTime { get; set; } - /// - /// Name of the modset. - /// - [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] - public string? ModsetName { get; set; } + /// + /// Name of the modset. + /// + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? ModsetName { get; set; } - /// - /// Owner of the mission. - /// - [JsonProperty(Required = Required.Always)] - public string Owner { get; set; } = string.Empty; - } -} + /// + /// Owner of the mission. + /// + [JsonProperty(Required = Required.Always)] + public string Owner { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index d329056..82ad2ee 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -1,15 +1,14 @@ using System; -namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; + +public record MissionDto { - public record MissionDto - { - public int MissionId { get; init; } + public int MissionId { get; init; } - public string Title { get; init; } = string.Empty; + public string Title { get; init; } = string.Empty; - public DateTime? MissionDate { get; init; } + public DateTime? MissionDate { get; init; } - public SignupDto? Signups { get; init; } - } -} + public SignupDto? Signups { get; init; } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs index 010b346..ec70ac4 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs @@ -3,45 +3,44 @@ using ArmaForces.Boderator.Core.Missions.Models; using Newtonsoft.Json; -namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; + +public class SignupCreateRequestDto { - public class SignupCreateRequestDto - { - /// - /// Id of the mission for which the signup will be created. - /// - [JsonProperty(Required = Required.DisallowNull)] - public int? MissionId { get; set; } + /// + /// Id of the mission for which the signup will be created. + /// + [JsonProperty(Required = Required.DisallowNull)] + public int? MissionId { get; set; } - /// - /// Mission for which the signups will be created. - /// Mission will be created. - /// - [JsonProperty(Required = Required.DisallowNull)] - public MissionCreateRequestDto? Mission { get; set; } + /// + /// Mission for which the signups will be created. + /// Mission will be created. + /// + [JsonProperty(Required = Required.DisallowNull)] + public MissionCreateRequestDto? Mission { get; set; } - /// - /// Desired status of signups. - /// - [JsonProperty(Required = Required.DisallowNull)] - public SignupStatus? SignupStatus { get; set; } + /// + /// Desired status of signups. + /// + [JsonProperty(Required = Required.DisallowNull)] + public SignupStatus? SignupStatus { get; set; } - /// - /// Starting date of signup. - /// - [JsonProperty(Required = Required.DisallowNull)] - public DateTime? StartDate { get; set; } + /// + /// Starting date of signup. + /// + [JsonProperty(Required = Required.DisallowNull)] + public DateTime? StartDate { get; set; } - /// - /// Closing date of signup. - /// - [JsonProperty(Required = Required.DisallowNull)] - public DateTime? CloseDate { get; set; } + /// + /// Closing date of signup. + /// + [JsonProperty(Required = Required.DisallowNull)] + public DateTime? CloseDate { get; set; } - /// - /// Teams available in signup. - /// - [JsonProperty(Required = Required.Always)] - public List Teams { get; set; } = new List(); - } -} + /// + /// Teams available in signup. + /// + [JsonProperty(Required = Required.Always)] + public List Teams { get; set; } = new List(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs index c19c685..b35a117 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs @@ -3,29 +3,28 @@ using ArmaForces.Boderator.BotService.Features.Missions.DTOs; using ArmaForces.Boderator.Core.Missions.Models; -namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers +namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers; + +public static class MissionMapper { - public static class MissionMapper - { - public static MissionDto Map(Mission mission) - => new() - { - Title = mission.Title, - MissionDate = mission.MissionTime, - MissionId = mission.MissionId - }; + public static MissionDto Map(Mission mission) + => new() + { + Title = mission.Title, + MissionDate = mission.MissionTime, + MissionId = mission.MissionId + }; - public static List Map(List missions) - => missions.Select(Map).ToList(); + public static List Map(List missions) + => missions.Select(Map).ToList(); - public static MissionCreateRequest Map(MissionCreateRequestDto request) - => new MissionCreateRequest - { - Title = request.Title, - Description = request.Description, - Owner = request.Owner, - ModsetName = request.ModsetName, - MissionTime = request.MissionTime - }; - } -} + public static MissionCreateRequest Map(MissionCreateRequestDto request) + => new MissionCreateRequest + { + Title = request.Title, + Description = request.Description, + Owner = request.Owner, + ModsetName = request.ModsetName, + MissionTime = request.MissionTime + }; +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index 9f4044d..e7c3a7a 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -8,86 +8,85 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -namespace ArmaForces.Boderator.BotService.Features.Missions +namespace ArmaForces.Boderator.BotService.Features.Missions; + +/// +/// Allows missions data retrieval and creation. +/// +[Route("api/[controller]")] +public class MissionsController : Controller { - /// - /// Allows missions data retrieval and creation. - /// - [Route("api/[controller]")] - public class MissionsController : Controller - { - private readonly IMissionCommandService _missionCommandService; - private readonly IMissionQueryService _missionQueryService; + private readonly IMissionCommandService _missionCommandService; + private readonly IMissionQueryService _missionQueryService; - public MissionsController(IMissionCommandService missionCommandService, IMissionQueryService missionQueryService) - { - _missionCommandService = missionCommandService; - _missionQueryService = missionQueryService; - } + public MissionsController(IMissionCommandService missionCommandService, IMissionQueryService missionQueryService) + { + _missionCommandService = missionCommandService; + _missionQueryService = missionQueryService; + } - /// - /// - /// - /// - /// - [HttpPost(Name = "Create Mission")] - public async Task> CreateMission([FromBody] MissionCreateRequestDto request) - => await _missionCommandService.CreateMission(MissionMapper.Map(request)) - .Map(MissionMapper.Map) - .Match, MissionDto>( - onSuccess: mission => Created(mission.MissionId.ToString(), mission), - onFailure: error => BadRequest(error)); + /// + /// + /// + /// + /// + [HttpPost(Name = "Create Mission")] + public async Task> CreateMission([FromBody] MissionCreateRequestDto request) + => await _missionCommandService.CreateMission(MissionMapper.Map(request)) + .Map(MissionMapper.Map) + .Match, MissionDto>( + onSuccess: mission => Created(mission.MissionId.ToString(), mission), + onFailure: error => BadRequest(error)); - /// - /// - /// - /// Updated mission data. - [HttpPatch("{missionId:int}", Name = "Update Mission")] - public ActionResult UpdateMission(int missionId) - { - throw new NotImplementedException(); - } + /// + /// + /// + /// Updated mission data. + [HttpPatch("{missionId:int}", Name = "Update Mission")] + public ActionResult UpdateMission(int missionId) + { + throw new NotImplementedException(); + } - /// - /// - /// - /// Deleted mission data. - [HttpDelete("{missionId:int}", Name = "Delete Mission")] - public ActionResult DeleteMission(int missionId) - { - throw new NotImplementedException(); - } + /// + /// + /// + /// Deleted mission data. + [HttpDelete("{missionId:int}", Name = "Delete Mission")] + public ActionResult DeleteMission(int missionId) + { + throw new NotImplementedException(); + } - /// - /// - /// - /// - [HttpGet(Name = "Get Missions")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetMissions() - => await _missionQueryService.GetMissions() - .Map(MissionMapper.Map) - .Match>, List>( - onSuccess: missions => Ok(missions), - onFailure: error => BadRequest(error)); + /// + /// + /// + /// + [HttpGet(Name = "Get Missions")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetMissions() + => await _missionQueryService.GetMissions() + .Map(MissionMapper.Map) + .Match>, List>( + onSuccess: missions => Ok(missions), + onFailure: error => BadRequest(error)); - /// - /// Retrieves mission with given . - /// - /// Unique identifier of a mission - /// - [HttpGet("{missionId:int}", Name = "Get Mission")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetMission(int missionId) - => await _missionQueryService.GetMission(missionId) - .Map(MissionMapper.Map) - .Match, MissionDto>( - onSuccess: mission => Ok(mission), - onFailure: error => NotFound(error)); + /// + /// Retrieves mission with given . + /// + /// Unique identifier of a mission + /// + [HttpGet("{missionId:int}", Name = "Get Mission")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetMission(int missionId) + => await _missionQueryService.GetMission(missionId) + .Map(MissionMapper.Map) + .Match, MissionDto>( + onSuccess: mission => Ok(mission), + onFailure: error => NotFound(error)); - private ActionResult ReturnSomething(Result result) - => result.Match( - onSuccess: x => Ok(x), - onFailure: error => (ActionResult) BadRequest(error)); - } -} + private ActionResult ReturnSomething(Result result) + => result.Match( + onSuccess: x => Ok(x), + onFailure: error => (ActionResult) BadRequest(error)); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index baab162..fe9697c 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -7,84 +7,83 @@ using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Mvc; -namespace ArmaForces.Boderator.BotService.Features.Missions +namespace ArmaForces.Boderator.BotService.Features.Missions; + +/// +/// Allows signups data retrieval and creation. +/// +[Route("api/[controller]")] +public class SignupsController : ControllerBase { - /// - /// Allows signups data retrieval and creation. - /// - [Route("api/[controller]")] - public class SignupsController : ControllerBase - { - private readonly ISignupsQueryService _signupsQueryService; + private readonly ISignupsQueryService _signupsQueryService; - public SignupsController(ISignupsQueryService signupsQueryService) - { - _signupsQueryService = signupsQueryService; - } + public SignupsController(ISignupsQueryService signupsQueryService) + { + _signupsQueryService = signupsQueryService; + } - /// - /// - /// - /// - [HttpPost(Name = "Create Signup")] - public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) - => throw new NotImplementedException(); + /// + /// + /// + /// + [HttpPost(Name = "Create Signup")] + public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) + => throw new NotImplementedException(); - /// - /// - /// - /// - /// - [HttpPatch("{signupId:long}", Name = "Update Signup")] - public ActionResult UpdateSignup(long signupId) - => throw new NotImplementedException(); + /// + /// + /// + /// + /// + [HttpPatch("{signupId:long}", Name = "Update Signup")] + public ActionResult UpdateSignup(long signupId) + => throw new NotImplementedException(); - /// - /// - /// - /// - /// - [HttpDelete("{signupId:long}", Name = "Delete Signup")] - public ActionResult DeleteSignup(long signupId) - => throw new NotImplementedException(); + /// + /// + /// + /// + /// + [HttpDelete("{signupId:long}", Name = "Delete Signup")] + public ActionResult DeleteSignup(long signupId) + => throw new NotImplementedException(); - /// - /// - /// - /// - /// - [HttpGet("{signupId:long}", Name = "Get Signup")] - public async Task> GetSignup(long signupId) - => await _signupsQueryService.GetSignup(signupId) - .Map(SignupsMapper.Map) - .Match, SignupDto>( - onSuccess: signup => Ok(signup), - onFailure: error => BadRequest(error)); + /// + /// + /// + /// + /// + [HttpGet("{signupId:long}", Name = "Get Signup")] + public async Task> GetSignup(long signupId) + => await _signupsQueryService.GetSignup(signupId) + .Map(SignupsMapper.Map) + .Match, SignupDto>( + onSuccess: signup => Ok(signup), + onFailure: error => BadRequest(error)); - /// - /// Returns all signups satisfying query parameters. - /// - /// - [HttpGet(Name = "Get Signups")] - public ActionResult> GetSignups() - => throw new NotImplementedException(); + /// + /// Returns all signups satisfying query parameters. + /// + /// + [HttpGet(Name = "Get Signups")] + public ActionResult> GetSignups() + => throw new NotImplementedException(); - /// - /// Signs up a player for a given slot. - /// - [HttpPost("{signupId:long}/sign", Name = "Sign Player")] - public ActionResult SignPlayer(long signupId, [FromBody] PlayerSignupRequestDto request) - => throw new NotImplementedException(); + /// + /// Signs up a player for a given slot. + /// + [HttpPost("{signupId:long}/sign", Name = "Sign Player")] + public ActionResult SignPlayer(long signupId, [FromBody] PlayerSignupRequestDto request) + => throw new NotImplementedException(); - [HttpPost("{signupId:long}/open", Name = "Open Signup")] - public ActionResult OpenSignup(long signupId) - => throw new NotImplementedException(); + [HttpPost("{signupId:long}/open", Name = "Open Signup")] + public ActionResult OpenSignup(long signupId) + => throw new NotImplementedException(); - /// - /// Immediately closes given signups. - /// - [HttpPost("{signupId:long}/close", Name = "Close Signup")] - public ActionResult CloseSignup(long signupId) - => throw new NotImplementedException(); - } -} + /// + /// Immediately closes given signups. + /// + [HttpPost("{signupId:long}/close", Name = "Close Signup")] + public ActionResult CloseSignup(long signupId) + => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Properties/launchSettings.json b/ArmaForces.Boderator.BotService/Properties/launchSettings.json index b4178bd..396026e 100644 --- a/ArmaForces.Boderator.BotService/Properties/launchSettings.json +++ b/ArmaForces.Boderator.BotService/Properties/launchSettings.json @@ -22,7 +22,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "AF_Boderator_DiscordToken": "", - "AF_Boderator_ConnectionString": "Data Source=" + "AF_Boderator_ConnectionString": "Data Source=D:\\Git\\ArmaForces\\Boderator\\Mydlarz_Test.db" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" } diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs index dd354fe..cee369c 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Models; @@ -15,7 +13,7 @@ public SignupsDbHelper(MissionContext missionContext) _missionContext = missionContext; } - public async Task CreateTestSignup(int missionId) + public async Task CreateTestSignup(int missionId) { var signup = SignupsFixture.CreateTestSignup(missionId); @@ -24,19 +22,4 @@ public SignupsDbHelper(MissionContext missionContext) return addedEntry.Entity; } -} - -internal static class SignupsFixture -{ - public static Core.Missions.Models.Signups CreateTestSignup(int missionId) - { - return new Core.Missions.Models.Signups - { - MissionId = missionId, - SignupStatus = SignupStatus.Open, - StartDate = DateTime.Now, - CloseDate = DateTime.Now.AddHours(1), - Teams = new List() - }; - } -} +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs new file mode 100644 index 0000000..155cf79 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; + +internal static class SignupsFixture +{ + public static Signups CreateTestSignup(int missionId) + { + return new Signups + { + MissionId = missionId, + SignupStatus = SignupStatus.Open, + StartDate = DateTime.Now, + CloseDate = DateTime.Now.AddHours(1), + Teams = new List() + }; + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs index ef02863..ab23baf 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/IMissionCommandRepository.cs @@ -2,10 +2,9 @@ using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; + +internal interface IMissionCommandRepository { - internal interface IMissionCommandRepository - { - Task> CreateMission(Mission missionToCreate); - } -} + Task> CreateMission(Mission missionToCreate); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs index 027496a..5ecb53a 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs @@ -1,23 +1,22 @@ using ArmaForces.Boderator.Core.Missions.Models; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence; + +internal sealed class MissionContext : DbContext { - internal sealed class MissionContext : DbContext + public MissionContext(DbContextOptions options) + : base(options) { - public MissionContext(DbContextOptions options) - : base(options) - { - Database.EnsureCreated(); - } + Database.EnsureCreated(); + } - public DbSet Missions { get; set; } - public DbSet Signups { get; set; } + public DbSet Missions { get; set; } + public DbSet Signups { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasKey(x => new {x.SignupsId, x.Name}); - } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(x => new {x.SignupsId, x.Name}); } -} +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs index 0978e41..17d638e 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; + +internal interface ISignupsQueryRepository { - internal interface ISignupsQueryRepository - { - public Task> GetAllSignups(); + public Task> GetAllSignups(); - public Task> GetOpenSignups(); + public Task> GetOpenSignups(); - public Task GetSignup(long signupId); - } -} + public Task GetSignup(long signupId); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs index dd40739..fa9be0d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs @@ -4,30 +4,29 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; + +internal class SignupsQueryRepository : ISignupsQueryRepository { - internal class SignupsQueryRepository : ISignupsQueryRepository - { - private readonly MissionContext _context; + private readonly MissionContext _context; - public SignupsQueryRepository(MissionContext context) - { - _context = context; - } + public SignupsQueryRepository(MissionContext context) + { + _context = context; + } - public async Task> GetAllSignups() - { - return await _context.Signups.ToListAsync(); - } + public async Task> GetAllSignups() + { + return await _context.Signups.ToListAsync(); + } - public async Task> GetOpenSignups() - => await _context.Signups - .Where(x => x.CloseDate > DateTime.Now) - .ToListAsync(); + public async Task> GetOpenSignups() + => await _context.Signups + .Where(x => x.CloseDate > DateTime.Now) + .ToListAsync(); - public async Task GetSignup(long signupId) - => await _context.Signups - .FindAsync(signupId) - .AsTask(); - } -} + public async Task GetSignup(long signupId) + => await _context.Signups + .FindAsync(signupId) + .AsTask(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs index fe8b645..87a8070 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs @@ -4,22 +4,21 @@ using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using CSharpFunctionalExtensions; -namespace ArmaForces.Boderator.Core.Missions.Implementation +namespace ArmaForces.Boderator.Core.Missions.Implementation; + +internal class SignupsQueryService : ISignupsQueryService { - internal class SignupsQueryService : ISignupsQueryService - { - private readonly ISignupsQueryRepository _signupsQueryRepository; + private readonly ISignupsQueryRepository _signupsQueryRepository; - public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) - { - _signupsQueryRepository = signupsQueryRepository; - } + public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) + { + _signupsQueryRepository = signupsQueryRepository; + } - public async Task> GetSignup(long signupId) - => await _signupsQueryRepository.GetSignup(signupId) - ?? Result.Failure($"Signup with ID {signupId} not found"); + public async Task> GetSignup(long signupId) + => await _signupsQueryRepository.GetSignup(signupId) + ?? Result.Failure($"Signup with ID {signupId} not found"); - public async Task>> GetOpenSignups() - => await _signupsQueryRepository.GetOpenSignups(); - } -} + public async Task>> GetOpenSignups() + => await _signupsQueryRepository.GetOpenSignups(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs index 8100b3d..1c5ca7c 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs @@ -1,19 +1,18 @@ using System; -namespace ArmaForces.Boderator.Core.Missions.Models +namespace ArmaForces.Boderator.Core.Missions.Models; + +public record Mission { - public record Mission - { - public int MissionId { get; init; } + public int MissionId { get; init; } - public string Title { get; init; } = string.Empty; + public string Title { get; init; } = string.Empty; - public DateTime? MissionTime { get; init; } + public DateTime? MissionTime { get; init; } - public string? ModsetName { get; init; } = string.Empty; + public string? ModsetName { get; init; } = string.Empty; - public string Owner { get; init; } = string.Empty; + public string Owner { get; init; } = string.Empty; - public Signups? Signups { get; init; } - } -} + public Signups? Signups { get; init; } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs index 9ccdddf..93ef575 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs @@ -1,33 +1,32 @@ using System; -namespace ArmaForces.Boderator.Core.Missions.Models +namespace ArmaForces.Boderator.Core.Missions.Models; + +public record MissionCreateRequest { - public record MissionCreateRequest - { - private readonly string _title = null!; - private readonly string _owner = null!; + private readonly string _title = null!; + private readonly string _owner = null!; - public string Title - { - get => _title; - init => _title = ValidateStringNotEmpty(value, nameof(Title)); - } + public string Title + { + get => _title; + init => _title = ValidateStringNotEmpty(value, nameof(Title)); + } - public string? Description { get; init; } + public string? Description { get; init; } - public DateTime? MissionTime { get; init; } + public DateTime? MissionTime { get; init; } - public string? ModsetName { get; init; } + public string? ModsetName { get; init; } - public string Owner - { - get => _owner; - init => _owner = ValidateStringNotEmpty(value, nameof(Owner)); - } - - private static string ValidateStringNotEmpty(string? value, string propertyName) - => !string.IsNullOrWhiteSpace(value) - ? value - : throw new ArgumentNullException(propertyName); + public string Owner + { + get => _owner; + init => _owner = ValidateStringNotEmpty(value, nameof(Owner)); } -} + + private static string ValidateStringNotEmpty(string? value, string propertyName) + => !string.IsNullOrWhiteSpace(value) + ? value + : throw new ArgumentNullException(propertyName); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index d8ba229..e781eef 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -1,20 +1,19 @@ using System; using System.Collections.Generic; -namespace ArmaForces.Boderator.Core.Missions.Models +namespace ArmaForces.Boderator.Core.Missions.Models; + +public record Signups { - public record Signups - { - public int SignupsId { get; init; } + public int SignupsId { get; init; } - public SignupStatus SignupStatus { get; init; } + public SignupStatus SignupStatus { get; init; } - public DateTime StartDate { get; init; } + public DateTime StartDate { get; init; } - public DateTime CloseDate { get; init; } + public DateTime CloseDate { get; init; } - public int MissionId { get; init; } + public int MissionId { get; init; } - public IReadOnlyList Teams { get; init; } = new List(); - } -} + public IReadOnlyList Teams { get; init; } = new List(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs index aebb064..c826f42 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs @@ -1,13 +1,12 @@ -namespace ArmaForces.Boderator.Core.Missions.Models +namespace ArmaForces.Boderator.Core.Missions.Models; + +public record Slot { - public record Slot - { - public long? SlotId { get; init; } + public long? SlotId { get; init; } - public string Name { get; init; } = string.Empty; + public string Name { get; init; } = string.Empty; - public string Vehicle { get; init; } = string.Empty; + public string Vehicle { get; init; } = string.Empty; - public string? Occupant { get; init; } - } -} + public string? Occupant { get; init; } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs index ff1f471..d900745 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; -namespace ArmaForces.Boderator.Core.Missions.Models +namespace ArmaForces.Boderator.Core.Missions.Models; + +public record Team { - public record Team - { - public string Name { get; init; } = string.Empty; + public string Name { get; init; } = string.Empty; - public string Vehicle { get; init; } = string.Empty; + public string Vehicle { get; init; } = string.Empty; - public IReadOnlyList Slots { get; init; } = new List(); + public IReadOnlyList Slots { get; init; } = new List(); - public int SignupsId { get; init; } - } -} + public int SignupsId { get; init; } +} \ No newline at end of file From a8872f047af19d30e0cea18a29d5dc759bf9fdc8 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 19:58:45 +0200 Subject: [PATCH 57/87] Add GetSignups(id) tests --- .../Features/Missions/DTOs/MissionDto.cs | 2 +- .../Missions/DTOs/SignupCreateRequestDto.cs | 2 +- .../Features/Missions/DTOs/SignupDto.cs | 6 ++-- .../Missions/Mappers/SignupsMapper.cs | 2 +- .../Features/Missions/SignupsController.cs | 2 +- .../Missions/Helpers/SignupsDbHelper.cs | 14 +++++++-- .../Missions/Helpers/SignupsFixture.cs | 4 +-- .../MissionQueryServiceIntegrationTests.cs | 6 ++-- .../MissionQueryServiceUnitTests.cs | 4 +-- .../SignupsQueryServiceIntegrationTests.cs | 29 ++++++++++++++++--- .../Features/Missions/IMissionQueryService.cs | 2 +- .../Features/Missions/ISignupsQueryService.cs | 2 +- .../Implementation/MissionQueryService.cs | 2 +- .../Query/IMissionQueryRepository.cs | 2 +- .../Query/MissionQueryRepository.cs | 2 +- .../Implementation/SignupsQueryService.cs | 2 +- .../Features/Missions/Models/Mission.cs | 2 +- .../Features/Missions/Models/Signups.cs | 6 ++-- .../{SignupStatus.cs => SignupsStatus.cs} | 2 +- 19 files changed, 62 insertions(+), 31 deletions(-) rename ArmaForces.Boderator.Core.Tests/Features/Missions/{ => Implementation}/MissionQueryServiceIntegrationTests.cs (85%) rename ArmaForces.Boderator.Core.Tests/Features/Missions/{ => Implementation}/MissionQueryServiceUnitTests.cs (88%) rename ArmaForces.Boderator.Core.Tests/Features/Missions/{ => Implementation}/SignupsQueryServiceIntegrationTests.cs (54%) rename ArmaForces.Boderator.Core/Features/Missions/Models/{SignupStatus.cs => SignupsStatus.cs} (74%) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index 82ad2ee..c8632ce 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -4,7 +4,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record MissionDto { - public int MissionId { get; init; } + public long MissionId { get; init; } public string Title { get; init; } = string.Empty; diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs index ec70ac4..715523f 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs @@ -24,7 +24,7 @@ public class SignupCreateRequestDto /// Desired status of signups. /// [JsonProperty(Required = Required.DisallowNull)] - public SignupStatus? SignupStatus { get; set; } + public SignupsStatus? SignupsStatus { get; set; } /// /// Starting date of signup. diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs index cd6a855..ab33bab 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs @@ -6,9 +6,9 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record SignupDto { - public int SignupId { get; init; } + public long SignupId { get; init; } - public SignupStatus SignupStatus { get; init; } + public SignupsStatus SignupsStatus { get; init; } public DateTime? StartDate { get; init; } @@ -16,5 +16,5 @@ public record SignupDto public List Teams { get; init; } = new(); - public int MissionId { get; init; } + public long MissionId { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 526b39d..ef640b4 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -7,7 +7,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers; public static class SignupsMapper { - public static SignupDto Map(Core.Missions.Models.Signups signups) + public static SignupDto Map(Signups signups) => new() { SignupId = signups.SignupsId, diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index fe9697c..83b1f21 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -55,7 +55,7 @@ public ActionResult DeleteSignup(long signupId) /// [HttpGet("{signupId:long}", Name = "Get Signup")] public async Task> GetSignup(long signupId) - => await _signupsQueryService.GetSignup(signupId) + => await _signupsQueryService.GetSignups(signupId) .Map(SignupsMapper.Map) .Match, SignupDto>( onSuccess: signup => Ok(signup), diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs index cee369c..a61e0a9 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs @@ -6,14 +6,18 @@ namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; internal class SignupsDbHelper { + private readonly MissionsDbHelper _missionsDbHelper; private readonly MissionContext _missionContext; - public SignupsDbHelper(MissionContext missionContext) + public SignupsDbHelper( + MissionsDbHelper missionsDbHelper, + MissionContext missionContext) { + _missionsDbHelper = missionsDbHelper; _missionContext = missionContext; } - public async Task CreateTestSignup(int missionId) + public async Task CreateTestSignup(long missionId) { var signup = SignupsFixture.CreateTestSignup(missionId); @@ -22,4 +26,10 @@ public async Task CreateTestSignup(int missionId) return addedEntry.Entity; } + + public async Task CreateTestSignup() + { + var mission = await _missionsDbHelper.CreateTestMission(); + return await CreateTestSignup(mission.MissionId); + } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs index 155cf79..6d43851 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs @@ -6,12 +6,12 @@ namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; internal static class SignupsFixture { - public static Signups CreateTestSignup(int missionId) + public static Signups CreateTestSignup(long missionId) { return new Signups { MissionId = missionId, - SignupStatus = SignupStatus.Open, + SignupsStatus = SignupsStatus.Open, StartDate = DateTime.Now, CloseDate = DateTime.Now.AddHours(1), Teams = new List() diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs similarity index 85% rename from ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs rename to ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs index 8444edb..cde77ea 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Models; using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; using ArmaForces.Boderator.Core.Tests.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace ArmaForces.Boderator.Core.Tests.Features.Missions; +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation; public class MissionQueryServiceIntegrationTests : DatabaseTestBase { @@ -19,7 +20,8 @@ public MissionQueryServiceIntegrationTests() _missionsDbHelper = ServiceProvider.GetRequiredService(); _missionQueryService = ServiceProvider.GetRequiredService(); - // DbContextTransaction = _missionContext.Database.BeginTransaction(); + var missionContext = ServiceProvider.GetRequiredService(); + DbContextTransaction = missionContext.Database.BeginTransaction(); } [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs similarity index 88% rename from ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs rename to ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs index e85066d..32bd689 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/MissionQueryServiceUnitTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs @@ -1,9 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Missions; using ArmaForces.Boderator.Core.Missions.Implementation; -using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using ArmaForces.Boderator.Core.Missions.Models; using ArmaForces.Boderator.Core.Tests.TestUtilities; @@ -11,7 +9,7 @@ using Moq; using Xunit; -namespace ArmaForces.Boderator.Core.Tests.Features.Missions; +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation; public class MissionQueryServiceUnitTests { diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs similarity index 54% rename from ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs rename to ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs index 3b7b848..f143443 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/SignupsQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs @@ -1,12 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; using ArmaForces.Boderator.Core.Tests.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace ArmaForces.Boderator.Core.Tests.Features.Missions; +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation; public class SignupsQueryServiceIntegrationTests : DatabaseTestBase { @@ -20,14 +22,15 @@ public SignupsQueryServiceIntegrationTests() _signupsDbHelper = ServiceProvider.GetRequiredService(); _signupsQueryService = ServiceProvider.GetRequiredService(); - // DbContextTransaction = _missionContext.Database.BeginTransaction(); + var missionContext = ServiceProvider.GetRequiredService(); + DbContextTransaction = missionContext.Database.BeginTransaction(); } [Fact, Trait("Category", "Integration")] public async Task GetOpenSignups_NoSignupsInDatabase_ReturnsEmptyList() { var result = await _signupsQueryService.GetOpenSignups(); - result.ShouldBeSuccess(new List()); + result.ShouldBeSuccess(new List()); } [Fact, Trait("Category", "Integration")] @@ -37,6 +40,24 @@ public async Task GetOpenSignups_NoOpenSignupsInDatabase_ReturnsEmptyList() var signup = await _signupsDbHelper.CreateTestSignup(testMission.MissionId); var result = await _signupsQueryService.GetOpenSignups(); - result.ShouldBeSuccess(new List{signup}); + result.ShouldBeSuccess(new List{signup}); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetSignup_SignupWithGivenIdDoesntExist_ReturnsFailure() + { + const int nonExistingSignupsId = 0; + var result = await _signupsQueryService.GetSignups(nonExistingSignupsId); + + result.ShouldBeFailure($"Signup with ID {nonExistingSignupsId} not found"); + } + + [Fact, Trait("Category", "Integration")] + public async Task GetSignup_SignupWithGivenIdExists_ReturnsSignups() + { + var signups = await _signupsDbHelper.CreateTestSignup(); + var result = await _signupsQueryService.GetSignups(signups.SignupsId); + + result.ShouldBeSuccess(signups); } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs index aa100a6..90a1a2d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs @@ -7,7 +7,7 @@ namespace ArmaForces.Boderator.Core.Missions; public interface IMissionQueryService { - Task> GetMission(int missionId); + Task> GetMission(long missionId); Task>> GetMissions(); } diff --git a/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs index a78e8a5..3d4a86f 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/ISignupsQueryService.cs @@ -7,7 +7,7 @@ namespace ArmaForces.Boderator.Core.Missions; public interface ISignupsQueryService { - Task> GetSignup(long signupId); + Task> GetSignups(long signupId); Task>> GetOpenSignups(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs index f4e31fe..7c96ed8 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs @@ -16,7 +16,7 @@ public MissionQueryService(IMissionQueryRepository missionQueryRepository) _missionQueryRepository = missionQueryRepository; } - public async Task> GetMission(int missionId) + public async Task> GetMission(long missionId) => await _missionQueryRepository.GetMission(missionId) ?? Result.Failure($"Mission with ID {missionId} does not exist."); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs index ecc5482..7c8abda 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs @@ -6,7 +6,7 @@ namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; internal interface IMissionQueryRepository { - Task GetMission(int missionId); + Task GetMission(long missionId); Task> GetMissions(); } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs index bfee158..a06ef82 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs @@ -15,7 +15,7 @@ public MissionQueryRepository(MissionContext context) _context = context ?? throw new ArgumentNullException(nameof(context)); } - public async Task GetMission(int missionId) + public async Task GetMission(long missionId) => await _context.Missions.FindAsync(missionId); public async Task> GetMissions() diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs index 87a8070..4906cf6 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs @@ -15,7 +15,7 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) _signupsQueryRepository = signupsQueryRepository; } - public async Task> GetSignup(long signupId) + public async Task> GetSignups(long signupId) => await _signupsQueryRepository.GetSignup(signupId) ?? Result.Failure($"Signup with ID {signupId} not found"); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs index 1c5ca7c..402f4d8 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs @@ -4,7 +4,7 @@ namespace ArmaForces.Boderator.Core.Missions.Models; public record Mission { - public int MissionId { get; init; } + public long MissionId { get; init; } public string Title { get; init; } = string.Empty; diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index e781eef..f206efe 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -5,15 +5,15 @@ namespace ArmaForces.Boderator.Core.Missions.Models; public record Signups { - public int SignupsId { get; init; } + public long SignupsId { get; init; } - public SignupStatus SignupStatus { get; init; } + public SignupsStatus SignupsStatus { get; init; } public DateTime StartDate { get; init; } public DateTime CloseDate { get; init; } - public int MissionId { get; init; } + public long MissionId { get; init; } public IReadOnlyList Teams { get; init; } = new List(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupStatus.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs similarity index 74% rename from ArmaForces.Boderator.Core/Features/Missions/Models/SignupStatus.cs rename to ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs index 9d1bb79..f84ef51 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupStatus.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs @@ -1,6 +1,6 @@ namespace ArmaForces.Boderator.Core.Missions.Models; -public enum SignupStatus +public enum SignupsStatus { Prebeton, From 6ccbaba53758f6bedf1a770d9e5a2624f715c5e5 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 21:36:22 +0200 Subject: [PATCH 58/87] Add API CreateMission tests --- .../Missions/MissionsControllerTests.cs | 97 ++++++++++++++----- .../Missions/DTOs/MissionCreateRequestDto.cs | 8 +- .../Features/Missions/DTOs/MissionDto.cs | 8 ++ .../Missions/Mappers/MissionMapper.cs | 12 ++- .../Missions/Mappers/SignupsMapper.cs | 20 ++-- .../ResultAssertionsExtensions.cs | 30 +++++- .../Implementation/MissionCommandService.cs | 20 +++- .../Features/Missions/Models/Mission.cs | 6 +- .../Missions/Models/MissionCreateRequest.cs | 2 +- 9 files changed, 154 insertions(+), 49 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs index 91ccd3b..5563740 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -1,10 +1,11 @@ +using System; +using System.Collections.Generic; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; using ArmaForces.Boderator.Core.Tests.TestUtilities; using AutoFixture; -using Newtonsoft.Json; using Xunit; namespace ArmaForces.Boderator.BotService.Tests.Features.Missions @@ -14,34 +15,21 @@ public class MissionsControllerTests : ApiTestBase { public MissionsControllerTests(TestApiServiceFixture testApi) : base(testApi) { } - - [Fact, Trait("Category", "Integration")] - public async Task CreateMission_InvalidRequest_ReturnsBadRequest() + + [Theory, ClassData(typeof(CreateMissionInvalidRequestTestData)), Trait("Category", "Integration")] + public async Task CreateMission_InvalidRequest_ReturnsBadRequest(MissionCreateRequestDto missionCreateRequestDto) { - var missionCreateRequestWithoutOwner = new MissionCreateRequestDto - { - Title = Fixture.Create(), - Description = Fixture.Create() - }; - - var result = await HttpPostAsync("api/missions", missionCreateRequestWithoutOwner); + var result = await HttpPostAsync("api/missions", missionCreateRequestDto); result.ShouldBeFailure(); } - [Fact, Trait("Category", "Integration")] - public async Task CreateMission_ValidRequest_MissionCreated() + [Theory, ClassData(typeof(CreateMissionValidRequestTestData)), Trait("Category", "Integration")] + public async Task CreateMission_ValidRequest_MissionCreated(MissionCreateRequestDto missionCreateRequestDto) { - var missionCreateRequest = new MissionCreateRequestDto - { - Title = Fixture.Create(), - Owner = Fixture.Create(), - Description = Fixture.Create() - }; + var result = await HttpPostAsync("api/missions", missionCreateRequestDto); - var result = await HttpPostAsync("api/missions", missionCreateRequest); - - result.ShouldBeSuccess(); + result.ShouldBeSuccess(missionCreateRequestDto); } [Fact, Trait("Category", "Integration")] @@ -59,6 +47,8 @@ public async Task GetMission_MissionExists_ReturnsExistingMission() var expectedMission = new MissionDto { Title = missionCreateResult.Value.Title, + Description = missionCreateRequest.Description, + Owner = missionCreateRequest.Owner, MissionDate = missionCreateResult.Value.MissionDate, MissionId = missionCreateResult.Value.MissionId }; @@ -68,4 +58,67 @@ public async Task GetMission_MissionExists_ReturnsExistingMission() result.ShouldBeSuccess(expectedMission); } } + + public class CreateMissionValidRequestTestData : TheoryData + { + private readonly MissionCreateRequestDto _minimalRequest = new() + { + Title = "Test mission title", + Owner = "Test mission owner" + }; + + public CreateMissionValidRequestTestData() + { + var testCases = new List + { + _minimalRequest, + _minimalRequest with + { + Description = "Test mission description" + }, + _minimalRequest with + { + MissionDate = DateTime.Now.AddHours(-1) + }, + _minimalRequest with + { + ModsetName = "Test-mission-modset" + }, + _minimalRequest with + { + Description = "Test mission description", + MissionDate = DateTime.Now.AddHours(-1), + ModsetName = "Test-mission-modset" + } + }; + + foreach (var testCase in testCases) Add(testCase); + } + } + + public class CreateMissionInvalidRequestTestData : TheoryData + { + public CreateMissionInvalidRequestTestData() + { + var testCases = new List + { + new() + { + Title = "Test mission title without owner" + }, + new() + { + Owner = "Test mission owner without title" + }, + new() + { + Title = "Test mission title", + Owner = "Test mission owner", + ModsetName = "Modset name with whitespace characters" + } + }; + + foreach (var testCase in testCases) Add(testCase); + } + } } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs index 266f0da..a35e3ba 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs @@ -3,7 +3,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; -public class MissionCreateRequestDto +public record MissionCreateRequestDto { /// /// Mission title. @@ -14,14 +14,14 @@ public class MissionCreateRequestDto /// /// Mission description. /// - [JsonProperty(Required = Required.Always)] - public string Description { get; set; } = string.Empty; + [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + public string? Description { get; set; } /// /// Mission start time. /// [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] - public DateTime? MissionTime { get; set; } + public DateTime? MissionDate { get; set; } /// /// Name of the modset. diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index c8632ce..894cef4 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; @@ -7,8 +8,15 @@ public record MissionDto public long MissionId { get; init; } public string Title { get; init; } = string.Empty; + + public string? Description { get; init; } public DateTime? MissionDate { get; init; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? ModsetName { get; init; } + + public string Owner { get; init; } = string.Empty; public SignupDto? Signups { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs index b35a117..85b87b2 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs @@ -11,20 +11,24 @@ public static MissionDto Map(Mission mission) => new() { Title = mission.Title, - MissionDate = mission.MissionTime, - MissionId = mission.MissionId + Description = mission.Description, + Owner = mission.Owner, + ModsetName = mission.ModsetName, + MissionDate = mission.MissionDate, + MissionId = mission.MissionId, + Signups = SignupsMapper.Map(mission.Signups) }; public static List Map(List missions) => missions.Select(Map).ToList(); public static MissionCreateRequest Map(MissionCreateRequestDto request) - => new MissionCreateRequest + => new() { Title = request.Title, Description = request.Description, Owner = request.Owner, ModsetName = request.ModsetName, - MissionTime = request.MissionTime + MissionDate = request.MissionDate }; } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index ef640b4..de87a56 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -7,15 +7,17 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers; public static class SignupsMapper { - public static SignupDto Map(Signups signups) - => new() - { - SignupId = signups.SignupsId, - StartDate = signups.StartDate, - CloseDate = signups.CloseDate, - MissionId = signups.MissionId, - Teams = Map(signups.Teams) - }; + public static SignupDto? Map(Signups? signups) + => signups is null + ? null + : new SignupDto + { + SignupId = signups.SignupsId, + StartDate = signups.StartDate, + CloseDate = signups.CloseDate, + MissionId = signups.MissionId, + Teams = Map(signups.Teams) + }; public static TeamDto Map(Team team) => new() diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs index fde4120..541ded3 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -15,6 +15,17 @@ public static void ShouldBeFailure(this Result result) result.IsSuccess.Should().BeFalse(); } } + + public static void ShouldBeFailure(this Result result) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + result.IsSuccess.Should().BeFalse(); + result.Value.Should().BeNull(); + } + } public static void ShouldBeFailure(this Result result, string expectedError) { @@ -43,7 +54,7 @@ public static void ShouldBeFailure(this Result result, string expectedErro result.Error.Should().Be(expectedError); } } - + public static void ShouldBeSuccess(this Result result) { using var scope = new AssertionScope(); @@ -54,8 +65,21 @@ public static void ShouldBeSuccess(this Result result) result.Error.Should().BeNull(); } } - - public static void ShouldBeSuccess(this Result result, T expectedValue) + + public static void ShouldBeSuccess(this Result result) + { + using var scope = new AssertionScope(); + + if (result.IsFailure) + { + result.IsSuccess.Should().BeTrue(); + result.Error.Should().BeNull(); + } + + result.Value.Should().NotBeNull(); + } + + public static void ShouldBeSuccess(this Result result, T2 expectedValue) { using var scope = new AssertionScope(); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs index 10ea298..e90791e 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs @@ -1,5 +1,5 @@ +using System.Linq; using System.Threading.Tasks; -using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; @@ -16,12 +16,24 @@ public MissionCommandService(IMissionCommandRepository missionCommandRepository) } public async Task> CreateMission(MissionCreateRequest missionCreateRequest) - => await _missionCommandRepository.CreateMission( + { + return await ValidateRequest(missionCreateRequest) + .Bind(() => _missionCommandRepository.CreateMission( new Mission { Title = missionCreateRequest.Title, + Description = missionCreateRequest.Description, Owner = missionCreateRequest.Owner, - MissionTime = missionCreateRequest.MissionTime, + MissionDate = missionCreateRequest.MissionDate, ModsetName = missionCreateRequest.ModsetName - }); + })); + } + + private static Result ValidateRequest(MissionCreateRequest missionCreateRequest) + { + if (missionCreateRequest.ModsetName?.Any(char.IsWhiteSpace) ?? false) + return Result.Failure($"{nameof(MissionCreateRequest.ModsetName)} cannot contain whitespace characters."); + + return Result.Success(); + } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs index 402f4d8..1ef68b9 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Mission.cs @@ -7,10 +7,12 @@ public record Mission public long MissionId { get; init; } public string Title { get; init; } = string.Empty; + + public string? Description { get; init; } = string.Empty; - public DateTime? MissionTime { get; init; } + public DateTime? MissionDate { get; init; } - public string? ModsetName { get; init; } = string.Empty; + public string? ModsetName { get; init; } public string Owner { get; init; } = string.Empty; diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs index 93ef575..cbc9b0e 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/MissionCreateRequest.cs @@ -15,7 +15,7 @@ public string Title public string? Description { get; init; } - public DateTime? MissionTime { get; init; } + public DateTime? MissionDate { get; init; } public string? ModsetName { get; init; } From 0b56b07790d71471f0268c3de64ec7bc6e461db0 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 21:37:56 +0200 Subject: [PATCH 59/87] Revert accidental launchSettings.json change --- ArmaForces.Boderator.BotService/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.BotService/Properties/launchSettings.json b/ArmaForces.Boderator.BotService/Properties/launchSettings.json index 396026e..b4178bd 100644 --- a/ArmaForces.Boderator.BotService/Properties/launchSettings.json +++ b/ArmaForces.Boderator.BotService/Properties/launchSettings.json @@ -22,7 +22,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "AF_Boderator_DiscordToken": "", - "AF_Boderator_ConnectionString": "Data Source=D:\\Git\\ArmaForces\\Boderator\\Mydlarz_Test.db" + "AF_Boderator_ConnectionString": "Data Source=" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" } From 37ebbe873d93b1add9cab8c8d7a5744caf931e18 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 22:02:00 +0200 Subject: [PATCH 60/87] Store SignupsStatus as short in db --- .../Features/Missions/DTOs/SignupDto.cs | 2 +- .../Features/Missions/Mappers/SignupsMapper.cs | 1 + .../Features/Missions/Helpers/SignupsFixture.cs | 2 +- .../Features/Missions/Models/Signups.cs | 4 ++-- .../Features/Missions/Models/SignupsStatus.cs | 6 ++++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs index ab33bab..34b7e70 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs @@ -8,7 +8,7 @@ public record SignupDto { public long SignupId { get; init; } - public SignupsStatus SignupsStatus { get; init; } + public SignupsStatus Status { get; init; } public DateTime? StartDate { get; init; } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index de87a56..25aa206 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -13,6 +13,7 @@ public static class SignupsMapper : new SignupDto { SignupId = signups.SignupsId, + Status = (SignupsStatus) signups.Status, StartDate = signups.StartDate, CloseDate = signups.CloseDate, MissionId = signups.MissionId, diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs index 6d43851..3ba4588 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs @@ -11,7 +11,7 @@ public static Signups CreateTestSignup(long missionId) return new Signups { MissionId = missionId, - SignupsStatus = SignupsStatus.Open, + Status = (int) SignupsStatus.Open, StartDate = DateTime.Now, CloseDate = DateTime.Now.AddHours(1), Teams = new List() diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index f206efe..848af41 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -6,8 +6,8 @@ namespace ArmaForces.Boderator.Core.Missions.Models; public record Signups { public long SignupsId { get; init; } - - public SignupsStatus SignupsStatus { get; init; } + + public ushort Status { get; init; } = (ushort) SignupsStatus.Closed; public DateTime StartDate { get; init; } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs index f84ef51..4a65559 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs @@ -1,6 +1,8 @@ -namespace ArmaForces.Boderator.Core.Missions.Models; +using System; -public enum SignupsStatus +namespace ArmaForces.Boderator.Core.Missions.Models; + +public enum SignupsStatus : ushort { Prebeton, From 767b2bc0652286e52fe0a662dcfcf7edbc61f88d Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 22:32:44 +0200 Subject: [PATCH 61/87] Fix SignupsMapper --- .../Missions/Mappers/MissionMapper.cs | 4 +++- .../Missions/Mappers/SignupsMapper.cs | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs index 85b87b2..694990c 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/MissionMapper.cs @@ -16,7 +16,9 @@ public static MissionDto Map(Mission mission) ModsetName = mission.ModsetName, MissionDate = mission.MissionDate, MissionId = mission.MissionId, - Signups = SignupsMapper.Map(mission.Signups) + Signups = mission.Signups is null + ? null + : SignupsMapper.Map(mission.Signups) }; public static List Map(List missions) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 25aa206..9d55ce2 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -7,19 +7,17 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers; public static class SignupsMapper { - public static SignupDto? Map(Signups? signups) - => signups is null - ? null - : new SignupDto - { - SignupId = signups.SignupsId, - Status = (SignupsStatus) signups.Status, - StartDate = signups.StartDate, - CloseDate = signups.CloseDate, - MissionId = signups.MissionId, - Teams = Map(signups.Teams) - }; - + public static SignupDto Map(Signups signups) + => new() + { + SignupId = signups.SignupsId, + Status = (SignupsStatus) signups.Status, + StartDate = signups.StartDate, + CloseDate = signups.CloseDate, + MissionId = signups.MissionId, + Teams = Map(signups.Teams) + }; + public static TeamDto Map(Team team) => new() { From c5316d458323f7adbffa001b2d37e8df20432ac9 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 23:07:09 +0200 Subject: [PATCH 62/87] Fix Signups spelling --- .../Features/Missions/DTOs/MissionDto.cs | 2 +- ...questDto.cs => SignupsCreateRequestDto.cs} | 2 +- .../DTOs/{SignupDto.cs => SignupsDto.cs} | 2 +- .../Missions/Mappers/SignupsMapper.cs | 2 +- .../Features/Missions/SignupsController.cs | 44 +++++++++---------- 5 files changed, 26 insertions(+), 26 deletions(-) rename ArmaForces.Boderator.BotService/Features/Missions/DTOs/{SignupCreateRequestDto.cs => SignupsCreateRequestDto.cs} (97%) rename ArmaForces.Boderator.BotService/Features/Missions/DTOs/{SignupDto.cs => SignupsDto.cs} (91%) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index 894cef4..6e88484 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -18,5 +18,5 @@ public record MissionDto public string Owner { get; init; } = string.Empty; - public SignupDto? Signups { get; init; } + public SignupsDto? Signups { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs similarity index 97% rename from ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs index 715523f..8a1e8b9 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs @@ -5,7 +5,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; -public class SignupCreateRequestDto +public class SignupsCreateRequestDto { /// /// Id of the mission for which the signup will be created. diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs similarity index 91% rename from ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs index 34b7e70..d7194f4 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs @@ -4,7 +4,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; -public record SignupDto +public record SignupsDto { public long SignupId { get; init; } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 9d55ce2..305afcf 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -7,7 +7,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.Mappers; public static class SignupsMapper { - public static SignupDto Map(Signups signups) + public static SignupsDto Map(Signups signups) => new() { SignupId = signups.SignupsId, diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index 83b1f21..2b36260 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -26,64 +26,64 @@ public SignupsController(ISignupsQueryService signupsQueryService) /// /// /// - [HttpPost(Name = "Create Signup")] - public ActionResult CreateSignup([FromBody] SignupCreateRequestDto request) + [HttpPost(Name = "Create Signups")] + public ActionResult CreateSignup([FromBody] SignupsCreateRequestDto request) => throw new NotImplementedException(); /// /// /// - /// + /// /// - [HttpPatch("{signupId:long}", Name = "Update Signup")] - public ActionResult UpdateSignup(long signupId) + [HttpPatch("{signupsId:long}", Name = "Update Signups")] + public ActionResult UpdateSignup(long signupsId) => throw new NotImplementedException(); /// /// /// - /// + /// /// - [HttpDelete("{signupId:long}", Name = "Delete Signup")] - public ActionResult DeleteSignup(long signupId) + [HttpDelete("{signupsId:long}", Name = "Delete Signups")] + public ActionResult DeleteSignup(long signupsId) => throw new NotImplementedException(); /// /// /// - /// + /// /// - [HttpGet("{signupId:long}", Name = "Get Signup")] - public async Task> GetSignup(long signupId) - => await _signupsQueryService.GetSignups(signupId) + [HttpGet("{signupsId:long}", Name = "Get Signups")] + public async Task> GetSignups(long signupsId) + => await _signupsQueryService.GetSignups(signupsId) .Map(SignupsMapper.Map) - .Match, SignupDto>( - onSuccess: signup => Ok(signup), + .Match, SignupsDto>( + onSuccess: signups => Ok(signups), onFailure: error => BadRequest(error)); /// /// Returns all signups satisfying query parameters. /// /// - [HttpGet(Name = "Get Signups")] - public ActionResult> GetSignups() + [HttpGet(Name = "Query Signups")] + public ActionResult> QuerySignups() => throw new NotImplementedException(); /// /// Signs up a player for a given slot. /// - [HttpPost("{signupId:long}/sign", Name = "Sign Player")] - public ActionResult SignPlayer(long signupId, [FromBody] PlayerSignupRequestDto request) + [HttpPost("{signupsId:long}/sign", Name = "Sign Player")] + public ActionResult SignPlayer(long signupsId, [FromBody] PlayerSignupRequestDto request) => throw new NotImplementedException(); - [HttpPost("{signupId:long}/open", Name = "Open Signup")] - public ActionResult OpenSignup(long signupId) + [HttpPost("{signupsId:long}/open", Name = "Open Signups")] + public ActionResult OpenSignups(long signupsId) => throw new NotImplementedException(); /// /// Immediately closes given signups. /// - [HttpPost("{signupId:long}/close", Name = "Close Signup")] - public ActionResult CloseSignup(long signupId) + [HttpPost("{signupsId:long}/close", Name = "Close Signups")] + public ActionResult CloseSignups(long signupsId) => throw new NotImplementedException(); } \ No newline at end of file From 63f97b7621e86942ae1843fa202e15a68991a459 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 1 Apr 2022 23:51:14 +0200 Subject: [PATCH 63/87] Fix bug in AutoAddInterfacesAsScoped() --- .../ServiceCollectionExtensionsTests.cs | 27 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 2 +- .../Features/Missions/Models/SignupsStatus.cs | 4 +-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs index 232902c..9322f1e 100644 --- a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs +++ b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using ArmaForces.Boderator.Core.DependencyInjection; using FluentAssertions; @@ -36,6 +37,19 @@ public void AutoAddInterfacesAsScoped_ImplementationRegisteredAsSingleton_DoesNo AssertServiceRegisteredCorrectly(serviceCollection, ServiceLifetime.Singleton); } + [Fact, Trait("Category", "Unit")] + public void AutoAddInterfacesAsScoped_EmptyCollection_RegistersOnlyInterfacesFromThisAssembly() + { + var serviceCollection = new ServiceCollection() + .AutoAddInterfacesAsScoped(typeof(ServiceCollectionExtensionsTests).Assembly); + + using (new AssertionScope()) + { + serviceCollection.Should() + .OnlyContain(x => x.ServiceType.Assembly == typeof(ServiceCollectionExtensionsTests).Assembly); + } + } + private static void AssertServiceRegisteredCorrectly(IServiceCollection serviceCollection, ServiceLifetime expectedLifetime) { var expectedServiceDescriptor = new ServiceDescriptor(typeof(TService), typeof(TExpectedImplementation), expectedLifetime); @@ -45,6 +59,11 @@ private static void AssertServiceRegisteredCorrectly + /// Used to check if interfaces from other assemblies aren't registered automatically when there is one implementation. + /// + private enum TestEnum { } private interface ITest1 { } @@ -55,5 +74,13 @@ private interface ITest3 { } private class Test1 : ITest1 { } private class Test2 : ITest1, ITest2 { } + + /// + /// Used to check if interfaces from other assemblies aren't registered automatically when there is one implementation. + /// + private class Test3 : ICloneable2 + { + public object Clone() => throw new NotImplementedException(); + } } } diff --git a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs index cfe0478..bb3348f 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -32,7 +32,7 @@ public static IServiceCollection AddOrReplaceSingleton(this IServiceCo public static IServiceCollection AutoAddInterfacesAsScoped(this IServiceCollection services, Assembly assembly) { assembly.DefinedTypes - .Where(x => x.ImplementedInterfaces.Any()) + .Where(x => x.ImplementedInterfaces.Any(interfaceType => interfaceType.Assembly == assembly)) .SelectMany( implementingClass => implementingClass.ImplementedInterfaces, (implementingClass, implementedInterface) => new {implementedInterface, implementingClass}) diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs index 4a65559..07142c0 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs @@ -1,6 +1,4 @@ -using System; - -namespace ArmaForces.Boderator.Core.Missions.Models; +namespace ArmaForces.Boderator.Core.Missions.Models; public enum SignupsStatus : ushort { From 09b9e40a2acce77c72bed16ad4dfc768b261bb8d Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 00:20:40 +0200 Subject: [PATCH 64/87] Handle NotImplementedException as 501 status code --- .../Filters/ExceptionFilter.cs | 39 +++++++++++++++++++ .../Filters/NotImplementedResult.cs | 23 +++++++++++ ArmaForces.Boderator.BotService/Startup.cs | 28 +------------ 3 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs create mode 100644 ArmaForces.Boderator.BotService/Filters/NotImplementedResult.cs diff --git a/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs b/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs new file mode 100644 index 0000000..3437f84 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace ArmaForces.Boderator.BotService.Filters; + +public class ExceptionFilter : IExceptionFilter, IAsyncExceptionFilter +{ + public Task OnExceptionAsync(ExceptionContext context) + { + OnException(context); + return Task.CompletedTask; + } + + public void OnException(ExceptionContext context) + { + if (context.Exception is ArgumentNullException) HandleValidationError(context); + if (context.Exception is NotImplementedException) HandleNotImplemented(context); + } + + private static void HandleNotImplemented(ExceptionContext context) + { + context.Result = new NotImplementedResult(); + context.ExceptionHandled = true; + } + + private static void HandleValidationError(ExceptionContext context) + { + var error = new + { + Message = "Validation error", + Details = context.Exception.Message + }; + + context.Result = new BadRequestObjectResult(error); + context.ExceptionHandled = true; + } +} diff --git a/ArmaForces.Boderator.BotService/Filters/NotImplementedResult.cs b/ArmaForces.Boderator.BotService/Filters/NotImplementedResult.cs new file mode 100644 index 0000000..b038111 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Filters/NotImplementedResult.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace ArmaForces.Boderator.BotService.Filters; + +/// +/// A that when +/// executed will produce a Not Implemented (501) response. +/// +[DefaultStatusCode(DefaultStatusCode)] +public class NotImplementedResult : StatusCodeResult +{ + private const int DefaultStatusCode = StatusCodes.Status501NotImplemented; + + /// + /// Creates a new instance. + /// + public NotImplementedResult() + : base(DefaultStatusCode) + { + } +} diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 5e16740..35f07d8 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -1,17 +1,14 @@ using System; using System.ComponentModel; using System.Text.Json.Serialization; -using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Documentation; using ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection; +using ArmaForces.Boderator.BotService.Filters; using ArmaForces.Boderator.Core.DependencyInjection; using Discord.WebSocket; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -74,27 +71,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) }); } } - - public class ExceptionFilter : IExceptionFilter, IAsyncExceptionFilter - { - public Task OnExceptionAsync(ExceptionContext context) - { - OnException(context); - return Task.CompletedTask; - } - - public void OnException(ExceptionContext context) - { - if (context.Exception is not ArgumentNullException) return; - - var error = new - { - Message = "Validation error", - Details = context.Exception.Message - }; - - context.Result = new BadRequestObjectResult(error); - context.ExceptionHandled = true; - } - } } From 6b3e2e1dfc989f93766788f5931150931a714b07 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 00:20:57 +0200 Subject: [PATCH 65/87] Add response status codes to endpoints --- .../Features/Missions/MissionsController.cs | 11 ++++++ .../Features/Missions/SignupsController.cs | 36 ++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index e7c3a7a..54eb276 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -31,6 +31,8 @@ public MissionsController(IMissionCommandService missionCommandService, IMission /// /// [HttpPost(Name = "Create Mission")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> CreateMission([FromBody] MissionCreateRequestDto request) => await _missionCommandService.CreateMission(MissionMapper.Map(request)) .Map(MissionMapper.Map) @@ -43,6 +45,10 @@ public async Task> CreateMission([FromBody] MissionCrea /// /// Updated mission data. [HttpPatch("{missionId:int}", Name = "Update Mission")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateMission(int missionId) { throw new NotImplementedException(); @@ -53,6 +59,9 @@ public ActionResult UpdateMission(int missionId) /// /// Deleted mission data. [HttpDelete("{missionId:int}", Name = "Delete Mission")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult DeleteMission(int missionId) { throw new NotImplementedException(); @@ -64,6 +73,7 @@ public ActionResult DeleteMission(int missionId) /// [HttpGet(Name = "Get Missions")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task>> GetMissions() => await _missionQueryService.GetMissions() .Map(MissionMapper.Map) @@ -78,6 +88,7 @@ public async Task>> GetMissions() /// [HttpGet("{missionId:int}", Name = "Get Mission")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetMission(int missionId) => await _missionQueryService.GetMission(missionId) .Map(MissionMapper.Map) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index 2b36260..b94f10d 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -5,6 +5,7 @@ using ArmaForces.Boderator.BotService.Features.Missions.Mappers; using ArmaForces.Boderator.Core.Missions; using CSharpFunctionalExtensions; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArmaForces.Boderator.BotService.Features.Missions; @@ -27,7 +28,9 @@ public SignupsController(ISignupsQueryService signupsQueryService) /// /// [HttpPost(Name = "Create Signups")] - public ActionResult CreateSignup([FromBody] SignupsCreateRequestDto request) + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto request) => throw new NotImplementedException(); /// @@ -36,7 +39,11 @@ public ActionResult CreateSignup([FromBody] SignupsCreateRequestDto reques /// /// [HttpPatch("{signupsId:long}", Name = "Update Signups")] - public ActionResult UpdateSignup(long signupsId) + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateSignups(long signupsId) => throw new NotImplementedException(); /// @@ -45,7 +52,10 @@ public ActionResult UpdateSignup(long signupsId) /// /// [HttpDelete("{signupsId:long}", Name = "Delete Signups")] - public ActionResult DeleteSignup(long signupsId) + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteSignups(long signupsId) => throw new NotImplementedException(); /// @@ -54,6 +64,8 @@ public ActionResult DeleteSignup(long signupsId) /// /// [HttpGet("{signupsId:long}", Name = "Get Signups")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetSignups(long signupsId) => await _signupsQueryService.GetSignups(signupsId) .Map(SignupsMapper.Map) @@ -65,18 +77,28 @@ public async Task> GetSignups(long signupsId) /// Returns all signups satisfying query parameters. /// /// - [HttpGet(Name = "Query Signups")] - public ActionResult> QuerySignups() + [HttpGet(Name = "Lookup Signups")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult> LookupSignups() => throw new NotImplementedException(); /// /// Signs up a player for a given slot. /// [HttpPost("{signupsId:long}/sign", Name = "Sign Player")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult SignPlayer(long signupsId, [FromBody] PlayerSignupRequestDto request) => throw new NotImplementedException(); [HttpPost("{signupsId:long}/open", Name = "Open Signups")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult OpenSignups(long signupsId) => throw new NotImplementedException(); @@ -84,6 +106,10 @@ public ActionResult OpenSignups(long signupsId) /// Immediately closes given signups. /// [HttpPost("{signupsId:long}/close", Name = "Close Signups")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult CloseSignups(long signupsId) => throw new NotImplementedException(); } \ No newline at end of file From 7f289e00d0566d0e6921f289ce6c3151959d08ff Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 00:24:28 +0200 Subject: [PATCH 66/87] Fix Team.SignupsId incorrect type int -> long --- ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs index d900745..f94b5a8 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs @@ -10,5 +10,5 @@ public record Team public IReadOnlyList Slots { get; init; } = new List(); - public int SignupsId { get; init; } + public long SignupsId { get; init; } } \ No newline at end of file From faacc2714edc1ff0cc4c8b98aa0ba53d1271d65e Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 00:59:46 +0200 Subject: [PATCH 67/87] Add more to documentation generation --- .../ArmaForces.Boderator.BotService.csproj | 2 + .../Documentation/DocumentationExtensions.cs | 6 ++ .../Features/Missions/MissionsController.cs | 27 ++++----- .../Features/Missions/SignupsController.cs | 55 ++++++++++--------- 4 files changed, 52 insertions(+), 38 deletions(-) diff --git a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj index 6a4c23b..072fc86 100644 --- a/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj +++ b/ArmaForces.Boderator.BotService/ArmaForces.Boderator.BotService.csproj @@ -5,6 +5,7 @@ a9e33227-0f21-4863-9830-8aa69ac1e928 Linux enable + true @@ -24,6 +25,7 @@ + diff --git a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs index 1f1c59b..283d72f 100644 --- a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs +++ b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs @@ -1,3 +1,4 @@ +using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -14,6 +15,10 @@ public static IServiceCollection AddDocumentation(this IServiceCollection servic options => { options.SwaggerDoc(openApiConfig.Version, openApiConfig); + options.EnableAnnotations(); + + var filePath = Path.Combine(System.AppContext.BaseDirectory, "ArmaForces.Boderator.BotService.xml"); + options.IncludeXmlComments(filePath); }); } @@ -31,6 +36,7 @@ public static IApplicationBuilder AddDocumentation( options => { options.DocumentTitle = openApiConfig.Title; + options.ExpandResponses(""); options.SpecUrl = url; }); } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index 54eb276..5051fe9 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -7,6 +7,7 @@ using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions; @@ -31,8 +32,8 @@ public MissionsController(IMissionCommandService missionCommandService, IMission /// /// [HttpPost(Name = "Create Mission")] - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] + [SwaggerResponse(StatusCodes.Status201Created, "The mission was created")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public async Task> CreateMission([FromBody] MissionCreateRequestDto request) => await _missionCommandService.CreateMission(MissionMapper.Map(request)) .Map(MissionMapper.Map) @@ -45,10 +46,10 @@ public async Task> CreateMission([FromBody] MissionCrea /// /// Updated mission data. [HttpPatch("{missionId:int}", Name = "Update Mission")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "The mission was updated")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to update the mission")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] public ActionResult UpdateMission(int missionId) { throw new NotImplementedException(); @@ -59,9 +60,9 @@ public ActionResult UpdateMission(int missionId) /// /// Deleted mission data. [HttpDelete("{missionId:int}", Name = "Delete Mission")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "The mission was deleted")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to delete the mission")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] public ActionResult DeleteMission(int missionId) { throw new NotImplementedException(); @@ -72,8 +73,8 @@ public ActionResult DeleteMission(int missionId) /// /// [HttpGet(Name = "Get Missions")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] + [SwaggerResponse(StatusCodes.Status200OK, "Missions retrieved")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public async Task>> GetMissions() => await _missionQueryService.GetMissions() .Map(MissionMapper.Map) @@ -87,8 +88,8 @@ public async Task>> GetMissions() /// Unique identifier of a mission /// [HttpGet("{missionId:int}", Name = "Get Mission")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status200OK, "Mission retrieved")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] public async Task> GetMission(int missionId) => await _missionQueryService.GetMission(missionId) .Map(MissionMapper.Map) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index b94f10d..1ca1e88 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -7,6 +7,7 @@ using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions; @@ -28,8 +29,8 @@ public SignupsController(ISignupsQueryService signupsQueryService) /// /// [HttpPost(Name = "Create Signups")] - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] + [SwaggerResponse(StatusCodes.Status201Created, "Signups created")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto request) => throw new NotImplementedException(); @@ -39,10 +40,10 @@ public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto reque /// /// [HttpPatch("{signupsId:long}", Name = "Update Signups")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "Signups updated")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to update signups")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult UpdateSignups(long signupsId) => throw new NotImplementedException(); @@ -52,9 +53,9 @@ public ActionResult UpdateSignups(long signupsId) /// /// [HttpDelete("{signupsId:long}", Name = "Delete Signups")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "Signups deleted")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to delete signups")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult DeleteSignups(long signupsId) => throw new NotImplementedException(); @@ -64,8 +65,8 @@ public ActionResult DeleteSignups(long signupsId) /// /// [HttpGet("{signupsId:long}", Name = "Get Signups")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status200OK, "Signups retrieved")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public async Task> GetSignups(long signupsId) => await _signupsQueryService.GetSignups(signupsId) .Map(SignupsMapper.Map) @@ -76,10 +77,11 @@ public async Task> GetSignups(long signupsId) /// /// Returns all signups satisfying query parameters. /// + /// Lookup Signups /// [HttpGet(Name = "Lookup Signups")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status200OK, "Signups retrieved")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult> LookupSignups() => throw new NotImplementedException(); @@ -87,18 +89,21 @@ public ActionResult> LookupSignups() /// Signs up a player for a given slot. /// [HttpPost("{signupsId:long}/sign", Name = "Sign Player")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "Player signed up")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to sign up player")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult SignPlayer(long signupsId, [FromBody] PlayerSignupRequestDto request) => throw new NotImplementedException(); + /// + /// Opens given signups allowing all players to sign-up. + /// [HttpPost("{signupsId:long}/open", Name = "Open Signups")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "Signups opened")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to open signups")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult OpenSignups(long signupsId) => throw new NotImplementedException(); @@ -106,10 +111,10 @@ public ActionResult OpenSignups(long signupsId) /// Immediately closes given signups. /// [HttpPost("{signupsId:long}/close", Name = "Close Signups")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponse(StatusCodes.Status204NoContent, "Signups closed")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to close signups")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult CloseSignups(long signupsId) => throw new NotImplementedException(); } \ No newline at end of file From aafeda90295830eec0166e4d449f04bcbd9d3b96 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 02:07:19 +0200 Subject: [PATCH 68/87] Add more documentation --- .../Missions/DTOs/MissionCreateRequestDto.cs | 18 ++++-- .../Missions/DTOs/PlayerSignupRequestDto.cs | 15 ++++- .../Missions/DTOs/SignupsCreateRequestDto.cs | 24 +++++--- .../Features/Missions/DTOs/SlotDto.cs | 30 +++++++++- .../Features/Missions/DTOs/TeamDto.cs | 26 ++++++++- .../Features/Missions/MissionsController.cs | 36 ++++-------- .../Features/Missions/SignupsController.cs | 58 +++++++------------ .../Features/Dlcs/Models/Dlc.cs | 6 ++ .../Features/Missions/Models/SignupsStatus.cs | 2 + 9 files changed, 138 insertions(+), 77 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs index a35e3ba..447702b 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs @@ -1,37 +1,43 @@ using System; using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +/// +/// Mission creation request. +/// public record MissionCreateRequestDto { /// /// Mission title. /// [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] public string Title { get; set; } = string.Empty; /// - /// Mission description. + /// Optional mission description. It's required before signups for the mission can be opened. /// - [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public string? Description { get; set; } /// - /// Mission start time. + /// Mission start time. It's required before signups for the mission can be opened. /// - [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public DateTime? MissionDate { get; set; } /// - /// Name of the modset. + /// Name of the modset. It's required before signups for the mission can be opened. /// - [JsonProperty(Required = Required.DisallowNull, DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public string? ModsetName { get; set; } /// /// Owner of the mission. /// [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] public string Owner { get; set; } = string.Empty; } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs index c6dccc4..4f9be8b 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs @@ -1,8 +1,21 @@ -namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using Newtonsoft.Json; +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; + +/// +/// Player sign-up request for a slot. +/// public record PlayerSignupRequestDto { + /// + /// Id of a slot. + /// + [JsonProperty(Required = Required.Always)] public long SlotId { get; set; } + /// + /// Player name. + /// + [JsonProperty(Required = Required.Always)] public string Player { get; set; } = string.Empty; } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs index 8a1e8b9..1fa4a13 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs @@ -2,45 +2,53 @@ using System.Collections.Generic; using ArmaForces.Boderator.Core.Missions.Models; using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +/// +/// Mission create +/// public class SignupsCreateRequestDto { /// - /// Id of the mission for which the signup will be created. + /// Id of the mission for which the signups will be created. + /// It can't be specified if "Mission" is specified. /// [JsonProperty(Required = Required.DisallowNull)] public int? MissionId { get; set; } /// /// Mission for which the signups will be created. - /// Mission will be created. + /// Mission will be created before creating signups and must be ready for signups creation. + /// It can't be specified if "MissionId" is specified. /// [JsonProperty(Required = Required.DisallowNull)] + [SwaggerSchema(Nullable = true)] public MissionCreateRequestDto? Mission { get; set; } - + /// /// Desired status of signups. /// [JsonProperty(Required = Required.DisallowNull)] - public SignupsStatus? SignupsStatus { get; set; } + public SignupsStatus SignupsStatus { get; set; } = SignupsStatus.Created; /// - /// Starting date of signup. + /// Starting date of signups. /// [JsonProperty(Required = Required.DisallowNull)] public DateTime? StartDate { get; set; } /// - /// Closing date of signup. + /// Closing date of signups. /// [JsonProperty(Required = Required.DisallowNull)] public DateTime? CloseDate { get; set; } /// - /// Teams available in signup. + /// Teams available in signups. /// [JsonProperty(Required = Required.Always)] - public List Teams { get; set; } = new List(); + [SwaggerSchema(Nullable = false)] + public List Teams { get; set; } = new(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs index e7ae746..9219f8e 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs @@ -1,17 +1,45 @@ using System.Collections.Generic; using ArmaForces.Boderator.Core.Dlcs.Models; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +/// +/// Represents a slot in game. +/// public record SlotDto { + /// + /// Id of a slot. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + [SwaggerSchema(Nullable = true)] public long? SlotId { get; init; } + /// + /// Name of a slot. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] public string Name { get; init; } = string.Empty; + /// + /// List of required DLCs to play on this slot. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + [SwaggerSchema(Nullable = false)] public List RequiredDlcs { get; init; } = new(); - public string Vehicle { get; init; } = string.Empty; + /// + /// Optional vehicle information + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public string? Vehicle { get; init; } + /// + /// Name of a player who occupies the slot. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Include)] public string? Occupant { get; init; } } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs index 555b7e9..eccbb7a 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs @@ -1,15 +1,39 @@ using System.Collections.Generic; using ArmaForces.Boderator.Core.Dlcs.Models; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +/// +/// Represents a team in game. +/// public record TeamDto { + /// + /// Name of a team. + /// Must be unique within signups. + /// + [JsonProperty(Required = Required.Always)] public string Name { get; init; } = string.Empty; + /// + /// List of required DLCs to play in this team. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + [SwaggerSchema(Nullable = false)] public List RequiredDlcs { get; init; } = new(); - public string Vehicle { get; init; } = string.Empty; + /// + /// Optional vehicle information. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + public string? Vehicle { get; init; } + /// + /// Slots within a team. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] public List Slots { get; init; } = new(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index 5051fe9..24c5956 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -20,17 +20,15 @@ public class MissionsController : Controller private readonly IMissionCommandService _missionCommandService; private readonly IMissionQueryService _missionQueryService; + /// public MissionsController(IMissionCommandService missionCommandService, IMissionQueryService missionQueryService) { _missionCommandService = missionCommandService; _missionQueryService = missionQueryService; } - /// - /// - /// - /// - /// + /// Creates requested mission. + /// Mission creation request [HttpPost(Name = "Create Mission")] [SwaggerResponse(StatusCodes.Status201Created, "The mission was created")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] @@ -41,37 +39,30 @@ public async Task> CreateMission([FromBody] MissionCrea onSuccess: mission => Created(mission.MissionId.ToString(), mission), onFailure: error => BadRequest(error)); - /// - /// - /// - /// Updated mission data. + /// Updates given mission. + /// Id of a mission to update [HttpPatch("{missionId:int}", Name = "Update Mission")] [SwaggerResponse(StatusCodes.Status204NoContent, "The mission was updated")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to update the mission")] [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] - public ActionResult UpdateMission(int missionId) + public ActionResult UpdateMission(int missionId) { throw new NotImplementedException(); } - /// - /// - /// - /// Deleted mission data. + /// Deletes given mission. + /// Id of a mission to deleted. [HttpDelete("{missionId:int}", Name = "Delete Mission")] [SwaggerResponse(StatusCodes.Status204NoContent, "The mission was deleted")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to delete the mission")] [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] - public ActionResult DeleteMission(int missionId) + public ActionResult DeleteMission(int missionId) { throw new NotImplementedException(); } - /// - /// - /// - /// + /// Retrieves missions satisfying query parameters. [HttpGet(Name = "Get Missions")] [SwaggerResponse(StatusCodes.Status200OK, "Missions retrieved")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] @@ -82,11 +73,8 @@ public async Task>> GetMissions() onSuccess: missions => Ok(missions), onFailure: error => BadRequest(error)); - /// - /// Retrieves mission with given . - /// - /// Unique identifier of a mission - /// + /// Retrieves mission with given . + /// Id of a mission to retrieve [HttpGet("{missionId:int}", Name = "Get Mission")] [SwaggerResponse(StatusCodes.Status200OK, "Mission retrieved")] [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index 1ca1e88..72adb75 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -12,33 +12,31 @@ namespace ArmaForces.Boderator.BotService.Features.Missions; /// -/// Allows signups data retrieval and creation. +/// Allows signups retrieval and creation. /// [Route("api/[controller]")] public class SignupsController : ControllerBase { private readonly ISignupsQueryService _signupsQueryService; + /// public SignupsController(ISignupsQueryService signupsQueryService) { _signupsQueryService = signupsQueryService; } - /// - /// - /// - /// + /// + /// Creates requested signups for a mission. + /// Either "MissionId" of existing mission without signups or a "Mission" object which satisfies the preconditions for creating signups must be provided. + /// [HttpPost(Name = "Create Signups")] [SwaggerResponse(StatusCodes.Status201Created, "Signups created")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto request) => throw new NotImplementedException(); - /// - /// - /// - /// - /// + /// Updates signups with given . + /// Id of signups to update [HttpPatch("{signupsId:long}", Name = "Update Signups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups updated")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] @@ -47,11 +45,8 @@ public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto reque public ActionResult UpdateSignups(long signupsId) => throw new NotImplementedException(); - /// - /// - /// - /// - /// + /// Deletes signups with given . + /// Id of signups to delete. [HttpDelete("{signupsId:long}", Name = "Delete Signups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups deleted")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to delete signups")] @@ -59,11 +54,8 @@ public ActionResult UpdateSignups(long signupsId) public ActionResult DeleteSignups(long signupsId) => throw new NotImplementedException(); - /// - /// - /// - /// - /// + /// Retrieves signups with given . + /// Id of signups to retrieve. [HttpGet("{signupsId:long}", Name = "Get Signups")] [SwaggerResponse(StatusCodes.Status200OK, "Signups retrieved")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] @@ -74,31 +66,26 @@ public async Task> GetSignups(long signupsId) onSuccess: signups => Ok(signups), onFailure: error => BadRequest(error)); - /// - /// Returns all signups satisfying query parameters. - /// - /// Lookup Signups - /// + /// Returns all signups satisfying query parameters. [HttpGet(Name = "Lookup Signups")] [SwaggerResponse(StatusCodes.Status200OK, "Signups retrieved")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult> LookupSignups() => throw new NotImplementedException(); - /// - /// Signs up a player for a given slot. - /// - [HttpPost("{signupsId:long}/sign", Name = "Sign Player")] + /// Signs up a player for a given slot. + /// Id of signups to sign-up player + /// Sign-up details + [HttpPost("{signupsId:long}/sign", Name = "Sign-up Player")] [SwaggerResponse(StatusCodes.Status204NoContent, "Player signed up")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to sign up player")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] - public ActionResult SignPlayer(long signupsId, [FromBody] PlayerSignupRequestDto request) + public ActionResult SignUpPlayer(long signupsId, [FromBody] PlayerSignupRequestDto request) => throw new NotImplementedException(); - /// - /// Opens given signups allowing all players to sign-up. - /// + /// Immediately opens given signups allowing all players to sign-up. + /// Id of signups to open [HttpPost("{signupsId:long}/open", Name = "Open Signups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups opened")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] @@ -107,9 +94,8 @@ public ActionResult SignPlayer(long signupsId, [FromBody] PlayerSignupRequestDto public ActionResult OpenSignups(long signupsId) => throw new NotImplementedException(); - /// - /// Immediately closes given signups. - /// + /// Immediately closes given signups. + /// Id of signups to close [HttpPost("{signupsId:long}/close", Name = "Close Signups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups closed")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] diff --git a/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs b/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs index e19d121..95da5be 100644 --- a/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs +++ b/ArmaForces.Boderator.Core/Features/Dlcs/Models/Dlc.cs @@ -2,7 +2,13 @@ public class Dlc { + /// + /// Id of a DLC. + /// public int DlcId { get; init; } + /// + /// Name of a DLC. + /// public string Name { get; init; } = string.Empty; } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs index 07142c0..c3bd9e6 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs @@ -2,6 +2,8 @@ public enum SignupsStatus : ushort { + Created, + Prebeton, Open, From 2f19ff0618cbdeb6e51c699a3b18e3e37dbfc0b7 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 12:18:28 +0200 Subject: [PATCH 69/87] Fix remaining Signup misspelling --- .../Features/Missions/DTOs/SignupsDto.cs | 2 +- .../Features/Missions/Mappers/SignupsMapper.cs | 2 +- .../Features/Missions/Helpers/SignupsDbHelper.cs | 6 +++--- .../Implementation/SignupsQueryServiceIntegrationTests.cs | 8 ++++---- .../Persistence/Query/ISignupsQueryRepository.cs | 7 ++++--- .../Persistence/Query/SignupsQueryRepository.cs | 7 ++++--- .../Missions/Implementation/SignupsQueryService.cs | 7 ++++--- 7 files changed, 21 insertions(+), 18 deletions(-) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs index d7194f4..36d6ee0 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs @@ -6,7 +6,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; public record SignupsDto { - public long SignupId { get; init; } + public long SignupsId { get; init; } public SignupsStatus Status { get; init; } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 305afcf..0a8aede 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -10,7 +10,7 @@ public static class SignupsMapper public static SignupsDto Map(Signups signups) => new() { - SignupId = signups.SignupsId, + SignupsId = signups.SignupsId, Status = (SignupsStatus) signups.Status, StartDate = signups.StartDate, CloseDate = signups.CloseDate, diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs index a61e0a9..994a891 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs @@ -17,7 +17,7 @@ public SignupsDbHelper( _missionContext = missionContext; } - public async Task CreateTestSignup(long missionId) + public async Task CreateTestSignups(long missionId) { var signup = SignupsFixture.CreateTestSignup(missionId); @@ -27,9 +27,9 @@ public async Task CreateTestSignup(long missionId) return addedEntry.Entity; } - public async Task CreateTestSignup() + public async Task CreateTestSignups() { var mission = await _missionsDbHelper.CreateTestMission(); - return await CreateTestSignup(mission.MissionId); + return await CreateTestSignups(mission.MissionId); } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs index f143443..f1bdd9e 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs @@ -37,14 +37,14 @@ public async Task GetOpenSignups_NoSignupsInDatabase_ReturnsEmptyList() public async Task GetOpenSignups_NoOpenSignupsInDatabase_ReturnsEmptyList() { var testMission = await _missionsDbHelper.CreateTestMission(); - var signup = await _signupsDbHelper.CreateTestSignup(testMission.MissionId); + var signup = await _signupsDbHelper.CreateTestSignups(testMission.MissionId); var result = await _signupsQueryService.GetOpenSignups(); result.ShouldBeSuccess(new List{signup}); } [Fact, Trait("Category", "Integration")] - public async Task GetSignup_SignupWithGivenIdDoesntExist_ReturnsFailure() + public async Task GetSignups_SignupsWithGivenIdDoesntExist_ReturnsFailure() { const int nonExistingSignupsId = 0; var result = await _signupsQueryService.GetSignups(nonExistingSignupsId); @@ -53,9 +53,9 @@ public async Task GetSignup_SignupWithGivenIdDoesntExist_ReturnsFailure() } [Fact, Trait("Category", "Integration")] - public async Task GetSignup_SignupWithGivenIdExists_ReturnsSignups() + public async Task GetSignups_SignupsWithGivenIdExists_ReturnsSignups() { - var signups = await _signupsDbHelper.CreateTestSignup(); + var signups = await _signupsDbHelper.CreateTestSignups(); var result = await _signupsQueryService.GetSignups(signups.SignupsId); result.ShouldBeSuccess(signups); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs index 17d638e..6f0b8d7 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/ISignupsQueryRepository.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; internal interface ISignupsQueryRepository { - public Task> GetAllSignups(); + public Task> GetAllSignups(); - public Task> GetOpenSignups(); + public Task> GetOpenSignups(); - public Task GetSignup(long signupId); + public Task GetSignups(long signupId); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs index fa9be0d..8b3e8df 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/SignupsQueryRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; using Microsoft.EntityFrameworkCore; namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; @@ -15,17 +16,17 @@ public SignupsQueryRepository(MissionContext context) _context = context; } - public async Task> GetAllSignups() + public async Task> GetAllSignups() { return await _context.Signups.ToListAsync(); } - public async Task> GetOpenSignups() + public async Task> GetOpenSignups() => await _context.Signups .Where(x => x.CloseDate > DateTime.Now) .ToListAsync(); - public async Task GetSignup(long signupId) + public async Task GetSignups(long signupId) => await _context.Signups .FindAsync(signupId) .AsTask(); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs index 4906cf6..631bf9f 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; +using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; namespace ArmaForces.Boderator.Core.Missions.Implementation; @@ -15,10 +16,10 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) _signupsQueryRepository = signupsQueryRepository; } - public async Task> GetSignups(long signupId) - => await _signupsQueryRepository.GetSignup(signupId) + public async Task> GetSignups(long signupId) + => await _signupsQueryRepository.GetSignups(signupId) ?? Result.Failure($"Signup with ID {signupId} not found"); - public async Task>> GetOpenSignups() + public async Task>> GetOpenSignups() => await _signupsQueryRepository.GetOpenSignups(); } \ No newline at end of file From d9b237793e028e283db116661ffbd9adfe5c0043 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Apr 2022 15:34:56 +0200 Subject: [PATCH 70/87] Fill missing documentation + enhancements --- .../Documentation/DocumentationExtensions.cs | 6 ++- .../Missions/DTOs/MissionCreateRequestDto.cs | 6 +++ .../Features/Missions/DTOs/MissionDto.cs | 45 ++++++++++++++++++- .../Missions/DTOs/PlayerSignOutRequestDto.cs | 26 +++++++++++ ...equestDto.cs => PlayerSignUpRequestDto.cs} | 12 +++-- .../Missions/DTOs/SignupsCreateRequestDto.cs | 13 ++++-- .../Features/Missions/DTOs/SignupsDto.cs | 38 ++++++++++++++++ .../Features/Missions/DTOs/SlotDto.cs | 4 +- .../Features/Missions/DTOs/TeamDto.cs | 4 ++ .../Features/Missions/MissionsController.cs | 17 ++++--- .../Features/Missions/SignupsController.cs | 45 ++++++++++++++----- .../Features/Missions/Models/Signups.cs | 2 +- .../Features/Missions/Models/SignupsStatus.cs | 14 +++++- 13 files changed, 202 insertions(+), 30 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignOutRequestDto.cs rename ArmaForces.Boderator.BotService/Features/Missions/DTOs/{PlayerSignupRequestDto.cs => PlayerSignUpRequestDto.cs} (60%) diff --git a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs index 283d72f..883acce 100644 --- a/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs +++ b/ArmaForces.Boderator.BotService/Documentation/DocumentationExtensions.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; @@ -16,8 +17,9 @@ public static IServiceCollection AddDocumentation(this IServiceCollection servic { options.SwaggerDoc(openApiConfig.Version, openApiConfig); options.EnableAnnotations(); - - var filePath = Path.Combine(System.AppContext.BaseDirectory, "ArmaForces.Boderator.BotService.xml"); + options.UseAllOfToExtendReferenceSchemas(); + + var filePath = Path.Combine(System.AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"); options.IncludeXmlComments(filePath); }); } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs index 447702b..3532bac 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionCreateRequestDto.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; using Swashbuckle.AspNetCore.Annotations; @@ -14,24 +15,28 @@ public record MissionCreateRequestDto /// [JsonProperty(Required = Required.Always)] [SwaggerSchema(Nullable = false)] + [Required] public string Title { get; set; } = string.Empty; /// /// Optional mission description. It's required before signups for the mission can be opened. /// [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + [SwaggerSchema(Nullable = true)] public string? Description { get; set; } /// /// Mission start time. It's required before signups for the mission can be opened. /// [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + [SwaggerSchema(Nullable = true)] public DateTime? MissionDate { get; set; } /// /// Name of the modset. It's required before signups for the mission can be opened. /// [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] + [SwaggerSchema(Nullable = true)] public string? ModsetName { get; set; } /// @@ -39,5 +44,6 @@ public record MissionCreateRequestDto /// [JsonProperty(Required = Required.Always)] [SwaggerSchema(Nullable = false)] + [Required] public string Owner { get; set; } = string.Empty; } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs index 6e88484..3d6708d 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/MissionDto.cs @@ -1,22 +1,63 @@ using System; +using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +/// +/// Represents a mission. +/// public record MissionDto { + /// + /// Id of a mission. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public long MissionId { get; init; } + /// + /// Mission title. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public string Title { get; init; } = string.Empty; + /// + /// Description for a mission. + /// Must be provided before signups can be created. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public string? Description { get; init; } - + + /// + /// Planned mission start date. + /// Must be provided before signups can be created. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public DateTime? MissionDate { get; init; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + /// + /// Name of modset for a mission. + /// Must be provided before signups can be created. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public string? ModsetName { get; init; } + /// + /// Mission owner/organizer. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public string Owner { get; init; } = string.Empty; + /// + /// Signups for a mission. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public SignupsDto? Signups { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignOutRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignOutRequestDto.cs new file mode 100644 index 0000000..8988b6a --- /dev/null +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignOutRequestDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; + +namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; + +/// +/// Player sign out request from a slot or signups. +/// +public record PlayerSignOutRequestDto +{ + /// + /// Optional id of a slot. If not provided, player will be signed out from all slots. + /// + [JsonProperty(Required = Required.DisallowNull)] + [SwaggerSchema(Nullable = false)] + public long SlotId { get; set; } + + /// + /// Player name. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] + public string Player { get; set; } = string.Empty; +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignUpRequestDto.cs similarity index 60% rename from ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs rename to ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignUpRequestDto.cs index 4f9be8b..cac70f9 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignupRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/PlayerSignUpRequestDto.cs @@ -1,21 +1,27 @@ -using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; /// /// Player sign-up request for a slot. /// -public record PlayerSignupRequestDto +public record PlayerSignUpRequestDto { /// /// Id of a slot. /// [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public long SlotId { get; set; } /// /// Player name. /// [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public string Player { get; set; } = string.Empty; -} +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs index 1fa4a13..0f9e358 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using ArmaForces.Boderator.Core.Missions.Models; using Newtonsoft.Json; using Swashbuckle.AspNetCore.Annotations; @@ -7,30 +9,32 @@ namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; /// -/// Mission create +/// Signups create request. /// public class SignupsCreateRequestDto { /// /// Id of the mission for which the signups will be created. - /// It can't be specified if "Mission" is specified. + /// It can't be specified if "mission" is specified. /// [JsonProperty(Required = Required.DisallowNull)] + [SwaggerSchema(Nullable = false)] public int? MissionId { get; set; } /// /// Mission for which the signups will be created. /// Mission will be created before creating signups and must be ready for signups creation. - /// It can't be specified if "MissionId" is specified. + /// It can't be specified if "missionId" is specified. /// [JsonProperty(Required = Required.DisallowNull)] - [SwaggerSchema(Nullable = true)] + [SwaggerSchema(Nullable = false)] public MissionCreateRequestDto? Mission { get; set; } /// /// Desired status of signups. /// [JsonProperty(Required = Required.DisallowNull)] + [DefaultValue(SignupsStatus.Created)] public SignupsStatus SignupsStatus { get; set; } = SignupsStatus.Created; /// @@ -50,5 +54,6 @@ public class SignupsCreateRequestDto /// [JsonProperty(Required = Required.Always)] [SwaggerSchema(Nullable = false)] + [Required] public List Teams { get; set; } = new(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs index 36d6ee0..d27bf2b 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsDto.cs @@ -1,20 +1,58 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using ArmaForces.Boderator.Core.Missions.Models; +using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace ArmaForces.Boderator.BotService.Features.Missions.DTOs; +/// +/// Represents a signups for a mission. +/// public record SignupsDto { + /// + /// Id of a signups. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public long SignupsId { get; init; } + /// + /// Current signups status. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public SignupsStatus Status { get; init; } + /// + /// Scheduled start time for signups. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public DateTime? StartDate { get; init; } + /// + /// Scheduled close time for signups. + /// + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public DateTime? CloseDate { get; init; } + /// + /// List of teams available for players to sign up. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public List Teams { get; init; } = new(); + /// + /// Id of a mission which the signups are for. + /// + [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public long MissionId { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs index 9219f8e..9c477a8 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SlotDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using ArmaForces.Boderator.Core.Dlcs.Models; using Newtonsoft.Json; using Swashbuckle.AspNetCore.Annotations; @@ -22,6 +23,7 @@ public record SlotDto /// [JsonProperty(Required = Required.Always)] [SwaggerSchema(Nullable = false)] + [Required] public string Name { get; init; } = string.Empty; /// @@ -38,7 +40,7 @@ public record SlotDto public string? Vehicle { get; init; } /// - /// Name of a player who occupies the slot. + /// Optional name of a player who occupies the slot. /// [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Include)] public string? Occupant { get; init; } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs index eccbb7a..c007302 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/TeamDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using ArmaForces.Boderator.Core.Dlcs.Models; using Newtonsoft.Json; using Swashbuckle.AspNetCore.Annotations; @@ -15,6 +16,8 @@ public record TeamDto /// Must be unique within signups. /// [JsonProperty(Required = Required.Always)] + [SwaggerSchema(Nullable = false)] + [Required] public string Name { get; init; } = string.Empty; /// @@ -35,5 +38,6 @@ public record TeamDto /// [JsonProperty(Required = Required.Always)] [SwaggerSchema(Nullable = false)] + [Required] public List Slots { get; init; } = new(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index 24c5956..d89ccc3 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Mime; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; using ArmaForces.Boderator.BotService.Features.Missions.Mappers; @@ -15,6 +16,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions; /// Allows missions data retrieval and creation. /// [Route("api/[controller]")] +[Produces(MediaTypeNames.Application.Json)] public class MissionsController : Controller { private readonly IMissionCommandService _missionCommandService; @@ -27,9 +29,10 @@ public MissionsController(IMissionCommandService missionCommandService, IMission _missionQueryService = missionQueryService; } + /// Create Mission /// Creates requested mission. /// Mission creation request - [HttpPost(Name = "Create Mission")] + [HttpPost(Name = "CreateMission")] [SwaggerResponse(StatusCodes.Status201Created, "The mission was created")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public async Task> CreateMission([FromBody] MissionCreateRequestDto request) @@ -39,9 +42,10 @@ public async Task> CreateMission([FromBody] MissionCrea onSuccess: mission => Created(mission.MissionId.ToString(), mission), onFailure: error => BadRequest(error)); + /// Update Mission /// Updates given mission. /// Id of a mission to update - [HttpPatch("{missionId:int}", Name = "Update Mission")] + [HttpPatch("{missionId:int}", Name = "UpdateMission")] [SwaggerResponse(StatusCodes.Status204NoContent, "The mission was updated")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to update the mission")] @@ -51,9 +55,10 @@ public ActionResult UpdateMission(int missionId) throw new NotImplementedException(); } + /// Delete Mission /// Deletes given mission. /// Id of a mission to deleted. - [HttpDelete("{missionId:int}", Name = "Delete Mission")] + [HttpDelete("{missionId:int}", Name = "DeleteMission")] [SwaggerResponse(StatusCodes.Status204NoContent, "The mission was deleted")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to delete the mission")] [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] @@ -62,8 +67,9 @@ public ActionResult DeleteMission(int missionId) throw new NotImplementedException(); } + /// Get Missions /// Retrieves missions satisfying query parameters. - [HttpGet(Name = "Get Missions")] + [HttpGet(Name = "GetMissions")] [SwaggerResponse(StatusCodes.Status200OK, "Missions retrieved")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public async Task>> GetMissions() @@ -73,9 +79,10 @@ public async Task>> GetMissions() onSuccess: missions => Ok(missions), onFailure: error => BadRequest(error)); + /// Get Mission /// Retrieves mission with given . /// Id of a mission to retrieve - [HttpGet("{missionId:int}", Name = "Get Mission")] + [HttpGet("{missionId:int}", Name = "GetMission")] [SwaggerResponse(StatusCodes.Status200OK, "Mission retrieved")] [SwaggerResponse(StatusCodes.Status404NotFound, "Mission not found")] public async Task> GetMission(int missionId) diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index 72adb75..33646af 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Mime; using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; using ArmaForces.Boderator.BotService.Features.Missions.Mappers; @@ -15,6 +16,7 @@ namespace ArmaForces.Boderator.BotService.Features.Missions; /// Allows signups retrieval and creation. /// [Route("api/[controller]")] +[Produces(MediaTypeNames.Application.Json)] public class SignupsController : ControllerBase { private readonly ISignupsQueryService _signupsQueryService; @@ -25,19 +27,22 @@ public SignupsController(ISignupsQueryService signupsQueryService) _signupsQueryService = signupsQueryService; } + /// Create Signups /// - /// Creates requested signups for a mission. - /// Either "MissionId" of existing mission without signups or a "Mission" object which satisfies the preconditions for creating signups must be provided. + /// Creates signups for a mission. Mission can have only one signups attached. + /// MissionId of an existing mission, which is valid for opening signups (has all details filled), must be provided. + /// Alternatively, Mission can be created alongside Signups through "mission" property but it must still be valid for opening signups. /// - [HttpPost(Name = "Create Signups")] + [HttpPost(Name = "CreateSignups")] [SwaggerResponse(StatusCodes.Status201Created, "Signups created")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto request) => throw new NotImplementedException(); + /// Update Signups /// Updates signups with given . /// Id of signups to update - [HttpPatch("{signupsId:long}", Name = "Update Signups")] + [HttpPatch("{signupsId:long}", Name = "UpdateSignups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups updated")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to update signups")] @@ -45,18 +50,20 @@ public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto reque public ActionResult UpdateSignups(long signupsId) => throw new NotImplementedException(); + /// Delete Signups /// Deletes signups with given . /// Id of signups to delete. - [HttpDelete("{signupsId:long}", Name = "Delete Signups")] + [HttpDelete("{signupsId:long}", Name = "DeleteSignups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups deleted")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to delete signups")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult DeleteSignups(long signupsId) => throw new NotImplementedException(); + /// Get Signups /// Retrieves signups with given . /// Id of signups to retrieve. - [HttpGet("{signupsId:long}", Name = "Get Signups")] + [HttpGet("{signupsId:long}", Name = "GetSignups")] [SwaggerResponse(StatusCodes.Status200OK, "Signups retrieved")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public async Task> GetSignups(long signupsId) @@ -66,27 +73,42 @@ public async Task> GetSignups(long signupsId) onSuccess: signups => Ok(signups), onFailure: error => BadRequest(error)); + /// Lookup Signups /// Returns all signups satisfying query parameters. - [HttpGet(Name = "Lookup Signups")] + [HttpGet(Name = "LookupSignups")] [SwaggerResponse(StatusCodes.Status200OK, "Signups retrieved")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] public ActionResult> LookupSignups() => throw new NotImplementedException(); + /// Sign Up Player /// Signs up a player for a given slot. /// Id of signups to sign-up player /// Sign-up details - [HttpPost("{signupsId:long}/sign", Name = "Sign-up Player")] + [HttpPost("{signupsId:long}/SignUp", Name = "SignUpPlayer")] [SwaggerResponse(StatusCodes.Status204NoContent, "Player signed up")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to sign up player")] [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] - public ActionResult SignUpPlayer(long signupsId, [FromBody] PlayerSignupRequestDto request) + public ActionResult SignUpPlayer(long signupsId, [FromBody] PlayerSignUpRequestDto request) => throw new NotImplementedException(); + /// Sign Out Player + /// Signs out a player from a slot or whole signups. + /// Id of signups to sign out player + /// Sign out details + [HttpPost("{signupsId:long}/SignOut", Name = "SignOutPlayer")] + [SwaggerResponse(StatusCodes.Status204NoContent, "Player signed out")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] + [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to sign out player")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Signups not found")] + public ActionResult SignOutPlayer(long signupsId, [FromBody] PlayerSignOutRequestDto request) + => throw new NotImplementedException(); + + /// Open Signups /// Immediately opens given signups allowing all players to sign-up. /// Id of signups to open - [HttpPost("{signupsId:long}/open", Name = "Open Signups")] + [HttpPost("{signupsId:long}/Open", Name = "OpenSignups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups opened")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to open signups")] @@ -94,9 +116,10 @@ public ActionResult SignUpPlayer(long signupsId, [FromBody] PlayerSignupRequestD public ActionResult OpenSignups(long signupsId) => throw new NotImplementedException(); + /// Close Signups /// Immediately closes given signups. /// Id of signups to close - [HttpPost("{signupsId:long}/close", Name = "Close Signups")] + [HttpPost("{signupsId:long}/Close", Name = "CloseSignups")] [SwaggerResponse(StatusCodes.Status204NoContent, "Signups closed")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] [SwaggerResponse(StatusCodes.Status403Forbidden, "Not authorized to close signups")] diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index 848af41..31bb3cf 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -7,7 +7,7 @@ public record Signups { public long SignupsId { get; init; } - public ushort Status { get; init; } = (ushort) SignupsStatus.Closed; + public ushort Status { get; init; } = (ushort) SignupsStatus.Created; public DateTime StartDate { get; init; } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs index c3bd9e6..3e187fe 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsStatus.cs @@ -2,11 +2,23 @@ public enum SignupsStatus : ushort { + /// + /// Signups are created and waiting for opening. + /// Created, - Prebeton, + /// + /// Signups are open for some selected players. + /// + Preconcrete, + /// + /// Signups are open for all players. + /// Open, + /// + /// Signups are closed. + /// Closed } From 8f5d9bb815938e70d2426846d299b32ce6a3c62a Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Tue, 3 May 2022 14:45:09 +0200 Subject: [PATCH 71/87] Create EntityTypeConfigurations --- .../Missions/Mappers/SignupsMapper.cs | 3 +- .../Missions/Helpers/MissionsDbHelper.cs | 1 + .../Missions/Helpers/SignupsDbHelper.cs | 23 ++++++++++---- .../Missions/Helpers/SignupsFixture.cs | 5 ++-- .../SignupsQueryServiceIntegrationTests.cs | 2 +- .../Extensions/EntityTypeBuilderExtensions.cs | 14 +++++++++ .../MissionEntityTypeConfiguration.cs | 23 ++++++++++++++ .../SignupsEntityTypeConfiguration.cs | 30 +++++++++++++++++++ .../SlotEntityTypeConfiguration.cs | 27 +++++++++++++++++ .../TeamEntityTypeConfiguration.cs | 25 ++++++++++++++++ .../Persistence/MissionContext.cs | 10 +++++-- .../Features/Missions/Models/Signups.cs | 4 +-- .../Features/Missions/Models/Team.cs | 2 -- 13 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 ArmaForces.Boderator.Core/Extensions/EntityTypeBuilderExtensions.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/MissionEntityTypeConfiguration.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SignupsEntityTypeConfiguration.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SlotEntityTypeConfiguration.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/TeamEntityTypeConfiguration.cs diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 0a8aede..16b8fa0 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -11,10 +11,9 @@ public static SignupsDto Map(Signups signups) => new() { SignupsId = signups.SignupsId, - Status = (SignupsStatus) signups.Status, + Status = signups.Status, StartDate = signups.StartDate, CloseDate = signups.CloseDate, - MissionId = signups.MissionId, Teams = Map(signups.Teams) }; diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs index bf414dd..c7e0c2b 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsDbHelper.cs @@ -19,6 +19,7 @@ public async Task CreateTestMission() var addedEntry = _missionContext.Missions.Add(mission); await _missionContext.SaveChangesAsync(); + _missionContext.ChangeTracker.Clear(); return addedEntry.Entity; } diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs index 994a891..dc103e8 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsDbHelper.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Models; +using FluentAssertions; namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; @@ -17,19 +18,31 @@ public SignupsDbHelper( _missionContext = missionContext; } - public async Task CreateTestSignups(long missionId) + public async Task CreateTestSignups(Mission mission) { - var signup = SignupsFixture.CreateTestSignup(missionId); + var signups = SignupsFixture.CreateTestSignups(); + + var updatedMission = mission with + { + Signups = signups + }; + + _missionContext.Attach(updatedMission); + _missionContext.Entry(updatedMission).Reference(x => x.Signups).IsModified = true; - var addedEntry = _missionContext.Signups.Add(signup); await _missionContext.SaveChangesAsync(); + _missionContext.ChangeTracker.Clear(); + + var addedEntry = await _missionContext.Signups.FindAsync(signups.SignupsId); - return addedEntry.Entity; + addedEntry.Should().NotBeNull(); + + return addedEntry!; } public async Task CreateTestSignups() { var mission = await _missionsDbHelper.CreateTestMission(); - return await CreateTestSignups(mission.MissionId); + return await CreateTestSignups(mission); } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs index 3ba4588..9fa674f 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/SignupsFixture.cs @@ -6,12 +6,11 @@ namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; internal static class SignupsFixture { - public static Signups CreateTestSignup(long missionId) + public static Signups CreateTestSignups() { return new Signups { - MissionId = missionId, - Status = (int) SignupsStatus.Open, + Status = SignupsStatus.Open, StartDate = DateTime.Now, CloseDate = DateTime.Now.AddHours(1), Teams = new List() diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs index f1bdd9e..522dd64 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs @@ -37,7 +37,7 @@ public async Task GetOpenSignups_NoSignupsInDatabase_ReturnsEmptyList() public async Task GetOpenSignups_NoOpenSignupsInDatabase_ReturnsEmptyList() { var testMission = await _missionsDbHelper.CreateTestMission(); - var signup = await _signupsDbHelper.CreateTestSignups(testMission.MissionId); + var signup = await _signupsDbHelper.CreateTestSignups(testMission); var result = await _signupsQueryService.GetOpenSignups(); result.ShouldBeSuccess(new List{signup}); diff --git a/ArmaForces.Boderator.Core/Extensions/EntityTypeBuilderExtensions.cs b/ArmaForces.Boderator.Core/Extensions/EntityTypeBuilderExtensions.cs new file mode 100644 index 0000000..0e4ec8a --- /dev/null +++ b/ArmaForces.Boderator.Core/Extensions/EntityTypeBuilderExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ArmaForces.Boderator.Core.Extensions; + +internal static class EntityTypeBuilderExtensions +{ + public static PropertyBuilder IsNotRequired(this PropertyBuilder propertyBuilder) + => propertyBuilder.IsRequired(false); + + public static ReferenceCollectionBuilder IsNotRequired(this ReferenceCollectionBuilder builder) + where T1 : class + where T2 : class + => builder.IsRequired(false); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/MissionEntityTypeConfiguration.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/MissionEntityTypeConfiguration.cs new file mode 100644 index 0000000..32d1c2b --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/MissionEntityTypeConfiguration.cs @@ -0,0 +1,23 @@ +using ArmaForces.Boderator.Core.Extensions; +using ArmaForces.Boderator.Core.Missions.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.EntityConfigurations; + +internal class MissionEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder missionConfiguration) + { + missionConfiguration.HasKey(x => x.MissionId); + + missionConfiguration.Property(x => x.Title) + .IsRequired(); + + missionConfiguration.Property(x => x.Description) + .IsNotRequired(); + + missionConfiguration.Property(x => x.Owner) + .IsRequired(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SignupsEntityTypeConfiguration.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SignupsEntityTypeConfiguration.cs new file mode 100644 index 0000000..2790f7c --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SignupsEntityTypeConfiguration.cs @@ -0,0 +1,30 @@ +using ArmaForces.Boderator.Core.Extensions; +using ArmaForces.Boderator.Core.Missions.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.EntityConfigurations; + +internal class SignupsEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder signupsConfiguration) + { + signupsConfiguration.HasKey(x => x.SignupsId); + + signupsConfiguration.Property(x => x.Status) + .HasDefaultValue(SignupsStatus.Created) + .HasConversion() + .IsRequired(); + + signupsConfiguration.Property("MissionId") + .IsRequired(); + + signupsConfiguration.HasOne() + .WithOne(x => x.Signups) + .IsRequired(); + + signupsConfiguration.HasMany() + .WithOne() + .IsNotRequired(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SlotEntityTypeConfiguration.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SlotEntityTypeConfiguration.cs new file mode 100644 index 0000000..1c698e7 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/SlotEntityTypeConfiguration.cs @@ -0,0 +1,27 @@ +using ArmaForces.Boderator.Core.Extensions; +using ArmaForces.Boderator.Core.Missions.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.EntityConfigurations; + +internal class SlotEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder slotConfiguration) + { + slotConfiguration.HasKey(x => x.SlotId); + + slotConfiguration.Property("Name") + .IsRequired(); + + slotConfiguration.Property("Vehicle") + .IsNotRequired(); + + slotConfiguration.Property("Occupant") + .IsNotRequired(); + + slotConfiguration.HasOne() + .WithMany(x => x.Slots) + .HasForeignKey("SignupsId", "TeamName"); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/TeamEntityTypeConfiguration.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/TeamEntityTypeConfiguration.cs new file mode 100644 index 0000000..f077a2f --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/EntityConfigurations/TeamEntityTypeConfiguration.cs @@ -0,0 +1,25 @@ +using ArmaForces.Boderator.Core.Extensions; +using ArmaForces.Boderator.Core.Missions.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.EntityConfigurations; + +internal class TeamEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder teamConfiguration) + { + teamConfiguration.HasKey("SignupsId", "Name"); + + teamConfiguration.Property("SignupsId") + .IsRequired(); + + teamConfiguration.Property("Vehicle") + .IsNotRequired(); + + teamConfiguration.HasOne() + .WithMany(x => x.Teams) + .HasForeignKey("SignupsId") + .IsRequired(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs index 5ecb53a..dc9d06b 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/MissionContext.cs @@ -1,3 +1,4 @@ +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.EntityConfigurations; using ArmaForces.Boderator.Core.Missions.Models; using Microsoft.EntityFrameworkCore; @@ -13,10 +14,15 @@ public MissionContext(DbContextOptions options) public DbSet Missions { get; set; } public DbSet Signups { get; set; } + public DbSet Teams { get; set; } + public DbSet Slots { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() - .HasKey(x => new {x.SignupsId, x.Name}); + modelBuilder + .ApplyConfiguration(new MissionEntityTypeConfiguration()) + .ApplyConfiguration(new SignupsEntityTypeConfiguration()) + .ApplyConfiguration(new TeamEntityTypeConfiguration()) + .ApplyConfiguration(new SlotEntityTypeConfiguration()); } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index 31bb3cf..659686d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -7,13 +7,11 @@ public record Signups { public long SignupsId { get; init; } - public ushort Status { get; init; } = (ushort) SignupsStatus.Created; + public SignupsStatus Status { get; init; } = SignupsStatus.Created; public DateTime StartDate { get; init; } public DateTime CloseDate { get; init; } - public long MissionId { get; init; } - public IReadOnlyList Teams { get; init; } = new List(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs index f94b5a8..9c70362 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs @@ -9,6 +9,4 @@ public record Team public string Vehicle { get; init; } = string.Empty; public IReadOnlyList Slots { get; init; } = new List(); - - public long SignupsId { get; init; } } \ No newline at end of file From ae13436890c36053c71ea1a90328d48888318021 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 2 Jul 2022 10:17:31 +0200 Subject: [PATCH 72/87] Start work on MissionSpecification --- .../MissionSpecificationTests.cs | 56 +++++++++++ .../IBuildingMissionSpecification.cs | 8 ++ .../IBuildingSignupsSpecification.cs | 8 ++ .../IDescribedMissionSpecification.cs | 8 ++ .../IModsetSetMissionSpecification.cs | 8 ++ .../IOwnedMissionSpecification.cs | 6 ++ .../IScheduledMissionSpecification.cs | 6 ++ .../ITitledMissionSpecification.cs | 6 ++ .../Specification/MissionSpecification.cs | 97 +++++++++++++++++++ .../Specification/SignupsSpecification.cs | 45 +++++++++ .../IBuildingModsetSpecification.cs | 6 ++ .../Features/Modsets/Specification/Modset.cs | 11 +++ .../Specification/ModsetSpecification.cs | 23 +++++ .../Features/Users/User.cs | 18 ++++ 14 files changed, 306 insertions(+) create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Modsets/Specification/Modset.cs create mode 100644 ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Users/User.cs diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs new file mode 100644 index 0000000..a4c460c --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Specification; +using ArmaForces.Boderator.Core.Modsets.Specification; +using ArmaForces.Boderator.Core.Users; +using AutoFixture; +using FluentAssertions; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation.Specification; + +public class MissionSpecificationTests +{ + private readonly Fixture _fixture = new(); + + [Fact] + public void CreateSpecification_FullSpecification_ValidMissionBuilt() + { + var expectedOwner = new User + { + Name = _fixture.Create() + }; + + var expectedMission = new Mission + { + Title = _fixture.Create(), + Description = _fixture.Create(), + MissionDate = _fixture.Create(), + ModsetName = _fixture.Create(), + Owner = expectedOwner.Name, + Signups = new Signups + { + StartDate = _fixture.Create(), + CloseDate = _fixture.Create(), + Status = SignupsStatus.Created, + Teams = new List() + } + }; + + var specification = MissionSpecification + .OwnedBy(new User(expectedMission.Owner)) + .WithTitle("Test title") + .WithDescription("Test description") + .WithModset(ModsetSpecification + .ByName("modset name")) + .ScheduledAt(DateTimeOffset.Now) + .WithSignups(SignupsSpecification + .StartingAt(DateTimeOffset.Now.AddDays(1)) + .ClosingAt(DateTimeOffset.Now.AddDays(1))); + + var mission = specification.Build(); + + mission.Should().BeEquivalentTo(expectedMission); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs new file mode 100644 index 0000000..715ba4c --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs @@ -0,0 +1,8 @@ +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface IBuildingMissionSpecification +{ + Mission Build(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs new file mode 100644 index 0000000..8625b84 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs @@ -0,0 +1,8 @@ +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface IBuildingSignupsSpecification +{ + Signups Build(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs new file mode 100644 index 0000000..83de18c --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs @@ -0,0 +1,8 @@ +using ArmaForces.Boderator.Core.Modsets.Specification; + +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface IDescribedMissionSpecification +{ + IModsetSetMissionSpecification WithModset(IBuildingModsetSpecification modsetSpecification); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs new file mode 100644 index 0000000..fed3822 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs @@ -0,0 +1,8 @@ +using System; + +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface IModsetSetMissionSpecification +{ + IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs new file mode 100644 index 0000000..fdc29b7 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface IOwnedMissionSpecification +{ + ITitledMissionSpecification WithTitle(string title); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs new file mode 100644 index 0000000..7ccc1f6 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface IScheduledMissionSpecification +{ + IBuildingMissionSpecification WithSignups(IBuildingSignupsSpecification signupsSpecification); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs new file mode 100644 index 0000000..ee069ee --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public interface ITitledMissionSpecification +{ + IDescribedMissionSpecification WithDescription(string description); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs new file mode 100644 index 0000000..e58a52d --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs @@ -0,0 +1,97 @@ +using System; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Modsets.Specification; +using ArmaForces.Boderator.Core.Users; + +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public record MissionSpecification : + IOwnedMissionSpecification, + ITitledMissionSpecification, + IDescribedMissionSpecification, + IModsetSetMissionSpecification, + IScheduledMissionSpecification, + IBuildingMissionSpecification +{ + private MissionSpecification() { } + + private User User { get; init; } = new(); + + private string Title { get; init; } = string.Empty; + + private string Description { get; init; } = string.Empty; + + private IBuildingModsetSpecification? ModsetSpecification { get; init; } + + private DateTimeOffset Time { get; init; } + + private IBuildingSignupsSpecification? SignupsSpecification { get; init; } + + public static IOwnedMissionSpecification OwnedBy(User user) + { + return new MissionSpecification + { + User = user + }; + } + + public ITitledMissionSpecification WithTitle(string title) + { + if (string.IsNullOrEmpty(title)) + throw new ArgumentException("Mission title cannot be null or empty", nameof(title)); + + return this with + { + Title = title + }; + } + + public IDescribedMissionSpecification WithDescription(string description) + { + if (string.IsNullOrEmpty(description)) + throw new ArgumentException("Mission title cannot be null or empty", nameof(description)); + + return this with + { + Description = description + }; + } + + public IModsetSetMissionSpecification WithModset(IBuildingModsetSpecification modsetSpecification) + { + if (modsetSpecification is null) + throw new ArgumentNullException(nameof(modsetSpecification)); + + return this with + { + ModsetSpecification = modsetSpecification + }; + } + + public IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime) + { + return this with + { + Time = dateTime + }; + } + + public IBuildingMissionSpecification WithSignups(IBuildingSignupsSpecification signupsSpecification) + { + return this with + { + SignupsSpecification = signupsSpecification + }; + } + + public Mission Build() + => new() + { + Owner = User.ToString(), + Title = Title, + Description = Description, + MissionDate = Time.DateTime, + ModsetName = ModsetSpecification!.Build().Name, + Signups = SignupsSpecification!.Build() + }; +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs new file mode 100644 index 0000000..9583c0f --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs @@ -0,0 +1,45 @@ +using System; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Users; + +namespace ArmaForces.Boderator.Core.Missions.Specification; + +public record SignupsSpecification : + IStartingSignupsSpecification, + IBuildingSignupsSpecification +{ + private SignupsSpecification() { } + + private DateTimeOffset StartAt { get; init; } + + private DateTimeOffset CloseAt { get; init; } + + public static IStartingSignupsSpecification StartingAt(DateTimeOffset dateTime) + { + return new SignupsSpecification() + { + StartAt = dateTime + }; + } + + public IBuildingSignupsSpecification ClosingAt(DateTimeOffset dateTime) + { + return this with + { + CloseAt = dateTime + }; + } + + public Signups Build() + => new() + { + Status = SignupsStatus.Created, + StartDate = StartAt.DateTime, + CloseDate = CloseAt.DateTime + }; +} + +public interface IStartingSignupsSpecification +{ + IBuildingSignupsSpecification ClosingAt(DateTimeOffset dateTime); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs b/ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs new file mode 100644 index 0000000..393d3f1 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Modsets.Specification; + +public interface IBuildingModsetSpecification +{ + public Modset Build(); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Modsets/Specification/Modset.cs b/ArmaForces.Boderator.Core/Features/Modsets/Specification/Modset.cs new file mode 100644 index 0000000..b854720 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Modsets/Specification/Modset.cs @@ -0,0 +1,11 @@ +namespace ArmaForces.Boderator.Core.Modsets.Specification; + +public record Modset +{ + /// + /// Internal constructor to block construction of invalid object. + /// + internal Modset() { } + + public string Name { get; init; } = string.Empty; +} diff --git a/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs b/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs new file mode 100644 index 0000000..5f72fd7 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs @@ -0,0 +1,23 @@ +using System; + +namespace ArmaForces.Boderator.Core.Modsets.Specification; + +public class ModsetSpecification : IBuildingModsetSpecification +{ + private ModsetSpecification() { } + + public static IBuildingModsetSpecification ByName(string modsetName) + { + return new ModsetSpecification(); + } + + public static IBuildingModsetSpecification ByUrl(Uri modsetUrl) + { + return new ModsetSpecification(); + } + + public Modset Build() + { + return new Modset(); + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Users/User.cs b/ArmaForces.Boderator.Core/Features/Users/User.cs new file mode 100644 index 0000000..fc44b1f --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Users/User.cs @@ -0,0 +1,18 @@ +namespace ArmaForces.Boderator.Core.Users; + +public record User +{ + /// + /// Internal constructor to block construction of invalid object. + /// + internal User() { } + + internal User(string userName) + { + Name = userName; + } + + public string Name { get; init; } = string.Empty; + + public override string ToString() => Name; +} From 593d37afb08f267e08743ed3720a5d14a4fa1d1a Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sun, 3 Jul 2022 09:09:46 +0200 Subject: [PATCH 73/87] Introduce IBulidingSpecification --- .../MissionSpecificationTests.cs | 2 +- .../Implementation/SignupsQueryService.cs | 2 +- .../IBuildingMissionSpecification.cs | 8 ------ .../IBuildingSignupsSpecification.cs | 8 ------ .../IDescribedMissionSpecification.cs | 8 ------ .../IScheduledMissionSpecification.cs | 6 ----- .../IDescribedMissionSpecification.cs | 9 +++++++ .../IModsetSetMissionSpecification.cs | 2 +- .../Interfaces}/IOwnedMissionSpecification.cs | 2 +- .../IScheduledMissionSpecification.cs | 9 +++++++ .../IStartingSignupsSpecification.cs | 10 +++++++ .../ITitledMissionSpecification.cs | 2 +- .../MissionSpecification.cs | 27 +++++++++++-------- .../SignupsSpecification.cs | 14 ++++------ .../IBuildingModsetSpecification.cs | 6 ----- .../Specification/ModsetSpecification.cs | 7 ++--- .../Specifications/IBuildingSpecification.cs | 6 +++++ 17 files changed, 64 insertions(+), 64 deletions(-) delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs rename ArmaForces.Boderator.Core/Features/Missions/{Specification => Specifications/Interfaces}/IModsetSetMissionSpecification.cs (64%) rename ArmaForces.Boderator.Core/Features/Missions/{Specification => Specifications/Interfaces}/IOwnedMissionSpecification.cs (56%) create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs rename ArmaForces.Boderator.Core/Features/Missions/{Specification => Specifications/Interfaces}/ITitledMissionSpecification.cs (60%) rename ArmaForces.Boderator.Core/Features/Missions/{Specification => Specifications}/MissionSpecification.cs (64%) rename ArmaForces.Boderator.Core/Features/Missions/{Specification => Specifications}/SignupsSpecification.cs (67%) delete mode 100644 ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Infrastructure/Specifications/IBuildingSpecification.cs diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs index a4c460c..e0a793a 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using ArmaForces.Boderator.Core.Missions.Models; -using ArmaForces.Boderator.Core.Missions.Specification; +using ArmaForces.Boderator.Core.Missions.Specifications; using ArmaForces.Boderator.Core.Modsets.Specification; using ArmaForces.Boderator.Core.Users; using AutoFixture; diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs index 631bf9f..81b305f 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsQueryService.cs @@ -18,7 +18,7 @@ public SignupsQueryService(ISignupsQueryRepository signupsQueryRepository) public async Task> GetSignups(long signupId) => await _signupsQueryRepository.GetSignups(signupId) - ?? Result.Failure($"Signup with ID {signupId} not found"); + ?? Result.Failure($"Signup with ID {signupId} not found"); public async Task>> GetOpenSignups() => await _signupsQueryRepository.GetOpenSignups(); diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs deleted file mode 100644 index 715ba4c..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingMissionSpecification.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ArmaForces.Boderator.Core.Missions.Models; - -namespace ArmaForces.Boderator.Core.Missions.Specification; - -public interface IBuildingMissionSpecification -{ - Mission Build(); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs deleted file mode 100644 index 8625b84..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/IBuildingSignupsSpecification.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ArmaForces.Boderator.Core.Missions.Models; - -namespace ArmaForces.Boderator.Core.Missions.Specification; - -public interface IBuildingSignupsSpecification -{ - Signups Build(); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs deleted file mode 100644 index 83de18c..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/IDescribedMissionSpecification.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ArmaForces.Boderator.Core.Modsets.Specification; - -namespace ArmaForces.Boderator.Core.Missions.Specification; - -public interface IDescribedMissionSpecification -{ - IModsetSetMissionSpecification WithModset(IBuildingModsetSpecification modsetSpecification); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs deleted file mode 100644 index 7ccc1f6..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/IScheduledMissionSpecification.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ArmaForces.Boderator.Core.Missions.Specification; - -public interface IScheduledMissionSpecification -{ - IBuildingMissionSpecification WithSignups(IBuildingSignupsSpecification signupsSpecification); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs new file mode 100644 index 0000000..4807ed7 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs @@ -0,0 +1,9 @@ +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Modsets.Specification; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IDescribedMissionSpecification +{ + IModsetSetMissionSpecification WithModset(IBuildingSpecification modsetSpecification); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs similarity index 64% rename from ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs index fed3822..7908b0d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/IModsetSetMissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs @@ -1,6 +1,6 @@ using System; -namespace ArmaForces.Boderator.Core.Missions.Specification; +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; public interface IModsetSetMissionSpecification { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs similarity index 56% rename from ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs index fdc29b7..dd2ac38 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/IOwnedMissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs @@ -1,4 +1,4 @@ -namespace ArmaForces.Boderator.Core.Missions.Specification; +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; public interface IOwnedMissionSpecification { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs new file mode 100644 index 0000000..754f1f7 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs @@ -0,0 +1,9 @@ +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IScheduledMissionSpecification +{ + IBuildingSpecification WithSignups(IBuildingSpecification signupsSpecification); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs new file mode 100644 index 0000000..610d451 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs @@ -0,0 +1,10 @@ +using System; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IStartingSignupsSpecification +{ + IBuildingSpecification ClosingAt(DateTimeOffset dateTime); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs similarity index 60% rename from ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs index ee069ee..d19bb65 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/ITitledMissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs @@ -1,4 +1,4 @@ -namespace ArmaForces.Boderator.Core.Missions.Specification; +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; public interface ITitledMissionSpecification { diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/MissionSpecification.cs similarity index 64% rename from ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/MissionSpecification.cs index e58a52d..c06891e 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/MissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/MissionSpecification.cs @@ -1,9 +1,11 @@ using System; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; using ArmaForces.Boderator.Core.Modsets.Specification; using ArmaForces.Boderator.Core.Users; -namespace ArmaForces.Boderator.Core.Missions.Specification; +namespace ArmaForces.Boderator.Core.Missions.Specifications; public record MissionSpecification : IOwnedMissionSpecification, @@ -11,21 +13,21 @@ public record MissionSpecification : IDescribedMissionSpecification, IModsetSetMissionSpecification, IScheduledMissionSpecification, - IBuildingMissionSpecification + IBuildingSpecification { private MissionSpecification() { } - private User User { get; init; } = new(); + public User User { get; private init; } = new(); - private string Title { get; init; } = string.Empty; + public string Title { get; private init; } = string.Empty; - private string Description { get; init; } = string.Empty; + public string Description { get; private init; } = string.Empty; - private IBuildingModsetSpecification? ModsetSpecification { get; init; } + public IBuildingSpecification? ModsetSpecification { get; private init; } - private DateTimeOffset Time { get; init; } + public DateTimeOffset Time { get; private init; } - private IBuildingSignupsSpecification? SignupsSpecification { get; init; } + public IBuildingSpecification? SignupsSpecification { get; private init; } public static IOwnedMissionSpecification OwnedBy(User user) { @@ -57,7 +59,7 @@ public IDescribedMissionSpecification WithDescription(string description) }; } - public IModsetSetMissionSpecification WithModset(IBuildingModsetSpecification modsetSpecification) + public IModsetSetMissionSpecification WithModset(IBuildingSpecification modsetSpecification) { if (modsetSpecification is null) throw new ArgumentNullException(nameof(modsetSpecification)); @@ -76,15 +78,18 @@ public IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime) }; } - public IBuildingMissionSpecification WithSignups(IBuildingSignupsSpecification signupsSpecification) + public IBuildingSpecification WithSignups(IBuildingSpecification signupsSpecification) { + if (signupsSpecification is null) + throw new ArgumentNullException(nameof(signupsSpecification)); + return this with { SignupsSpecification = signupsSpecification }; } - public Mission Build() + public Models.Mission Build() => new() { Owner = User.ToString(), diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs similarity index 67% rename from ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs index 9583c0f..df64cd7 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specification/SignupsSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs @@ -1,12 +1,13 @@ using System; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; using ArmaForces.Boderator.Core.Missions.Models; -using ArmaForces.Boderator.Core.Users; +using ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; -namespace ArmaForces.Boderator.Core.Missions.Specification; +namespace ArmaForces.Boderator.Core.Missions.Specifications; public record SignupsSpecification : IStartingSignupsSpecification, - IBuildingSignupsSpecification + IBuildingSpecification { private SignupsSpecification() { } @@ -22,7 +23,7 @@ public static IStartingSignupsSpecification StartingAt(DateTimeOffset dateTime) }; } - public IBuildingSignupsSpecification ClosingAt(DateTimeOffset dateTime) + public IBuildingSpecification ClosingAt(DateTimeOffset dateTime) { return this with { @@ -37,9 +38,4 @@ public Signups Build() StartDate = StartAt.DateTime, CloseDate = CloseAt.DateTime }; -} - -public interface IStartingSignupsSpecification -{ - IBuildingSignupsSpecification ClosingAt(DateTimeOffset dateTime); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs b/ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs deleted file mode 100644 index 393d3f1..0000000 --- a/ArmaForces.Boderator.Core/Features/Modsets/Specification/IBuildingModsetSpecification.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ArmaForces.Boderator.Core.Modsets.Specification; - -public interface IBuildingModsetSpecification -{ - public Modset Build(); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs b/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs index 5f72fd7..1160730 100644 --- a/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs @@ -1,17 +1,18 @@ using System; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; namespace ArmaForces.Boderator.Core.Modsets.Specification; -public class ModsetSpecification : IBuildingModsetSpecification +public class ModsetSpecification : IBuildingSpecification { private ModsetSpecification() { } - public static IBuildingModsetSpecification ByName(string modsetName) + public static IBuildingSpecification ByName(string modsetName) { return new ModsetSpecification(); } - public static IBuildingModsetSpecification ByUrl(Uri modsetUrl) + public static IBuildingSpecification ByUrl(Uri modsetUrl) { return new ModsetSpecification(); } diff --git a/ArmaForces.Boderator.Core/Infrastructure/Specifications/IBuildingSpecification.cs b/ArmaForces.Boderator.Core/Infrastructure/Specifications/IBuildingSpecification.cs new file mode 100644 index 0000000..6e1fb37 --- /dev/null +++ b/ArmaForces.Boderator.Core/Infrastructure/Specifications/IBuildingSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Infrastructure.Specifications; + +public interface IBuildingSpecification +{ + T Build(); +} From ce1a6e946f5423b97281ae4da6b98bd225dc5d1c Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sun, 3 Jul 2022 10:29:25 +0200 Subject: [PATCH 74/87] Add Team and Slot specification --- .../MissionSpecificationTests.cs | 51 ++++++++-- ...maForces.Boderator.Core.csproj.DotSettings | 6 +- .../Features/Missions/Models/Slot.cs | 2 +- .../Features/Missions/Models/Team.cs | 2 +- .../IModsetSetMissionSpecification.cs | 8 -- .../Interfaces/IOwnedMissionSpecification.cs | 6 -- .../IStartingSignupsSpecification.cs | 10 -- .../Interfaces/ITitledMissionSpecification.cs | 6 -- .../IExpectMissionDateSpecification.cs | 8 ++ .../IExpectMissionDescriptionSpecification.cs | 6 ++ .../IExpectMissionSignupsSpecification.cs} | 2 +- .../IExpectMissionTitleSpecification.cs | 6 ++ .../Interfaces/IExpectModsetSpecification.cs} | 4 +- .../{ => Mission}/MissionSpecification.cs | 44 ++++----- .../IExpectAnotherTeamSpecification.cs | 13 +++ .../IExpectCloseDateSpecification.cs | 8 ++ .../Interfaces/IExpectTeamSpecification.cs | 11 +++ .../Signups/SignupsSpecification.cs | 77 ++++++++++++++++ .../Specifications/SignupsSpecification.cs | 41 --------- .../Specifications/Slot/SlotSpecification.cs | 87 ++++++++++++++++++ .../IExpectAnotherSlotSpecification.cs | 16 ++++ ...IExpectOptionalTeamVehicleSpecification.cs | 8 ++ .../Interfaces/IExpectSlotSpecification.cs | 11 +++ .../Specifications/Team/TeamSpecification.cs | 92 +++++++++++++++++++ .../Specification/ModsetSpecification.cs | 20 +++- 25 files changed, 435 insertions(+), 110 deletions(-) delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDateSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDescriptionSpecification.cs rename ArmaForces.Boderator.Core/Features/Missions/Specifications/{Interfaces/IScheduledMissionSpecification.cs => Mission/Interfaces/IExpectMissionSignupsSpecification.cs} (83%) create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionTitleSpecification.cs rename ArmaForces.Boderator.Core/Features/Missions/Specifications/{Interfaces/IDescribedMissionSpecification.cs => Mission/Interfaces/IExpectModsetSpecification.cs} (56%) rename ArmaForces.Boderator.Core/Features/Missions/Specifications/{ => Mission}/MissionSpecification.cs (59%) create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectAnotherTeamSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectCloseDateSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectTeamSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/SignupsSpecification.cs delete mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Slot/SlotSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectAnotherSlotSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectOptionalTeamVehicleSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectSlotSpecification.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/TeamSpecification.cs diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs index e0a793a..876b5ce 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs @@ -34,20 +34,57 @@ public void CreateSpecification_FullSpecification_ValidMissionBuilt() StartDate = _fixture.Create(), CloseDate = _fixture.Create(), Status = SignupsStatus.Created, - Teams = new List() + Teams = new List + { + new() + { + Name = "Alpha", + Slots = new List + { + new() + { + Name = "SL" + } + } + }, + new() + { + Name = "Bravo", + Slots = new List + { + new() + { + Name = "SL" + } + } + } + } } }; var specification = MissionSpecification .OwnedBy(new User(expectedMission.Owner)) - .WithTitle("Test title") - .WithDescription("Test description") + .Titled(expectedMission.Title) + .WithDescription(expectedMission.Description) .WithModset(ModsetSpecification - .ByName("modset name")) - .ScheduledAt(DateTimeOffset.Now) + .Named(expectedMission.ModsetName)) + .ScheduledAt(expectedMission.MissionDate.Value) .WithSignups(SignupsSpecification - .StartingAt(DateTimeOffset.Now.AddDays(1)) - .ClosingAt(DateTimeOffset.Now.AddDays(1))); + .StartingAt(expectedMission.Signups.StartDate) + .ClosingAt(expectedMission.Signups.CloseDate) + .WithTeam(TeamSpecification + .Named("Alpha") + .WithoutVehicle() + .WithSlot(SlotSpecification + .UnoccupiedWithoutVehicleNamed("SL")) + .AndNoMoreSlots()) + .AndTeam(TeamSpecification + .Named("Bravo") + .WithoutVehicle() + .WithSlot(SlotSpecification + .UnoccupiedWithoutVehicleNamed("SL")) + .AndNoMoreSlots()) + .AnoNoMoreTeams()); var mission = specification.Build(); diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings index 3a36d17..791ecd0 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj.DotSettings @@ -1,2 +1,6 @@  - True \ No newline at end of file + True + True + True + True + True \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs index c826f42..bf35883 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Slot.cs @@ -6,7 +6,7 @@ public record Slot public string Name { get; init; } = string.Empty; - public string Vehicle { get; init; } = string.Empty; + public string? Vehicle { get; init; } public string? Occupant { get; init; } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs index 9c70362..d229aab 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Team.cs @@ -6,7 +6,7 @@ public record Team { public string Name { get; init; } = string.Empty; - public string Vehicle { get; init; } = string.Empty; + public string? Vehicle { get; init; } public IReadOnlyList Slots { get; init; } = new List(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs deleted file mode 100644 index 7908b0d..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IModsetSetMissionSpecification.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; - -public interface IModsetSetMissionSpecification -{ - IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs deleted file mode 100644 index dd2ac38..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IOwnedMissionSpecification.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; - -public interface IOwnedMissionSpecification -{ - ITitledMissionSpecification WithTitle(string title); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs deleted file mode 100644 index 610d451..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IStartingSignupsSpecification.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using ArmaForces.Boderator.Core.Infrastructure.Specifications; -using ArmaForces.Boderator.Core.Missions.Models; - -namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; - -public interface IStartingSignupsSpecification -{ - IBuildingSpecification ClosingAt(DateTimeOffset dateTime); -} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs deleted file mode 100644 index d19bb65..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/ITitledMissionSpecification.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; - -public interface ITitledMissionSpecification -{ - IDescribedMissionSpecification WithDescription(string description); -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDateSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDateSpecification.cs new file mode 100644 index 0000000..c0c4f19 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDateSpecification.cs @@ -0,0 +1,8 @@ +using System; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectMissionDateSpecification +{ + IExpectMissionSignupsSpecification ScheduledAt(DateTimeOffset dateTime); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDescriptionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDescriptionSpecification.cs new file mode 100644 index 0000000..5e0a8ae --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionDescriptionSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectMissionDescriptionSpecification +{ + IExpectModsetSpecification WithDescription(string description); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionSignupsSpecification.cs similarity index 83% rename from ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionSignupsSpecification.cs index 754f1f7..23dd8e0 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IScheduledMissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionSignupsSpecification.cs @@ -3,7 +3,7 @@ namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; -public interface IScheduledMissionSpecification +public interface IExpectMissionSignupsSpecification { IBuildingSpecification WithSignups(IBuildingSpecification signupsSpecification); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionTitleSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionTitleSpecification.cs new file mode 100644 index 0000000..5a419c4 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectMissionTitleSpecification.cs @@ -0,0 +1,6 @@ +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectMissionTitleSpecification +{ + IExpectMissionDescriptionSpecification Titled(string title); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectModsetSpecification.cs similarity index 56% rename from ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectModsetSpecification.cs index 4807ed7..e7cbc56 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Interfaces/IDescribedMissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/Interfaces/IExpectModsetSpecification.cs @@ -3,7 +3,7 @@ namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; -public interface IDescribedMissionSpecification +public interface IExpectModsetSpecification { - IModsetSetMissionSpecification WithModset(IBuildingSpecification modsetSpecification); + IExpectMissionDateSpecification WithModset(IBuildingSpecification modsetSpecification); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/MissionSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/MissionSpecification.cs similarity index 59% rename from ArmaForces.Boderator.Core/Features/Missions/Specifications/MissionSpecification.cs rename to ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/MissionSpecification.cs index c06891e..ad65795 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/MissionSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Mission/MissionSpecification.cs @@ -8,28 +8,28 @@ namespace ArmaForces.Boderator.Core.Missions.Specifications; public record MissionSpecification : - IOwnedMissionSpecification, - ITitledMissionSpecification, - IDescribedMissionSpecification, - IModsetSetMissionSpecification, - IScheduledMissionSpecification, - IBuildingSpecification + IExpectMissionTitleSpecification, + IExpectMissionDescriptionSpecification, + IExpectModsetSpecification, + IExpectMissionDateSpecification, + IExpectMissionSignupsSpecification, + IBuildingSpecification { private MissionSpecification() { } - public User User { get; private init; } = new(); - - public string Title { get; private init; } = string.Empty; + private User User { get; init; } = new(); - public string Description { get; private init; } = string.Empty; + private string Title { get; init; } = string.Empty; - public IBuildingSpecification? ModsetSpecification { get; private init; } - - public DateTimeOffset Time { get; private init; } - - public IBuildingSpecification? SignupsSpecification { get; private init; } + private string Description { get; init; } = string.Empty; - public static IOwnedMissionSpecification OwnedBy(User user) + private IBuildingSpecification? ModsetSpecification { get; init; } + + private DateTimeOffset Time { get; init; } + + private IBuildingSpecification? SignupsSpecification { get; init; } + + public static IExpectMissionTitleSpecification OwnedBy(User user) { return new MissionSpecification { @@ -37,7 +37,7 @@ public static IOwnedMissionSpecification OwnedBy(User user) }; } - public ITitledMissionSpecification WithTitle(string title) + public IExpectMissionDescriptionSpecification Titled(string title) { if (string.IsNullOrEmpty(title)) throw new ArgumentException("Mission title cannot be null or empty", nameof(title)); @@ -48,7 +48,7 @@ public ITitledMissionSpecification WithTitle(string title) }; } - public IDescribedMissionSpecification WithDescription(string description) + public IExpectModsetSpecification WithDescription(string description) { if (string.IsNullOrEmpty(description)) throw new ArgumentException("Mission title cannot be null or empty", nameof(description)); @@ -59,7 +59,7 @@ public IDescribedMissionSpecification WithDescription(string description) }; } - public IModsetSetMissionSpecification WithModset(IBuildingSpecification modsetSpecification) + public IExpectMissionDateSpecification WithModset(IBuildingSpecification modsetSpecification) { if (modsetSpecification is null) throw new ArgumentNullException(nameof(modsetSpecification)); @@ -70,7 +70,7 @@ public IModsetSetMissionSpecification WithModset(IBuildingSpecification }; } - public IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime) + public IExpectMissionSignupsSpecification ScheduledAt(DateTimeOffset dateTime) { return this with { @@ -78,7 +78,7 @@ public IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime) }; } - public IBuildingSpecification WithSignups(IBuildingSpecification signupsSpecification) + public IBuildingSpecification WithSignups(IBuildingSpecification signupsSpecification) { if (signupsSpecification is null) throw new ArgumentNullException(nameof(signupsSpecification)); @@ -89,7 +89,7 @@ public IScheduledMissionSpecification ScheduledAt(DateTimeOffset dateTime) }; } - public Models.Mission Build() + public Mission Build() => new() { Owner = User.ToString(), diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectAnotherTeamSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectAnotherTeamSpecification.cs new file mode 100644 index 0000000..721b02a --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectAnotherTeamSpecification.cs @@ -0,0 +1,13 @@ +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectAnotherTeamSpecification +{ + bool CanAdd(IBuildingSpecification? team); + + IExpectAnotherTeamSpecification AndTeam(IBuildingSpecification team); + + IBuildingSpecification AnoNoMoreTeams(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectCloseDateSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectCloseDateSpecification.cs new file mode 100644 index 0000000..cdc1fa1 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectCloseDateSpecification.cs @@ -0,0 +1,8 @@ +using System; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectCloseDateSpecification +{ + IExpectTeamSpecification ClosingAt(DateTimeOffset dateTime); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectTeamSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectTeamSpecification.cs new file mode 100644 index 0000000..99c8b7c --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/Interfaces/IExpectTeamSpecification.cs @@ -0,0 +1,11 @@ +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectTeamSpecification +{ + bool CanAdd(IBuildingSpecification? team); + + IExpectAnotherTeamSpecification WithTeam(IBuildingSpecification team); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/SignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/SignupsSpecification.cs new file mode 100644 index 0000000..148b3f8 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Signups/SignupsSpecification.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +namespace ArmaForces.Boderator.Core.Missions.Specifications; + +public record SignupsSpecification : + IExpectCloseDateSpecification, + IExpectTeamSpecification, + IExpectAnotherTeamSpecification, + IBuildingSpecification +{ + private SignupsSpecification() { } + + private DateTimeOffset StartAt { get; init; } + + private DateTimeOffset CloseAt { get; init; } + + private IReadOnlyList> Teams { get; init; } = new List>(); + + public static IExpectCloseDateSpecification StartingAt(DateTimeOffset dateTime) + { + // TODO: Add date validation + return new SignupsSpecification + { + StartAt = dateTime + }; + } + + public IExpectTeamSpecification ClosingAt(DateTimeOffset dateTime) + { + // TODO: Add date validation + return this with + { + CloseAt = dateTime + }; + } + + public bool CanAdd(IBuildingSpecification? team) => + team is not null && + Teams.All(x => x.Build().Name != team.Build().Name); + + public IExpectAnotherTeamSpecification WithTeam(IBuildingSpecification team) => AddTeam(team); + + public IExpectAnotherTeamSpecification AndTeam(IBuildingSpecification team) => AddTeam(team); + + public IBuildingSpecification AnoNoMoreTeams() + { + return Teams.Any() + ? this + : throw new InvalidOperationException("At least one team must be added"); + } + + public Signups Build() + => new() + { + Status = SignupsStatus.Created, + StartDate = StartAt.DateTime, + CloseDate = CloseAt.DateTime, + Teams = Teams.Select(x => x.Build()).ToList() + }; + + private SignupsSpecification AddTeam(IBuildingSpecification team) + { + if (!CanAdd(team)) + throw new ArgumentException( + "Team cannot be added as it's either null or other team with the same name was already added"); + + return this with + { + Teams = new List>(Teams) {team} + }; + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs deleted file mode 100644 index df64cd7..0000000 --- a/ArmaForces.Boderator.Core/Features/Missions/Specifications/SignupsSpecification.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using ArmaForces.Boderator.Core.Infrastructure.Specifications; -using ArmaForces.Boderator.Core.Missions.Models; -using ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; - -namespace ArmaForces.Boderator.Core.Missions.Specifications; - -public record SignupsSpecification : - IStartingSignupsSpecification, - IBuildingSpecification -{ - private SignupsSpecification() { } - - private DateTimeOffset StartAt { get; init; } - - private DateTimeOffset CloseAt { get; init; } - - public static IStartingSignupsSpecification StartingAt(DateTimeOffset dateTime) - { - return new SignupsSpecification() - { - StartAt = dateTime - }; - } - - public IBuildingSpecification ClosingAt(DateTimeOffset dateTime) - { - return this with - { - CloseAt = dateTime - }; - } - - public Signups Build() - => new() - { - Status = SignupsStatus.Created, - StartDate = StartAt.DateTime, - CloseDate = CloseAt.DateTime - }; -} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Slot/SlotSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Slot/SlotSpecification.cs new file mode 100644 index 0000000..dc09e7c --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Slot/SlotSpecification.cs @@ -0,0 +1,87 @@ +using System; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Users; + +namespace ArmaForces.Boderator.Core.Missions.Specifications; + +public record SlotSpecification : + IExpectOptionalSlotOccupantSpecification, + IExpectSlotOptionalVehicleSpecification, + IBuildingSpecification +{ + private SlotSpecification() { } + + private string Name { get; init; } = string.Empty; + + private string? Occupant { get; init; } + + private string? Vehicle { get; init; } + + public static IExpectOptionalSlotOccupantSpecification Named(string slotName) + { + if (string.IsNullOrEmpty(slotName)) + throw new ArgumentException("Slot name cannot be null or empty."); + + return new SlotSpecification + { + Name = slotName + }; + } + + public static IBuildingSpecification UnoccupiedWithoutVehicleNamed(string slotName) + { + if (string.IsNullOrEmpty(slotName)) + throw new ArgumentException("Slot name cannot be null or empty."); + + return new SlotSpecification + { + Name = slotName + }; + } + + public IExpectSlotOptionalVehicleSpecification OccupiedBy(User user) => + this with + { + Occupant = user.Name + }; + + public IExpectSlotOptionalVehicleSpecification Unoccupied() => this; + + public IBuildingSpecification WithVehicle(string vehicleName) + { + if (string.IsNullOrEmpty(vehicleName)) + throw new ArgumentException("Specified vehicle name is null or empty."); + + return this with + { + Vehicle = vehicleName + }; + } + + public IBuildingSpecification WithoutVehicle() => this; + + public Slot Build() + { + return new Slot + { + Name = Name, + Occupant = Occupant, + Vehicle = Vehicle + }; + } +} + +public interface IExpectSlotOptionalVehicleSpecification +{ + IBuildingSpecification WithVehicle(string vehicleName); + + IBuildingSpecification WithoutVehicle(); +} + +public interface IExpectOptionalSlotOccupantSpecification +{ + IExpectSlotOptionalVehicleSpecification OccupiedBy(User user); + + IExpectSlotOptionalVehicleSpecification Unoccupied(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectAnotherSlotSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectAnotherSlotSpecification.cs new file mode 100644 index 0000000..0818f63 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectAnotherSlotSpecification.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectAnotherSlotSpecification +{ + bool CanAdd(IBuildingSpecification? slot); + + IExpectAnotherSlotSpecification AndSlot(IBuildingSpecification slot); + + IExpectAnotherSlotSpecification WithSlots(List> slots); + + IBuildingSpecification AndNoMoreSlots(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectOptionalTeamVehicleSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectOptionalTeamVehicleSpecification.cs new file mode 100644 index 0000000..01073aa --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectOptionalTeamVehicleSpecification.cs @@ -0,0 +1,8 @@ +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectOptionalTeamVehicleSpecification +{ + IExpectSlotSpecification WithVehicle(string vehicleName); + + IExpectSlotSpecification WithoutVehicle(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectSlotSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectSlotSpecification.cs new file mode 100644 index 0000000..bb7145a --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/Interfaces/IExpectSlotSpecification.cs @@ -0,0 +1,11 @@ +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +public interface IExpectSlotSpecification +{ + bool CanAdd(IBuildingSpecification? slot); + + IExpectAnotherSlotSpecification WithSlot(IBuildingSpecification slot); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/TeamSpecification.cs b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/TeamSpecification.cs new file mode 100644 index 0000000..b39f385 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Specifications/Team/TeamSpecification.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ArmaForces.Boderator.Core.Infrastructure.Specifications; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Specifications.Interfaces; + +namespace ArmaForces.Boderator.Core.Missions.Specifications; + +public record TeamSpecification : + IExpectSlotSpecification, + IExpectAnotherSlotSpecification, + IExpectOptionalTeamVehicleSpecification, + IBuildingSpecification +{ + private TeamSpecification() { } + + private string Name { get; init; } = string.Empty; + + private string? Vehicle { get; init; } + + private IReadOnlyList> Slots { get; init; } = new List>(); + + public static IExpectOptionalTeamVehicleSpecification Named(string teamName) + { + if (string.IsNullOrEmpty(teamName)) + throw new ArgumentException("Team name cannot be empty", nameof(teamName)); + + return new TeamSpecification + { + Name = teamName + }; + } + + public IExpectSlotSpecification WithVehicle(string vehicleName) + { + if (string.IsNullOrEmpty(vehicleName)) + throw new ArgumentException("Specified vehicle name is null or empty"); + + return this with + { + Vehicle = vehicleName + }; + } + + public IExpectSlotSpecification WithoutVehicle() => this; + + public bool CanAdd(IBuildingSpecification? slot) + { + var builtSlotToAdd = slot?.Build(); + + return builtSlotToAdd is not null && Slots.All(x => + { + var builtSlot = x.Build(); + return (builtSlotToAdd.SlotId is null || + builtSlot.SlotId != builtSlotToAdd.SlotId) && + (builtSlot.Occupant is null || + builtSlot.Occupant != builtSlotToAdd.Occupant); + }); + } + + + public IExpectAnotherSlotSpecification WithSlot(IBuildingSpecification slot) => AddSlot(slot); + + public IExpectAnotherSlotSpecification WithSlots(List> slots) + => slots.Aggregate(this, (currentTeam, slot) => currentTeam.AddSlot(slot)); + + public IExpectAnotherSlotSpecification AndSlot(IBuildingSpecification slot) => AddSlot(slot); + + public IBuildingSpecification AndNoMoreSlots() => this; + + public Team Build() + { + return new Team + { + Name = Name, + Vehicle = Vehicle, + Slots = Slots.Select(x => x.Build()).ToList() + }; + } + + private TeamSpecification AddSlot(IBuildingSpecification slot) + { + if (!CanAdd(slot)) + throw new ArgumentException("Slot cannot be added due to slot ID or occupant duplication."); + + return this with + { + Slots = new List>(Slots) {slot} + }; + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs b/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs index 1160730..a6ad28f 100644 --- a/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs +++ b/ArmaForces.Boderator.Core/Features/Modsets/Specification/ModsetSpecification.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using ArmaForces.Boderator.Core.Infrastructure.Specifications; namespace ArmaForces.Boderator.Core.Modsets.Specification; @@ -7,18 +8,29 @@ public class ModsetSpecification : IBuildingSpecification { private ModsetSpecification() { } - public static IBuildingSpecification ByName(string modsetName) + private string Name { get; init; } = string.Empty; + + public static IBuildingSpecification Named(string modsetName) { - return new ModsetSpecification(); + return new ModsetSpecification + { + Name = modsetName + }; } public static IBuildingSpecification ByUrl(Uri modsetUrl) { - return new ModsetSpecification(); + return new ModsetSpecification + { + Name = modsetUrl.Segments.Last() + }; } public Modset Build() { - return new Modset(); + return new Modset + { + Name = Name + }; } } \ No newline at end of file From 8b1f3470410bc76584ac998f55d445c57af680ad Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Tue, 27 Dec 2022 12:19:04 +0100 Subject: [PATCH 75/87] Exclude specification interfaces from autoregistration --- .../DependencyInjection/ServiceCollectionExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs index bb3348f..87b462d 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -25,6 +25,7 @@ public static IServiceCollection AddOrReplaceSingleton(this IServiceCo /// /// Adds all interfaces from with single implementation type as scoped service. /// Does not replace existing registrations. + /// Excludes specification interfaces. /// /// Service collection where services will be registered /// Assembly which will be searched for interfaces and implementations. @@ -40,6 +41,7 @@ public static IServiceCollection AutoAddInterfacesAsScoped(this IServiceCollecti .Where(x => x.Count() == 1) .Select(x => x.Single()) .Where(x => services.IsNotServiceRegistered(x.implementedInterface)) + .Where(x => x.implementedInterface.IsNotSpecification()) .ToList() .ForEach(x => services.AddScoped(x.implementedInterface, x.implementingClass)); @@ -51,5 +53,8 @@ private static bool IsNotServiceRegistered(this IServiceCollection services, Typ private static bool IsServiceRegistered(this IServiceCollection services, Type serviceType) => services.Any(descriptor => descriptor.ServiceType == serviceType); + + private static bool IsNotSpecification(this Type interfaceType) + => !interfaceType.FullName?.Contains("Specification") ?? true; } } From 440b056bf025a843427cdada94c4556b3d32a3bd Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Tue, 27 Dec 2022 15:00:58 +0100 Subject: [PATCH 76/87] Add SignupsCommandService.CreateSignups + start tests --- .../Missions/Helpers/MissionsFixture.cs | 22 ++++- .../MissionCommandServiceIntegrationTests.cs | 61 ++++++++++++++ .../SignupsCommandServiceIntegrationTests.cs | 82 +++++++++++++++++++ .../MissionSpecificationTests.cs | 4 +- .../Missions/ISignupsCommandService.cs | 10 +++ .../Implementation/MissionCommandService.cs | 11 +-- .../Command/ISignupsCommandRepository.cs | 10 +++ .../Command/SignupsCommandRepository.cs | 36 ++++++++ .../Implementation/SignupsCommandService.cs | 44 ++++++++++ .../Features/Missions/Models/Signups.cs | 4 +- .../Missions/Models/SignupsCreateRequest.cs | 19 +++++ .../MissionCreateRequestValidators.cs | 16 ++++ .../SignupsCreateRequestValidators.cs | 24 ++++++ 13 files changed, 329 insertions(+), 14 deletions(-) create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs create mode 100644 ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/ISignupsCommandService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/ISignupsCommandRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsCommandService.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Models/SignupsCreateRequest.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Validators/MissionCreateRequestValidators.cs create mode 100644 ArmaForces.Boderator.Core/Features/Missions/Validators/SignupsCreateRequestValidators.cs diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs index 656cee7..cce59a8 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Helpers/MissionsFixture.cs @@ -1,15 +1,35 @@ +using System; using ArmaForces.Boderator.Core.Missions.Models; namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; internal static class MissionsFixture { - public static Mission CreateTestMission() + public static MissionCreateRequest PrepareCreateRequest( + string? modsetName = null) + { + var fixtureMission = CreateTestMission(modsetName); + + return new MissionCreateRequest + { + Title = fixtureMission.Title, + Description = fixtureMission.Description, + Owner = fixtureMission.Owner, + MissionDate = fixtureMission.MissionDate, + ModsetName = fixtureMission.ModsetName + }; + } + + public static Mission CreateTestMission( + string? modsetName = null) { return new Mission { Title = "Test mission", Owner = "Tester", + MissionDate = DateTime.Today.AddHours(20), + ModsetName = modsetName ?? "Test-modset", + Description = "Test description" }; } } diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs new file mode 100644 index 0000000..79dac12 --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation; + +public class MissionCommandServiceIntegrationTests : DatabaseTestBase +{ + private readonly MissionsDbHelper _missionsDbHelper; + private readonly IMissionQueryService _missionQueryService; + private readonly IMissionCommandService _missionCommandService; + + public MissionCommandServiceIntegrationTests() + { + _missionsDbHelper = ServiceProvider.GetRequiredService(); + _missionQueryService = ServiceProvider.GetRequiredService(); + _missionCommandService = ServiceProvider.GetRequiredService(); + + var missionContext = ServiceProvider.GetRequiredService(); + DbContextTransaction = missionContext.Database.BeginTransaction(); + } + + [Fact, Trait("Category", "Integration")] + public async Task CreateMission_ValidCreateRequest_MissionCreatedAndReturned() + { + var request = MissionsFixture.PrepareCreateRequest(); + + var expectedMission = new Mission + { + MissionId = 1, + Title = request.Title, + Description = request.Description, + Owner = request.Owner, + MissionDate = request.MissionDate, + ModsetName = request.ModsetName + }; + + var result = await _missionCommandService.CreateMission(request); + + result.ShouldBeSuccess(expectedMission); + } + + [Fact, Trait("Category", "Integration")] + public async Task CreateMission_InvalidModsetNameWithWhitespace_Failure() + { + var request = MissionsFixture.PrepareCreateRequest(modsetName: "Modset with whitespaces"); + + const string expectedError = $"{nameof(MissionCreateRequest.ModsetName)} cannot contain whitespace characters."; + + var result = await _missionCommandService.CreateMission(request); + + result.ShouldBeFailure(expectedError); + } +} diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs new file mode 100644 index 0000000..43b8bbd --- /dev/null +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation; + +public class SignupsCommandServiceIntegrationTests : DatabaseTestBase +{ + private readonly MissionsDbHelper _missionsDbHelper; + private readonly IMissionCommandService _missionCommandService; + private readonly ISignupsCommandService _signupsCommandService; + + public SignupsCommandServiceIntegrationTests() + { + _missionsDbHelper = ServiceProvider.GetRequiredService(); + _missionCommandService = ServiceProvider.GetRequiredService(); + _signupsCommandService = ServiceProvider.GetRequiredService(); + + var missionContext = ServiceProvider.GetRequiredService(); + DbContextTransaction = missionContext.Database.BeginTransaction(); + } + + [Fact, Trait("Category", "Integration")] + public async Task CreateSignups_ValidCreateRequest_SignupsCreatedAndReturned() + { + var request = PrepareRequest(); + + var missionCreationResult = await _missionCommandService.CreateMission(MissionsFixture.PrepareCreateRequest()); + + missionCreationResult.ShouldBeSuccess(); + + var expectedSignups = new Signups() + { + SignupsId = missionCreationResult.Value.MissionId, + Status = request.SignupsStatus, + StartDate = request.StartDate, + CloseDate = request.CloseDate, + Teams = request.Teams + }; + + await _missionsDbHelper.CreateTestMission(); + + var result = await _signupsCommandService.CreateSignups(request); + + result.ShouldBeSuccess(expectedSignups); + } + + // [Fact, Trait("Category", "Integration")] + // public async Task CreateMission_InvalidModsetNameWithWhitespace_Failure() + // { + // var request = PrepareRequest(modsetName: "Modset with whitespaces"); + // + // const string expectedError = $"{nameof(MissionCreateRequest.ModsetName)} cannot contain whitespace characters."; + // + // var result = await _signupsCommandService.CreateMission(request); + // + // result.ShouldBeFailure(expectedError); + // } + + private static SignupsCreateRequest PrepareRequest(long missionId = 1) + { + var fixtureSignups = SignupsFixture.CreateTestSignups(); + + return new SignupsCreateRequest + { + MissionId = missionId, + SignupsStatus = fixtureSignups.Status, + StartDate = fixtureSignups.StartDate, + CloseDate = fixtureSignups.CloseDate, + Teams = fixtureSignups.Teams.ToList() + }; + } +} + diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs index 876b5ce..d2ad882 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs @@ -70,8 +70,8 @@ public void CreateSpecification_FullSpecification_ValidMissionBuilt() .Named(expectedMission.ModsetName)) .ScheduledAt(expectedMission.MissionDate.Value) .WithSignups(SignupsSpecification - .StartingAt(expectedMission.Signups.StartDate) - .ClosingAt(expectedMission.Signups.CloseDate) + .StartingAt(expectedMission.Signups.StartDate.Value) + .ClosingAt(expectedMission.Signups.CloseDate.Value) .WithTeam(TeamSpecification .Named("Alpha") .WithoutVehicle() diff --git a/ArmaForces.Boderator.Core/Features/Missions/ISignupsCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/ISignupsCommandService.cs new file mode 100644 index 0000000..1597518 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/ISignupsCommandService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions; + +public interface ISignupsCommandService +{ + Task> CreateSignups(SignupsCreateRequest signupsCreateRequest); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs index e90791e..fcc50fd 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionCommandService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Validators; using CSharpFunctionalExtensions; namespace ArmaForces.Boderator.Core.Missions.Implementation; @@ -17,7 +18,7 @@ public MissionCommandService(IMissionCommandRepository missionCommandRepository) public async Task> CreateMission(MissionCreateRequest missionCreateRequest) { - return await ValidateRequest(missionCreateRequest) + return await missionCreateRequest.ValidateRequest() .Bind(() => _missionCommandRepository.CreateMission( new Mission { @@ -28,12 +29,4 @@ public async Task> CreateMission(MissionCreateRequest missionCre ModsetName = missionCreateRequest.ModsetName })); } - - private static Result ValidateRequest(MissionCreateRequest missionCreateRequest) - { - if (missionCreateRequest.ModsetName?.Any(char.IsWhiteSpace) ?? false) - return Result.Failure($"{nameof(MissionCreateRequest.ModsetName)} cannot contain whitespace characters."); - - return Result.Success(); - } } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/ISignupsCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/ISignupsCommandRepository.cs new file mode 100644 index 0000000..a8dc931 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/ISignupsCommandRepository.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; + +internal interface ISignupsCommandRepository +{ + Task> CreateSignups(long missionId, Signups signups); +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs new file mode 100644 index 0000000..bedd6c0 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; + +internal class SignupsCommandRepository : ISignupsCommandRepository +{ + private readonly MissionContext _context; + + public SignupsCommandRepository(MissionContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public async Task> CreateSignups(long missionId, Signups signups) + { + var signupsEntityEntry = await _context.Signups.AddAsync(signups); + + if (signupsEntityEntry is null) return Result.Failure("Failure creating signups."); + + var mission = await _context.Missions.FindAsync(missionId); + if (mission is null) return Result.Failure($"Mission with ID {missionId} doesn't exist."); + + var updatedMission = mission with + { + Signups = signupsEntityEntry.Entity + }; + + _context.Update(updatedMission); + + await _context.SaveChangesAsync(); + return signupsEntityEntry.Entity; + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsCommandService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsCommandService.cs new file mode 100644 index 0000000..26c349a --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/SignupsCommandService.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Missions.Validators; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Implementation; + +internal class SignupsCommandService : ISignupsCommandService +{ + private readonly IMissionCommandService _missionCommandService; + private readonly ISignupsCommandRepository _signupsCommandRepository; + + public SignupsCommandService( + IMissionCommandService missionCommandService, + ISignupsCommandRepository signupsCommandRepository) + { + _missionCommandService = missionCommandService; + _signupsCommandRepository = signupsCommandRepository; + } + + public async Task> CreateSignups(SignupsCreateRequest signupsCreateRequest) + { + return await signupsCreateRequest.ValidateRequest() + .Bind(() => CreateOrGetMissionId(signupsCreateRequest)) + .Bind(missionId => _signupsCommandRepository.CreateSignups(missionId, new Signups + { + Status = signupsCreateRequest.SignupsStatus, + StartDate = signupsCreateRequest.StartDate ?? + (signupsCreateRequest.SignupsStatus == SignupsStatus.Open ? DateTime.Now : null), + CloseDate = signupsCreateRequest.CloseDate, + Teams = signupsCreateRequest.Teams + })); + } + + private async Task> CreateOrGetMissionId(SignupsCreateRequest signupsCreateRequest) + { + if (signupsCreateRequest.MissionCreateRequest is not null) + return await _missionCommandService.CreateMission(signupsCreateRequest.MissionCreateRequest) + .Map(x => x.MissionId); + return Result.Success(signupsCreateRequest.MissionId!.Value); + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs index 659686d..f6c5c8d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/Signups.cs @@ -9,9 +9,9 @@ public record Signups public SignupsStatus Status { get; init; } = SignupsStatus.Created; - public DateTime StartDate { get; init; } + public DateTime? StartDate { get; init; } - public DateTime CloseDate { get; init; } + public DateTime? CloseDate { get; init; } public IReadOnlyList Teams { get; init; } = new List(); } \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsCreateRequest.cs b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsCreateRequest.cs new file mode 100644 index 0000000..7374530 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Models/SignupsCreateRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace ArmaForces.Boderator.Core.Missions.Models; + +public record SignupsCreateRequest +{ + public long? MissionId { get; init; } + + public MissionCreateRequest? MissionCreateRequest { get; init; } + + public SignupsStatus SignupsStatus { get; init; } = SignupsStatus.Created; + + public DateTime? StartDate { get; init; } + + public DateTime? CloseDate { get; init; } + + public List Teams { get; init; } = new(); +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Validators/MissionCreateRequestValidators.cs b/ArmaForces.Boderator.Core/Features/Missions/Validators/MissionCreateRequestValidators.cs new file mode 100644 index 0000000..9d52295 --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Validators/MissionCreateRequestValidators.cs @@ -0,0 +1,16 @@ +using System.Linq; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Validators; + +internal static class MissionCreateRequestValidators +{ + public static Result ValidateRequest(this MissionCreateRequest missionCreateRequest) + { + if (missionCreateRequest.ModsetName?.Any(char.IsWhiteSpace) ?? false) + return Result.Failure($"{nameof(MissionCreateRequest.ModsetName)} cannot contain whitespace characters."); + + return Result.Success(); + } +} diff --git a/ArmaForces.Boderator.Core/Features/Missions/Validators/SignupsCreateRequestValidators.cs b/ArmaForces.Boderator.Core/Features/Missions/Validators/SignupsCreateRequestValidators.cs new file mode 100644 index 0000000..9e0453b --- /dev/null +++ b/ArmaForces.Boderator.Core/Features/Missions/Validators/SignupsCreateRequestValidators.cs @@ -0,0 +1,24 @@ +using ArmaForces.Boderator.Core.Missions.Implementation; +using ArmaForces.Boderator.Core.Missions.Models; +using CSharpFunctionalExtensions; + +namespace ArmaForces.Boderator.Core.Missions.Validators; + +internal static class SignupsCreateRequestValidators +{ + public static Result ValidateRequest(this SignupsCreateRequest signupsCreateRequest) + { + if (signupsCreateRequest.MissionId.HasValue && signupsCreateRequest.MissionCreateRequest is not null) + { + return Result.Failure($"Only one of {nameof(SignupsCreateRequest.MissionId)} and {nameof(SignupsCreateRequest.MissionCreateRequest)} can be specified."); + } + + if (signupsCreateRequest.MissionCreateRequest is not null) + return signupsCreateRequest.MissionCreateRequest.ValidateRequest(); + + if (signupsCreateRequest.MissionId > 0) + return Result.Success(); + + return Result.Failure("Signups creation request validation failure"); + } +} From 209a368d8ba041232e0077f5f0c628a8518bd55d Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sat, 31 Dec 2022 11:29:11 +0100 Subject: [PATCH 77/87] Fix CreateMission throwing already tracking exception --- .../Persistence/Command/SignupsCommandRepository.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs index bedd6c0..866244e 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; +using Microsoft.EntityFrameworkCore; namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Command; @@ -20,15 +21,16 @@ public async Task> CreateSignups(long missionId, Signups signups if (signupsEntityEntry is null) return Result.Failure("Failure creating signups."); - var mission = await _context.Missions.FindAsync(missionId); + var mission = await _context.Missions.AsNoTracking().SingleOrDefaultAsync(x => x.MissionId == missionId); if (mission is null) return Result.Failure($"Mission with ID {missionId} doesn't exist."); var updatedMission = mission with { Signups = signupsEntityEntry.Entity }; - - _context.Update(updatedMission); + + _context.Attach(updatedMission); + _context.Entry(updatedMission).Reference(x => x.Signups).IsModified = true; await _context.SaveChangesAsync(); return signupsEntityEntry.Entity; From 8ae0163ca2f7c4abe7ac80dd3d5d9056d0b14c32 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sun, 1 Jan 2023 14:19:52 +0100 Subject: [PATCH 78/87] Implement CreateSignups endpoint --- .../Missions/MissionsControllerTests.cs | 104 +++++++++--------- .../Missions/SignupsControllerTests.cs | 58 ++++++++++ .../Missions/DTOs/SignupsCreateRequestDto.cs | 10 +- .../Missions/Mappers/SignupsMapper.cs | 39 +++++++ .../Features/Missions/SignupsController.cs | 12 +- 5 files changed, 163 insertions(+), 60 deletions(-) create mode 100644 ArmaForces.Boderator.BotService.Tests/Features/Missions/SignupsControllerTests.cs diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs index 5563740..357c020 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs @@ -57,68 +57,68 @@ public async Task GetMission_MissionExists_ReturnsExistingMission() result.ShouldBeSuccess(expectedMission); } - } - public class CreateMissionValidRequestTestData : TheoryData - { - private readonly MissionCreateRequestDto _minimalRequest = new() + private class CreateMissionValidRequestTestData : TheoryData { - Title = "Test mission title", - Owner = "Test mission owner" - }; - - public CreateMissionValidRequestTestData() - { - var testCases = new List + private readonly MissionCreateRequestDto _minimalRequest = new() { - _minimalRequest, - _minimalRequest with - { - Description = "Test mission description" - }, - _minimalRequest with - { - MissionDate = DateTime.Now.AddHours(-1) - }, - _minimalRequest with - { - ModsetName = "Test-mission-modset" - }, - _minimalRequest with - { - Description = "Test mission description", - MissionDate = DateTime.Now.AddHours(-1), - ModsetName = "Test-mission-modset" - } + Title = "Test mission title", + Owner = "Test mission owner" }; - foreach (var testCase in testCases) Add(testCase); + public CreateMissionValidRequestTestData() + { + var testCases = new List + { + _minimalRequest, + _minimalRequest with + { + Description = "Test mission description" + }, + _minimalRequest with + { + MissionDate = DateTime.Now.AddHours(-1) + }, + _minimalRequest with + { + ModsetName = "Test-mission-modset" + }, + _minimalRequest with + { + Description = "Test mission description", + MissionDate = DateTime.Now.AddHours(-1), + ModsetName = "Test-mission-modset" + } + }; + + foreach (var testCase in testCases) Add(testCase); + } } - } - - public class CreateMissionInvalidRequestTestData : TheoryData - { - public CreateMissionInvalidRequestTestData() + + private class CreateMissionInvalidRequestTestData : TheoryData { - var testCases = new List + public CreateMissionInvalidRequestTestData() { - new() - { - Title = "Test mission title without owner" - }, - new() + var testCases = new List { - Owner = "Test mission owner without title" - }, - new() - { - Title = "Test mission title", - Owner = "Test mission owner", - ModsetName = "Modset name with whitespace characters" - } - }; + new() + { + Title = "Test mission title without owner" + }, + new() + { + Owner = "Test mission owner without title" + }, + new() + { + Title = "Test mission title", + Owner = "Test mission owner", + ModsetName = "Modset name with whitespace characters" + } + }; - foreach (var testCase in testCases) Add(testCase); + foreach (var testCase in testCases) Add(testCase); + } } } } diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/SignupsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/SignupsControllerTests.cs new file mode 100644 index 0000000..2b76b1f --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/SignupsControllerTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests.Features.Missions +{ + public class SignupsControllerTests : ApiTestBase + { + public SignupsControllerTests(TestApiServiceFixture testApi) + : base(testApi) { } + + [Theory, ClassData(typeof(CreateSignupsInvalidRequestTestData)), Trait("Category", "Integration")] + public async Task CreateSignups_InvalidRequest_ReturnsBadRequest( + SignupsCreateRequestDto signupsCreateRequestDto) + { + var result = + await HttpPostAsync("api/signups", signupsCreateRequestDto); + + result.ShouldBeFailure(); + } + + private class CreateSignupsInvalidRequestTestData : TheoryData + { + public CreateSignupsInvalidRequestTestData() + { + var testCases = new List + { + new() + { + Teams = new List() + }, + new() + { + Mission = new MissionCreateRequestDto(), + Teams = new List() + }, + new() + { + MissionId = 0, + Teams = new List() + }, + new() + { + MissionId = 0, + Mission = new MissionCreateRequestDto(), + Teams = new List() + } + }; + + foreach (var testCase in testCases) Add(testCase); + } + } + } +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs index 0f9e358..787d251 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/DTOs/SignupsCreateRequestDto.cs @@ -17,7 +17,7 @@ public class SignupsCreateRequestDto /// Id of the mission for which the signups will be created. /// It can't be specified if "mission" is specified. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] [SwaggerSchema(Nullable = false)] public int? MissionId { get; set; } @@ -26,27 +26,27 @@ public class SignupsCreateRequestDto /// Mission will be created before creating signups and must be ready for signups creation. /// It can't be specified if "missionId" is specified. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] [SwaggerSchema(Nullable = false)] public MissionCreateRequestDto? Mission { get; set; } /// /// Desired status of signups. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] [DefaultValue(SignupsStatus.Created)] public SignupsStatus SignupsStatus { get; set; } = SignupsStatus.Created; /// /// Starting date of signups. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public DateTime? StartDate { get; set; } /// /// Closing date of signups. /// - [JsonProperty(Required = Required.DisallowNull)] + [JsonProperty(Required = Required.DisallowNull, NullValueHandling = NullValueHandling.Ignore)] public DateTime? CloseDate { get; set; } /// diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 16b8fa0..81e1b09 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -28,6 +28,17 @@ public static TeamDto Map(Team team) public static List Map(IEnumerable teams) => teams.Select(Map).ToList(); + public static List Map(IEnumerable teams) + => teams.Select(Map).ToList(); + + public static Team Map(TeamDto team) + => new() + { + Name = team.Name, + Vehicle = team.Vehicle, + Slots = Map(team.Slots) + }; + public static SlotDto Map(Slot slot) => new() { @@ -39,4 +50,32 @@ public static SlotDto Map(Slot slot) public static List Map(IEnumerable slots) => slots.Select(Map).ToList(); + + public static List Map(IEnumerable slots) + => slots.Select(Map).ToList(); + + public static Slot Map(SlotDto slot) => new() + { + SlotId = slot.SlotId, + Name = slot.Name, + Occupant = slot.Occupant, + Vehicle = slot.Vehicle + }; + + public static SignupsCreateRequest Map(SignupsCreateRequestDto request) + { + return new SignupsCreateRequest + { + MissionId = request.MissionId, + MissionCreateRequest = request.Mission is not null ? new MissionCreateRequest + { + Title = request.Mission.Title, + Description = request.Mission.Description, + Owner = request.Mission.Owner, + MissionDate = request.Mission.MissionDate, + ModsetName = request.Mission.ModsetName + } : null, + Teams = Map(request.Teams) + }; + } } diff --git a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs index 33646af..0d6f4d6 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/SignupsController.cs @@ -19,12 +19,14 @@ namespace ArmaForces.Boderator.BotService.Features.Missions; [Produces(MediaTypeNames.Application.Json)] public class SignupsController : ControllerBase { + private readonly ISignupsCommandService _signupsCommandService; private readonly ISignupsQueryService _signupsQueryService; /// - public SignupsController(ISignupsQueryService signupsQueryService) + public SignupsController(ISignupsQueryService signupsQueryService, ISignupsCommandService signupsCommandService) { _signupsQueryService = signupsQueryService; + _signupsCommandService = signupsCommandService; } /// Create Signups @@ -36,8 +38,12 @@ public SignupsController(ISignupsQueryService signupsQueryService) [HttpPost(Name = "CreateSignups")] [SwaggerResponse(StatusCodes.Status201Created, "Signups created")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] - public ActionResult CreateSignups([FromBody] SignupsCreateRequestDto request) - => throw new NotImplementedException(); + public async Task> CreateSignups([FromBody] SignupsCreateRequestDto request) + => await _signupsCommandService.CreateSignups(SignupsMapper.Map(request)) + .Map(SignupsMapper.Map) + .Match, SignupsDto>( + onSuccess: signups => Created(signups.SignupsId.ToString(), signups), + onFailure: error => BadRequest(error)); /// Update Signups /// Updates signups with given . From a15375596b3fafa1da0d025b064f43efae40e8e2 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Sun, 1 Jan 2023 14:20:02 +0100 Subject: [PATCH 79/87] Change Test projects to library --- .../ArmaForces.Boderator.BotService.Tests.csproj | 1 + .../ArmaForces.Boderator.Core.Tests.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 79f7a73..4711b28 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -4,6 +4,7 @@ net6.0 enable false + Library diff --git a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj index 34d60e5..1b5581a 100644 --- a/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj +++ b/ArmaForces.Boderator.Core.Tests/ArmaForces.Boderator.Core.Tests.csproj @@ -4,6 +4,7 @@ net6.0 enable false + Library From 0a7810c7b58f34f612c40e24d5bc8ce6d945eb7e Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 24 Mar 2023 09:48:21 +0100 Subject: [PATCH 80/87] Add some more tests --- .../ServiceCollectionExtensionsTests.cs | 17 ++++++++++ .../SignupsCommandServiceIntegrationTests.cs | 32 ++++++++++++++++++- .../Command/SignupsCommandRepository.cs | 3 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs index 9322f1e..8aff358 100644 --- a/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs +++ b/ArmaForces.Boderator.Core.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -10,6 +10,23 @@ namespace ArmaForces.Boderator.Core.Tests.DependencyInjection { public class ServiceCollectionExtensionsTests { + [Fact] + [Trait("Category", "Unit")] + public void AddOrReplaceSingleton() + { + var replacedInstance = new Test1(); + + var serviceProvider = new ServiceCollection() + .AddSingleton(replacedInstance) + .AddOrReplaceSingleton() + .BuildServiceProvider(); + + using (new AssertionScope()) + { + serviceProvider.GetService().Should().NotBe(replacedInstance); + } + } + [Fact] [Trait("Category", "Unit")] public void AutoAddInterfacesAsScoped_EmptyCollection_RegistersOnlyInterfacesWithOneImplementation() diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs index 43b8bbd..841fb37 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs @@ -39,7 +39,7 @@ public async Task CreateSignups_ValidCreateRequest_SignupsCreatedAndReturned() var expectedSignups = new Signups() { - SignupsId = missionCreationResult.Value.MissionId, + SignupsId = 1, Status = request.SignupsStatus, StartDate = request.StartDate, CloseDate = request.CloseDate, @@ -53,6 +53,25 @@ public async Task CreateSignups_ValidCreateRequest_SignupsCreatedAndReturned() result.ShouldBeSuccess(expectedSignups); } + [Fact, Trait("Category", "Integration")] + public async Task CreateSignups_ValidCreateRequestWithMission_SignupsAndMissionCreatedAndReturned() + { + var request = WithMissionCreation(PrepareRequest()); + + var expectedSignups = new Signups + { + SignupsId = 1, + Status = request.SignupsStatus, + StartDate = request.StartDate, + CloseDate = request.CloseDate, + Teams = request.Teams + }; + + var result = await _signupsCommandService.CreateSignups(request); + + result.ShouldBeSuccess(expectedSignups); + } + // [Fact, Trait("Category", "Integration")] // public async Task CreateMission_InvalidModsetNameWithWhitespace_Failure() // { @@ -78,5 +97,16 @@ private static SignupsCreateRequest PrepareRequest(long missionId = 1) Teams = fixtureSignups.Teams.ToList() }; } + + private static SignupsCreateRequest WithMissionCreation(SignupsCreateRequest signupsCreateRequest) + { + var missionCreateRequest = MissionsFixture.PrepareCreateRequest(); + + return signupsCreateRequest with + { + MissionId = null, + MissionCreateRequest = missionCreateRequest + }; + } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs index 866244e..bea7b6a 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Command/SignupsCommandRepository.cs @@ -21,7 +21,7 @@ public async Task> CreateSignups(long missionId, Signups signups if (signupsEntityEntry is null) return Result.Failure("Failure creating signups."); - var mission = await _context.Missions.AsNoTracking().SingleOrDefaultAsync(x => x.MissionId == missionId); + var mission = await _context.Missions.SingleOrDefaultAsync(x => x.MissionId == missionId); if (mission is null) return Result.Failure($"Mission with ID {missionId} doesn't exist."); var updatedMission = mission with @@ -29,6 +29,7 @@ public async Task> CreateSignups(long missionId, Signups signups Signups = signupsEntityEntry.Entity }; + _context.Entry(mission).State = EntityState.Detached; _context.Attach(updatedMission); _context.Entry(updatedMission).Reference(x => x.Signups).IsModified = true; From c191b582cc3b3895c74e94771a187a0fc246d201 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 24 Mar 2023 10:28:02 +0100 Subject: [PATCH 81/87] Add basic test for GetMissions --- ...ts.cs => CreateMissionsControllerTests.cs} | 27 ------ .../Missions/GetMissionsControllerTests.cs | 87 +++++++++++++++++++ 2 files changed, 87 insertions(+), 27 deletions(-) rename ArmaForces.Boderator.BotService.Tests/Features/Missions/{MissionsControllerTests.cs => CreateMissionsControllerTests.cs} (76%) create mode 100644 ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/CreateMissionsControllerTests.cs similarity index 76% rename from ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs rename to ArmaForces.Boderator.BotService.Tests/Features/Missions/CreateMissionsControllerTests.cs index 357c020..89e8b08 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/MissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/CreateMissionsControllerTests.cs @@ -5,7 +5,6 @@ using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; using ArmaForces.Boderator.Core.Tests.TestUtilities; -using AutoFixture; using Xunit; namespace ArmaForces.Boderator.BotService.Tests.Features.Missions @@ -31,32 +30,6 @@ public async Task CreateMission_ValidRequest_MissionCreated(MissionCreateRequest result.ShouldBeSuccess(missionCreateRequestDto); } - - [Fact, Trait("Category", "Integration")] - public async Task GetMission_MissionExists_ReturnsExistingMission() - { - var missionCreateRequest = new MissionCreateRequestDto - { - Title = Fixture.Create(), - Owner = Fixture.Create(), - Description = Fixture.Create() - }; - - var missionCreateResult = await HttpPostAsync("api/missions", missionCreateRequest); - - var expectedMission = new MissionDto - { - Title = missionCreateResult.Value.Title, - Description = missionCreateRequest.Description, - Owner = missionCreateRequest.Owner, - MissionDate = missionCreateResult.Value.MissionDate, - MissionId = missionCreateResult.Value.MissionId - }; - - var result = await HttpGetAsync($"api/missions/{expectedMission.MissionId}"); - - result.ShouldBeSuccess(expectedMission); - } private class CreateMissionValidRequestTestData : TheoryData { diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs new file mode 100644 index 0000000..063b064 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using AutoFixture; +using CSharpFunctionalExtensions; +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests.Features.Missions; + +[Trait("Category", "Integration")] +public class GetMissionsControllerTests : ApiTestBase +{ + public GetMissionsControllerTests(TestApiServiceFixture testApi) + : base(testApi) { } + + [Fact] + public async Task GetMission_MissionExists_ReturnsExistingMission() + { + var missionCreateRequest = new MissionCreateRequestDto + { + Title = Fixture.Create(), + Owner = Fixture.Create(), + Description = Fixture.Create() + }; + + var missionCreateResult = await HttpPostAsync("api/missions", missionCreateRequest); + + var expectedMission = new MissionDto + { + Title = missionCreateResult.Value.Title, + Description = missionCreateRequest.Description, + Owner = missionCreateRequest.Owner, + MissionDate = missionCreateResult.Value.MissionDate, + MissionId = missionCreateResult.Value.MissionId + }; + + var result = await HttpGetAsync($"api/missions/{expectedMission.MissionId}"); + + result.ShouldBeSuccess(expectedMission); + } + + [Fact] + public async Task GetMissions_SomeMissionsExist_ReturnsAllMissions() + { + var createdMissions = await CreateSomeMissions(count: 5); + var creationResult = createdMissions.Combine(); + creationResult.ShouldBeSuccess(); + + var expectedMissions = creationResult.Value + .Select(x => new MissionDto + { + Title = x.createdMission.Title, + Description = x.createdMission.Description, + Owner = x.createdMission.Owner, + MissionDate = x.createdMission.MissionDate, + MissionId = x.createdMission.MissionId + }) + .ToList(); + + var result = await HttpGetAsync>($"api/missions"); + + result.ShouldBeSuccess(expectedMissions); + } + + private async Task>> CreateSomeMissions(int count = 1) + { + return await AsyncEnumerable.Range(0, count) + .Select(x => new MissionCreateRequestDto + { + Title = Fixture.Create(), + Owner = Fixture.Create(), + Description = Fixture.Create() + }) + .SelectAwait(async createRequest => { + var createResult = await HttpPostAsync("api/missions", createRequest); + return (createResult, createRequest); + }) + .Select(x => x.createResult.IsSuccess + ? Result.Success((x.createResult.Value, x.createRequest)) + : x.createResult.ConvertFailure<(MissionDto, MissionCreateRequestDto)>()) + .ToListAsync(); + } +} From d18a494ed37a10c951fc14257979d1de4675dfaa Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 24 Mar 2023 11:12:56 +0100 Subject: [PATCH 82/87] WIP not working, TODO: test with other DB than SQLite --- .../TestUtilities/TestBases/ApiTestBase.cs | 24 +++++++++++++++---- .../TestFixtures/TestApiServiceFixture.cs | 2 ++ .../Middleware/TransactionMiddleware.cs | 14 +++++++++++ .../ArmaForces.Boderator.Core.csproj | 3 +++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs index 36554fb..9acb1b3 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs @@ -2,10 +2,13 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; +using System.Transactions; using ArmaForces.Boderator.BotService.Tests.TestUtilities.Collections; using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using AutoFixture; using CSharpFunctionalExtensions; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Xunit; @@ -17,19 +20,32 @@ namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases /// Provider test server and methods to invoke endpoints. /// [Collection(CollectionsNames.ApiTest)] - public abstract class ApiTestBase + public abstract class ApiTestBase : IDisposable { private readonly HttpClient _httpClient; protected Fixture Fixture { get; } = new Fixture(); + protected IServiceProvider Provider { get; } + public void Dispose() => TransactionScope?.Dispose();// ?? DbContextTransaction?.Dispose(); + // private IDbContextTransaction? DbContextTransaction { get; } + private TransactionScope? TransactionScope { get; } + protected ApiTestBase(TestApiServiceFixture testApi) { _httpClient = testApi.HttpClient; - - Provider = new ServiceCollection() - .BuildServiceProvider(); + Provider = testApi.ServiceProvider; + + // var missionContext = Provider.GetRequiredService(); + TransactionScope = new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted + }, + TransactionScopeAsyncFlowOption.Enabled); + // DbContextTransaction = missionContext.Database.BeginTransaction(); } protected async Task> HttpGetAsync(string path) diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs index 4d4657e..2dcc851 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs @@ -18,6 +18,8 @@ public class TestApiServiceFixture : IDisposable public HttpClient HttpClient { get; } + internal IServiceProvider ServiceProvider => _host.Services; + public TestApiServiceFixture() { _socketsHttpHandler = new SocketsHttpHandler(); diff --git a/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs b/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs new file mode 100644 index 0000000..a2a7cc6 --- /dev/null +++ b/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace ArmaForces.Boderator.BotService.Middleware +{ + public class TransactionMiddleware : IMiddleware + { + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + // context.RequestServices.GetRequiredService() + } + } +} diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 546333a..01328f4 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -20,6 +20,9 @@ <_Parameter1>$(MSBuildProjectName).Tests + + <_Parameter1>ArmaForces.Boderator.BotService.Tests + <_Parameter1>DynamicProxyGenAssembly2 From 4afddd458b7814e677c601c89966a9785620ef7f Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Thu, 30 Mar 2023 13:47:50 +0200 Subject: [PATCH 83/87] Test SQL Server --- .../TestUtilities/TestConfigurationFactory.cs | 2 +- .../TestUtilities/DatabaseTestBase.cs | 8 ++++++-- .../TestUtilities/TestDatabaseConstants.cs | 5 ++++- .../ArmaForces.Boderator.Core.csproj | 1 + .../DependencyInjection/BoderatorCoreServiceExtensions.cs | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs index 67e090d..289db58 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs @@ -7,7 +7,7 @@ internal class TestConfigurationFactory : IBoderatorConfigurationFactory { public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration { - ConnectionString = TestDatabaseConstants.TestConnectionString, + ConnectionString = TestDatabaseConstants.TestSqlServerConnectionString, DiscordToken = string.Empty }; } diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs index 756b8d7..1da3c22 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs @@ -9,7 +9,7 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities; public class DatabaseTestBase : IDisposable { protected readonly IServiceProvider ServiceProvider = new ServiceCollection() - .AddBoderatorCore(_ => TestDatabaseConstants.TestConnectionString) + .AddBoderatorCore(_ => TestDatabaseConstants.TestSqlServerConnectionString) .AddScoped() .AddScoped() .BuildServiceProvider(); @@ -18,5 +18,9 @@ public class DatabaseTestBase : IDisposable protected DatabaseTestBase() { } - public void Dispose() => DbContextTransaction?.Dispose(); + public void Dispose() + { + DbContextTransaction?.Rollback(); + DbContextTransaction?.Dispose(); + } } diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs index ebe2c46..64d29ac 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs @@ -4,5 +4,8 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities; public static class TestDatabaseConstants { - public static readonly string TestConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); + public static readonly string TestSqliteConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); + + public static readonly string TestSqlServerConnectionString = + "Data Source=127.0.0.1,49443; User ID=Boderator; Password=boderator-test1; Database=BODERATOR_TEST"; } diff --git a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj index 01328f4..e96bf26 100644 --- a/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj +++ b/ArmaForces.Boderator.Core/ArmaForces.Boderator.Core.csproj @@ -12,6 +12,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs index 34621a9..6df1302 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/BoderatorCoreServiceExtensions.cs @@ -17,6 +17,8 @@ private static IServiceCollection AddDbContext(this IServiceCollection servic Func connectionStringFactory) where T : DbContext => services.AddDbContext((serviceProvider, options) => - options.UseSqlite(connectionStringFactory(serviceProvider))); + options + .UseSqlServer(connectionStringFactory(serviceProvider))); + //.UseSqlite(connectionStringFactory(serviceProvider))); } } From b6da0ba1107689be3fa83092e01796cfcce53051 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 31 Mar 2023 11:44:02 +0200 Subject: [PATCH 84/87] Make all* tests pass on SQL Server and SQLite --- .../Missions/GetMissionsControllerTests.cs | 3 +- .../TestUtilities/TestConfigurationFactory.cs | 2 +- .../MissionCommandServiceIntegrationTests.cs | 3 +- .../MissionQueryServiceIntegrationTests.cs | 12 ++++--- .../SignupsCommandServiceIntegrationTests.cs | 10 +++--- .../TestUtilities/DatabaseTestBase.cs | 2 +- .../ResultAssertionsExtensions.cs | 35 +++++++++++++++++++ .../TestUtilities/TestDatabaseConstants.cs | 10 +++--- 8 files changed, 57 insertions(+), 20 deletions(-) diff --git a/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs b/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs index 063b064..32c3ab4 100644 --- a/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs +++ b/ArmaForces.Boderator.BotService.Tests/Features/Missions/GetMissionsControllerTests.cs @@ -7,6 +7,7 @@ using ArmaForces.Boderator.Core.Tests.TestUtilities; using AutoFixture; using CSharpFunctionalExtensions; +using FluentAssertions; using Xunit; namespace ArmaForces.Boderator.BotService.Tests.Features.Missions; @@ -63,7 +64,7 @@ public async Task GetMissions_SomeMissionsExist_ReturnsAllMissions() var result = await HttpGetAsync>($"api/missions"); - result.ShouldBeSuccess(expectedMissions); + result.ShouldBeSuccess(x => x.Should().Contain(expectedMissions)); } private async Task>> CreateSomeMissions(int count = 1) diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs index 289db58..4921b82 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestConfigurationFactory.cs @@ -7,7 +7,7 @@ internal class TestConfigurationFactory : IBoderatorConfigurationFactory { public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration { - ConnectionString = TestDatabaseConstants.TestSqlServerConnectionString, + ConnectionString = TestDatabaseConstants.TestDbConnectionString, DiscordToken = string.Empty }; } diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs index 79dac12..bb4b5c9 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs @@ -34,7 +34,6 @@ public async Task CreateMission_ValidCreateRequest_MissionCreatedAndReturned() var expectedMission = new Mission { - MissionId = 1, Title = request.Title, Description = request.Description, Owner = request.Owner, @@ -44,7 +43,7 @@ public async Task CreateMission_ValidCreateRequest_MissionCreatedAndReturned() var result = await _missionCommandService.CreateMission(request); - result.ShouldBeSuccess(expectedMission); + result.ShouldBeSuccess(expectedMission, opt => opt.Excluding(x => x.MissionId)); } [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs index cde77ea..2d06998 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs @@ -5,11 +5,13 @@ using ArmaForces.Boderator.Core.Missions.Models; using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; using ArmaForces.Boderator.Core.Tests.TestUtilities; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation; +[Trait("Category", "Integration")] public class MissionQueryServiceIntegrationTests : DatabaseTestBase { private readonly MissionsDbHelper _missionsDbHelper; @@ -24,24 +26,24 @@ public MissionQueryServiceIntegrationTests() DbContextTransaction = missionContext.Database.BeginTransaction(); } - [Fact, Trait("Category", "Integration")] + [Fact(Skip = "TODO: Un-skip when transactions are solved for SQL Server.")] public async Task GetMissions_NoMissionsInDatabase_ReturnsEmptyList() { var result = await _missionQueryService.GetMissions(); result.ShouldBeSuccess(new List()); } - [Fact, Trait("Category", "Integration")] + [Fact] public async Task GetMissions_OneMissionInDatabase_ReturnsOneMission() { var createdMission = await _missionsDbHelper.CreateTestMission(); var result = await _missionQueryService.GetMissions(); - result.ShouldBeSuccess(new List{createdMission}); + result.ShouldBeSuccess(x => x.Should().Contain(createdMission)); } - [Fact, Trait("Category", "Integration")] + [Fact] public async Task GetMission_MissionIdInDatabase_ReturnsMission() { var createdMission = await _missionsDbHelper.CreateTestMission(); @@ -51,7 +53,7 @@ public async Task GetMission_MissionIdInDatabase_ReturnsMission() result.ShouldBeSuccess(createdMission); } - [Fact, Trait("Category", "Integration")] + [Fact] public async Task GetMission_MissionIdNotInDatabase_ReturnsFailure() { var createdMission = await _missionsDbHelper.CreateTestMission(); diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs index 841fb37..3072b42 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs @@ -31,15 +31,14 @@ public SignupsCommandServiceIntegrationTests() [Fact, Trait("Category", "Integration")] public async Task CreateSignups_ValidCreateRequest_SignupsCreatedAndReturned() { - var request = PrepareRequest(); - var missionCreationResult = await _missionCommandService.CreateMission(MissionsFixture.PrepareCreateRequest()); missionCreationResult.ShouldBeSuccess(); + var request = PrepareRequest(missionCreationResult.Value.MissionId); + var expectedSignups = new Signups() { - SignupsId = 1, Status = request.SignupsStatus, StartDate = request.StartDate, CloseDate = request.CloseDate, @@ -50,7 +49,7 @@ public async Task CreateSignups_ValidCreateRequest_SignupsCreatedAndReturned() var result = await _signupsCommandService.CreateSignups(request); - result.ShouldBeSuccess(expectedSignups); + result.ShouldBeSuccess(expectedSignups, opt => opt.Excluding(x => x.SignupsId)); } [Fact, Trait("Category", "Integration")] @@ -60,7 +59,6 @@ public async Task CreateSignups_ValidCreateRequestWithMission_SignupsAndMissionC var expectedSignups = new Signups { - SignupsId = 1, Status = request.SignupsStatus, StartDate = request.StartDate, CloseDate = request.CloseDate, @@ -69,7 +67,7 @@ public async Task CreateSignups_ValidCreateRequestWithMission_SignupsAndMissionC var result = await _signupsCommandService.CreateSignups(request); - result.ShouldBeSuccess(expectedSignups); + result.ShouldBeSuccess(expectedSignups, opt => opt.Excluding(x => x.SignupsId)); } // [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs index 1da3c22..34cfbb5 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs @@ -9,7 +9,7 @@ namespace ArmaForces.Boderator.Core.Tests.TestUtilities; public class DatabaseTestBase : IDisposable { protected readonly IServiceProvider ServiceProvider = new ServiceCollection() - .AddBoderatorCore(_ => TestDatabaseConstants.TestSqlServerConnectionString) + .AddBoderatorCore(_ => TestDatabaseConstants.TestDbConnectionString) .AddScoped() .AddScoped() .BuildServiceProvider(); diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs index 541ded3..7ad64c0 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/ResultAssertionsExtensions.cs @@ -1,5 +1,7 @@ +using System; using CSharpFunctionalExtensions; using FluentAssertions; +using FluentAssertions.Equivalency; using FluentAssertions.Execution; namespace ArmaForces.Boderator.Core.Tests.TestUtilities @@ -93,5 +95,38 @@ public static void ShouldBeSuccess(this Result result, T2 expectedVa result.Error.Should().BeNull(); } } + + public static void ShouldBeSuccess( + this Result result, + T2 expectedValue, + Func,EquivalencyAssertionOptions> config) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + result.Value.Should().BeEquivalentTo(expectedValue, config); + } + else + { + result.IsSuccess.Should().BeTrue(); + result.Error.Should().BeNull(); + } + } + + public static void ShouldBeSuccess(this Result result, Action valueAssertion) + { + using var scope = new AssertionScope(); + + if (result.IsSuccess) + { + valueAssertion(result.Value); + } + else + { + result.IsSuccess.Should().BeTrue(); + result.Error.Should().BeNull(); + } + } } } diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs index 64d29ac..5fb8882 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/TestDatabaseConstants.cs @@ -1,11 +1,13 @@ -using System.IO; +using System; +using System.IO; namespace ArmaForces.Boderator.Core.Tests.TestUtilities; public static class TestDatabaseConstants { - public static readonly string TestSqliteConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); + public static string TestDbConnectionString => TestSqlServerConnectionString ?? throw new InvalidOperationException("Connection string is empty"); + + private static readonly string TestSqliteConnectionString = "Data Source=" + Path.Join(Directory.GetCurrentDirectory(), "test.db"); - public static readonly string TestSqlServerConnectionString = - "Data Source=127.0.0.1,49443; User ID=Boderator; Password=boderator-test1; Database=BODERATOR_TEST"; + private const string TestSqlServerConnectionString = "Data Source=127.0.0.1,49443; User ID=Boderator; Password=boderator-test1; Database=BODERATOR_TEST"; } From 5b482bbb0f239e8bb092b449ed92b9d2aa659b8c Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 31 Mar 2023 15:08:30 +0200 Subject: [PATCH 85/87] Add legacy import test --- .../LegacySignupsImportTest.cs | 68 +++++++++++++++++++ .../MissionsApi/WebMission.cs | 25 +++++++ .../Missions/Mappers/SignupsMapper.cs | 3 + .../MissionSpecificationTests.cs | 1 + 4 files changed, 97 insertions(+) create mode 100644 ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs create mode 100644 ArmaForces.Boderator.BotService.Tests/LegacyImportTest/MissionsApi/WebMission.cs diff --git a/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs b/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs new file mode 100644 index 0000000..4696463 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using ArmaForces.Boderator.BotService.Features.Missions.DTOs; +using ArmaForces.Boderator.BotService.Tests.LegacyImportTest.MissionsApi; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestBases; +using ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; +using ArmaForces.Boderator.Core.Missions.Models; +using ArmaForces.Boderator.Core.Tests.TestUtilities; +using CSharpFunctionalExtensions; +using FluentAssertions; +using Xunit; + +namespace ArmaForces.Boderator.BotService.Tests.LegacyImportTest; + +[Trait("Category", "Functional")] +public class LegacySignupsImportTest : ApiTestBase +{ + private const string MissionsResource = @"https://boderator.armaforces.com/api/missions?includeArchive=true"; + + public LegacySignupsImportTest(TestApiServiceFixture testApi) : base(testApi) + { + } + + [Fact] + public async Task ImportLegacySignups_OnlyMissionData_Imported() + { + var legacyMissionsResult = await HttpGetAsync>(MissionsResource); + + var createdMissions = await legacyMissionsResult + .Map(missions => missions + .OrderBy(x => x.Date) + .Select( + mission => new SignupsCreateRequestDto + { + CloseDate = mission.CloseDate, + SignupsStatus = mission.Archive ? SignupsStatus.Closed : SignupsStatus.Open, + StartDate = mission.CloseDate, // TODO: Try to figure signups start date possibly? + Mission = new MissionCreateRequestDto + { + Title = mission.Title, + ModsetName = mission.Modlist.Replace(" ", "-"), + Description = mission.Description, + MissionDate = mission.Date, + Owner = "AF" + } + })) + .Bind(async newMissions => + { + var creationResults = new List>(); + + foreach (var signupsCreateRequestDto in newMissions) + { + var result = + await HttpPostAsync("api/signups", + signupsCreateRequestDto); + + creationResults.Add(result); + } + + return creationResults.Combine(); + }); + + var missions = await HttpGetAsync>($"api/missions"); + + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/MissionsApi/WebMission.cs b/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/MissionsApi/WebMission.cs new file mode 100644 index 0000000..440a5bf --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/MissionsApi/WebMission.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; + +namespace ArmaForces.Boderator.BotService.Tests.LegacyImportTest.MissionsApi; + +public class WebMission +{ + public string Title { get; set; } = string.Empty; + + public DateTime Date { get; set; } + + public DateTime CloseDate { get; set; } + + public string Description { get; set; } = string.Empty; + + public string Modlist + { + get => _modlist; + set => _modlist = value.Split('/').Last(); + } + + public bool Archive { get; set; } + + private string _modlist = string.Empty; +} diff --git a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs index 81e1b09..869c209 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/Mappers/SignupsMapper.cs @@ -75,6 +75,9 @@ public static SignupsCreateRequest Map(SignupsCreateRequestDto request) MissionDate = request.Mission.MissionDate, ModsetName = request.Mission.ModsetName } : null, + StartDate = request.StartDate, + CloseDate = request.CloseDate, + SignupsStatus = request.SignupsStatus, Teams = Map(request.Teams) }; } diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs index d2ad882..fe37fb7 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/Specification/MissionSpecificationTests.cs @@ -10,6 +10,7 @@ namespace ArmaForces.Boderator.Core.Tests.Features.Missions.Implementation.Specification; +[Trait("Category", "Unit")] public class MissionSpecificationTests { private readonly Fixture _fixture = new(); From 5f3636db88c3902ce611554051ebb68b08f799a1 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 31 Mar 2023 18:25:22 +0200 Subject: [PATCH 86/87] Working production transactions + Core.Tests too Seems like BotService.Tests can't have working transactions --- ...maForces.Boderator.BotService.Tests.csproj | 2 + .../LegacySignupsImportTest.cs | 2 +- .../TestUtilities/TestBases/ApiTestBase.cs | 8 ++-- .../TestFixtures/TestApiServiceFixture.cs | 42 ++++--------------- .../TestFixtures/TestApplicationFactory.cs | 15 +++++++ .../Middleware/TransactionMiddleware.cs | 32 ++++++++++++-- ArmaForces.Boderator.BotService/Program.cs | 2 +- ArmaForces.Boderator.BotService/Startup.cs | 10 +++-- .../MissionCommandServiceIntegrationTests.cs | 3 -- .../MissionQueryServiceIntegrationTests.cs | 5 +-- .../SignupsCommandServiceIntegrationTests.cs | 3 -- .../SignupsQueryServiceIntegrationTests.cs | 3 -- .../TestUtilities/DatabaseTestBase.cs | 20 +++++++-- 13 files changed, 83 insertions(+), 64 deletions(-) create mode 100644 ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApplicationFactory.cs diff --git a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj index 4711b28..9393114 100644 --- a/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj +++ b/ArmaForces.Boderator.BotService.Tests/ArmaForces.Boderator.BotService.Tests.csproj @@ -16,6 +16,8 @@ + + diff --git a/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs b/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs index 4696463..2753561 100644 --- a/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs +++ b/ArmaForces.Boderator.BotService.Tests/LegacyImportTest/LegacySignupsImportTest.cs @@ -23,7 +23,7 @@ public LegacySignupsImportTest(TestApiServiceFixture testApi) : base(testApi) { } - [Fact] + [Fact(Skip = "Manual test")] public async Task ImportLegacySignups_OnlyMissionData_Imported() { var legacyMissionsResult = await HttpGetAsync>(MissionsResource); diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs index 9acb1b3..366d0a0 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestBases/ApiTestBase.cs @@ -28,16 +28,15 @@ public abstract class ApiTestBase : IDisposable protected IServiceProvider Provider { get; } - public void Dispose() => TransactionScope?.Dispose();// ?? DbContextTransaction?.Dispose(); - // private IDbContextTransaction? DbContextTransaction { get; } + public void Dispose() => TransactionScope?.Dispose(); private TransactionScope? TransactionScope { get; } protected ApiTestBase(TestApiServiceFixture testApi) { _httpClient = testApi.HttpClient; Provider = testApi.ServiceProvider; - - // var missionContext = Provider.GetRequiredService(); + + // TODO: This doesn't and probably won't work TransactionScope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions @@ -45,7 +44,6 @@ protected ApiTestBase(TestApiServiceFixture testApi) IsolationLevel = IsolationLevel.ReadCommitted }, TransactionScopeAsyncFlowOption.Enabled); - // DbContextTransaction = missionContext.Database.BeginTransaction(); } protected async Task> HttpGetAsync(string path) diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs index 2dcc851..107cc10 100644 --- a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApiServiceFixture.cs @@ -1,54 +1,30 @@ using System; using System.Net.Http; -using ArmaForces.Boderator.BotService.Configuration; -using ArmaForces.Boderator.Core.DependencyInjection; -using ArmaForces.Boderator.Core.Tests.TestUtilities; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Moq; +using Microsoft.AspNetCore.Mvc.Testing; namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures { + // This class is created by XUnit test runner once for TestApiCollection + // ReSharper disable once ClassNeverInstantiated.Global public class TestApiServiceFixture : IDisposable { - private const int Port = 43421; - private readonly SocketsHttpHandler _socketsHttpHandler; - private readonly IHost _host; + private readonly WebApplicationFactory _testAppFactory; public HttpClient HttpClient { get; } - internal IServiceProvider ServiceProvider => _host.Services; - + internal IServiceProvider ServiceProvider => _testAppFactory.Services; + public TestApiServiceFixture() { - _socketsHttpHandler = new SocketsHttpHandler(); - HttpClient = new HttpClient(_socketsHttpHandler) - { - BaseAddress = new Uri($"http://localhost:{Port}") - }; - - _host = Host.CreateDefaultBuilder() - .ConfigureWebHostDefaults(ConfigureWebBuilder()) - .Build(); - - _host.Start(); + _testAppFactory = new TestApplicationFactory(); + HttpClient = _testAppFactory.CreateClient(); } public void Dispose() { HttpClient.Dispose(); - _socketsHttpHandler.Dispose(); - _host.Dispose(); + _testAppFactory.Dispose(); GC.SuppressFinalize(this); } - - private static Action ConfigureWebBuilder() => webBuilder => - { - webBuilder.UseStartup(); - webBuilder.ConfigureServices( - x => x.AddOrReplaceSingleton(new TestConfigurationFactory().CreateConfiguration())); - webBuilder.UseKestrel(x => x.ListenLocalhost(Port)); - }; } } diff --git a/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApplicationFactory.cs b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApplicationFactory.cs new file mode 100644 index 0000000..85e1538 --- /dev/null +++ b/ArmaForces.Boderator.BotService.Tests/TestUtilities/TestFixtures/TestApplicationFactory.cs @@ -0,0 +1,15 @@ +using ArmaForces.Boderator.Core.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace ArmaForces.Boderator.BotService.Tests.TestUtilities.TestFixtures; + +internal class TestApplicationFactory : WebApplicationFactory +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + base.ConfigureWebHost(builder); + builder.ConfigureServices( + x => x.AddOrReplaceSingleton(new TestConfigurationFactory().CreateConfiguration())); + } +} diff --git a/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs b/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs index a2a7cc6..a26587e 100644 --- a/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs +++ b/ArmaForces.Boderator.BotService/Middleware/TransactionMiddleware.cs @@ -1,14 +1,38 @@ using System.Threading.Tasks; +using System.Transactions; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; namespace ArmaForces.Boderator.BotService.Middleware { - public class TransactionMiddleware : IMiddleware + internal class TransactionMiddleware { - public async Task InvokeAsync(HttpContext context, RequestDelegate next) + private readonly RequestDelegate _next; + + public TransactionMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) { - // context.RequestServices.GetRequiredService() + if (context.Request.Method != "GET") + { + using var transaction = new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted + }, + TransactionScopeAsyncFlowOption.Enabled); + + await _next.Invoke(context); + + transaction.Complete(); + } + else + { + await _next.Invoke(context); + } } } } diff --git a/ArmaForces.Boderator.BotService/Program.cs b/ArmaForces.Boderator.BotService/Program.cs index 281be83..e0818f4 100644 --- a/ArmaForces.Boderator.BotService/Program.cs +++ b/ArmaForces.Boderator.BotService/Program.cs @@ -4,7 +4,7 @@ namespace ArmaForces.Boderator.BotService { - public static class Program + internal class Program { public static void Main(string[] args) { diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index 35f07d8..f2833b7 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -4,7 +4,9 @@ using ArmaForces.Boderator.BotService.Configuration; using ArmaForces.Boderator.BotService.Documentation; using ArmaForces.Boderator.BotService.Features.DiscordClient.Infrastructure.DependencyInjection; +using ArmaForces.Boderator.BotService.Features.Health; using ArmaForces.Boderator.BotService.Filters; +using ArmaForces.Boderator.BotService.Middleware; using ArmaForces.Boderator.Core.DependencyInjection; using Discord.WebSocket; using Microsoft.AspNetCore.Builder; @@ -16,7 +18,7 @@ namespace ArmaForces.Boderator.BotService { - public class Startup + internal class Startup { private OpenApiInfo OpenApiConfiguration { get; } = new() { @@ -41,7 +43,7 @@ public void ConfigureServices(IServiceCollection services) services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); services.AddMvc(options => options - .Filters.Add(new ExceptionFilter())) + .Filters.Add(new ExceptionFilter())) .AddJsonOptions(opt => { opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); @@ -58,12 +60,14 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseDeveloperExceptionPage(); app.AddDocumentation(OpenApiConfiguration); } - + app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); + + app.UseMiddleware(); app.UseEndpoints(endpoints => { diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs index bb4b5c9..77f060c 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionCommandServiceIntegrationTests.cs @@ -22,9 +22,6 @@ public MissionCommandServiceIntegrationTests() _missionsDbHelper = ServiceProvider.GetRequiredService(); _missionQueryService = ServiceProvider.GetRequiredService(); _missionCommandService = ServiceProvider.GetRequiredService(); - - var missionContext = ServiceProvider.GetRequiredService(); - DbContextTransaction = missionContext.Database.BeginTransaction(); } [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs index 2d06998..89d6ee1 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceIntegrationTests.cs @@ -21,12 +21,9 @@ public MissionQueryServiceIntegrationTests() { _missionsDbHelper = ServiceProvider.GetRequiredService(); _missionQueryService = ServiceProvider.GetRequiredService(); - - var missionContext = ServiceProvider.GetRequiredService(); - DbContextTransaction = missionContext.Database.BeginTransaction(); } - [Fact(Skip = "TODO: Un-skip when transactions are solved for SQL Server.")] + [Fact] public async Task GetMissions_NoMissionsInDatabase_ReturnsEmptyList() { var result = await _missionQueryService.GetMissions(); diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs index 3072b42..4a6248d 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsCommandServiceIntegrationTests.cs @@ -23,9 +23,6 @@ public SignupsCommandServiceIntegrationTests() _missionsDbHelper = ServiceProvider.GetRequiredService(); _missionCommandService = ServiceProvider.GetRequiredService(); _signupsCommandService = ServiceProvider.GetRequiredService(); - - var missionContext = ServiceProvider.GetRequiredService(); - DbContextTransaction = missionContext.Database.BeginTransaction(); } [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs index 522dd64..1b48c39 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/SignupsQueryServiceIntegrationTests.cs @@ -21,9 +21,6 @@ public SignupsQueryServiceIntegrationTests() _missionsDbHelper = ServiceProvider.GetRequiredService(); _signupsDbHelper = ServiceProvider.GetRequiredService(); _signupsQueryService = ServiceProvider.GetRequiredService(); - - var missionContext = ServiceProvider.GetRequiredService(); - DbContextTransaction = missionContext.Database.BeginTransaction(); } [Fact, Trait("Category", "Integration")] diff --git a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs index 34cfbb5..a3e5ab5 100644 --- a/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs +++ b/ArmaForces.Boderator.Core.Tests/TestUtilities/DatabaseTestBase.cs @@ -1,4 +1,5 @@ using System; +using System.Transactions; using ArmaForces.Boderator.Core.DependencyInjection; using ArmaForces.Boderator.Core.Tests.Features.Missions.Helpers; using Microsoft.EntityFrameworkCore.Storage; @@ -14,13 +15,24 @@ public class DatabaseTestBase : IDisposable .AddScoped() .BuildServiceProvider(); - protected IDbContextTransaction? DbContextTransaction { get; init; } + // protected IDbContextTransaction? DbContextTransaction { get; init; } + private TransactionScope? TransactionScope { get; } - protected DatabaseTestBase() { } + protected DatabaseTestBase() + { + TransactionScope = new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions + { + IsolationLevel = IsolationLevel.ReadCommitted + }, + TransactionScopeAsyncFlowOption.Enabled); + } public void Dispose() { - DbContextTransaction?.Rollback(); - DbContextTransaction?.Dispose(); + TransactionScope?.Dispose(); + // DbContextTransaction?.Rollback(); + // DbContextTransaction?.Dispose(); } } From 38c9fa920715bab434d991dbeebd3b743bd2e314 Mon Sep 17 00:00:00 2001 From: 3Mydlo3 Date: Fri, 31 Mar 2023 22:18:01 +0200 Subject: [PATCH 87/87] Attempt at query specifications --- .../BoderatorConfigurationFactory.cs | 11 ++++ .../Features/Missions/MissionsController.cs | 16 ++++- .../Filters/ExceptionFilter.cs | 19 ++++++ ArmaForces.Boderator.BotService/Startup.cs | 2 +- .../appsettings.json | 2 +- .../MissionQueryServiceUnitTests.cs | 3 +- .../Specifications/BaseQuerySpecification.cs | 60 +++++++++++++++++++ .../Specifications/IQuerySpecification.cs | 16 +++++ .../Specifications/SpecificationEvaluator.cs | 39 ++++++++++++ .../ServiceCollectionExtensions.cs | 2 +- .../Features/Missions/IMissionQueryService.cs | 3 +- .../Implementation/MissionQueryService.cs | 5 +- .../Query/IMissionQueryRepository.cs | 3 +- .../Query/MissionQueryRepository.cs | 9 ++- 14 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 ArmaForces.Boderator.Core/Common/Specifications/BaseQuerySpecification.cs create mode 100644 ArmaForces.Boderator.Core/Common/Specifications/IQuerySpecification.cs create mode 100644 ArmaForces.Boderator.Core/Common/Specifications/SpecificationEvaluator.cs diff --git a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs index a0eb5cc..557a264 100644 --- a/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs +++ b/ArmaForces.Boderator.BotService/Configuration/BoderatorConfigurationFactory.cs @@ -1,6 +1,8 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Configuration; +using Microsoft.Extensions.Configuration; namespace ArmaForces.Boderator.BotService.Configuration { @@ -12,6 +14,15 @@ public BoderatorConfigurationFactory() { _environmentVariables = Environment.GetEnvironmentVariables(); } + + public BoderatorConfigurationFactory(IConfiguration configuration) + { + _environmentVariables = new Dictionary + { + {$"AF_Boderator_{nameof(BoderatorConfiguration.ConnectionString)}", configuration.GetConnectionString("DefaultConnection")}, + {$"AF_Boderator_{nameof(BoderatorConfiguration.DiscordToken)}", ""} + }; + } // TODO: Consider making this a bit more automatic so configuration is easily extensible public BoderatorConfiguration CreateConfiguration() => new BoderatorConfiguration diff --git a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs index d89ccc3..eb0751e 100644 --- a/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs +++ b/ArmaForces.Boderator.BotService/Features/Missions/MissionsController.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; using ArmaForces.Boderator.BotService.Features.Missions.DTOs; using ArmaForces.Boderator.BotService.Features.Missions.Mappers; +using ArmaForces.Boderator.Core.Common.Specifications; using ArmaForces.Boderator.Core.Missions; +using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -69,15 +71,25 @@ public ActionResult DeleteMission(int missionId) /// Get Missions /// Retrieves missions satisfying query parameters. + /// Include only missions after given date. Missions without a date are treated as always after. + /// Include only missions before given date. + /// Include missions only by given user. [HttpGet(Name = "GetMissions")] [SwaggerResponse(StatusCodes.Status200OK, "Missions retrieved")] [SwaggerResponse(StatusCodes.Status400BadRequest, "Request is invalid")] - public async Task>> GetMissions() - => await _missionQueryService.GetMissions() + public async Task>> GetMissions( + [FromQuery] DateTime? after = null, + [FromQuery] DateTime? before = null, + [FromQuery] string? owner = null) + { + var query = new MissionQuerySpecification(after, before, owner); + + return await _missionQueryService.GetMissions(query) .Map(MissionMapper.Map) .Match>, List>( onSuccess: missions => Ok(missions), onFailure: error => BadRequest(error)); + } /// Get Mission /// Retrieves mission with given . diff --git a/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs b/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs index 3437f84..3b9d299 100644 --- a/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs +++ b/ArmaForces.Boderator.BotService/Filters/ExceptionFilter.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Newtonsoft.Json; namespace ArmaForces.Boderator.BotService.Filters; @@ -17,6 +18,7 @@ public void OnException(ExceptionContext context) { if (context.Exception is ArgumentNullException) HandleValidationError(context); if (context.Exception is NotImplementedException) HandleNotImplemented(context); + if (context.ExceptionHandled is false) HandleOtherException(context); } private static void HandleNotImplemented(ExceptionContext context) @@ -36,4 +38,21 @@ private static void HandleValidationError(ExceptionContext context) context.Result = new BadRequestObjectResult(error); context.ExceptionHandled = true; } + + private static void HandleOtherException(ExceptionContext context) + { + var error = new + { + Message = "Internal Server Error", + Details = context.Exception.Message, + Timestamp = DateTimeOffset.Now + }; + + context.Result = new ContentResult + { + Content = JsonConvert.SerializeObject(error), + StatusCode = 500 + }; + context.ExceptionHandled = true; + } } diff --git a/ArmaForces.Boderator.BotService/Startup.cs b/ArmaForces.Boderator.BotService/Startup.cs index f2833b7..c4b83a4 100644 --- a/ArmaForces.Boderator.BotService/Startup.cs +++ b/ArmaForces.Boderator.BotService/Startup.cs @@ -38,7 +38,7 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); services.AddDocumentation(OpenApiConfiguration); services.AddBoderatorCore(serviceProvider => serviceProvider.GetRequiredService().ConnectionString); - services.AddSingleton(_ => new BoderatorConfigurationFactory().CreateConfiguration()); + services.AddSingleton(serviceProvider => new BoderatorConfigurationFactory(serviceProvider.GetRequiredService()).CreateConfiguration()); services.AddDiscordClient(); services.AutoAddInterfacesAsScoped(typeof(Startup).Assembly); diff --git a/ArmaForces.Boderator.BotService/appsettings.json b/ArmaForces.Boderator.BotService/appsettings.json index a46cc8c..626c509 100644 --- a/ArmaForces.Boderator.BotService/appsettings.json +++ b/ArmaForces.Boderator.BotService/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BoderatorTestDB;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Data Source=127.0.0.1,49443; User ID=Boderator; Password=boderator-test1; Database=BODERATOR_TEST" }, "Serilog": { "MinimumLevel": { diff --git a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs index 32bd689..159fd94 100644 --- a/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs +++ b/ArmaForces.Boderator.Core.Tests/Features/Missions/Implementation/MissionQueryServiceUnitTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Common.Specifications; using ArmaForces.Boderator.Core.Missions.Implementation; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using ArmaForces.Boderator.Core.Missions.Models; @@ -42,7 +43,7 @@ private static IMissionQueryRepository CreateRepositoryMock(IEnumerable { var missionQueryRepositoryMock = new Mock(); missionQueryRepositoryMock - .Setup(x => x.GetMissions()) + .Setup(x => x.GetMissions(It.IsAny?>())) .Returns(Task.FromResult(missions.ToList())); return missionQueryRepositoryMock.Object; diff --git a/ArmaForces.Boderator.Core/Common/Specifications/BaseQuerySpecification.cs b/ArmaForces.Boderator.Core/Common/Specifications/BaseQuerySpecification.cs new file mode 100644 index 0000000..7282ff2 --- /dev/null +++ b/ArmaForces.Boderator.Core/Common/Specifications/BaseQuerySpecification.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using ArmaForces.Boderator.Core.Missions.Models; + +namespace ArmaForces.Boderator.Core.Common.Specifications; + +public class MissionQuerySpecification : BaseQuerySpecification +{ + public MissionQuerySpecification(DateTime? after = null, DateTime? before = null, string? owner = null) + : base(x => IsMissionAfter(x, after) && IsMissionBefore(x, after) && IsMissionBy(x, owner)) + { } + + /// + /// If a date was given, checks if mission is after that date. Returns true for missions without date. + /// + private static bool IsMissionAfter(Mission mission, DateTime? after) + => after is null || mission.MissionDate is null || mission.MissionDate > after; + + /// + /// If a date was given, checks if mission is before that date. + /// + private static bool IsMissionBefore(Mission mission, DateTime? before) + => before is null || mission.MissionDate is not null && mission.MissionDate < before; + + /// + /// If an owner was given, checks if mission is created by given user. + /// + private static bool IsMissionBy(Mission mission, string? owner) + => owner is null || mission.Owner == owner; +} + +public abstract class BaseQuerySpecification : IQuerySpecification +{ + protected BaseQuerySpecification() { } + + protected BaseQuerySpecification(Expression> criteria) + { + Criteria = criteria; + } + public Expression>? Criteria { get; } + public List>> Includes { get; } = new List>>(); + public Expression>? OrderBy { get; private set; } + public Expression>? OrderByDescending { get; private set; } + + protected void AddInclude(Expression> includeExpression) + { + Includes.Add(includeExpression); + } + + protected void AddOrderBy(Expression> orderByExpression) + { + OrderBy = orderByExpression; + } + + protected void AddOrderByDescending(Expression> orderByDescExpression) + { + OrderByDescending = orderByDescExpression; + } +} \ No newline at end of file diff --git a/ArmaForces.Boderator.Core/Common/Specifications/IQuerySpecification.cs b/ArmaForces.Boderator.Core/Common/Specifications/IQuerySpecification.cs new file mode 100644 index 0000000..af77495 --- /dev/null +++ b/ArmaForces.Boderator.Core/Common/Specifications/IQuerySpecification.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace ArmaForces.Boderator.Core.Common.Specifications; + +public interface IQuerySpecification +{ + Expression>? Criteria { get; } + + List>> Includes { get; } + + Expression>? OrderBy { get; } + + Expression>? OrderByDescending { get; } +} diff --git a/ArmaForces.Boderator.Core/Common/Specifications/SpecificationEvaluator.cs b/ArmaForces.Boderator.Core/Common/Specifications/SpecificationEvaluator.cs new file mode 100644 index 0000000..34dee9a --- /dev/null +++ b/ArmaForces.Boderator.Core/Common/Specifications/SpecificationEvaluator.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace ArmaForces.Boderator.Core.Common.Specifications; + +public static class SpecificationEvaluator where TEntity : class +{ + public static IQueryable GetQuery(IQueryable query, IQuerySpecification specifications) + { + // Do not apply anything if specifications is null + if (specifications == null) + { + return query; + } + + // Modify the IQueryable + // Apply filter conditions + if (specifications.Criteria != null) + { + query = query.Where(specifications.Criteria); + } + + // Includes + query = specifications.Includes + .Aggregate(query, (current, include) => current.Include(include)); + + // Apply ordering + if (specifications.OrderBy != null) + { + query = query.OrderBy(specifications.OrderBy); + } + else if (specifications.OrderByDescending != null) + { + query = query.OrderByDescending(specifications.OrderByDescending); + } + + return query; + } +} diff --git a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs index 87b462d..ee4502d 100644 --- a/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/ArmaForces.Boderator.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -55,6 +55,6 @@ private static bool IsServiceRegistered(this IServiceCollection services, Type s => services.Any(descriptor => descriptor.ServiceType == serviceType); private static bool IsNotSpecification(this Type interfaceType) - => !interfaceType.FullName?.Contains("Specification") ?? true; + => !interfaceType.FullName?.Contains("Specification") ?? !interfaceType.Name.Contains("Specification"); } } diff --git a/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs index 90a1a2d..8cb4c91 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/IMissionQueryService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Common.Specifications; using ArmaForces.Boderator.Core.Missions.Models; using CSharpFunctionalExtensions; @@ -9,5 +10,5 @@ public interface IMissionQueryService { Task> GetMission(long missionId); - Task>> GetMissions(); + Task>> GetMissions(IQuerySpecification? query = null); } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs index 7c96ed8..62c3aca 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/MissionQueryService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Common.Specifications; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence; using ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; using ArmaForces.Boderator.Core.Missions.Models; @@ -20,6 +21,6 @@ public async Task> GetMission(long missionId) => await _missionQueryRepository.GetMission(missionId) ?? Result.Failure($"Mission with ID {missionId} does not exist."); - public async Task>> GetMissions() - => await _missionQueryRepository.GetMissions(); + public async Task>> GetMissions(IQuerySpecification? query = null) + => await _missionQueryRepository.GetMissions(query); } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs index 7c8abda..af39e7f 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/IMissionQueryRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Common.Specifications; using ArmaForces.Boderator.Core.Missions.Models; namespace ArmaForces.Boderator.Core.Missions.Implementation.Persistence.Query; @@ -8,5 +9,5 @@ internal interface IMissionQueryRepository { Task GetMission(long missionId); - Task> GetMissions(); + Task> GetMissions(IQuerySpecification? query = null); } diff --git a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs index a06ef82..7a6bd5d 100644 --- a/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs +++ b/ArmaForces.Boderator.Core/Features/Missions/Implementation/Persistence/Query/MissionQueryRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using ArmaForces.Boderator.Core.Common.Specifications; using ArmaForces.Boderator.Core.Missions.Models; using Microsoft.EntityFrameworkCore; @@ -18,6 +20,9 @@ public MissionQueryRepository(MissionContext context) public async Task GetMission(long missionId) => await _context.Missions.FindAsync(missionId); - public async Task> GetMissions() - => await _context.Missions.ToListAsync(); + public async Task> GetMissions(IQuerySpecification? query = null) + { + return await SpecificationEvaluator.GetQuery(_context.Set().AsQueryable(), query ?? new MissionQuerySpecification()) + .ToListAsync(); + } }