diff --git a/.github/workflows/pull_request_tester.yml b/.github/workflows/pull_request_tester.yml index bd29cde7..e4988ef6 100644 --- a/.github/workflows/pull_request_tester.yml +++ b/.github/workflows/pull_request_tester.yml @@ -10,15 +10,18 @@ jobs: dotnet-tests: runs-on: ubuntu-latest name: ".NET Tests & Reports" - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 + - name: Download RDFox license key + run: echo "${{ secrets.RDFOX_LICENSE_CONTENT }}" > ./RDFox.lic + - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x + run: | + sudo add-apt-repository ppa:dotnet/backports + sudo apt-get update && sudo apt-get install -y dotnet-sdk-9.0 - name: Restore dependencies run: dotnet restore ./client/Boundaries/Boundaries.sln @@ -29,3 +32,6 @@ jobs: - name: Test run: | dotnet test ./client/Boundaries/Boundaries.sln --no-build --verbosity normal + + - name: Delete license file + run: rm ./RDFox.lic \ No newline at end of file diff --git a/.github/workflows/rdf_tests.yml b/.github/workflows/rdf_tests.yml index ce9ed1c8..f35f4872 100644 --- a/.github/workflows/rdf_tests.yml +++ b/.github/workflows/rdf_tests.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: shell: bash diff --git a/client/Boundaries/Backend/Backend.csproj b/client/Boundaries/Backend/Backend.csproj index ae50c99f..53e00b80 100644 --- a/client/Boundaries/Backend/Backend.csproj +++ b/client/Boundaries/Backend/Backend.csproj @@ -1,15 +1,15 @@  - net8.0 + net9.0 enable enable - - + + diff --git a/client/Boundaries/Backend/Endpoints/BoundaryEndpoints.cs b/client/Boundaries/Backend/Endpoints/BoundaryEndpoints.cs index c7c75616..3aa7af86 100644 --- a/client/Boundaries/Backend/Endpoints/BoundaryEndpoints.cs +++ b/client/Boundaries/Backend/Endpoints/BoundaryEndpoints.cs @@ -1,71 +1,52 @@ using System.Text.Json; using Backend.Utils; using Boundaries; +using Microsoft.AspNetCore.Mvc; namespace Backend.Endpoints; public static class BoundaryEndpoints { - public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, RdfoxApi.ConnectionSettings connectionSettings) + public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints) { #region Boundaries //Update boundary - endpoints.MapPost("/commissioning-package/{packageId}/update-boundary/{nodeId}", async (string packageId, string nodeId) => + endpoints.MapPost("/commissioning-package/{packageId}/update-boundary/{nodeId}", async (string packageId, string nodeId, [FromServices] IRdfoxApi rdfoxApi) => { packageId = Uri.UnescapeDataString(packageId); nodeId = Uri.UnescapeDataString(nodeId); - if (!await QueryUtils.CommissioningPackageExists(packageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(packageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {packageId} not found."); } - var isSelectedInternal = await QueryUtils.IsSelectedInternalOf(packageId, nodeId, connectionSettings); - var isBoundary = await QueryUtils.IsBoundaryOf(packageId, nodeId, connectionSettings); + var isSelectedInternal = await QueryUtils.IsSelectedInternalOf(packageId, nodeId, rdfoxApi); + var isBoundary = await QueryUtils.IsBoundaryOf(packageId, nodeId, rdfoxApi); if (isSelectedInternal) - await QueryUtils.DeleteIsSelectedInternalOf(packageId, nodeId, connectionSettings); + await QueryUtils.DeleteIsSelectedInternalOf(packageId, nodeId, rdfoxApi); if (isBoundary) { - await QueryUtils.DeleteIsBoundaryOf(packageId, nodeId, connectionSettings); + await QueryUtils.DeleteIsBoundaryOf(packageId, nodeId, rdfoxApi); } else { - await QueryUtils.AddIsBoundaryOf(packageId, nodeId, connectionSettings); + await QueryUtils.AddIsBoundaryOf(packageId, nodeId, rdfoxApi); } return Results.Ok(); }).WithTags("Boundary"); - // Add node as boundary - endpoints.MapPost("/commissioning-package/{packageId}/boundary/{nodeId}", async (string packageId, string nodeId) => - { - packageId = Uri.UnescapeDataString(packageId); - nodeId = Uri.UnescapeDataString(nodeId); - - if (!await QueryUtils.CommissioningPackageExists(packageId, connectionSettings)) - { - return Results.NotFound($"Commissioning package {packageId} not found."); - } - - var data = $@" - <{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> ."; - - await RdfoxApi.LoadData(connectionSettings, data); - - return Results.Ok($"Triple with subject {packageId} and object {nodeId} inserted successfully."); - }).WithTags("Boundary"); - - // Remove node as boundary - endpoints.MapDelete("/commissioning-package/{packageId}/boundary/{nodeId}", async (string packageId, string nodeId) => + endpoints.MapDelete("/commissioning-package/{packageId}/boundary/{nodeId}", async (string packageId, string nodeId, [FromServices] IRdfoxApi rdfoxApi) => { packageId = Uri.UnescapeDataString(packageId); nodeId = Uri.UnescapeDataString(nodeId); - if (!await QueryUtils.CommissioningPackageExists(packageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(packageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {packageId} not found."); } @@ -74,7 +55,7 @@ public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, Rd <{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> . "; - await RdfoxApi.DeleteData(connectionSettings, data); + await rdfoxApi.DeleteData(data); return Results.Ok($"Triple for package {packageId} and node {nodeId} deleted successfully."); }).WithTags("Boundary"); @@ -82,29 +63,29 @@ public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, Rd #region Internal //Update selected internal - endpoints.MapPost("/commissioning-package/{packageId}/update-internal/{nodeId}", async (string packageId, string nodeId) => + endpoints.MapPost("/commissioning-package/{packageId}/update-internal/{nodeId}", async (string packageId, string nodeId, [FromServices] IRdfoxApi rdfoxApi) => { packageId = Uri.UnescapeDataString(packageId); nodeId = Uri.UnescapeDataString(nodeId); - if (!await QueryUtils.CommissioningPackageExists(packageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(packageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {packageId} not found."); } - var isSelectedInternal = await QueryUtils.IsSelectedInternalOf(packageId, nodeId, connectionSettings); - var isBoundary = await QueryUtils.IsBoundaryOf(packageId, nodeId, connectionSettings); + var isSelectedInternal = await QueryUtils.IsSelectedInternalOf(packageId, nodeId, rdfoxApi); + var isBoundary = await QueryUtils.IsBoundaryOf(packageId, nodeId, rdfoxApi); if (isBoundary) - await QueryUtils.DeleteIsBoundaryOf(packageId, nodeId, connectionSettings); + await QueryUtils.DeleteIsBoundaryOf(packageId, nodeId, rdfoxApi); if (isSelectedInternal) { - await QueryUtils.DeleteIsSelectedInternalOf(packageId, nodeId, connectionSettings); + await QueryUtils.DeleteIsSelectedInternalOf(packageId, nodeId, rdfoxApi); } else { - await QueryUtils.AddIsSelectedInternalOf(packageId, nodeId, connectionSettings); + await QueryUtils.AddIsSelectedInternalOf(packageId, nodeId, rdfoxApi); } return Results.Ok(); @@ -112,12 +93,12 @@ public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, Rd //Add node as internal - endpoints.MapPost("/commissioning-package/{packageId}/internal/{nodeId}", async (string packageId, string nodeId) => + endpoints.MapPost("/commissioning-package/{packageId}/internal/{nodeId}", async (string packageId, string nodeId, [FromServices] IRdfoxApi rdfoxApi) => { packageId = Uri.UnescapeDataString(packageId); nodeId = Uri.UnescapeDataString(nodeId); - if (!await QueryUtils.CommissioningPackageExists(packageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(packageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {packageId} not found."); } @@ -126,19 +107,19 @@ public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, Rd <{nodeId}> {PropertiesProvider.isInPackage} <{packageId}> . "; - await RdfoxApi.LoadData(connectionSettings, data); + await rdfoxApi.LoadData(data); return Results.Ok($"Triple for package {packageId} and node {nodeId} inserted successfully."); }).WithTags("Internal"); // Remove node as internal - endpoints.MapDelete("/commissioning-package/{packageId}/internal/{nodeId}", async (string packageId, string nodeId) => + endpoints.MapDelete("/commissioning-package/{packageId}/internal/{nodeId}", async (string packageId, string nodeId, [FromServices] IRdfoxApi rdfoxApi) => { packageId = Uri.UnescapeDataString(packageId); nodeId = Uri.UnescapeDataString(nodeId); - if (!await QueryUtils.CommissioningPackageExists(packageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(packageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {packageId} not found."); } @@ -147,7 +128,7 @@ public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, Rd <{nodeId}> {PropertiesProvider.isInPackage} <{packageId}> . "; - await RdfoxApi.DeleteData(connectionSettings, data); + await rdfoxApi.DeleteData(data); return Results.Ok($"Triple for package {packageId} and node {nodeId} deleted successfully."); }).WithTags("Internal"); @@ -155,13 +136,13 @@ public static void MapBoundaryEndpoints(this IEndpointRouteBuilder endpoints, Rd #region Nodes //Get adjacent nodes - endpoints.MapGet("/nodes/{nodeId}/adjacent", async (string nodeId) => + endpoints.MapGet("/nodes/{nodeId}/adjacent", async (string nodeId, [FromServices] IRdfoxApi rdfoxApi) => { nodeId = Uri.UnescapeDataString(nodeId); var query = $@"SELECT ?neighbour WHERE {{ <{nodeId}> {PropertiesProvider.adjacentTo} ?neighbour }}"; - var result = await RdfoxApi.QuerySparql(connectionSettings, query); + var result = await rdfoxApi.QuerySparql(query); var adjacentNodes = new List(); diff --git a/client/Boundaries/Backend/Endpoints/CommissioningPackageEndpoints.cs b/client/Boundaries/Backend/Endpoints/CommissioningPackageEndpoints.cs index 21e6539c..19a06412 100644 --- a/client/Boundaries/Backend/Endpoints/CommissioningPackageEndpoints.cs +++ b/client/Boundaries/Backend/Endpoints/CommissioningPackageEndpoints.cs @@ -3,29 +3,30 @@ using Backend.Model; using Backend.Utils; using Boundaries; +using Microsoft.AspNetCore.Mvc; namespace Backend.Endpoints; public static class CommissioningPackageEndpoints { - public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder endpoints, RdfoxApi.ConnectionSettings connectionSettings) + public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder endpoints) { //Add commissioning package - endpoints.MapPost("/commissioning-package", async (CommissioningPackage commissioningPackage) => + endpoints.MapPost("/commissioning-package", async (CommissioningPackage commissioningPackage, [FromServices] IRdfoxApi rdfoxApi) => { var data = new StringBuilder(); data.AppendLine($@"<{commissioningPackage.Id}> {TypesProvider.type} {PropertiesProvider.CommissioningPackage} ."); data.AppendLine($@"<{commissioningPackage.Id}> {PropertiesProvider.hasName} ""{commissioningPackage.Name}"" ."); data.AppendLine($@"<{commissioningPackage.Id}> {PropertiesProvider.hasColor} ""{commissioningPackage.Color}"" ."); - await RdfoxApi.LoadData(connectionSettings, data.ToString()); + await rdfoxApi.LoadData(data.ToString()); - return Results.Ok($"Commissioning package {commissioningPackage.Id} added successfully."); + return Results.Ok(); }).WithTags("Commissioning Package"); // Update commissioning package - updating information like name and color while persisting the calculated internal nodes, and boundaries. - endpoints.MapPut("/commissioning-package", async (CommissioningPackage updatedPackage) => + endpoints.MapPut("/commissioning-package", async (CommissioningPackage updatedPackage, [FromServices] IRdfoxApi rdfoxApi) => { var getQuery = $@" SELECT ?object WHERE {{ @@ -35,7 +36,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e }} "; - var result = await RdfoxApi.QuerySparql(connectionSettings, getQuery); + var result = await rdfoxApi.QuerySparql(getQuery); var oldPackageName = string.Empty; var oldPackageColor = string.Empty; @@ -56,25 +57,25 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e <{updatedPackage.Id}> {PropertiesProvider.hasName} ""{oldPackageName}"" . "; - await RdfoxApi.DeleteData(connectionSettings, deleteData); + await rdfoxApi.DeleteData(deleteData); var data = $@" <{updatedPackage.Id}> {PropertiesProvider.hasName} ""{updatedPackage.Name}"" . <{updatedPackage.Id}> {PropertiesProvider.hasColor} ""{updatedPackage.Color}"" . "; - await RdfoxApi.LoadData(connectionSettings, data); + await rdfoxApi.LoadData(data); return Results.Ok($"Commissioning package {updatedPackage.Id} updated successfully."); }).WithTags("Commissioning Package"); //Get commissioning package - endpoints.MapGet("/commissioning-package/{commissioningPackageId}", async (string commissioningPackageId) => + endpoints.MapGet("/commissioning-package/{commissioningPackageId}", async (string commissioningPackageId, [FromServices] IRdfoxApi rdfoxApi) => { commissioningPackageId = Uri.UnescapeDataString(commissioningPackageId); - if (!await QueryUtils.CommissioningPackageExists(commissioningPackageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(commissioningPackageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {commissioningPackageId} not found."); } @@ -87,7 +88,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e }} "; - var result = await RdfoxApi.QuerySparql(connectionSettings, query); + var result = await rdfoxApi.QuerySparql(query); CommissioningPackage commissioningPackage; @@ -109,11 +110,11 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e //Delete commissioning package - endpoints.MapDelete("/commissioning-package/{commissioningPackageId}", async (string commissioningPackageId) => + endpoints.MapDelete("/commissioning-package/{commissioningPackageId}", async (string commissioningPackageId, [FromServices] IRdfoxApi rdfoxApi) => { commissioningPackageId = Uri.UnescapeDataString(commissioningPackageId); - if (!await QueryUtils.CommissioningPackageExists(commissioningPackageId, connectionSettings)) + if (!await QueryUtils.CommissioningPackageExists(commissioningPackageId, rdfoxApi)) { return Results.NotFound($"Commissioning package {commissioningPackageId} not found."); } @@ -123,7 +124,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e {{ <{commissioningPackageId}> ?x ?y . }} }}"; - var result = await RdfoxApi.QuerySparql(connectionSettings, query); + var result = await rdfoxApi.QuerySparql(query); var deleteQueryBuilder = new StringBuilder(); @@ -147,7 +148,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e } } - await RdfoxApi.DeleteData(connectionSettings, deleteQueryBuilder.ToString()); + await rdfoxApi.DeleteData( deleteQueryBuilder.ToString()); query = $@" SELECT ?x ?y WHERE {{ @@ -156,7 +157,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e try { - result = await RdfoxApi.QuerySparql(connectionSettings, query); + result = await rdfoxApi.QuerySparql(query); deleteQueryBuilder = new StringBuilder(); using (var docDel = JsonDocument.Parse(result)) @@ -172,7 +173,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e } } - await RdfoxApi.DeleteData(connectionSettings, deleteQueryBuilder.ToString()); + await rdfoxApi.DeleteData(deleteQueryBuilder.ToString()); } catch (Exception ex) { @@ -185,14 +186,14 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e //Get all commissioning packages - endpoints.MapGet("/commissioning-package/all", async () => + endpoints.MapGet("/commissioning-package/all", async ([FromServices] IRdfoxApi rdfoxApi) => { var query = $@" SELECT ?packageId WHERE {{ ?packageId rdf:type {PropertiesProvider.CommissioningPackage} . }}"; - var result = await RdfoxApi.QuerySparql(connectionSettings, query); + var result = await rdfoxApi.QuerySparql(query); var jsonResponse = JsonDocument.Parse(result); var packageIds = jsonResponse.RootElement @@ -214,7 +215,7 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e }} "; - result = await RdfoxApi.QuerySparql(connectionSettings, query); + result = await rdfoxApi.QuerySparql(query); CommissioningPackage commissioningPackage; @@ -237,14 +238,14 @@ public static void MapCommissioningPackageEndpoints(this IEndpointRouteBuilder e }).WithTags("Commissioning Package"); //Get the ID of all commissioning packages - endpoints.MapGet("/commissioning-package/ids", async () => + endpoints.MapGet("/commissioning-package/ids", async ([FromServices] IRdfoxApi rdfoxApi) => { var query = $@" SELECT ?packageId WHERE {{ ?packageId rdf:type {PropertiesProvider.CommissioningPackage} . }}"; - var result = await RdfoxApi.QuerySparql(connectionSettings, query); + var result = await rdfoxApi.QuerySparql(query); var jsonResponse = JsonDocument.Parse(result); var packageIds = jsonResponse.RootElement diff --git a/client/Boundaries/Backend/Program.cs b/client/Boundaries/Backend/Program.cs index 74ea6c16..b7c9cac2 100644 --- a/client/Boundaries/Backend/Program.cs +++ b/client/Boundaries/Backend/Program.cs @@ -7,6 +7,20 @@ builder.Services.AddSwaggerGen(); builder.Services.AddCors(); +builder.Services.AddSingleton(sp => +{ + var connectionSettings = new ConnectionSettings + { + Host = "rdfox", + Port = 12110, + Username = "guest", + Password = "guest", + Datastore = "boundaries" + }; + + return new RdfoxApi(connectionSettings); +}); + var app = builder.Build(); app.UseSwagger(); @@ -18,12 +32,14 @@ ); app.UseHttpsRedirection(); -// Establish connection to Rdfox -var rdfoxConnectionSettings = RdfoxApi.GetDefaultConnectionSettings(); - -// Map endpoints -app.MapBoundaryEndpoints(rdfoxConnectionSettings); -app.MapCommissioningPackageEndpoints(rdfoxConnectionSettings); +app.MapBoundaryEndpoints(); +app.MapCommissioningPackageEndpoints(); app.MapGraphicalDataFormatEndpoints(); app.Run(); + + +// Necessary for integration testing +namespace Backend{ + public partial class Program { } +} \ No newline at end of file diff --git a/client/Boundaries/Backend/Utils/QueryUtils.cs b/client/Boundaries/Backend/Utils/QueryUtils.cs index 132be58b..f690924a 100644 --- a/client/Boundaries/Backend/Utils/QueryUtils.cs +++ b/client/Boundaries/Backend/Utils/QueryUtils.cs @@ -1,6 +1,6 @@ using Backend.Model; +using Boundaries; using System.Text.Json; -using static Boundaries.RdfoxApi; namespace Backend.Utils; @@ -8,39 +8,39 @@ public static class QueryUtils { #region Boundary actions - public static async Task IsBoundaryOf(string packageId, string nodeId, ConnectionSettings conn) - => await AskSparql(conn, $@"ASK WHERE {{ <{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> .}}"); + public static async Task IsBoundaryOf(string packageId, string nodeId, IRdfoxApi rdfoxApi) + => await rdfoxApi.AskSparql($@"ASK WHERE {{ <{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> .}}"); - public static async Task DeleteIsBoundaryOf(string packageId, string nodeId, ConnectionSettings conn) - => await DeleteData(conn, $@"<{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> ."); + public static async Task DeleteIsBoundaryOf(string packageId, string nodeId, IRdfoxApi rdfoxApi) + => await rdfoxApi.DeleteData($@"<{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> ."); - public static async Task AddIsBoundaryOf(string packageId, string nodeId, ConnectionSettings conn) - => await LoadData(conn, $@"<{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> ."); + public static async Task AddIsBoundaryOf(string packageId, string nodeId, IRdfoxApi rdfoxApi) + => await rdfoxApi.LoadData($@"<{nodeId}> {PropertiesProvider.isBoundaryOf} <{packageId}> ."); #endregion #region SelectedInternal actions - public static async Task IsSelectedInternalOf(string packageId, string nodeId, ConnectionSettings conn) - => await AskSparql(conn, $@"ASK WHERE {{ <{nodeId}> {PropertiesProvider.isSelectedInternalOf} <{packageId}> . }}"); + public static async Task IsSelectedInternalOf(string packageId, string nodeId, IRdfoxApi rdfoxApi) + => await rdfoxApi.AskSparql($@"ASK WHERE {{ <{nodeId}> {PropertiesProvider.isSelectedInternalOf} <{packageId}> . }}"); - public static async Task DeleteIsSelectedInternalOf(string packageId, string nodeId, ConnectionSettings conn) - => await DeleteData(conn, $@"<{nodeId}> {PropertiesProvider.isSelectedInternalOf} <{packageId}> ."); + public static async Task DeleteIsSelectedInternalOf(string packageId, string nodeId, IRdfoxApi rdfoxApi) + => await rdfoxApi.DeleteData($@"<{nodeId}> {PropertiesProvider.isSelectedInternalOf} <{packageId}> ."); - public static async Task AddIsSelectedInternalOf(string packageId, string nodeId, ConnectionSettings conn) - => await LoadData(conn, $@"<{nodeId}> {PropertiesProvider.isSelectedInternalOf} <{packageId}> ."); + public static async Task AddIsSelectedInternalOf(string packageId, string nodeId, IRdfoxApi rdfoxApi) + => await rdfoxApi.LoadData($@"<{nodeId}> {PropertiesProvider.isSelectedInternalOf} <{packageId}> ."); #endregion #region IsInPackage actions - public static async Task NodeIsInPackage(string packageId, string nodeId, ConnectionSettings conn) - => await AskSparql(conn, $@"ASK WHERE {{ <{nodeId}> {PropertiesProvider.isInPackage} <{packageId}> . }}"); + public static async Task NodeIsInPackage(string packageId, string nodeId, RdfoxApi rdfoxApi) + => await rdfoxApi.AskSparql($@"ASK WHERE {{ <{nodeId}> {PropertiesProvider.isInPackage} <{packageId}> . }}"); - public static async Task DeleteNodeFromPackage(string packageId, string nodeId, ConnectionSettings conn) - => await LoadData(conn, $"<{nodeId}> {PropertiesProvider.isInPackage} <{packageId}> ."); + public static async Task DeleteNodeFromPackage(string packageId, string nodeId, RdfoxApi rdfoxApi) + => await rdfoxApi.LoadData($"<{nodeId}> {PropertiesProvider.isInPackage} <{packageId}> ."); #endregion #region CommissioningPackage actions - public static async Task CommissioningPackageExists(string packageId, ConnectionSettings conn) - => await AskSparql(conn, $@"ASK WHERE {{ <{packageId}> {TypesProvider.type} {PropertiesProvider.CommissioningPackage} . }}"); + public static async Task CommissioningPackageExists(string packageId, IRdfoxApi rdfoxApi) + => await rdfoxApi.AskSparql($@"ASK WHERE {{ <{packageId}> {TypesProvider.type} {PropertiesProvider.CommissioningPackage} . }}"); public static CommissioningPackage ParseCommissioningPackageQueryResult(string id, string queryResult) { diff --git a/client/Boundaries/Boundaries/Boundaries.csproj b/client/Boundaries/Boundaries/Boundaries.csproj index da63e1e5..e5f74d8b 100644 --- a/client/Boundaries/Boundaries/Boundaries.csproj +++ b/client/Boundaries/Boundaries/Boundaries.csproj @@ -1,8 +1,7 @@  - Exe - net8.0 + net9.0 enable enable diff --git a/client/Boundaries/Boundaries/ConnectionSettings.cs b/client/Boundaries/Boundaries/ConnectionSettings.cs new file mode 100644 index 00000000..bb35340d --- /dev/null +++ b/client/Boundaries/Boundaries/ConnectionSettings.cs @@ -0,0 +1,26 @@ +using System.Net.Http.Headers; +using System.Text; + +namespace Boundaries; + +public struct ConnectionSettings +{ + public string Host { get; set; } + public int Port { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string Datastore { get; set; } + + public AuthenticationHeaderValue GetRequestAuthHeader() + { + var credentials = $"{Username}:{Password}"; + var base64Credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(credentials)); + return new AuthenticationHeaderValue("Basic", base64Credentials); + } + public HttpClient GetAuthenticatedClient() + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Authorization = GetRequestAuthHeader(); + return client; + } + } \ No newline at end of file diff --git a/client/Boundaries/Boundaries/DexpiApi.cs b/client/Boundaries/Boundaries/DexpiApi.cs deleted file mode 100644 index 23467756..00000000 --- a/client/Boundaries/Boundaries/DexpiApi.cs +++ /dev/null @@ -1,43 +0,0 @@ -using IriTools; - -namespace Boundaries; - -public static class DexpiApi -{ - public static async Task GetCommissioningPackage(string internalComponentLabel, IriReference[] borderComponentIris, string dexpiFilePath) - { - var datalogCreator = new DatalogCreator(); - var datalog = datalogCreator.CreateBoundaryDatalogRule(internalComponentLabel, borderComponentIris); - var conn = RdfoxApi.GetDefaultConnectionSettings(); - await RdfoxApi.LoadDatalog(conn, datalog); - - var data = File.ReadAllText(dexpiFilePath); - await RdfoxApi.LoadData(conn, data); - - var queryString = datalogCreator.CreateCommissioningSparqlQuery(); - var result = await RdfoxApi.QuerySparql(conn, queryString); - - await RdfoxApi.DeleteData(conn, data); - await RdfoxApi.DeleteDatalog(conn, datalog); - return result; - } - - - public static async Task GetConnectedEquipment(string internalComponentLabel, string dexpiFilePath) - { - var datalogCreator = new DatalogCreator(); - var datalog = datalogCreator.CreateConnectedDatalogRule(internalComponentLabel); - var conn = RdfoxApi.GetDefaultConnectionSettings(); - await RdfoxApi.LoadDatalog(conn, datalog); - - var data = File.ReadAllText(dexpiFilePath); - await RdfoxApi.LoadData(conn, data); - - var queryString = datalogCreator.CreateConnectedSparqlQuery(); - var result = await RdfoxApi.QuerySparql(conn, queryString); - - await RdfoxApi.DeleteData(conn, data); - await RdfoxApi.DeleteDatalog(conn, datalog); - return result; - } -} \ No newline at end of file diff --git a/client/Boundaries/Boundaries/IRdfoxApi.cs b/client/Boundaries/Boundaries/IRdfoxApi.cs new file mode 100644 index 00000000..b191c727 --- /dev/null +++ b/client/Boundaries/Boundaries/IRdfoxApi.cs @@ -0,0 +1,18 @@ +namespace Boundaries; + + +public interface IRdfoxApi +{ + public Task DeleteDatalog( string datalog); + + public Task LoadDatalog(string datalog); + + public Task DeleteData(string data); + + public Task LoadData(string data); + + public Task QuerySparql(string query, string acceptHeader = "application/sparql-results+json"); + + public Task AskSparql(string query); + +} \ No newline at end of file diff --git a/client/Boundaries/Boundaries/Program.cs b/client/Boundaries/Boundaries/Program.cs deleted file mode 100644 index fe1f70a0..00000000 --- a/client/Boundaries/Boundaries/Program.cs +++ /dev/null @@ -1,36 +0,0 @@ -using IriTools; - -namespace Boundaries; - -internal class Program -{ - private static async Task Main(string[] args) - { - if (args.Length < 2) - { - Console.WriteLine( - "Usage \"dotnet run filename-for-dexpi-in-rdf \"label-of-internal-componenet\" iri-of-border-component ... iri-of-border-component "); - Console.WriteLine( - "For finding a commissioning package from boundary use f.ex. \"dotnet run ../../../rml/pandid.trig \"T4750\" https://assetid.equinor.com/plantx#Nozzle-12 https://assetid.equinor.com/plantx#Nozzle-8 https://assetid.equinor.com/plantx#PlateHeatExchanger-1 https://assetid.equinor.com/plantx#ReciprocatingPump-1\""); - Console.WriteLine("For finding directly connected equipemnt use f.ex. \"dotnet run ../../../rml/pandid.trig \"P4711\""); - return; - } - - var dexpiFilePath = args[0]; - var internalComponentLabel = args[1]; - if (!File.Exists(dexpiFilePath)) - { - Console.WriteLine( - $"Could not find one of the input file {dexpiFilePath}"); - return; - } - - var borderComponentIris = args.Skip(2).Select(iri => new IriReference(iri)).ToArray(); - var result = borderComponentIris.Any() ? - await DexpiApi.GetCommissioningPackage(internalComponentLabel, borderComponentIris, dexpiFilePath) - : await DexpiApi.GetConnectedEquipment(internalComponentLabel, dexpiFilePath); - Console.WriteLine("Commissioning package:"); - Console.WriteLine(result); - - } -} \ No newline at end of file diff --git a/client/Boundaries/Boundaries/RdfoxApi.cs b/client/Boundaries/Boundaries/RdfoxApi.cs index 1ebe1bb3..a525be1e 100644 --- a/client/Boundaries/Boundaries/RdfoxApi.cs +++ b/client/Boundaries/Boundaries/RdfoxApi.cs @@ -1,32 +1,16 @@ using System.Text; using System.Text.Json; -using IriTools; namespace Boundaries; -public class RdfoxApi +public class RdfoxApi(ConnectionSettings conn) : IRdfoxApi { - public struct ConnectionSettings - { - public string Host { get; set; } - public int Port { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public string Datastore { get; set; } - - } - public static ConnectionSettings GetDefaultConnectionSettings() + //Return the CONNECTION conn that is used to connect to the RDFox server + public ConnectionSettings GetDefaultConnectionSettings() { - return new ConnectionSettings - { - Host = "rdfox", - Port = 12110, - Username = "admin", - Password = "admin", - Datastore = "boundaries" - }; + return conn; } @@ -35,9 +19,9 @@ public static ConnectionSettings GetDefaultConnectionSettings() /// /// /// - public static async Task DeleteDatalog(ConnectionSettings conn, string datalog) + public async Task DeleteDatalog(string datalog) { - using (var client = new HttpClient()) + using (var client = conn.GetAuthenticatedClient()) { var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content?operation=delete-content"); var content = new StringContent(datalog, Encoding.UTF8, "application/x.datalog"); @@ -57,9 +41,9 @@ public static async Task DeleteDatalog(ConnectionSettings conn, string datalog) /// /// /// - public static async Task LoadDatalog(ConnectionSettings conn, string datalog) + public async Task LoadDatalog(string datalog) { - using (var client = new HttpClient()) + using (var client = conn.GetAuthenticatedClient()) { var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content"); var content = new StringContent(datalog, Encoding.UTF8, "application/x.datalog"); @@ -80,9 +64,9 @@ public static async Task LoadDatalog(ConnectionSettings conn, string datalog) /// /// /// - public static async Task DeleteData(ConnectionSettings conn, string data) + public async Task DeleteData(string data) { - using (var client = new HttpClient()) + using (var client = conn.GetAuthenticatedClient()) { var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content?operation=delete-content"); var content = new StringContent(data, Encoding.UTF8, "application/trig"); @@ -103,9 +87,9 @@ public static async Task DeleteData(ConnectionSettings conn, string data) /// /// /// - public static async Task LoadData(ConnectionSettings conn, string data) + public async Task LoadData(string data) { - using (var client = new HttpClient()) + using (var client = conn.GetAuthenticatedClient()) { var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content"); var content = new StringContent(data, Encoding.UTF8, "application/trig"); @@ -119,7 +103,7 @@ public static async Task LoadData(ConnectionSettings conn, string data) var response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync()); - response.EnsureSuccessStatusCode(); + } } @@ -129,9 +113,9 @@ public static async Task LoadData(ConnectionSettings conn, string data) /// /// /// - public static async Task QuerySparql(ConnectionSettings conn, string query, string acceptHeader = "application/sparql-results+json") + public async Task QuerySparql( string query, string acceptHeader = "application/sparql-results+json") { - using (var client = new HttpClient()) + using (var client = conn.GetAuthenticatedClient()) { var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/sparql"); var content = new StringContent(query, Encoding.UTF8, "application/sparql-query"); @@ -149,9 +133,9 @@ public static async Task QuerySparql(ConnectionSettings conn, string que } } - public static async Task AskSparql(ConnectionSettings conn, string query) + public async Task AskSparql(string query) { - using (var client = new HttpClient()) + using (var client = conn.GetAuthenticatedClient()) { var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/sparql"); var content = new StringContent(query, Encoding.UTF8, "application/sparql-query"); @@ -171,5 +155,4 @@ public static async Task AskSparql(ConnectionSettings conn, string query) return jsonResponse.RootElement.GetProperty("boolean").GetBoolean(); } } - } \ No newline at end of file diff --git a/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs b/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs index ef3c6b6c..c6af6567 100644 --- a/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs +++ b/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs @@ -1,3 +1,5 @@ +using System.Text.RegularExpressions; + namespace TestBoundaries; public class DatalogCreatorTests @@ -14,21 +16,25 @@ public void TestCreateDatalog() new IriTools.IriReference("https://assetid.equinor.com/plantx#PlateHeatExchanger-1"), new IriTools.IriReference("https://assetid.equinor.com/plantx#ReciprocatingPump-1") }); - Assert.Equal($$""" + var expected = $$""" + + <{{graphIri}}> [?node] :- + rdfs:label [?internal, "T4750"], + imf:connectedTo [?internal, ?node], + dexpi:PipingOrEquipment [?node]. + + <{{graphIri}}> [?node] :- + <{{graphIri}}> [?node1], + imf:connectedTo [?node1, ?node], + NOT FILTER(?node1 = ), + NOT FILTER(?node1 = ), + NOT FILTER(?node1 = ), + NOT FILTER(?node1 = ), + dexpi:PipingOrEquipment [?node]. + """; + + string NormalizeWhitespace(string input) => Regex.Replace(input, @"\s+", " ").Trim(); - <{{graphIri}}> [?node] :- - rdfs:label [?internal, "T4750"], - imf:connectedTo [?internal, ?node], - dexpi:PipingOrEquipment [?node]. - - <{{graphIri}}> [?node] :- - <{{graphIri}}> [?node1], - imf:connectedTo [?node1, ?node], - NOT FILTER(?node1 = ), - NOT FILTER(?node1 = ), - NOT FILTER(?node1 = ), - NOT FILTER(?node1 = ), - dexpi:PipingOrEquipment [?node]. - """, datalog); + Assert.Equal(NormalizeWhitespace(expected), NormalizeWhitespace(datalog)); } } \ No newline at end of file diff --git a/client/Boundaries/TestBoundaries/IntegrationTests.cs b/client/Boundaries/TestBoundaries/IntegrationTests.cs new file mode 100644 index 00000000..0f291d7f --- /dev/null +++ b/client/Boundaries/TestBoundaries/IntegrationTests.cs @@ -0,0 +1,93 @@ +using System.Net.Http.Json; +using Backend.Model; +using System.Text.Json; +using Xunit.Abstractions; + +namespace TestBoundaries; + +public class IntegrationTests(TestFactory factory, ITestOutputHelper testOutputHelper) : IClassFixture +{ + [Fact] + public async Task TestCreatePackage() + { + // Arrange + var client = factory.CreateAuthenticatingClient(); + var commissioningPackage = new CommissioningPackage + { + Id = "package1", + Name = "Test Package", + Color = "Red" + }; + + // Act + var response = await client.PostAsJsonAsync("/commissioning-package", commissioningPackage); + + // Assert + if (!response.IsSuccessStatusCode) + { + var errorString = await response.Content.ReadAsStringAsync(); + testOutputHelper.WriteLine($"Error: {response.StatusCode}"); + testOutputHelper.WriteLine(errorString); + } + response.EnsureSuccessStatusCode(); + } + + [Fact] + public async Task TestGetAllPackages() + { + // Arrange + var client = factory.CreateAuthenticatingClient(); + var commissioningPackage = new CommissioningPackage + { + Id = "package1", + Name = "Test Package", + Color = "Red" + }; + + var createResponse = await client.PostAsJsonAsync("/commissioning-package", commissioningPackage); + createResponse.EnsureSuccessStatusCode(); + + // Act + var response = await client.GetAsync("/commissioning-package/all"); + + // Assert + if (!response.IsSuccessStatusCode) + { + var errorString = await response.Content.ReadAsStringAsync(); + testOutputHelper.WriteLine($"Error: {response.StatusCode}"); + testOutputHelper.WriteLine(errorString); + } + await response.Content.ReadFromJsonAsync(); + response.EnsureSuccessStatusCode(); + } + + [Fact] + public async Task TestGetAllPackageIds() + { + // Arrange + var client = factory.CreateAuthenticatingClient(); + var commissioningPackage = new CommissioningPackage + { + Id = "package1", + Name = "Test Package", + Color = "Red" + }; + + var createResponse = await client.PostAsJsonAsync("/commissioning-package", commissioningPackage); + createResponse.EnsureSuccessStatusCode(); + + // Act + var response = await client.GetAsync("/commissioning-package/ids"); + + // Assert + if (!response.IsSuccessStatusCode) + { + var errorString = await response.Content.ReadAsStringAsync(); + testOutputHelper.WriteLine($"Error: {response.StatusCode}"); + testOutputHelper.WriteLine(errorString); + } + var stringResult = await response.Content.ReadAsStringAsync(); + JsonSerializer.Deserialize>(stringResult); + response.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/client/Boundaries/TestBoundaries/TestBoundaries.cs b/client/Boundaries/TestBoundaries/TestBoundaries.cs new file mode 100644 index 00000000..af08bd6e --- /dev/null +++ b/client/Boundaries/TestBoundaries/TestBoundaries.cs @@ -0,0 +1,84 @@ +using System.Net.Http.Json; +using Backend.Model; +using Xunit.Abstractions; +using FluentAssertions; + +namespace TestBoundaries; + +public class BoundaryIntegrationTests(TestFactory factory, ITestOutputHelper testOutputHelper) : IClassFixture +{ + [Fact] + public async Task TestAddBoundary() + { + // Arrange + var client = factory.CreateAuthenticatingClient(); + const string packageId = "packageX"; + const string nodeId = "node1"; + + var commissioningPackage = new CommissioningPackage + { + Id = packageId, + Name = "Test Package", + Color = "Red" + }; + var responsePackage = await client.PostAsJsonAsync("/commissioning-package", commissioningPackage); + responsePackage.EnsureSuccessStatusCode(); + + // Act + var response = await client.PostAsync($"/commissioning-package/{packageId}/update-boundary/{nodeId}", null); + + // Assert + if (!response.IsSuccessStatusCode) + { + var errorString = await response.Content.ReadAsStringAsync(); + testOutputHelper.WriteLine($"Error: {response.StatusCode}"); + testOutputHelper.WriteLine(errorString); + } + response.EnsureSuccessStatusCode(); + } + + [Fact] + public async Task TestAddBoundaryWithWrongPackage() + { + // Arrange + var client = factory.CreateAuthenticatingClient(); + var packageId = $"package{Guid.NewGuid()}"; + const string nodeId = "node1"; + + // Act + var response = await client.PostAsync($"/commissioning-package/{packageId}/update-boundary/{nodeId}", null); + + // Assert + response.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound); + } + + [Fact] + public async Task TestRemoveBoundary() + { + // Arrange + var client = factory.CreateAuthenticatingClient(); + var packageId = $"package{Guid.NewGuid()}"; + const string nodeId = "node1"; + + var commissioningPackage = new CommissioningPackage + { + Id = packageId, + Name = "Test Package", + Color = "Red" + }; + var responsePackage = await client.PostAsJsonAsync("/commissioning-package", commissioningPackage); + responsePackage.EnsureSuccessStatusCode(); + var addResponse = await client.PostAsync($"/commissioning-package/{packageId}/update-boundary/{nodeId}", null); + addResponse.EnsureSuccessStatusCode(); + + // Act + var nodeUri = $"/commissioning-package/{packageId}/boundary/{nodeId}"; + var message = new HttpRequestMessage() + { + Method = HttpMethod.Delete, + RequestUri = new Uri(client.BaseAddress ?? throw new Exception("Lacking client base address"), nodeUri) + }; + var deleteResponse = await client.SendAsync(message); + deleteResponse.IsSuccessStatusCode.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/client/Boundaries/TestBoundaries/TestBoundaries.csproj b/client/Boundaries/TestBoundaries/TestBoundaries.csproj index b205ab91..3476896d 100644 --- a/client/Boundaries/TestBoundaries/TestBoundaries.csproj +++ b/client/Boundaries/TestBoundaries/TestBoundaries.csproj @@ -1,28 +1,32 @@ - net8.0 + net9.0 enable enable - + true false true - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/client/Boundaries/TestBoundaries/TestFactory.cs b/client/Boundaries/TestBoundaries/TestFactory.cs new file mode 100644 index 00000000..e68e00f6 --- /dev/null +++ b/client/Boundaries/TestBoundaries/TestFactory.cs @@ -0,0 +1,82 @@ +using System.Net.Http.Headers; +using System.Reflection; +using Boundaries; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; +using DotNet.Testcontainers.Containers; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; + +namespace TestBoundaries; + +public class TestFactory : WebApplicationFactory, IAsyncLifetime +{ + private readonly IContainer _rdfoxContainer; + private string _hostname; + private int _port; + + private readonly string _outputFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException(); + private readonly string _tempRdfoxDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + private const string RdfoxUsername = "dexpi"; + private readonly string _rdfoxPassword = Guid.NewGuid().ToString("D"); + + public TestFactory() + { + Directory.CreateDirectory(_tempRdfoxDirectory); + var filePath = Path.Combine(_tempRdfoxDirectory, "RDFox.versions"); + const string content = "data-store-catalog 1\ndata-store-change-log 1\nendpoint.params 1\nrole-database 1\nserver.params 1"; + + File.WriteAllText(filePath, content); + + _rdfoxContainer = new ContainerBuilder() + .WithImage("oxfordsemantic/rdfox:7.3") + .WithBindMount($"{_outputFolderPath}/../../../../../../", "/home/rdfox/data/", AccessMode.ReadOnly) + .WithBindMount(_tempRdfoxDirectory, "/home/rdfox/.RDFox", AccessMode.ReadWrite) + .WithCommand("-license.file", "/home/rdfox/data/RDFox.lic", "-persistence", "off", "-request-logger", "elf", + "-sandbox-directory", "/home/rdfox/data", "daemon", "/home/rdfox/data/rdfox", "dexpi") + .WithPortBinding(12110, true) + .WithEnvironment("RDFOX_ROLE", RdfoxUsername) + .WithEnvironment("RDFOX_PASSWORD", _rdfoxPassword) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(12110)) + .Build(); + } + + public async Task InitializeAsync() + { + await _rdfoxContainer.StartAsync(); + _hostname = _rdfoxContainer.Hostname; + _port = _rdfoxContainer.GetMappedPublicPort(12110); + } + + public HttpClient CreateAuthenticatingClient(params string[] roles) + { + var client = CreateClient(); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue(scheme: "TestAuthenticationScheme", "Bearer null"); + return client; + } + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(services => + { + // Remove existing IRdfoxApi registration + services.Remove(ServiceDescriptor.Singleton(typeof(RdfoxApi))); + // Register a test-specific RdfoxApi instance + services.AddSingleton(new RdfoxApi(new ConnectionSettings + { + Host = _hostname, + Port = _port, + Username = RdfoxUsername, + Password = _rdfoxPassword, + Datastore = "boundaries" + })); + }); + } + + public new async Task DisposeAsync() + { + await _rdfoxContainer.DisposeAsync(); + } +} \ No newline at end of file diff --git a/client/Boundaries/TestBoundaries/TestOutputLoggerProvider.cs b/client/Boundaries/TestBoundaries/TestOutputLoggerProvider.cs new file mode 100644 index 00000000..5cd273b7 --- /dev/null +++ b/client/Boundaries/TestBoundaries/TestOutputLoggerProvider.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace TestBoundaries; + +public class TestOutputLoggerProvider : ILoggerProvider +{ + private readonly ITestOutputHelper _testOutputHelper; + + public TestOutputLoggerProvider(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + public ILogger CreateLogger(string categoryName) + { + return new TestOutputLogger(_testOutputHelper, categoryName); + } + + public void Dispose() + { + } + + private class TestOutputLogger : ILogger + { + private readonly ITestOutputHelper _testOutputHelper; + private readonly string _categoryName; + + public TestOutputLogger(ITestOutputHelper testOutputHelper, string categoryName) + { + _testOutputHelper = testOutputHelper; + _categoryName = categoryName; + } + + public IDisposable BeginScope(TState state) => null; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + _testOutputHelper.WriteLine($"{logLevel.ToString()}: {_categoryName} - {formatter(state, exception)}"); + } + } +} \ No newline at end of file diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index bf882bdd..465a35b4 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /app COPY client/Boundaries/Backend/. ./Backend/ @@ -6,7 +6,7 @@ COPY client/Boundaries/Boundaries/. ./Boundaries/ RUN dotnet publish ./Backend -c Release -o out -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime WORKDIR /app COPY --from=build /app/out ./