From 4df00167fc39646907a73a1bb2ce5de4738c873c Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Sun, 30 Mar 2025 13:46:25 +0300 Subject: [PATCH 1/4] style: applied csharpier --- .../Analysis/DownloadAnalysisFileResultsEndpoint.cs | 2 +- .../Analysis/GetAnalysisResultsEndpoint.cs | 4 ++-- .../EndpointDefinitions/Context/ProjectContextLinkEndpoint.cs | 2 +- .../Context/ProjectContextReloadEndpoint.cs | 2 +- .../EndpointDefinitions/Loaders/UploadLoaderFilesEndpoint.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/DownloadAnalysisFileResultsEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/DownloadAnalysisFileResultsEndpoint.cs index 606ab32..5edfe9d 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/DownloadAnalysisFileResultsEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/DownloadAnalysisFileResultsEndpoint.cs @@ -59,7 +59,7 @@ private static async Task DownloadIndividualFile( namedStream.Name ), error => error.ToProblem(context), - error =>error.ToProblem(context) + error => error.ToProblem(context) ); } diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/GetAnalysisResultsEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/GetAnalysisResultsEndpoint.cs index 091861c..ac9ba91 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/GetAnalysisResultsEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Analysis/GetAnalysisResultsEndpoint.cs @@ -51,7 +51,7 @@ private static async Task< return result.Match, NotFound>>( content => TypedResults.Ok(new WebGetAnalysisResultConsole(content)), - error =>error.ToProblem(context) + error => error.ToProblem(context) ); } @@ -95,7 +95,7 @@ private static async Task< return result.Match, NotFound>>( files => TypedResults.Ok(WebGetAnalysisResultFileList.Map(files)), - error =>error.ToProblem(context) + error => error.ToProblem(context) ); } } diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextLinkEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextLinkEndpoint.cs index e39568b..aefbc24 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextLinkEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextLinkEndpoint.cs @@ -43,7 +43,7 @@ CancellationToken cancellationToken return result.Match>>( _ => TypedResults.NoContent(), error => error.ToProblem(context), - error =>error.ToProblem(context) + error => error.ToProblem(context) ); } } diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextReloadEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextReloadEndpoint.cs index 4fcc2d4..59fa2e9 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextReloadEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Context/ProjectContextReloadEndpoint.cs @@ -41,7 +41,7 @@ CancellationToken cancellationToken return result.Match>>( _ => TypedResults.NoContent(), error => error.ToProblem(context), - error =>error.ToProblem(context) + error => error.ToProblem(context) ); } } diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Loaders/UploadLoaderFilesEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Loaders/UploadLoaderFilesEndpoint.cs index 0b28515..241f66d 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Loaders/UploadLoaderFilesEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Loaders/UploadLoaderFilesEndpoint.cs @@ -54,7 +54,7 @@ private static async Task UploadLoaderFiles( TypedResults.Ok( new WebUploadLoaderFilesResponse(loaderId, fileData.Select(f => f.Name)) ), - error =>error.ToProblem(context) + error => error.ToProblem(context) ); } } From 8fb47fe37b612054c374f6068a808c5a7abd75cb Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Sun, 30 Mar 2025 14:09:37 +0300 Subject: [PATCH 2/4] feat: implement get current instance use case --- .../CreateProjectScriptsEndpoint.cs | 2 + .../Web/Exceptions/ApiErrorExtensions.cs | 14 ++++ .../NoInstanceAllocatedForProjectError.cs | 5 ++ .../Analysis/GetCurrentInstanceService.cs | 22 ++++-- .../ProjectStructure/CreateScriptService.cs | 20 ++++- .../Analysis/IGetCurrentInstanceUseCase.cs | 6 +- .../ProjectStructure/ICreateScriptUseCase.cs | 2 + .../CreateProjectScriptsEndpointTest.cs | 55 +++++++++++--- .../Analysis/GetCurrentInstanceServiceTest.cs | 57 +++++++++++++++ .../CreateScriptServiceTest.cs | 73 +++++++++++++++++-- .../ProblemValidationUtils.Concrete.cs | 15 ++++ .../Tests.Common/ProblemValidationUtils.cs | 16 +++- 12 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 src/Application/Domain/Model/Analysis/NoInstanceAllocatedForProjectError.cs create mode 100644 test/Application/Domain/Service.Project.Tests/Analysis/GetCurrentInstanceServiceTest.cs diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs index a5b3096..c29d9f0 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs @@ -13,6 +13,7 @@ namespace ScriptBee.Web.EndpointDefinitions.ProjectStructure; using CreateResponse = Results< Created, NotFound, + BadRequest, ValidationProblem, Conflict >; @@ -51,6 +52,7 @@ private static async Task CreateProjectScript( ), error => error.ToProblem(context), error => error.ToProblem(context), + error => error.ToProblem(context), error => error.ToProblem(context) ); } diff --git a/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs b/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs index 7b17a80..ae2cee9 100644 --- a/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs +++ b/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using ScriptBee.Common.Web.Extensions; +using ScriptBee.Domain.Model.Analysis; using ScriptBee.Domain.Model.Errors; using ScriptBee.Web.EndpointDefinitions.ProjectStructure.Contracts; @@ -101,4 +102,17 @@ HttpContext context ) ); } + + public static BadRequest ToProblem( + this NoInstanceAllocatedForProjectError error, + HttpContext context + ) + { + return TypedResults.BadRequest( + context.ToProblemDetails( + "No Instance Allocated For Project", + $"There is no instance allocated for project with the ID '{error.ProjectId}'" + ) + ); + } } diff --git a/src/Application/Domain/Model/Analysis/NoInstanceAllocatedForProjectError.cs b/src/Application/Domain/Model/Analysis/NoInstanceAllocatedForProjectError.cs new file mode 100644 index 0000000..ecf8702 --- /dev/null +++ b/src/Application/Domain/Model/Analysis/NoInstanceAllocatedForProjectError.cs @@ -0,0 +1,5 @@ +using ScriptBee.Domain.Model.Project; + +namespace ScriptBee.Domain.Model.Analysis; + +public record NoInstanceAllocatedForProjectError(ProjectId ProjectId); diff --git a/src/Application/Domain/Service.Project/Analysis/GetCurrentInstanceService.cs b/src/Application/Domain/Service.Project/Analysis/GetCurrentInstanceService.cs index e026820..3be9702 100644 --- a/src/Application/Domain/Service.Project/Analysis/GetCurrentInstanceService.cs +++ b/src/Application/Domain/Service.Project/Analysis/GetCurrentInstanceService.cs @@ -1,17 +1,29 @@ -using ScriptBee.Domain.Model.Instance; +using OneOf; +using ScriptBee.Domain.Model.Analysis; +using ScriptBee.Domain.Model.Instance; using ScriptBee.Domain.Model.Project; +using ScriptBee.Ports.Instance; using ScriptBee.UseCases.Project.Analysis; namespace ScriptBee.Service.Project.Analysis; -public class GetCurrentInstanceService : IGetCurrentInstanceUseCase +public class GetCurrentInstanceService(IGetAllProjectInstances getAllProjectInstances) + : IGetCurrentInstanceUseCase { - public Task GetOrAllocate( + public async Task> GetCurrentInstance( ProjectId projectId, CancellationToken cancellationToken = default ) { - // TODO FIXIT(#45): implement it - throw new NotImplementedException(); + var instanceInfos = await getAllProjectInstances.GetAll(projectId, cancellationToken); + + var instanceInfo = instanceInfos.FirstOrDefault(); + + if (instanceInfo == null) + { + return new NoInstanceAllocatedForProjectError(projectId); + } + + return instanceInfo; } } diff --git a/src/Application/Domain/Service.Project/ProjectStructure/CreateScriptService.cs b/src/Application/Domain/Service.Project/ProjectStructure/CreateScriptService.cs index 6a44fe7..5ac74b1 100644 --- a/src/Application/Domain/Service.Project/ProjectStructure/CreateScriptService.cs +++ b/src/Application/Domain/Service.Project/ProjectStructure/CreateScriptService.cs @@ -1,6 +1,8 @@ using OneOf; using ScriptBee.Common; +using ScriptBee.Domain.Model.Analysis; using ScriptBee.Domain.Model.Errors; +using ScriptBee.Domain.Model.Instance; using ScriptBee.Domain.Model.Project; using ScriptBee.Domain.Model.ProjectStructure; using ScriptBee.Ports.Files; @@ -15,6 +17,7 @@ namespace ScriptBee.Service.Project.ProjectStructure; using CreateResult = OneOf< Script, ProjectDoesNotExistsError, + NoInstanceAllocatedForProjectError, ScriptLanguageDoesNotExistsError, ScriptPathAlreadyExistsError >; @@ -47,10 +50,25 @@ private async Task Create( CancellationToken cancellationToken = default ) { - var instanceInfo = await currentInstanceUseCase.GetOrAllocate( + var result = await currentInstanceUseCase.GetCurrentInstance( projectDetails.Id, cancellationToken ); + + return await result.Match>( + async instanceInfo => + await Create(command, instanceInfo, projectDetails, cancellationToken), + error => Task.FromResult(error) + ); + } + + private async Task Create( + CreateScriptCommand command, + InstanceInfo instanceInfo, + ProjectDetails projectDetails, + CancellationToken cancellationToken = default + ) + { var languageResult = await getScriptLanguages.Get( instanceInfo, command.Language, diff --git a/src/Application/Ports/Driving/UseCases.Project/Analysis/IGetCurrentInstanceUseCase.cs b/src/Application/Ports/Driving/UseCases.Project/Analysis/IGetCurrentInstanceUseCase.cs index 6046862..56fd1e3 100644 --- a/src/Application/Ports/Driving/UseCases.Project/Analysis/IGetCurrentInstanceUseCase.cs +++ b/src/Application/Ports/Driving/UseCases.Project/Analysis/IGetCurrentInstanceUseCase.cs @@ -1,11 +1,13 @@ -using ScriptBee.Domain.Model.Instance; +using OneOf; +using ScriptBee.Domain.Model.Analysis; +using ScriptBee.Domain.Model.Instance; using ScriptBee.Domain.Model.Project; namespace ScriptBee.UseCases.Project.Analysis; public interface IGetCurrentInstanceUseCase { - Task GetOrAllocate( + Task> GetCurrentInstance( ProjectId projectId, CancellationToken cancellationToken = default ); diff --git a/src/Application/Ports/Driving/UseCases.Project/ProjectStructure/ICreateScriptUseCase.cs b/src/Application/Ports/Driving/UseCases.Project/ProjectStructure/ICreateScriptUseCase.cs index 0cb971e..538c2e6 100644 --- a/src/Application/Ports/Driving/UseCases.Project/ProjectStructure/ICreateScriptUseCase.cs +++ b/src/Application/Ports/Driving/UseCases.Project/ProjectStructure/ICreateScriptUseCase.cs @@ -1,4 +1,5 @@ using OneOf; +using ScriptBee.Domain.Model.Analysis; using ScriptBee.Domain.Model.Errors; using ScriptBee.Domain.Model.ProjectStructure; @@ -10,6 +11,7 @@ public interface ICreateScriptUseCase OneOf< Script, ProjectDoesNotExistsError, + NoInstanceAllocatedForProjectError, ScriptLanguageDoesNotExistsError, ScriptPathAlreadyExistsError > diff --git a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs index e183606..f8d6bcc 100644 --- a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs +++ b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using NSubstitute; using OneOf; +using ScriptBee.Domain.Model.Analysis; using ScriptBee.Domain.Model.Errors; using ScriptBee.Domain.Model.Project; using ScriptBee.Domain.Model.ProjectStructure; @@ -18,6 +19,7 @@ namespace ScriptBee.Web.Tests.EndpointDefinitions.ProjectStructure; using CreateResponse = OneOf< Script, ProjectDoesNotExistsError, + NoInstanceAllocatedForProjectError, ScriptLanguageDoesNotExistsError, ScriptPathAlreadyExistsError >; @@ -108,8 +110,8 @@ public async Task ShouldReturnCreated_WhenNoParametersArePassed() [InlineData("boolean", true)] public async Task ShouldReturnCreated_WhenParametersArePassed(string type, object value) { - var createScriptUseCase = Substitute.For(); - createScriptUseCase + var useCase = Substitute.For(); + useCase .Create( Arg.Is(command => MatchCreateScriptCommand(command, type, value) @@ -142,7 +144,7 @@ public async Task ShouldReturnCreated_WhenParametersArePassed(string type, objec outputHelper, services => { - services.AddSingleton(createScriptUseCase); + services.AddSingleton(useCase); } ), new WebCreateScriptCommand( @@ -173,8 +175,8 @@ [new WebScriptParameter("parameter", type, value)] [Fact] public async Task ProjectNotExists_ShouldReturnNotFound() { - var createScriptUseCase = Substitute.For(); - createScriptUseCase + var useCase = Substitute.For(); + useCase .Create( new CreateScriptCommand(ProjectId.FromValue("id"), "path", "csharp", []), Arg.Any() @@ -190,7 +192,7 @@ public async Task ProjectNotExists_ShouldReturnNotFound() outputHelper, services => { - services.AddSingleton(createScriptUseCase); + services.AddSingleton(useCase); } ), new WebCreateScriptCommand("path", "csharp", null) @@ -199,11 +201,40 @@ public async Task ProjectNotExists_ShouldReturnNotFound() await AssertProjectNotFoundProblem(response, TestUrl, "id"); } + [Fact] + public async Task NoInstanceAllocatedForProject_ShouldReturnBadRequest() + { + var useCase = Substitute.For(); + useCase + .Create( + new CreateScriptCommand(ProjectId.FromValue("id"), "path", "csharp", []), + Arg.Any() + ) + .Returns( + Task.FromResult( + new NoInstanceAllocatedForProjectError(ProjectId.FromValue("id")) + ) + ); + + var response = await _api.PostApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ), + new WebCreateScriptCommand("path", "csharp", null) + ); + + await AssertNoInstanceAllocatedForProjectProblem(response, TestUrl, "id"); + } + [Fact] public async Task ScriptLanguageNotExists_ShouldReturnBadRequest() { - var createScriptUseCase = Substitute.For(); - createScriptUseCase + var useCase = Substitute.For(); + useCase .Create( new CreateScriptCommand(ProjectId.FromValue("id"), "path", "csharp", []), Arg.Any() @@ -217,7 +248,7 @@ public async Task ScriptLanguageNotExists_ShouldReturnBadRequest() outputHelper, services => { - services.AddSingleton(createScriptUseCase); + services.AddSingleton(useCase); } ), new WebCreateScriptCommand("path", "csharp", null) @@ -229,8 +260,8 @@ public async Task ScriptLanguageNotExists_ShouldReturnBadRequest() [Fact] public async Task ExistingPath_ShouldReturnConflict() { - var createScriptUseCase = Substitute.For(); - createScriptUseCase + var useCase = Substitute.For(); + useCase .Create( new CreateScriptCommand(ProjectId.FromValue("id"), "path", "csharp", []), Arg.Any() @@ -242,7 +273,7 @@ public async Task ExistingPath_ShouldReturnConflict() outputHelper, services => { - services.AddSingleton(createScriptUseCase); + services.AddSingleton(useCase); } ), new WebCreateScriptCommand("path", "csharp", null) diff --git a/test/Application/Domain/Service.Project.Tests/Analysis/GetCurrentInstanceServiceTest.cs b/test/Application/Domain/Service.Project.Tests/Analysis/GetCurrentInstanceServiceTest.cs new file mode 100644 index 0000000..a811b11 --- /dev/null +++ b/test/Application/Domain/Service.Project.Tests/Analysis/GetCurrentInstanceServiceTest.cs @@ -0,0 +1,57 @@ +using NSubstitute; +using ScriptBee.Domain.Model.Analysis; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Ports.Instance; +using ScriptBee.Service.Project.Analysis; +using ScriptBee.Tests.Common; + +namespace ScriptBee.Service.Project.Tests.Analysis; + +public class GetCurrentInstanceServiceTest +{ + private readonly IGetAllProjectInstances _getAllProjectInstances = + Substitute.For(); + + private readonly GetCurrentInstanceService _getCurrentInstanceService; + + public GetCurrentInstanceServiceTest() + { + _getCurrentInstanceService = new GetCurrentInstanceService(_getAllProjectInstances); + } + + [Fact] + public async Task GivenNoInstanceForProjectId_ThenExpectNoInstanceAllocatedForProjectError() + { + var projectId = ProjectId.FromValue("id"); + _getAllProjectInstances + .GetAll(projectId, Arg.Any()) + .Returns(Task.FromResult>(new List())); + + var result = await _getCurrentInstanceService.GetCurrentInstance(projectId); + + result.AsT1.ShouldBe(new NoInstanceAllocatedForProjectError(projectId)); + } + + [Fact] + public async Task GivenInstancesForProjectId_ThenReturnFirstInstance() + { + var projectId = ProjectId.FromValue("id"); + var instanceInfo1 = InstanceInfoFixture.BasicInstanceInfo(projectId) with + { + Id = new InstanceId("5ded901e-f237-449e-9782-5a3b3b1d7b6f"), + }; + var instanceInfo2 = InstanceInfoFixture.BasicInstanceInfo(projectId); + _getAllProjectInstances + .GetAll(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + new List { instanceInfo1, instanceInfo2 } + ) + ); + + var result = await _getCurrentInstanceService.GetCurrentInstance(projectId); + + result.AsT0.ShouldBe(instanceInfo1); + } +} diff --git a/test/Application/Domain/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs b/test/Application/Domain/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs index 25bbe1d..7b1a4d4 100644 --- a/test/Application/Domain/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs +++ b/test/Application/Domain/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs @@ -2,7 +2,9 @@ using NSubstitute; using OneOf; using ScriptBee.Common; +using ScriptBee.Domain.Model.Analysis; using ScriptBee.Domain.Model.Errors; +using ScriptBee.Domain.Model.Instance; using ScriptBee.Domain.Model.Project; using ScriptBee.Domain.Model.ProjectStructure; using ScriptBee.Ports.Files; @@ -73,6 +75,45 @@ public async Task ProjectDoesNotExists() result.ShouldBe(error); } + [Fact] + public async Task NoInstanceAllocatedForProjectError() + { + var projectId = ProjectId.FromValue("id"); + var error = new NoInstanceAllocatedForProjectError(projectId); + _getProject + .GetById(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + BasicProjectDetails(projectId) + ) + ); + _getCurrentInstanceUseCase + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + new NoInstanceAllocatedForProjectError(projectId) + ) + ); + + var result = await _createScriptService.Create( + new CreateScriptCommand( + projectId, + "path", + "language", + [ + new ScriptParameter + { + Name = "parameter", + Type = "string", + Value = "value", + }, + ] + ) + ); + + result.ShouldBe(error); + } + [Fact] public async Task ScriptLanguageDoesNotExists() { @@ -87,8 +128,12 @@ public async Task ScriptLanguageDoesNotExists() ) ); _getCurrentInstanceUseCase - .GetOrAllocate(projectId, Arg.Any()) - .Returns(Task.FromResult(instanceInfo)); + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + instanceInfo + ) + ); _getScriptLanguages .Get(instanceInfo, "language", Arg.Any()) .Returns( @@ -127,8 +172,12 @@ public async Task CreateFileAlreadyExists() ) ); _getCurrentInstanceUseCase - .GetOrAllocate(projectId, Arg.Any()) - .Returns(Task.FromResult(instanceInfo)); + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + instanceInfo + ) + ); _getScriptLanguages .Get(instanceInfo, "language", Arg.Any()) .Returns( @@ -176,8 +225,12 @@ public async Task CreateScriptSuccessfully() ) ); _getCurrentInstanceUseCase - .GetOrAllocate(projectId, Arg.Any()) - .Returns(Task.FromResult(instanceInfo)); + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + instanceInfo + ) + ); _getScriptLanguages .Get(instanceInfo, "language", Arg.Any()) .Returns( @@ -243,8 +296,12 @@ public async Task CreateScriptSuccessfullyWithExtension() ) ); _getCurrentInstanceUseCase - .GetOrAllocate(projectId, Arg.Any()) - .Returns(Task.FromResult(instanceInfo)); + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + instanceInfo + ) + ); _getScriptLanguages .Get(instanceInfo, "language", Arg.Any()) .Returns( diff --git a/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs b/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs index 2c911e1..dd73ac3 100644 --- a/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs +++ b/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs @@ -106,4 +106,19 @@ await AssertConflictProblem( "A script at that path already exists." ); } + + public static async Task AssertNoInstanceAllocatedForProjectProblem( + HttpResponseMessage response, + string testUrl, + string projectId + ) + { + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + await AssertBadRequestProblem( + response.Content, + testUrl, + "No Instance Allocated For Project", + $"There is no instance allocated for project with the ID '{projectId}'" + ); + } } diff --git a/test/Common/Tests.Common/ProblemValidationUtils.cs b/test/Common/Tests.Common/ProblemValidationUtils.cs index 4cec3bd..602d7ac 100644 --- a/test/Common/Tests.Common/ProblemValidationUtils.cs +++ b/test/Common/Tests.Common/ProblemValidationUtils.cs @@ -34,7 +34,7 @@ dynamic errors AssertDynamicProblemExtensionsNotNull(problemDetails); } - public static async Task AssertConflictProblem( + private static async Task AssertConflictProblem( HttpContent responseContent, string url, string title, @@ -51,7 +51,7 @@ string detail AssertDynamicProblemExtensionsNotNull(problemDetails); } - public static async Task AssertNotFoundProblem( + private static async Task AssertNotFoundProblem( HttpContent responseContent, string url, string title, @@ -68,6 +68,18 @@ string detail AssertDynamicProblemExtensionsNotNull(problemDetails); } + private static async Task AssertBadRequestProblem( + HttpContent responseContent, + string url, + string title, + string detail + ) + { + var problemDetails = (await responseContent.ReadFromJsonAsync())!; + + AssertBadRequest(url, problemDetails, title, detail); + } + private static void AssertBadRequest( string url, ProblemDetails problemDetails, From 637ff2e3f2276cf84279843461e62364f89f5ed3 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Sun, 30 Mar 2025 14:12:37 +0300 Subject: [PATCH 3/4] test: uncomment GetProjectInstancesEndpointTest --- .../GetProjectInstancesEndpointTest.cs | 102 +++++++++--------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetProjectInstancesEndpointTest.cs b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetProjectInstancesEndpointTest.cs index b5a4530..73a0076 100644 --- a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetProjectInstancesEndpointTest.cs +++ b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetProjectInstancesEndpointTest.cs @@ -1,53 +1,49 @@ -// TODO FIXIT: fix this -// using System.Net; -// using NSubstitute; -// using ScriptBee.Domain.Model.Analysis; -// using ScriptBee.Domain.Model.Project; -// using ScriptBee.Tests.Common; -// using ScriptBee.Web.EndpointDefinitions.Instances.Contracts; -// using Xunit.Abstractions; -// -// namespace ScriptBee.Web.Tests.EndpointDefinitions.Instances; -// -// public class GetProjectInstancesEndpointTest(ITestOutputHelper outputHelper) -// { -// private const string TestUrl = "/api/projects/project-id/instances"; -// private readonly TestApiCaller _api = new(TestUrl); -// -// [Fact] -// public async Task ShouldReturnProjectDetailsList() -// { -// var getProjectInstances = Substitute.For(); -// var creationDate = DateTimeOffset.Parse("2024-02-08"); -// var projectId = ProjectId.Create("project-id"); -// IEnumerable projectDetailsList = new List -// { -// new( -// InstanceId.FromValue("instance-id"), -// projectId, -// "http://url", -// creationDate -// ), -// }; -// getProjectInstances -// .GetAllInstances(projectId, Arg.Any()) -// .Returns(Task.FromResult(projectDetailsList)); -// -// var response = await _api.GetApi( -// new TestWebApplicationFactory( -// outputHelper, -// services => -// { -// services.AddSingleton(getProjectInstances); -// } -// ) -// ); -// -// response.StatusCode.ShouldBe(HttpStatusCode.OK); -// var getProjectListResponse = -// await response.ReadContentAsync(); -// getProjectListResponse.Instances.ShouldBeEquivalentTo( -// new List { new("instance-id", creationDate) } -// ); -// } -// } +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Tests.Common; +using ScriptBee.UseCases.Project.Analysis; +using ScriptBee.Web.EndpointDefinitions.Instances.Contracts; +using Xunit.Abstractions; + +namespace ScriptBee.Web.Tests.EndpointDefinitions.Instances; + +public class GetProjectInstancesEndpointTest(ITestOutputHelper outputHelper) +{ + private const string TestUrl = "/api/projects/project-id/instances"; + private readonly TestApiCaller _api = new(TestUrl); + + [Fact] + public async Task ShouldReturnProjectDetailsList() + { + var useCase = Substitute.For(); + var projectId = ProjectId.FromValue("project-id"); + var instanceInfo = InstanceInfoFixture.BasicInstanceInfo(projectId); + IEnumerable projectDetailsList = new List { instanceInfo }; + useCase + .GetAllInstances(projectId, Arg.Any()) + .Returns(Task.FromResult(projectDetailsList)); + + var response = await _api.GetApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ) + ); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var getProjectListResponse = + await response.ReadContentAsync(); + getProjectListResponse.Instances.ShouldBeEquivalentTo( + new List + { + new(instanceInfo.Id.ToString(), instanceInfo.CreationDate), + } + ); + } +} From 6b96a55849a0abc2064db197e22f3b68a7b8c7ae Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Sun, 30 Mar 2025 14:34:28 +0300 Subject: [PATCH 4/4] feat: implement get current instance endpoint --- .../Contracts/WebGetProjectInstanceInfo.cs | 9 --- .../Instances/GetCurrentInstanceEndpoint.cs | 43 +++++++++++ .../Instances/GetProjectInstancesEndpoint.cs | 25 ------ .../CreateProjectScriptsEndpoint.cs | 2 +- .../Web/Exceptions/ApiErrorExtensions.cs | 15 +++- .../GetCurrentInstanceEndpointTest.cs | 77 +++++++++++++++++++ .../CreateProjectScriptsEndpointTest.cs | 2 +- .../ProblemValidationUtils.Concrete.cs | 17 +++- 8 files changed, 152 insertions(+), 38 deletions(-) delete mode 100644 src/Adapters/Driving/Web/EndpointDefinitions/Instances/Contracts/WebGetProjectInstanceInfo.cs create mode 100644 src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetCurrentInstanceEndpoint.cs create mode 100644 test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetCurrentInstanceEndpointTest.cs diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Instances/Contracts/WebGetProjectInstanceInfo.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Instances/Contracts/WebGetProjectInstanceInfo.cs deleted file mode 100644 index cd1b313..0000000 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Instances/Contracts/WebGetProjectInstanceInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace ScriptBee.Web.EndpointDefinitions.Instances.Contracts; - -public record WebGetProjectInstanceInfo( - string Id, - IEnumerable Loaders, - IEnumerable Linkers, - IDictionary> LoadedModels, - DateTimeOffset CreationDate -); diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetCurrentInstanceEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetCurrentInstanceEndpoint.cs new file mode 100644 index 0000000..e1cf64a --- /dev/null +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetCurrentInstanceEndpoint.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using ScriptBee.Common.Web; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Service.Project.Analysis; +using ScriptBee.UseCases.Project.Analysis; +using ScriptBee.Web.EndpointDefinitions.Instances.Contracts; +using ScriptBee.Web.Exceptions; + +namespace ScriptBee.Web.EndpointDefinitions.Instances; + +using GetCurrentInstanceType = Results, NotFound>; + +public class GetCurrentInstanceEndpoint : IEndpointDefinition +{ + public void DefineServices(IServiceCollection services) + { + services.AddSingleton(); + } + + public void DefineEndpoints(IEndpointRouteBuilder app) + { + app.MapGet("/api/projects/{projectId}/instances/current", GetCurrentInstance); + } + + private static async Task GetCurrentInstance( + HttpContext context, + [FromRoute] string projectId, + IGetCurrentInstanceUseCase useCase, + CancellationToken cancellationToken + ) + { + var result = await useCase.GetCurrentInstance( + ProjectId.FromValue(projectId), + cancellationToken + ); + + return result.Match( + instanceInfo => TypedResults.Ok(WebProjectInstance.Map(instanceInfo)), + error => error.ToNotFoundProblem(context) + ); + } +} diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetProjectInstancesEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetProjectInstancesEndpoint.cs index 83e8072..34a135a 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetProjectInstancesEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Instances/GetProjectInstancesEndpoint.cs @@ -13,13 +13,11 @@ public class GetProjectInstancesEndpoint : IEndpointDefinition public void DefineServices(IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); } public void DefineEndpoints(IEndpointRouteBuilder app) { app.MapGet("/api/projects/{projectId}/instances", GetAllInstances); - app.MapGet("/api/projects/{projectId}/instances/current", GetCurrentInstance); } private static async Task> GetAllInstances( @@ -33,27 +31,4 @@ private static async Task> GetAllInstance return TypedResults.Ok(WebGetProjectInstancesListResponse.Map(calculationInstanceInfos)); } - - private static async Task> GetCurrentInstance( - [FromRoute] string projectId - ) - { - await Task.CompletedTask; - - // TODO FIXIT: remove hardcoded value - - return TypedResults.Ok( - new WebGetProjectInstanceInfo( - "instance-id", - ["honeydew", "InspectorGit"], - ["software-analysis"], - new Dictionary> - { - { "InspectorGit", ["honeydew.iglog"] }, - { "honeydew", ["honeydew-raw.json"] }, - }, - DateTimeOffset.UtcNow - ) - ); - } } diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs index c29d9f0..f13b92e 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpoint.cs @@ -51,7 +51,7 @@ private static async Task CreateProjectScript( WebScriptData.Map(script) ), error => error.ToProblem(context), - error => error.ToProblem(context), + error => error.ToBadRequestProblem(context), error => error.ToProblem(context), error => error.ToProblem(context) ); diff --git a/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs b/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs index ae2cee9..c7f292b 100644 --- a/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs +++ b/src/Adapters/Driving/Web/Exceptions/ApiErrorExtensions.cs @@ -103,7 +103,7 @@ HttpContext context ); } - public static BadRequest ToProblem( + public static BadRequest ToBadRequestProblem( this NoInstanceAllocatedForProjectError error, HttpContext context ) @@ -115,4 +115,17 @@ HttpContext context ) ); } + + public static NotFound ToNotFoundProblem( + this NoInstanceAllocatedForProjectError error, + HttpContext context + ) + { + return TypedResults.NotFound( + context.ToProblemDetails( + "No Instance Allocated For Project", + $"There is no instance allocated for project with the ID '{error.ProjectId}'" + ) + ); + } } diff --git a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetCurrentInstanceEndpointTest.cs b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetCurrentInstanceEndpointTest.cs new file mode 100644 index 0000000..564c10a --- /dev/null +++ b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Instances/GetCurrentInstanceEndpointTest.cs @@ -0,0 +1,77 @@ +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using OneOf; +using ScriptBee.Domain.Model.Analysis; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Tests.Common; +using ScriptBee.UseCases.Project.Analysis; +using ScriptBee.Web.EndpointDefinitions.Instances.Contracts; +using Xunit.Abstractions; +using static ScriptBee.Tests.Common.InstanceInfoFixture; +using static ScriptBee.Tests.Common.ProblemValidationUtils; + +namespace ScriptBee.Web.Tests.EndpointDefinitions.Instances; + +public class GetCurrentInstanceEndpointTest(ITestOutputHelper outputHelper) +{ + private const string TestUrl = "/api/projects/project-id/instances/current"; + private readonly TestApiCaller _api = new(TestUrl); + + [Fact] + public async Task ShouldReturnInstanceInfoWithContext() + { + var useCase = Substitute.For(); + var projectId = ProjectId.FromValue("project-id"); + var instanceInfo = BasicInstanceInfo(projectId); + useCase + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + instanceInfo + ) + ); + + var response = await _api.GetApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ) + ); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var webProjectInstanceInfo = await response.ReadContentAsync(); + webProjectInstanceInfo.Id.ShouldBe(instanceInfo.Id.ToString()); + webProjectInstanceInfo.CreationDate.ShouldBe(instanceInfo.CreationDate); + } + + [Fact] + public async Task NoInstanceAllocatedForProject_ShouldReturnNotFound() + { + var useCase = Substitute.For(); + var projectId = ProjectId.FromValue("project-id"); + useCase + .GetCurrentInstance(projectId, Arg.Any()) + .Returns( + Task.FromResult>( + new NoInstanceAllocatedForProjectError(projectId) + ) + ); + + var response = await _api.GetApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ) + ); + + await AssertNoInstanceAllocatedForProjectNotFoundProblem(response, TestUrl); + } +} diff --git a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs index f8d6bcc..2053c12 100644 --- a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs +++ b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/ProjectStructure/CreateProjectScriptsEndpointTest.cs @@ -227,7 +227,7 @@ public async Task NoInstanceAllocatedForProject_ShouldReturnBadRequest() new WebCreateScriptCommand("path", "csharp", null) ); - await AssertNoInstanceAllocatedForProjectProblem(response, TestUrl, "id"); + await AssertNoInstanceAllocatedForProjectBadRequestProblem(response, TestUrl, "id"); } [Fact] diff --git a/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs b/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs index dd73ac3..a4dc6d4 100644 --- a/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs +++ b/test/Common/Tests.Common/ProblemValidationUtils.Concrete.cs @@ -107,7 +107,7 @@ await AssertConflictProblem( ); } - public static async Task AssertNoInstanceAllocatedForProjectProblem( + public static async Task AssertNoInstanceAllocatedForProjectBadRequestProblem( HttpResponseMessage response, string testUrl, string projectId @@ -121,4 +121,19 @@ await AssertBadRequestProblem( $"There is no instance allocated for project with the ID '{projectId}'" ); } + + public static async Task AssertNoInstanceAllocatedForProjectNotFoundProblem( + HttpResponseMessage response, + string testUrl, + string projectId = "project-id" + ) + { + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + await AssertNotFoundProblem( + response.Content, + testUrl, + "No Instance Allocated For Project", + $"There is no instance allocated for project with the ID '{projectId}'" + ); + } }