diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html index b36d35c3..c38400de 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html @@ -2,7 +2,7 @@
Used Linkers: - + @for (linker of []; track linker) { {{ linker }} } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts index 81ab65f6..1015ddab 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts @@ -1,7 +1,6 @@ import { Component, computed, input } from '@angular/core'; import { TreeNode } from '../../../../../types/tree-node'; import { SelectableTreeComponent } from '../../../../../components/selectable-tree/selectable-tree.component'; -import { InstanceInfo } from '../../../../../types/instance'; @Component({ selector: 'app-currently-loaded-models', @@ -10,10 +9,10 @@ import { InstanceInfo } from '../../../../../types/instance'; imports: [SelectableTreeComponent], }) export class CurrentlyLoadedModelsComponent { - instanceInfo = input.required(); + instanceId = input.required(); loadedFiles = computed(() => { - // TODO FIXIT(#70): populate from api + // TODO FIXIT(#61): populate from api return convertToTreeNodes({}); }); } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-context/project-context.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-context/project-context.component.ts index e74c394c..2b76f608 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-context/project-context.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-context/project-context.component.ts @@ -1,5 +1,4 @@ import { Component, computed, input } from '@angular/core'; -import { InstanceInfo } from '../../../../../types/instance'; import { createRxResourceHandler } from '../../../../../utils/resource'; import { ProjectContextService } from '../../../../../services/projects/project-context.service'; import { CenteredSpinnerComponent } from '../../../../../components/centered-spinner/centered-spinner.component'; @@ -18,39 +17,35 @@ import { ProjectContext } from '../../../../../types/returned-context-slice'; }) export class ProjectContextComponent { projectId = input.required(); - instanceInfo = input.required(); + instanceId = input.required(); context = computed(() => { return convertToTreeNodes(this.projectContextResource.value() ?? []); }); projectContextResource = createRxResourceHandler({ - request: () => ({ projectId: this.projectId(), instanceInfo: this.instanceInfo() }), - loader: (params) => this.projectContextService.getProjectContext(params.request.projectId, params.request.instanceInfo.id), + request: () => ({ projectId: this.projectId(), instanceId: this.instanceId() }), + loader: (params) => this.projectContextService.getProjectContext(params.request.projectId, params.request.instanceId), }); clearContextHandler = apiHandler( (params: { projectId: string; instanceId: string }) => this.projectContextService.clearContext(params.projectId, params.instanceId), - (data) => { - console.log(data); - } + () => this.projectContextResource.reload() ); reloadContextHandler = apiHandler( (params: { projectId: string; instanceId: string }) => this.projectContextService.reloadContext(params.projectId, params.instanceId), - (data) => { - console.log(data); - } + () => this.projectContextResource.reload() ); constructor(private projectContextService: ProjectContextService) {} onReloadModelsClick() { - this.reloadContextHandler.execute({ projectId: this.projectId(), instanceId: this.instanceInfo().id }); + this.reloadContextHandler.execute({ projectId: this.projectId(), instanceId: this.instanceId() }); } onClearContextButtonClick() { - this.clearContextHandler.execute({ projectId: this.projectId(), instanceId: this.instanceInfo().id }); + this.clearContextHandler.execute({ projectId: this.projectId(), instanceId: this.instanceId() }); } } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html index 28937b69..c7ea66bb 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html @@ -31,7 +31,7 @@ } @else if (currentInstanceInfoResource.error()) { } @else { - + } @@ -41,7 +41,7 @@ } @else if (currentInstanceInfoResource.error()) { } @else { - + } } diff --git a/src/Adapters/Driven/Rest/Api/IContextApi.cs b/src/Adapters/Driven/Rest/Api/IContextApi.cs index 2051d7cb..fb145da2 100644 --- a/src/Adapters/Driven/Rest/Api/IContextApi.cs +++ b/src/Adapters/Driven/Rest/Api/IContextApi.cs @@ -5,6 +5,9 @@ namespace ScriptBee.Rest.Api; public interface IContextApi { + [Get("/api/context")] + Task> Get(CancellationToken cancellationToken); + [Post("/api/context/clear")] Task Clear(CancellationToken cancellationToken); diff --git a/src/Adapters/Driven/Rest/Contracts/RestContextSlice.cs b/src/Adapters/Driven/Rest/Contracts/RestContextSlice.cs new file mode 100644 index 00000000..0755777d --- /dev/null +++ b/src/Adapters/Driven/Rest/Contracts/RestContextSlice.cs @@ -0,0 +1,15 @@ +using ScriptBee.Domain.Model.Context; + +namespace ScriptBee.Rest.Contracts; + +public class RestContextSlice +{ + public required string Model { get; set; } + + public required IEnumerable PluginIds { get; set; } + + public ContextSlice Map() + { + return new ContextSlice(Model, PluginIds); + } +} diff --git a/src/Adapters/Driven/Rest/GetInstanceContextAdapter.cs b/src/Adapters/Driven/Rest/GetInstanceContextAdapter.cs new file mode 100644 index 00000000..59b75bfd --- /dev/null +++ b/src/Adapters/Driven/Rest/GetInstanceContextAdapter.cs @@ -0,0 +1,24 @@ +using Refit; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Ports.Instance; +using ScriptBee.Rest.Api; + +namespace ScriptBee.Rest; + +public class GetInstanceContextAdapter(IHttpClientFactory httpClientFactory) : IGetInstanceContext +{ + public async Task> Get( + InstanceInfo instanceInfo, + CancellationToken cancellationToken = default + ) + { + var client = httpClientFactory.CreateClient(); + client.BaseAddress = new Uri(instanceInfo.Url); + + var contextApi = RestService.For(client); + + var restContextSlices = await contextApi.Get(cancellationToken); + return restContextSlices.Select(s => s.Map()); + } +} diff --git a/src/Adapters/Driving/Analysis.Web/EndpointDefinitions/Context/Contracts/WebContextSlice.cs b/src/Adapters/Driving/Analysis.Web/EndpointDefinitions/Context/Contracts/WebContextSlice.cs new file mode 100644 index 00000000..70fb4ec3 --- /dev/null +++ b/src/Adapters/Driving/Analysis.Web/EndpointDefinitions/Context/Contracts/WebContextSlice.cs @@ -0,0 +1,11 @@ +using ScriptBee.Domain.Model.Context; + +namespace ScriptBee.Analysis.Web.EndpointDefinitions.Context.Contracts; + +public record WebContextSlice(string Model, IEnumerable PluginIds) +{ + public static WebContextSlice Map(ContextSlice contextSlice) + { + return new WebContextSlice(contextSlice.Model, contextSlice.PluginIds); + } +} diff --git a/src/Adapters/Driving/Analysis.Web/EndpointDefinitions/Context/GetContextEndpoint.cs b/src/Adapters/Driving/Analysis.Web/EndpointDefinitions/Context/GetContextEndpoint.cs new file mode 100644 index 00000000..b71793d7 --- /dev/null +++ b/src/Adapters/Driving/Analysis.Web/EndpointDefinitions/Context/GetContextEndpoint.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using ScriptBee.Analysis.Web.EndpointDefinitions.Context.Contracts; +using ScriptBee.Common.Web; +using ScriptBee.Service.Analysis; +using ScriptBee.UseCases.Analysis; + +namespace ScriptBee.Analysis.Web.EndpointDefinitions.Context; + +public class GetContextEndpoint : IEndpointDefinition +{ + public void DefineServices(IServiceCollection services) + { + services.AddSingleton(); + } + + public void DefineEndpoints(IEndpointRouteBuilder app) + { + app.MapGet("/api/context", GetContext); + } + + private static Ok> GetContext(IGetContextUseCase useCase) + { + var contextSlices = useCase.Get(); + + return TypedResults.Ok(contextSlices.Select(WebContextSlice.Map)); + } +} diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Context/Contracts/WebGetProjectContextResponse.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Context/Contracts/WebGetProjectContextResponse.cs deleted file mode 100644 index 39ef4dff..00000000 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Context/Contracts/WebGetProjectContextResponse.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace ScriptBee.Web.EndpointDefinitions.Context.Contracts; - -// TODO FIXIT: update PluginIds to return more information (id+name) -public record WebGetProjectContextResponse(string Model, IEnumerable PluginIds); diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Context/Contracts/WebProjectContextSlice.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Context/Contracts/WebProjectContextSlice.cs new file mode 100644 index 00000000..54a6e9f3 --- /dev/null +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Context/Contracts/WebProjectContextSlice.cs @@ -0,0 +1,11 @@ +using ScriptBee.Domain.Model.Context; + +namespace ScriptBee.Web.EndpointDefinitions.Context.Contracts; + +public record WebProjectContextSlice(string Model, IEnumerable PluginIds) +{ + public static WebProjectContextSlice Map(ContextSlice contextSlice) + { + return new WebProjectContextSlice(contextSlice.Model, contextSlice.PluginIds); + } +} diff --git a/src/Adapters/Driving/Web/EndpointDefinitions/Context/GetProjectContextEndpoint.cs b/src/Adapters/Driving/Web/EndpointDefinitions/Context/GetProjectContextEndpoint.cs index 55f28802..0208b21c 100644 --- a/src/Adapters/Driving/Web/EndpointDefinitions/Context/GetProjectContextEndpoint.cs +++ b/src/Adapters/Driving/Web/EndpointDefinitions/Context/GetProjectContextEndpoint.cs @@ -1,7 +1,12 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using ScriptBee.Common.Web; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Service.Project.Context; +using ScriptBee.UseCases.Project.Context; using ScriptBee.Web.EndpointDefinitions.Context.Contracts; +using ScriptBee.Web.Exceptions; namespace ScriptBee.Web.EndpointDefinitions.Context; @@ -9,7 +14,7 @@ public class GetProjectContextEndpoint : IEndpointDefinition { public void DefineServices(IServiceCollection services) { - // TODO FIXIT: update dependencies + services.AddSingleton(); } public void DefineEndpoints(IEndpointRouteBuilder app) @@ -17,21 +22,27 @@ public void DefineEndpoints(IEndpointRouteBuilder app) app.MapGet("/api/projects/{projectId}/instances/{instanceId}/context", GetCurrentContext); } - private static async Task>> GetCurrentContext( + private static async Task< + Results>, NotFound> + > GetCurrentContext( + HttpContext context, [FromRoute] string projectId, - [FromRoute] string instanceId + [FromRoute] string instanceId, + IGetInstanceContextUseCase useCase, + CancellationToken cancellationToken ) { - await Task.CompletedTask; - - // TODO FIXIT: remove hardcoded value + var query = new GetInstanceContextQuery( + ProjectId.FromValue(projectId), + new InstanceId(instanceId) + ); + var result = await useCase.Get(query, cancellationToken); - return TypedResults.Ok( - (IEnumerable) - [ - new WebGetProjectContextResponse("Repository", ["InspectorGit", "honeydew"]), - new WebGetProjectContextResponse("Class", ["honeydew"]), - ] + return result.Match< + Results>, NotFound> + >( + slices => TypedResults.Ok(slices.Select(s => WebProjectContextSlice.Map(s))), + error => error.ToProblem(context) ); } } diff --git a/src/Adapters/Driving/Web/Extensions/RestConfigExtensions.cs b/src/Adapters/Driving/Web/Extensions/RestConfigExtensions.cs index b78d6bfa..902c45a5 100644 --- a/src/Adapters/Driving/Web/Extensions/RestConfigExtensions.cs +++ b/src/Adapters/Driving/Web/Extensions/RestConfigExtensions.cs @@ -12,6 +12,7 @@ public static IServiceCollection AddRestConfig(this IServiceCollection services) .AddHttpClient() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/src/Application/Domain/Model/Context/ContextSlice.cs b/src/Application/Domain/Model/Context/ContextSlice.cs new file mode 100644 index 00000000..fbf18270 --- /dev/null +++ b/src/Application/Domain/Model/Context/ContextSlice.cs @@ -0,0 +1,3 @@ +namespace ScriptBee.Domain.Model.Context; + +public record ContextSlice(string Model, IEnumerable PluginIds); diff --git a/src/Application/Domain/Service.Analysis/GetContextService.cs b/src/Application/Domain/Service.Analysis/GetContextService.cs new file mode 100644 index 00000000..1b94d9eb --- /dev/null +++ b/src/Application/Domain/Service.Analysis/GetContextService.cs @@ -0,0 +1,19 @@ +using ScriptBee.Domain.Model.Context; +using ScriptBee.UseCases.Analysis; + +namespace ScriptBee.Service.Analysis; + +public class GetContextService(IProjectManager projectManager) : IGetContextUseCase +{ + public IEnumerable Get() + { + var project = projectManager.GetProject(); + + return project + .Context.Models.Keys.GroupBy(tuple => tuple.Item1) + .Select(grouping => new ContextSlice( + grouping.Key, + grouping.Select(tuple => tuple.Item2).ToList() + )); + } +} diff --git a/src/Application/Domain/Service.Project/Context/GetInstanceContextService.cs b/src/Application/Domain/Service.Project/Context/GetInstanceContextService.cs new file mode 100644 index 00000000..39b937fd --- /dev/null +++ b/src/Application/Domain/Service.Project/Context/GetInstanceContextService.cs @@ -0,0 +1,31 @@ +using OneOf; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Domain.Model.Errors; +using ScriptBee.Ports.Instance; +using ScriptBee.UseCases.Project.Context; + +namespace ScriptBee.Service.Project.Context; + +using GetInstanceContextResult = OneOf, InstanceDoesNotExistsError>; + +public class GetInstanceContextService( + IGetProjectInstance getProjectInstance, + IGetInstanceContext getInstanceContext +) : IGetInstanceContextUseCase +{ + public async Task Get( + GetInstanceContextQuery query, + CancellationToken cancellationToken = default + ) + { + var result = await getProjectInstance.Get(query.InstanceId, cancellationToken); + + return await result.Match>( + async instanceInfo => + GetInstanceContextResult.FromT0( + await getInstanceContext.Get(instanceInfo, cancellationToken) + ), + error => Task.FromResult(error) + ); + } +} diff --git a/src/Application/Ports/Driven/Ports.Instance/IGetInstanceContext.cs b/src/Application/Ports/Driven/Ports.Instance/IGetInstanceContext.cs new file mode 100644 index 00000000..a16b019b --- /dev/null +++ b/src/Application/Ports/Driven/Ports.Instance/IGetInstanceContext.cs @@ -0,0 +1,12 @@ +using ScriptBee.Domain.Model.Context; +using ScriptBee.Domain.Model.Instance; + +namespace ScriptBee.Ports.Instance; + +public interface IGetInstanceContext +{ + Task> Get( + InstanceInfo instanceInfo, + CancellationToken cancellationToken = default + ); +} diff --git a/src/Application/Ports/Driving/UseCases.Analysis/IGetContextUseCase.cs b/src/Application/Ports/Driving/UseCases.Analysis/IGetContextUseCase.cs new file mode 100644 index 00000000..2f710195 --- /dev/null +++ b/src/Application/Ports/Driving/UseCases.Analysis/IGetContextUseCase.cs @@ -0,0 +1,8 @@ +using ScriptBee.Domain.Model.Context; + +namespace ScriptBee.UseCases.Analysis; + +public interface IGetContextUseCase +{ + IEnumerable Get(); +} diff --git a/src/Application/Ports/Driving/UseCases.Project/Context/GetInstanceContextQuery.cs b/src/Application/Ports/Driving/UseCases.Project/Context/GetInstanceContextQuery.cs new file mode 100644 index 00000000..fc76d222 --- /dev/null +++ b/src/Application/Ports/Driving/UseCases.Project/Context/GetInstanceContextQuery.cs @@ -0,0 +1,6 @@ +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; + +namespace ScriptBee.UseCases.Project.Context; + +public record GetInstanceContextQuery(ProjectId ProjectId, InstanceId InstanceId); diff --git a/src/Application/Ports/Driving/UseCases.Project/Context/IGetInstanceContextUseCase.cs b/src/Application/Ports/Driving/UseCases.Project/Context/IGetInstanceContextUseCase.cs new file mode 100644 index 00000000..935f7f25 --- /dev/null +++ b/src/Application/Ports/Driving/UseCases.Project/Context/IGetInstanceContextUseCase.cs @@ -0,0 +1,13 @@ +using OneOf; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Domain.Model.Errors; + +namespace ScriptBee.UseCases.Project.Context; + +public interface IGetInstanceContextUseCase +{ + Task, InstanceDoesNotExistsError>> Get( + GetInstanceContextQuery query, + CancellationToken cancellationToken = default + ); +} diff --git a/test/Adapters/Driven/Rest.Tests/GetInstanceContextAdapterTest.cs b/test/Adapters/Driven/Rest.Tests/GetInstanceContextAdapterTest.cs new file mode 100644 index 00000000..707b83f0 --- /dev/null +++ b/test/Adapters/Driven/Rest.Tests/GetInstanceContextAdapterTest.cs @@ -0,0 +1,56 @@ +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; + +namespace ScriptBee.Rest.Tests; + +public sealed class GetInstanceContextAdapterTest : IDisposable +{ + private readonly WireMockServer _server = WireMockServer.Start(); + + private readonly GetInstanceContextAdapter _getInstanceContextAdapter = new( + new DefaultHttpClientFactory() + ); + + public void Dispose() + { + _server.Stop(); + } + + [Fact] + public async Task GetContextSlices() + { + _server + .Given(Request.Create().WithPath("/api/context").UsingGet()) + .RespondWith( + Response + .Create() + .WithStatusCode(200) + .WithBody( + """ + [ + { + "model": "model", + "pluginIds": ["plugin-id"] + } + ] + """ + ) + ); + + var contextSlices = await _getInstanceContextAdapter.Get( + new InstanceInfo( + new InstanceId(Guid.Empty), + ProjectId.FromValue("id"), + _server.Urls[0], + DateTimeOffset.Now + ) + ); + + var slice = contextSlices.ToList().Single(); + slice.Model.ShouldBe("model"); + slice.PluginIds.ShouldBeEquivalentTo(new List { "plugin-id" }); + } +} diff --git a/test/Adapters/Driving/Analysis.Web.Tests/EndpointDefinitions/Context/GetContextEndpointTest.cs b/test/Adapters/Driving/Analysis.Web.Tests/EndpointDefinitions/Context/GetContextEndpointTest.cs new file mode 100644 index 00000000..93e4ffe6 --- /dev/null +++ b/test/Adapters/Driving/Analysis.Web.Tests/EndpointDefinitions/Context/GetContextEndpointTest.cs @@ -0,0 +1,39 @@ +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using ScriptBee.Analysis.Web.EndpointDefinitions.Context.Contracts; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Tests.Common; +using ScriptBee.UseCases.Analysis; +using Xunit.Abstractions; + +namespace ScriptBee.Analysis.Web.Tests.EndpointDefinitions.Context; + +public class GetContextEndpointTest(ITestOutputHelper outputHelper) +{ + private const string TestUrl = "/api/context"; + private readonly TestApiCaller _api = new(TestUrl); + + [Fact] + public async Task ShouldReturnContextSlices() + { + var useCase = Substitute.For(); + useCase.Get().Returns([new ContextSlice("model", ["plugin-id"])]); + + var response = await _api.GetApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ) + ); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var contextResponse = await response.ReadContentAsync>(); + var slice = contextResponse.ToList().Single(); + slice.Model.ShouldBe("model"); + slice.PluginIds.ShouldBeEquivalentTo(new List { "plugin-id" }); + } +} diff --git a/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Context/GetProjectContextEndpointTest.cs b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Context/GetProjectContextEndpointTest.cs new file mode 100644 index 00000000..9e3655fb --- /dev/null +++ b/test/Adapters/Driving/Web.Tests/EndpointDefinitions/Context/GetProjectContextEndpointTest.cs @@ -0,0 +1,86 @@ +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using OneOf; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Domain.Model.Errors; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Tests.Common; +using ScriptBee.UseCases.Project.Context; +using ScriptBee.Web.EndpointDefinitions.Context.Contracts; +using Xunit.Abstractions; +using static ScriptBee.Tests.Common.ProblemValidationUtils; + +namespace ScriptBee.Web.Tests.EndpointDefinitions.Context; + +public class GetProjectContextEndpointTest(ITestOutputHelper outputHelper) +{ + private const string TestUrl = + "/api/projects/project-id/instances/b50d1f67-de23-45ea-ad99-f844be49e450/context"; + + private readonly TestApiCaller _api = new(TestUrl); + + [Fact] + public async Task ShouldReturnContextSlices() + { + var projectId = ProjectId.FromValue("project-id"); + var instanceId = new InstanceId("b50d1f67-de23-45ea-ad99-f844be49e450"); + var useCase = Substitute.For(); + useCase + .Get(new GetInstanceContextQuery(projectId, instanceId), Arg.Any()) + .Returns( + Task.FromResult, InstanceDoesNotExistsError>>( + new List { new("model", ["plugin-id"]) } + ) + ); + + var response = await _api.GetApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ) + ); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var contextResponse = await response.ReadContentAsync< + IEnumerable + >(); + var webProjectContextSlice = contextResponse.ToList().Single(); + webProjectContextSlice.Model.ShouldBe("model"); + webProjectContextSlice.PluginIds.ShouldBeEquivalentTo(new List { "plugin-id" }); + } + + [Fact] + public async Task InstanceNotExists_ShouldReturnNotFound() + { + var instanceId = new InstanceId("b50d1f67-de23-45ea-ad99-f844be49e450"); + var useCase = Substitute.For(); + useCase + .Get(Arg.Any(), Arg.Any()) + .Returns( + Task.FromResult, InstanceDoesNotExistsError>>( + new InstanceDoesNotExistsError(instanceId) + ) + ); + + var response = await _api.GetApi( + new TestWebApplicationFactory( + outputHelper, + services => + { + services.AddSingleton(useCase); + } + ) + ); + + await AssertInstanceNotFoundProblem( + response, + TestUrl, + "b50d1f67-de23-45ea-ad99-f844be49e450" + ); + } +} diff --git a/test/Application/Domain/Service.Analysis.Tests/GetContextServiceTest.cs b/test/Application/Domain/Service.Analysis.Tests/GetContextServiceTest.cs new file mode 100644 index 00000000..ead70737 --- /dev/null +++ b/test/Application/Domain/Service.Analysis.Tests/GetContextServiceTest.cs @@ -0,0 +1,83 @@ +using DxWorks.ScriptBee.Plugin.Api; +using NSubstitute; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Service.Analysis; + +namespace ScriptBee.Analysis.Service.Tests; + +public class GetContextServiceTest +{ + private readonly IProjectManager _projectManager = Substitute.For(); + private readonly GetContextService _getContextService; + + public GetContextServiceTest() + { + _getContextService = new GetContextService(_projectManager); + } + + [Fact] + public void ReturnsEmptyList_WhenProjectContextModelsIsEmpty() + { + var project = new Project(); + _projectManager.GetProject().Returns(project); + + var result = _getContextService.Get(); + + result.ShouldBeEmpty(); + } + + [Fact] + public void GroupsContextModelsByType() + { + // Arrange + var project = new Project(); + project.Context.Models.Add( + Tuple.Create("Class", "Model1"), + new Dictionary() + ); + project.Context.Models.Add( + Tuple.Create("Class", "Model2"), + new Dictionary() + ); + project.Context.Models.Add( + Tuple.Create("Interface", "IModel1"), + new Dictionary() + ); + _projectManager.GetProject().Returns(project); + + // Act + var result = _getContextService.Get().ToList(); + + // Assert + result.Count.ShouldBe(2); + + var classSlice = result.FirstOrDefault(slice => slice.Model == "Class"); + classSlice.ShouldNotBeNull(); + classSlice.PluginIds.ShouldBe(new List { "Model1", "Model2" }); + + var interfaceSlice = result.FirstOrDefault(slice => slice.Model == "Interface"); + interfaceSlice.ShouldNotBeNull(); + interfaceSlice.PluginIds.ShouldBe(new List { "IModel1" }); + } + + [Fact] + public void HandlesSingleContextType() + { + var project = new Project(); + project.Context.Models.Add( + Tuple.Create("Class", "ModelA"), + new Dictionary() + ); + project.Context.Models.Add( + Tuple.Create("Class", "ModelB"), + new Dictionary() + ); + _projectManager.GetProject().Returns(project); + + var result = _getContextService.Get().ToList(); + + result.Count.ShouldBe(1); + result.First().Model.ShouldBe("Class"); + result.First().PluginIds.ShouldBe(new List { "ModelA", "ModelB" }); + } +} diff --git a/test/Application/Domain/Service.Project.Tests/Context/GetInstanceContextServiceTest.cs b/test/Application/Domain/Service.Project.Tests/Context/GetInstanceContextServiceTest.cs new file mode 100644 index 00000000..5ae0c87c --- /dev/null +++ b/test/Application/Domain/Service.Project.Tests/Context/GetInstanceContextServiceTest.cs @@ -0,0 +1,73 @@ +using NSubstitute; +using OneOf; +using ScriptBee.Domain.Model.Context; +using ScriptBee.Domain.Model.Errors; +using ScriptBee.Domain.Model.Instance; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Ports.Instance; +using ScriptBee.Service.Project.Context; +using ScriptBee.UseCases.Project.Context; +using static ScriptBee.Tests.Common.InstanceInfoFixture; + +namespace ScriptBee.Service.Project.Tests.Context; + +public class GetInstanceContextServiceTest +{ + private readonly IGetProjectInstance _getProjectInstance = + Substitute.For(); + + private readonly IGetInstanceContext _getInstanceContext = + Substitute.For(); + + private readonly GetInstanceContextService _getInstanceContextService; + + public GetInstanceContextServiceTest() + { + _getInstanceContextService = new GetInstanceContextService( + _getProjectInstance, + _getInstanceContext + ); + } + + [Fact] + public async Task GivenInstance_ExpectContextSlices() + { + var projectId = ProjectId.FromValue("project-id"); + var instanceId = new InstanceId("6143ee26-8150-43b4-b1c3-e57da86061b8"); + var query = new GetInstanceContextQuery(projectId, instanceId); + var instanceInfo = BasicInstanceInfo(projectId); + List contextSlices = [new("model", ["plugin-id"])]; + _getProjectInstance + .Get(instanceId, Arg.Any()) + .Returns( + Task.FromResult>(instanceInfo) + ); + _getInstanceContext + .Get(instanceInfo, Arg.Any()) + .Returns(Task.FromResult>(contextSlices)); + + var result = await _getInstanceContextService.Get(query); + + result.AsT0.ShouldBe(contextSlices); + } + + [Fact] + public async Task GivenNoInstanceForInstanceId_ExpectInstanceDoesNotExistsError() + { + var projectId = ProjectId.FromValue("project-id"); + var instanceId = new InstanceId("6143ee26-8150-43b4-b1c3-e57da86061b8"); + var query = new GetInstanceContextQuery(projectId, instanceId); + var instanceDoesNotExistsError = new InstanceDoesNotExistsError(instanceId); + _getProjectInstance + .Get(instanceId, Arg.Any()) + .Returns( + Task.FromResult>( + instanceDoesNotExistsError + ) + ); + + var result = await _getInstanceContextService.Get(query); + + result.AsT1.ShouldBe(instanceDoesNotExistsError); + } +}