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);
+ }
+}