diff --git a/.github/workflows/test-api.yml b/.github/workflows/test-api.yml index 64580c491..b5406625a 100644 --- a/.github/workflows/test-api.yml +++ b/.github/workflows/test-api.yml @@ -38,7 +38,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 7.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/pipelines/function-pr-pipeline.yml b/pipelines/function-pr-pipeline.yml index 7013719a9..d4b216343 100644 --- a/pipelines/function-pr-pipeline.yml +++ b/pipelines/function-pr-pipeline.yml @@ -17,6 +17,7 @@ pr: variables: subscriptionService: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' webPackage: '$(Pipeline.Workspace)/drop/Fusion.Resources.Functions.zip' + prNumber: $(System.PullRequest.PullRequestNumber) stages: - template: templates/build-function.yml @@ -43,10 +44,11 @@ stages: runOnce: deploy: steps: - - template: templates/deploy-function-template.yml + - template: templates/deploy-function-pr-template.yml parameters: envName: $(environment) clientId: '5a842df8-3238-415d-b168-9f16a6a6031b' + pullRequestNumber: $(prNumber) - task: AzureFunctionApp@1 inputs: diff --git a/pipelines/templates/deploy-function-pr-template.yml b/pipelines/templates/deploy-function-pr-template.yml new file mode 100644 index 000000000..8fd90bf87 --- /dev/null +++ b/pipelines/templates/deploy-function-pr-template.yml @@ -0,0 +1,94 @@ +# +# Template for deploying pull request function app. +# +# Done this instead of making one template to include all ifs-and-buts. +# + + +parameters: + envName: '' + clientId: '' + pullRequestNumber: '' + fusionResource: '5a842df8-3238-415d-b168-9f16a6a6031b' + templateFile: $(Build.SourcesDirectory)/src/backend/function/Fusion.Resources.Functions/Deployment/function.template.json + disabledFunctionsFile: $(Build.SourcesDirectory)/src/backend/function/Fusion.Resources.Functions/Deployment/disabled-functions.json + +steps: +- checkout: self +- task: AzurePowerShell@4 + displayName: 'Deploy Function ARM template' + inputs: + azureSubscription: 'PROJECT_PORTAL (63b791ae-b2bc-41a1-ac66-806c4e69bffe)' + ScriptType: 'InlineScript' + FailOnStandardError: true + azurePowerShellVersion: 'LatestVersion' + Inline: | + # + # Hardcoded to PR env + # + $environment = "pr" + $pullRequestNumber = "${{ parameters.pullRequestNumber }}" + $fusionEnvironment = "ci" + $functionAppName = "func-fap-resources-$environment" + $envVaultName = "kv-fap-resources-$environment" + $envResourceGroup = Get-AzResourceGroup -Tag @{ "fusion-app-component" = "resources-rg-$environment" } + + if ($envResourceGroup -eq $null) { throw "Cannot locate resource group for environment '$environment'" } + + $resourceGroup = $envResourceGroup.ResourceGroupName + Write-Host "Using resource group $resourceGroup" + + # + # Generate the app settings + # + # Sett correct resources URI based on environment + $resourcesFunctionUri = "https://resources-api-pr-$pullRequestNumber.fusion-dev.net/" + + $settings = @{ + clientId = "${{ parameters.clientId }}" + secretIds = @{ + clientSecret = "https://$envVaultName.vault.azure.net:443/secrets/AzureAd--ClientSecret" + serviceBus = "https://$envVaultName.vault.azure.net:443/secrets/Connectionstrings--ServiceBus" + } + endpoints = @{ + lineorg = "https://fusion-s-lineorg-$fusionEnvironment.azurewebsites.net" + org = "https://fusion-s-org-$fusionEnvironment.azurewebsites.net" + people = "https://fusion-s-people-$fusionEnvironment.azurewebsites.net" + resources = "$resourcesFunctionUri" + notifications = "https://fusion-s-notification-$fusionEnvironment.azurewebsites.net" + context = "https://fusion-s-context-$fusionEnvironment.azurewebsites.net" + portal = "https://fusion-s-portal-$fusionEnvironment.azurewebsites.net" + } + resources = @{ + fusion = "${{ parameters.fusionResource }}" + } + queues = @{ + provisionPosition = "provision-position" + } + } + + New-AzResourceGroupDeployment -Mode Incremental -Name "resources-function" -ResourceGroupName $resourceGroup -TemplateFile "${{ parameters.templateFile }}" ` + -env-name $environment ` + -settings $settings + + $functionApp = Get-AzWebApp -ResourceGroupName $resourceGroup -Name $functionAppName + Set-AzKeyVaultAccessPolicy -VaultName $envVaultName -ResourceGroupName $resourceGroup -ObjectId $functionApp.Identity.PrincipalId -PermissionsToSecrets get + + ## Load disabled functions + $disabledFunctionConfig = ConvertFrom-Json (Get-Content "${{ parameters.disabledFunctionsFile }}" -Raw) + $disabledFunctions = $disabledFunctionConfig | where -Property environment -eq $environment | Select -expandproperty disabledFunctions + + Write-Host "Disabled functions" + $disabledFunctions + + $settings = @{} + ForEach ($kvp in $functionApp.SiteConfig.AppSettings) { + $settings[$kvp.Name] = $kvp.Value + } + + ## Mark functions as disabled + $disabledFunctions | ForEach-Object { $settings["AzureWebJobs.$_.Disabled"] = "true" } + + ## Update web app settings for function app + Set-AzWebApp -ResourceGroupName $resourceGroup -Name $functionAppName -AppSettings $settings + diff --git a/src/backend/.config/dotnet-tools.json b/src/backend/.config/dotnet-tools.json index 4ab94945c..2f789b031 100644 --- a/src/backend/.config/dotnet-tools.json +++ b/src/backend/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "5.0.2", + "version": "7.0.12", "commands": [ "dotnet-ef" ] diff --git a/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj b/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj index 9e11373a9..ce40b4324 100644 --- a/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj +++ b/src/backend/Fusion.Resources.Authorization/Fusion.Resources.Authorization.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 enable @@ -13,7 +13,7 @@ - + diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs index 685bf7d59..ee8416c22 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/AdminController.cs @@ -1,4 +1,8 @@ -using Fusion.Resources.Domain; +using Fusion.AspNetCore.FluentAuthorization; +using Fusion.Authorization; +using Fusion.Integration.Profile.Internal; +using Fusion.Resources.Domain; +using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; @@ -12,22 +16,69 @@ public class AdminController : ResourceControllerBase { private readonly IOrgUnitCache orgUnitCache; + private readonly IMediator mediator; - public AdminController(IOrgUnitCache orgUnitCache) + public AdminController(IOrgUnitCache orgUnitCache, IMediator mediator) { this.orgUnitCache = orgUnitCache; + this.mediator = mediator; } [HttpGet("admin/cache/org-units")] public async Task CleareCache() { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().FullControl().FullControlInternal(); + r.AnyOf(or => + { + or.FullControl(); + or.FullControlInternal(); + or.BeTrustedApplication(); + }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + await orgUnitCache.ClearOrgUnitCacheAsync(); return Ok(); } + + [HttpPost("admin/cache/reset-internal-cache")] + public async Task ClearInternalCache() + { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().FullControl().FullControlInternal(); + r.AnyOf(or => + { + or.FullControl(); + or.FullControlInternal(); + or.BeTrustedApplication(); + }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + + await mediator.Publish(new DistributedEvents.ResetCacheNotification()); + + return new OkObjectResult(new { message = "Cache reset has been queued for all instances."}); + } + } } \ No newline at end of file diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs index 22a2dd3a9..80059a5b2 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Departments/DepartmentsController.cs @@ -166,7 +166,7 @@ public async Task> AddDelegatedResourceOw UpdatedByAzureUniqueId = User.GetAzureUniqueId() ?? User.GetApplicationId() }.WithReason(request.Reason); - await DispatchAsync(command); + await DispatchCommandAsync(command); } catch (RoleDelegationExistsError ex) diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs index fc1159ca5..ce529a4f2 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonAbsenceController.cs @@ -352,7 +352,7 @@ public async Task DeletePersonAbsence([FromRoute] string personId, #endregion - await DispatchAsync(new DeletePersonAbsence(id, absenceId)); + await DispatchCommandAsync(new DeletePersonAbsence(id, absenceId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs index c14580da6..cee165dc5 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Person/PersonController.cs @@ -249,7 +249,7 @@ public async Task DeletePersonalNote(string personId, Guid noteId) if (!notes.Any(n => n.Id == noteId)) return ApiErrors.NotFound("Could not locate note for user"); - await DispatchAsync(new Domain.Commands.DeletePersonNote(noteId, user.azureId)); + await DispatchCommandAsync(new Domain.Commands.DeletePersonNote(noteId, user.azureId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs index 9a2444686..b467393ee 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Personnel/InternalPersonnelController.cs @@ -270,7 +270,7 @@ public async Task ResetAllocationState(string fullDepartmentString return ApiErrors.NotFound("Could not locate allocation on person"); - await DispatchAsync(new Domain.Commands.ResetAllocationState(allocation.Project.OrgProjectId, allocation.PositionId, instanceId)); + await DispatchCommandAsync(new Domain.Commands.ResetAllocationState(allocation.Project.OrgProjectId, allocation.PositionId, instanceId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs index 8d708a038..74094d3af 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/Requests/InternalRequestsController.cs @@ -786,7 +786,7 @@ public async Task> StartProjectReques await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var transaction = await BeginTransactionAsync(); - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); await transaction.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -833,7 +833,7 @@ public async Task> StartResourceOwner { await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var transaction = await BeginTransactionAsync(); - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Initialize(requestId)); await transaction.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -881,7 +881,7 @@ public async Task DeleteAllocationRequest(Guid requestId) await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var transaction = await BeginTransactionAsync(); - await DispatchAsync(new DeleteInternalRequest(requestId)); + await DispatchCommandAsync(new DeleteInternalRequest(requestId)); await transaction.CommitAsync(); await eventTransaction.CommitAsync(); @@ -921,7 +921,7 @@ public async Task> ProvisionProjectAl await using var eventTransaction = await notificationClient.BeginTransactionAsync(); await using var scope = await BeginTransactionAsync(); - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Provision(requestId) + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Provision(requestId) { ForceProvision = force }); @@ -956,7 +956,7 @@ public async Task> ApproveProjectAllo try { - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); await scope.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -995,7 +995,7 @@ public async Task> ApproveProjectAllo try { - await DispatchAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); + await DispatchCommandAsync(new Logic.Commands.ResourceAllocationRequest.Approve(requestId)); await scope.CommitAsync(); await eventTransaction.CommitAsync(); } @@ -1281,7 +1281,7 @@ public async Task> UpdateRequestComment(Guid req #endregion Authorization - await DispatchAsync(new UpdateComment(commentId, update.Content)); + await DispatchCommandAsync(new UpdateComment(commentId, update.Content)); comment = await DispatchAsync(new GetRequestComment(commentId)); return new ApiRequestComment(comment!); @@ -1321,7 +1321,7 @@ public async Task DeleteRequestComment(Guid requestId, Guid commen #endregion Authorization - await DispatchAsync(new DeleteComment(commentId)); + await DispatchCommandAsync(new DeleteComment(commentId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs b/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs index 66dd55703..8aa1aac38 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/ResourceControllerBase.cs @@ -57,7 +57,7 @@ protected Task BeginTransactionAsync() return scope.BeginTransactionAsync(); } - protected Task DispatchAsync(IRequest command) + protected Task DispatchCommandAsync(IRequest command) { var mediator = HttpContext.RequestServices.GetRequiredService(); return mediator.Send(command); diff --git a/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs b/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs index cbbae1715..1f64c788c 100644 --- a/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs +++ b/src/backend/api/Fusion.Resources.Api/Controllers/ResponsibilityMatrix/ResponsibilityMatrixController.cs @@ -226,7 +226,7 @@ public async Task DeleteResponsibilityMatrix(Guid matrixId) if (responsibilityMatrix == null) return FusionApiError.NotFound(matrixId, "Could not locate responsibility matrix"); - await DispatchAsync(new DeleteResponsibilityMatrix(matrixId)); + await DispatchCommandAsync(new DeleteResponsibilityMatrix(matrixId)); return NoContent(); } diff --git a/src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs b/src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs new file mode 100644 index 000000000..d0451af4b --- /dev/null +++ b/src/backend/api/Fusion.Resources.Api/DistributedEvents/ResetCacheNotification.cs @@ -0,0 +1,35 @@ +using Fusion.Infrastructure.MediatR.Distributed; +using Fusion.Integration.Profile.Internal; +using Fusion.Resources.Domain; +using MediatR; +using System.Threading; +using System.Threading.Tasks; + +namespace Fusion.Resources.Api.DistributedEvents +{ + /// + /// Trigger a reset of all internal caches. + /// + public class ResetCacheNotification : DistributedNotification + { + + public class Handler : INotificationHandler + { + private readonly IOrgUnitCache orgUnitCache; + private readonly IProfileCache fusionProfileResolverCache; + + public Handler(IOrgUnitCache orgUnitCache, IProfileCache fusionProfileResolverCache) + { + this.orgUnitCache = orgUnitCache; + this.fusionProfileResolverCache = fusionProfileResolverCache; + } + + public async Task Handle(ResetCacheNotification notification, CancellationToken cancellationToken) + { + await fusionProfileResolverCache.ClearAsync(); + await orgUnitCache.ClearOrgUnitCacheAsync(); + + } + } + } +} diff --git a/src/backend/api/Fusion.Resources.Api/Dockerfile b/src/backend/api/Fusion.Resources.Api/Dockerfile index e04b7a904..547974c48 100644 --- a/src/backend/api/Fusion.Resources.Api/Dockerfile +++ b/src/backend/api/Fusion.Resources.Api/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:6.0-bullseye-slim AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim AS build WORKDIR /src COPY ["api/Fusion.Resources.Api/Fusion.Resources.Api.csproj", "api/Fusion.Resources.Api/"] COPY ["api/Fusion.Resources.Database/Fusion.Resources.Database.csproj", "api/Fusion.Resources.Database/"] diff --git a/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj b/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj index c4bd24281..70606aff0 100644 --- a/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj +++ b/src/backend/api/Fusion.Resources.Api/Fusion.Resources.Api.csproj @@ -1,6 +1,6 @@  - net6.0 + net7.0 474454c7-2021-4f46-bfd4-02b221fc3fa0 Linux ..\.. @@ -15,28 +15,29 @@ + - - - - - - - - - + + + + + + + + + + - - - - + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs index fa88caee5..6a210ea73 100644 --- a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs +++ b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyRequestCreatorHandler.cs @@ -12,7 +12,7 @@ namespace Fusion.Resources.Api.Notifications { public partial class InternalRequestNotification { - public class NotifyRequestCreatorHandler : AsyncRequestHandler + public class NotifyRequestCreatorHandler : IRequestHandler { private readonly IFusionNotificationClient notificationClient; private readonly IMediator mediator; @@ -22,7 +22,7 @@ public NotifyRequestCreatorHandler(IFusionNotificationClient notificationClient, this.notificationClient = notificationClient; this.mediator = mediator; } - protected override async Task Handle(NotifyRequestCreator request, CancellationToken cancellationToken) + public async Task Handle(NotifyRequestCreator request, CancellationToken cancellationToken) { var allocationRequest = await GetInternalRequestAsync(request.RequestId); diff --git a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs index 2b73c062d..26380e7f3 100644 --- a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs +++ b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyResourceOwnerHandler.cs @@ -12,7 +12,7 @@ namespace Fusion.Resources.Api.Notifications { public partial class InternalRequestNotification { - public class NotifyResourceOwnerHandler : AsyncRequestHandler + public class NotifyResourceOwnerHandler : IRequestHandler { private readonly IFusionNotificationClient notificationClient; private readonly IMediator mediator; @@ -22,7 +22,7 @@ public NotifyResourceOwnerHandler(IFusionNotificationClient notificationClient, this.notificationClient = notificationClient; this.mediator = mediator; } - protected override async Task Handle(NotifyResourceOwner request, CancellationToken cancellationToken) + public async Task Handle(NotifyResourceOwner request, CancellationToken cancellationToken) { var recipients = await GenerateRecipientsAsync(request.Editor.Person.AzureUniqueId, request.AssignedDepartment); diff --git a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs index bbcb184a5..e5715a5c5 100644 --- a/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs +++ b/src/backend/api/Fusion.Resources.Api/Notifications/InternalRequestNotification/NotifyTaskOwnerHandler.cs @@ -12,7 +12,7 @@ namespace Fusion.Resources.Api.Notifications { public partial class InternalRequestNotification { - public class NotifyTaskOwnerHandler : AsyncRequestHandler + public class NotifyTaskOwnerHandler : IRequestHandler { private readonly IFusionNotificationClient notificationClient; private readonly IMediator mediator; @@ -22,7 +22,7 @@ public NotifyTaskOwnerHandler(IFusionNotificationClient notificationClient, IMed this.notificationClient = notificationClient; this.mediator = mediator; } - protected override async Task Handle(NotifyTaskOwner request, CancellationToken cancellationToken) + public async Task Handle(NotifyTaskOwner request, CancellationToken cancellationToken) { var allocationRequest = await GetInternalRequestAsync(request.RequestId); diff --git a/src/backend/api/Fusion.Resources.Api/Program.cs b/src/backend/api/Fusion.Resources.Api/Program.cs index f1a883b86..7dfa581cd 100644 --- a/src/backend/api/Fusion.Resources.Api/Program.cs +++ b/src/backend/api/Fusion.Resources.Api/Program.cs @@ -1,4 +1,5 @@ using System; +using Azure.Identity; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -38,6 +39,7 @@ private static void AddKeyVault(HostBuilderContext hostBuilderContext, IConfigur { var tempConfig = configBuilder.Build(); var clientId = tempConfig["AzureAd:ClientId"]; + var tenantId = tempConfig["AzureAd:TenantId"]; var clientSecret = tempConfig["AzureAd:ClientSecret"]; var keyVaultUrl = tempConfig["KEYVAULT_URL"]; @@ -45,7 +47,8 @@ private static void AddKeyVault(HostBuilderContext hostBuilderContext, IConfigur { Console.WriteLine($"Adding key vault using url: '{keyVaultUrl}', client id '{clientId}' and client secret {(string.IsNullOrEmpty(clientSecret) ? "[empty]" : "*****")}"); - configBuilder.AddAzureKeyVault(keyVaultUrl, clientId, clientSecret); + var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + configBuilder.AddAzureKeyVault(new Uri(keyVaultUrl), credential); } else { diff --git a/src/backend/api/Fusion.Resources.Api/Startup.cs b/src/backend/api/Fusion.Resources.Api/Startup.cs index fde80f540..7d2ce113a 100644 --- a/src/backend/api/Fusion.Resources.Api/Startup.cs +++ b/src/backend/api/Fusion.Resources.Api/Startup.cs @@ -8,6 +8,8 @@ using Fusion.Resources.Api.HostedServices; using Fusion.Resources.Api.Middleware; using Fusion.Resources.Domain; +using Fusion.Resources.Domain.Commands; +using Fusion.Resources.Logic; using JSM.FluentValidation.AspNet.AsyncFilter; using MediatR; using Microsoft.ApplicationInsights.DependencyCollector; @@ -19,6 +21,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; +using SixLabors.ImageSharp; +using System.Reflection; namespace Fusion.Resources.Api { @@ -127,11 +131,17 @@ public void ConfigureServices(IServiceCollection services) services.AddResourceDatabase(Configuration); services.AddResourceDomain(); - services.AddResourceLogic(); services.AddResourcesApplicationServices(); services.AddResourcesAuthorizationHandlers(); - services.AddMediatR(typeof(Startup)); // Add notification handlers in api project + + // Add mediatn from api, domain and logic assembly. + services.AddMediatR(c => c + .RegisterServicesFromAssemblyContaining() + .RegisterServicesFromAssemblyContaining() + .RegisterServicesFromAssemblyContaining()); + + services.AddMediatRDistributedNotification(setup => setup.ConnectionString = Configuration.GetConnectionString("ServiceBus")); services.AddHostedService(); #endregion Resource services diff --git a/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj b/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj index 01a65095a..1413ad79d 100644 --- a/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj +++ b/src/backend/api/Fusion.Resources.Application/Fusion.Resources.Application.csproj @@ -1,16 +1,16 @@  - net6.0 + net7.0 enable - - - - - + + + + + diff --git a/src/backend/api/Fusion.Resources.Application/ServiceBus/ServiceBusService.cs b/src/backend/api/Fusion.Resources.Application/ServiceBus/ServiceBusService.cs index 1d21db1f2..aee22f603 100644 --- a/src/backend/api/Fusion.Resources.Application/ServiceBus/ServiceBusService.cs +++ b/src/backend/api/Fusion.Resources.Application/ServiceBus/ServiceBusService.cs @@ -1,22 +1,32 @@ using System; -using Microsoft.Azure.ServiceBus.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using System.Collections.Generic; namespace Fusion.Resources.ServiceBus { internal class ServiceBusQueueSender : IQueueSender { - private readonly string connectionString; private readonly IConfiguration configuration; private readonly ILogger logger; + private readonly ServiceBusClient? client; + + // Caching the sender is recommended when the application is publishing messages regularly or semi-regularly. The sender is responsible for ensuring efficient network, CPU, and memory use + private Dictionary cachedSenders = new Dictionary(); public ServiceBusQueueSender(IConfiguration configuration, ILogger logger) { - connectionString = configuration.GetConnectionString("ServiceBus"); + var connectionString = configuration.GetConnectionString("ServiceBus"); + + // Leaving this as nullable so integration tests etc don't fail untill the functionality is required. + if (!string.IsNullOrEmpty(connectionString)) + { + this.client = new ServiceBusClient(connectionString); + } + this.configuration = configuration; this.logger = logger; } @@ -30,17 +40,17 @@ public async Task SendMessageDelayedAsync(QueuePath queue, object message, int d { if (!IsDisabled) { - var sender = GetClient(queue); var jsonMessage = JsonSerializer.Serialize(message); - var queueMessage = new Microsoft.Azure.ServiceBus.Message(Encoding.UTF8.GetBytes(jsonMessage)) { ContentType = "application/json" }; + var entityPath = ResolveQueuePath(queue); + var queueSender = GetQueueSender(entityPath); + + var sbMessage = new ServiceBusMessage(jsonMessage) { ContentType = "application/json" }; if (delayInSeconds > 0) - { - queueMessage.ScheduledEnqueueTimeUtc = DateTime.UtcNow.AddSeconds(delayInSeconds); - } + sbMessage.ScheduledEnqueueTime = DateTime.UtcNow.AddSeconds(delayInSeconds); - logger.LogInformation($"Posting message to {sender.Path}: {jsonMessage}"); - await sender.SendAsync(queueMessage); + logger.LogInformation($"Posting message to {entityPath}: {jsonMessage}"); + await queueSender.SendMessageAsync(sbMessage); } else { @@ -48,18 +58,41 @@ public async Task SendMessageDelayedAsync(QueuePath queue, object message, int d } } - private MessageSender GetClient(QueuePath queue) + private ServiceBusSender GetQueueSender(string queue) + { + if (client is null) + throw new InvalidOperationException("Service bus has not been configured. Missing connection string."); + + if (cachedSenders.ContainsKey(queue)) + { + return cachedSenders[queue]; + } + + cachedSenders[queue] = client.CreateSender(queue); + return cachedSenders[queue]; + } + + /// + /// Queue path should be configured in config. The config key should be the enum value. + /// + /// + /// + /// + private string ResolveQueuePath(QueuePath queue) { - var entityPath = configuration.GetValue($"ServiceBus:Queues:{queue}", DefaultQueuePath(queue)); + var entityPath = configuration.GetValue($"ServiceBus:Queues:{queue}"); + var entityPathOverride = configuration.GetValue($"SERVICEBUS_QUEUES_{queue}"); if (!string.IsNullOrEmpty(entityPathOverride)) entityPath = entityPathOverride; + if (string.IsNullOrEmpty(entityPath)) + entityPath = DefaultQueuePath(queue); + logger.LogInformation($"Using service bus queue: {entityPath}"); - var sender = new MessageSender(connectionString, entityPath); - return sender; + return entityPath; } private bool IsDisabled => configuration.GetValue("ServiceBus:Disabled", false); diff --git a/src/backend/api/Fusion.Resources.Database/Fusion.Resources.Database.csproj b/src/backend/api/Fusion.Resources.Database/Fusion.Resources.Database.csproj index 3853f6e15..4e37d056d 100644 --- a/src/backend/api/Fusion.Resources.Database/Fusion.Resources.Database.csproj +++ b/src/backend/api/Fusion.Resources.Database/Fusion.Resources.Database.csproj @@ -1,20 +1,20 @@  - net6.0 + net7.0 enable - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs b/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs index f8f7f9502..59d6b6863 100644 --- a/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs +++ b/src/backend/api/Fusion.Resources.Domain/Behaviours/AuditableCommandBehaviour.cs @@ -8,9 +8,9 @@ using System.Threading.Tasks; namespace Fusion.Resources.Domain.Behaviours -{ +{ - public class TrackableRequestBehaviour : IPipelineBehavior where TRequest : IRequest + public class TrackableRequestBehaviour : IPipelineBehavior where TRequest : notnull { private readonly IHttpContextAccessor httpContext; private readonly IProfileService profileServices; diff --git a/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs b/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs index fe26f2c50..8f5f47f9c 100644 --- a/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs +++ b/src/backend/api/Fusion.Resources.Domain/Behaviours/RequestValidationBehavior.cs @@ -8,7 +8,7 @@ namespace Fusion.Resources.Domain.Behaviours { - public class RequestValidationBehavior : IPipelineBehavior where TRequest : IRequest + public class RequestValidationBehavior : IPipelineBehavior where TRequest : notnull { private readonly IEnumerable> validators; diff --git a/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs b/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs index 1dc763cea..db30bb0e6 100644 --- a/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs +++ b/src/backend/api/Fusion.Resources.Domain/Behaviours/TelemetryBehaviour.cs @@ -9,7 +9,7 @@ namespace Fusion.Resources.Domain.Behaviours { - public class TelemetryBehaviour : IPipelineBehavior where TRequest : IRequest + public class TelemetryBehaviour : IPipelineBehavior where TRequest : notnull { private readonly TelemetryClient telemetryClient; diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs index 505cb89e1..beb69fbf0 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Departments/AddDelegatedResourceOwner.cs @@ -1,5 +1,4 @@ -using Fusion.Integration.Profile; -using Fusion.Integration.Roles; +using Fusion.Integration.Roles; using Fusion.Resources.Database; using Fusion.Resources.Database.Entities; using MediatR; @@ -7,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; namespace Fusion.Resources.Domain.Commands.Departments { @@ -44,7 +42,7 @@ public Handler(ResourcesDbContext db, IFusionRolesClient rolesClient) this.rolesClient = rolesClient; } - public async Task Handle(AddDelegatedResourceOwner request, CancellationToken cancellationToken) + public async Task Handle(AddDelegatedResourceOwner request, CancellationToken cancellationToken) { var alreadyDelegated = db.DelegatedDepartmentResponsibles.Any(x => x.ResponsibleAzureObjectId == request.ResponsibleAzureUniqueId && @@ -75,8 +73,6 @@ public async Task Handle(AddDelegatedResourceOwner request, CancellationTo db.DelegatedDepartmentResponsibles.Add(responsible); await db.SaveChangesAsync(); - - return Unit.Value; } } diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs b/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs index 93d0f3951..2664bae03 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/EmploymentStatus/DeletePersonAbsence.cs @@ -19,7 +19,7 @@ public DeletePersonAbsence(PersonId personId, Guid id) private PersonId PersonId { get; set; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext resourcesDb; @@ -28,7 +28,7 @@ public Handler(ResourcesDbContext resourcesDb) this.resourcesDb = resourcesDb; } - protected override async Task Handle(DeletePersonAbsence request, CancellationToken cancellationToken) + public async Task Handle(DeletePersonAbsence request, CancellationToken cancellationToken) { var dbEntity = await resourcesDb.PersonAbsences .GetById(request.PersonId, request.Id) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs index fe6b6c27b..30fdd47e7 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/DeletePersonNote.cs @@ -20,7 +20,7 @@ public DeletePersonNote(Guid noteId, Guid personAzureUniqueId) public Guid NoteId { get; } public Guid PersonAzureUniqueId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext dbContext; @@ -29,7 +29,7 @@ public Handler(ResourcesDbContext dbContext) this.dbContext = dbContext; } - protected override async Task Handle(DeletePersonNote request, CancellationToken cancellationToken) + public async Task Handle(DeletePersonNote request, CancellationToken cancellationToken) { var note = await dbContext.PersonNotes.FirstOrDefaultAsync(n => n.Id == request.NoteId && n.AzureUniqueId == request.PersonAzureUniqueId); if (note is null) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs index 185797151..8c1607283 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Personnel/ResetAllocationState.cs @@ -24,7 +24,7 @@ public ResetAllocationState(Guid orgProjectId, Guid orgPositionId, Guid orgInsta public Guid OrgPositionId { get; } public Guid OrgInstanceId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ILogger logger; private readonly ResourcesDbContext dbContext; @@ -37,7 +37,7 @@ public Handler(ILogger logger, ResourcesDbContext dbContext, IOrgApiCli this.orgApiClientFactory = orgApiClientFactory; } - protected override async Task Handle(ResetAllocationState request, CancellationToken cancellationToken) + public async Task Handle(ResetAllocationState request, CancellationToken cancellationToken) { var client = orgApiClientFactory.CreateClient(ApiClientMode.Application); diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs index 616cc0db3..dbdf83d6c 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/DeleteComment.cs @@ -16,7 +16,7 @@ public DeleteComment(Guid commentId) public Guid CommentId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext db; @@ -25,7 +25,7 @@ public Handler(ResourcesDbContext db) this.db = db; } - protected override async Task Handle(DeleteComment command, CancellationToken cancellationToken) + public async Task Handle(DeleteComment command, CancellationToken cancellationToken) { var comment = await db.RequestComments.FirstOrDefaultAsync(c => c.Id == command.CommentId); diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs index 71dad5e8d..00886b407 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/DeleteInternalRequest.cs @@ -19,7 +19,7 @@ public DeleteInternalRequest(Guid requestId) private Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext dbContext; private readonly IMediator mediator; @@ -30,7 +30,7 @@ public Handler(ResourcesDbContext dbContext, IMediator mediator) this.mediator = mediator; } - protected override async Task Handle(DeleteInternalRequest request, CancellationToken ct) + public async Task Handle(DeleteInternalRequest request, CancellationToken ct) { var req = await dbContext.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs index c5fae299c..ba7d57ba0 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/InternalRequests/ResetWorkflow.cs @@ -36,7 +36,6 @@ public async Task Handle(ResetWorkflow request, CancellationToken cancella requestItem.State = new DbOpState(); requestItem.IsDraft = true; requestItem.AssignedDepartment = null; - requestItem.ProposedPerson = new DbOpProposedPerson(); db.Workflows.RemoveRange( db.Workflows.Where(wf => wf.RequestId == request.requestId) diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs index af191959c..bff7efadb 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/Requests/UpdateComment.cs @@ -18,7 +18,7 @@ public UpdateComment(Guid commentId, string content) public Guid CommentId { get; } public string Content { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext db; @@ -27,7 +27,7 @@ public Handler(ResourcesDbContext db) this.db = db; } - protected override async Task Handle(UpdateComment request, CancellationToken cancellationToken) + public async Task Handle(UpdateComment request, CancellationToken cancellationToken) { var comment = await db.RequestComments.FirstOrDefaultAsync(c => c.Id == request.CommentId); diff --git a/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs b/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs index f986244ff..62dbcd86c 100644 --- a/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs +++ b/src/backend/api/Fusion.Resources.Domain/Commands/ResponsibilityMatrix/DeleteResponsibilityMatrix.cs @@ -17,7 +17,7 @@ public DeleteResponsibilityMatrix(Guid id) private Guid Id { get; set; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext resourcesDb; @@ -26,7 +26,7 @@ public Handler(ResourcesDbContext resourcesDb) this.resourcesDb = resourcesDb; } - protected override async Task Handle(DeleteResponsibilityMatrix request, CancellationToken cancellationToken) + public async Task Handle(DeleteResponsibilityMatrix request, CancellationToken cancellationToken) { var dbEntity = await resourcesDb.ResponsibilityMatrices .FirstOrDefaultAsync(x=>x.Id==request.Id); diff --git a/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs b/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs index c1bda93be..054d8b22b 100644 --- a/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs +++ b/src/backend/api/Fusion.Resources.Domain/Configuration/IServiceCollectionExtensions.cs @@ -12,8 +12,7 @@ public static class DomainConfigExtensions { public static IServiceCollection AddResourceDomain(this IServiceCollection services) - { - services.AddMediatR(typeof(DomainConfigExtensions)); + { services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TrackableRequestBehaviour<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TelemetryBehaviour<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>)); diff --git a/src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs b/src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs new file mode 100644 index 000000000..b7a6eeb29 --- /dev/null +++ b/src/backend/api/Fusion.Resources.Domain/DomainAssemblyMarkerType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Fusion.Resources.Domain +{ + /// + /// Type to easily target the assembly. + /// + public class DomainAssemblyMarkerType + { + } +} diff --git a/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj b/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj index c71b14437..fb438f836 100644 --- a/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj +++ b/src/backend/api/Fusion.Resources.Domain/Fusion.Resources.Domain.csproj @@ -1,21 +1,20 @@  - net6.0 + net7.0 enable - - - - - - - - - + + + + + + + + diff --git a/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs b/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs index 86f7590f2..bfb9b0099 100644 --- a/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs +++ b/src/backend/api/Fusion.Resources.Domain/Queries/GetPersonProfiles.cs @@ -34,9 +34,9 @@ public async Task> Handle(GetPersonProfile var tasks = new List>>(); // Max number of identifiers is 500, so we chunk the requests - foreach (var req in request.Identifiers.Chunk(500)) + foreach (var identifierBatch in request.Identifiers.Chunk(500)) { - tasks.Add(profileResolver.ResolvePersonsAsync(request.Identifiers)); + tasks.Add(profileResolver.ResolvePersonsAsync(identifierBatch)); } var results = await Task.WhenAll(tasks); diff --git a/src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs b/src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs deleted file mode 100644 index 1fd004db6..000000000 --- a/src/backend/api/Fusion.Resources.Logic/Configuration/LogicConfigExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MediatR; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class LogicConfigExtensions - { - public static IServiceCollection AddResourceLogic(this IServiceCollection services) - { - services.AddMediatR(typeof(LogicConfigExtensions)); - - return services; - } - } -} diff --git a/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj b/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj index 9c8cc338d..045d6375f 100644 --- a/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj +++ b/src/backend/api/Fusion.Resources.Logic/Fusion.Resources.Logic.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 enable @@ -11,5 +11,9 @@ + + + + diff --git a/src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs b/src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs new file mode 100644 index 000000000..5d7e1e21e --- /dev/null +++ b/src/backend/api/Fusion.Resources.Logic/LogicAssemblyMarkerType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Fusion.Resources.Logic +{ + /// + /// Type to allow easy loading of the assembly. + /// + public class LogicAssemblyMarkerType + { + } +} diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs index 3253ef68c..664d8f046 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Allocation/ProvisionAllocationRequest.cs @@ -25,7 +25,7 @@ public ProvisionAllocationRequest(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private IOrgApiClient client; private ResourcesDbContext resourcesDb; @@ -36,7 +36,7 @@ public Handler(ResourcesDbContext resourcesDb, IOrgApiClientFactory orgApiClient this.resourcesDb = resourcesDb; } - protected override async Task Handle(ProvisionAllocationRequest request, CancellationToken cancellationToken) + public async Task Handle(ProvisionAllocationRequest request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs index e913a4fc6..deb8201ff 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Approve.cs @@ -21,7 +21,7 @@ public Approve(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext dbContext; private readonly IMediator mediator; @@ -32,7 +32,7 @@ public Handler(ResourcesDbContext dbContext, IMediator mediator) this.mediator = mediator; } - protected override async Task Handle(Approve request, CancellationToken cancellationToken) + public async Task Handle(Approve request, CancellationToken cancellationToken) { var dbRequest = await dbContext.ResourceAllocationRequests.FirstOrDefaultAsync(r => r.Id == request.RequestId, cancellationToken); if (dbRequest is null) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs index 9280b4bd8..206f39a19 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Initialize.cs @@ -25,7 +25,7 @@ public Initialize(Guid requestId) - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ResourcesDbContext resourcesDb; @@ -37,7 +37,7 @@ public Handler(ResourcesDbContext resourcesDb, IMediator mediator) this.mediator = mediator; } - protected override async Task Handle(Initialize request, CancellationToken cancellationToken) + public async Task Handle(Initialize request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests.FirstOrDefaultAsync(r => r.Id == request.RequestId); if (dbRequest is null) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs index 3de76dd77..0735c8c4b 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/Provision.cs @@ -67,7 +67,7 @@ public Validator(ResourcesDbContext db, IProjectOrgResolver projectOrgResolver, } } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ILogger logger; private readonly ResourcesDbContext resourcesDb; @@ -80,7 +80,7 @@ public Handler(ILogger logger, ResourcesDbContext resourcesDb, IMediato this.mediator = mediator; } - protected override async Task Handle(Provision request, CancellationToken cancellationToken) + public async Task Handle(Provision request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs index 1c34299b7..96037b3ae 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/QueueProvisioning.cs @@ -18,7 +18,7 @@ public QueueProvisioning(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly int FixedDelayInSecondsBeforeProvisioning = 5; private readonly ResourcesDbContext dbContext; @@ -30,7 +30,7 @@ public Handler(ResourcesDbContext dbContext, IQueueSender queueSender) this.queueSender = queueSender; } - protected override async Task Handle(QueueProvisioning request, CancellationToken cancellationToken) + public async Task Handle(QueueProvisioning request, CancellationToken cancellationToken) { var dbRequest = await dbContext.ResourceAllocationRequests.FindAsync(request.RequestId); diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs index 31421ba84..0322019b8 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/ResourceOwner/ProvisionResourceOwnerRequest.cs @@ -26,7 +26,7 @@ public ProvisionResourceOwnerRequest(Guid requestId) public Guid RequestId { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly IOrgApiClient client; private readonly ResourcesDbContext resourcesDb; @@ -37,7 +37,7 @@ public Handler(ResourcesDbContext resourcesDb, IOrgApiClientFactory orgApiClient this.resourcesDb = resourcesDb; } - protected override async Task Handle(ProvisionResourceOwnerRequest request, CancellationToken cancellationToken) + public async Task Handle(ProvisionResourceOwnerRequest request, CancellationToken cancellationToken) { var dbRequest = await resourcesDb.ResourceAllocationRequests .Include(r => r.Project) diff --git a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs index 4f2ea8bb0..2d4f56166 100644 --- a/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs +++ b/src/backend/api/Fusion.Resources.Logic/Requests/Commands/ResourceAllocationRequest/UpdateOrgPositionInstanceHaveRequest.cs @@ -25,7 +25,7 @@ public UpdateOrgPositionInstanceHaveRequest(Guid orgProjectId, Guid orgPositionI public Guid OrgPositionInstanceId { get; } public bool HaveRequest { get; } - public class Handler : AsyncRequestHandler + public class Handler : IRequestHandler { private readonly ILogger logger; private readonly IOrgApiClient client; @@ -36,7 +36,7 @@ public Handler(IOrgApiClientFactory orgApiClientFactory, ILogger logger this.client = orgApiClientFactory.CreateClient(ApiClientMode.Application); } - protected override async Task Handle(UpdateOrgPositionInstanceHaveRequest request, CancellationToken cancellationToken) + public async Task Handle(UpdateOrgPositionInstanceHaveRequest request, CancellationToken cancellationToken) { // This command tries to update an existing position instance. If instance is not found, it may have been deleted in ORG service. // If unable to update instance, log error and proceed. diff --git a/src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs b/src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs new file mode 100644 index 000000000..add674152 --- /dev/null +++ b/src/backend/function/Fusion.Resources.Functions/Functions/ScheduledJobsFunctions.cs @@ -0,0 +1,41 @@ +using Fusion.Resources.Functions.ApiClients; +using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Fusion.Resources.Functions.Functions +{ + public class ScheduledJobsFunctions + { + private readonly ILogger logger; + private HttpClient resourcesApiClient; + + public ScheduledJobsFunctions(ILogger logger, IHttpClientFactory httpClientFactory) + { + resourcesApiClient = httpClientFactory.CreateClient(HttpClientNames.Application.Resources); + this.logger = logger; + } + + [Singleton] + [FunctionName("clear-api-internal-cache")] + public async Task ReassignResourceAllocationRequestsWithInvalidDepartment([TimerTrigger("0 0 4 * * 0", RunOnStartup = false)] TimerInfo timer) + { + + var resp = await resourcesApiClient.PostAsync("/admin/cache/reset-internal-cache", null); + var result = await resp.Content.ReadAsStringAsync(); + + if (!resp.IsSuccessStatusCode) + { + logger.LogError($"Error triggering cache reset. Response from service: {result}"); + } + + resp.EnsureSuccessStatusCode(); + + } + } +} diff --git a/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj b/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj index 8577257f7..e9183fc6f 100644 --- a/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj +++ b/src/backend/function/Fusion.Resources.Functions/Fusion.Resources.Functions.csproj @@ -5,7 +5,7 @@ <_FunctionsSkipCleanOutput>true - + diff --git a/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs b/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs index 41b37c841..83280465c 100644 --- a/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs +++ b/src/backend/function/Fusion.Resources.Functions/ServiceBus/QueueMessageProcessor.cs @@ -61,11 +61,12 @@ public async Task ProcessWithRetriesAsync(ServiceBusReceivedMessage message, Fun { var retryMessage = new ServiceBusMessage(message); retryCount++; - var interval = 10 * retryCount; + var interval = 30 * retryCount; var scheduledTime = DateTimeOffset.Now.AddSeconds(interval); retryMessage.ApplicationProperties["retry-count"] = retryCount; retryMessage.ApplicationProperties["original-SequenceNumber"] = originalSequence; + retryMessage.ScheduledEnqueueTime = scheduledTime; await sender.AddAsync(retryMessage); await receiver.CompleteMessageAsync(message); diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs b/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs index 4e7e1ffa9..89d5d1706 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs +++ b/src/backend/tests/Fusion.Resources.Api.Tests/Fixture/ResourcesApiWebAppFactory.cs @@ -143,6 +143,8 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) return clientFactoryMock.Object; }); + services.AddSingletonIfFound(); + services.AddSingletonIfFound(); }); } } diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/Fusion.Resources.Api.Tests.csproj b/src/backend/tests/Fusion.Resources.Api.Tests/Fusion.Resources.Api.Tests.csproj index 2b732a962..0d3d18739 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/Fusion.Resources.Api.Tests.csproj +++ b/src/backend/tests/Fusion.Resources.Api.Tests/Fusion.Resources.Api.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 false @@ -17,16 +17,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -34,7 +32,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/EventNotificationClientMock.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/EventNotificationClientMock.cs index 64dd57e4d..e67a9df81 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/EventNotificationClientMock.cs +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/EventNotificationClientMock.cs @@ -1,6 +1,6 @@ +using Azure.Messaging.ServiceBus; using Bogus; using Fusion.Events; -using Microsoft.Azure.ServiceBus; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -82,16 +82,16 @@ private Task DispatchNotification(FusionEventType type, FusionEventCategory c var messageBody = JsonConvert.SerializeObject(cEvent); - var sbMessage = new Message(Encoding.UTF8.GetBytes(messageBody)) + var sbMessage = new ServiceBusMessage(Encoding.UTF8.GetBytes(messageBody)) { MessageId = $"{type.Name}-{cEvent.Id}" }; - sbMessage.UserProperties.Add("type", type.Name); + sbMessage.ApplicationProperties.Add("type", type.Name); if (!string.IsNullOrEmpty(appContext)) { - sbMessage.UserProperties.Add("app", appContext); + sbMessage.ApplicationProperties.Add("app", appContext); } return bus.PublishMessageAsync(entityPath, sbMessage); diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs new file mode 100644 index 000000000..f91fa6275 --- /dev/null +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationChannel.cs @@ -0,0 +1,22 @@ +using Fusion.Infrastructure.MediatR.Distributed; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Fusion.Resources.Api.Tests.FusionMocks +{ + /// + /// Fusion.Infrastructure.MediatR. + /// Mock type for distributed mediatr implementation. + /// + public class FakeDistributedNotificationChannel : IDistributedNotificationChannel + { + public List Notifications = new List(); + + public Task Publish(T notification) where T : IDistributedNotification + { + Notifications.Add(notification); + + return Task.CompletedTask; + } + } +} diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs new file mode 100644 index 000000000..b51d6ddfd --- /dev/null +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/FakeDistributedNotificationReceiver.cs @@ -0,0 +1,23 @@ +using Fusion.Infrastructure.MediatR.Distributed; +using System.Threading; +using System.Threading.Tasks; + +namespace Fusion.Resources.Api.Tests.FusionMocks +{ + /// + /// Fusion.Infrastructure.MediatR. + /// Mock type for distributed mediatr implementation. + /// + public class FakeDistributedNotificationReceiver : IDistributedNotificationReceiver + { + public Task StartAsync(CancellationToken stoppingToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestMessageBus.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestMessageBus.cs index af4bfa289..bc05376c3 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestMessageBus.cs +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestMessageBus.cs @@ -1,4 +1,4 @@ -using Microsoft.Azure.ServiceBus; +using Azure.Messaging.ServiceBus; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -18,11 +18,11 @@ public class TestMessageBus private static List localMessages = new List(); private static List queueExecutionTasks = new List(); - private static Dictionary>> queueHandlers = new Dictionary>>(); + private static Dictionary>> queueHandlers = new Dictionary>>(); private static object writeLock = new object(); - public Task PublishMessageAsync(string entityPath, Message message) + public Task PublishMessageAsync(string entityPath, ServiceBusMessage message) { if (IsTemporarilyUnavailable) { @@ -53,22 +53,22 @@ public Task PublishMessageAsync(string entityPath, Message message) } - public void RegisterQueueHandler(string queuepath, Func callback) + public void RegisterQueueHandler(string queuepath, Func callback) { lock (writeLock) { - queueHandlers[queuepath] = new List>() { callback }; + queueHandlers[queuepath] = new List>() { callback }; } } - public void RegisterTopicHandler(string queuepath, Func callback) + public void RegisterTopicHandler(string queuepath, Func callback) { lock (writeLock) { if (queueHandlers.ContainsKey(queuepath)) queueHandlers[queuepath].Add(callback); else - queueHandlers[queuepath] = new List>() { callback }; + queueHandlers[queuepath] = new List>() { callback }; } } @@ -77,7 +77,7 @@ public void RegisterTopicHandler(string queuepath, Func callback) public static IReadOnlyCollection GetAllMessages() => allMessages.AsReadOnly(); public static List GetAllMessagePayloadsForPerson(Func predicate, string aadPersonId, T payloadType) { - var filtered = allMessages.Where(predicate).OrderBy(m => m.Message.ScheduledEnqueueTimeUtc); + var filtered = allMessages.Where(predicate).OrderBy(m => m.Message.ScheduledEnqueueTime); var result = new List(); foreach (var msg in filtered) @@ -104,7 +104,7 @@ public static List GetAllMessagePayloadsForPerson(Func GetAllMessages() { - var filtered = allMessages.OrderBy(m => m.Message.ScheduledEnqueueTimeUtc); + var filtered = allMessages.OrderBy(m => m.Message.ScheduledEnqueueTime); var result = new List(); foreach (var msg in filtered) @@ -138,7 +138,7 @@ public static Task FinishProcessingAsync() public List GetLocalMessages() { - var filtered = localMessages.OrderBy(m => m.Message.ScheduledEnqueueTimeUtc); + var filtered = localMessages.OrderBy(m => m.Message.ScheduledEnqueueTime); var result = new List(); foreach (var msg in filtered) diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestQueueMessage.cs b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestQueueMessage.cs index 7457b23d2..d24a63f71 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestQueueMessage.cs +++ b/src/backend/tests/Fusion.Resources.Api.Tests/FusionMocks/TestQueueMessage.cs @@ -1,11 +1,11 @@ -using Microsoft.Azure.ServiceBus; +using Azure.Messaging.ServiceBus; namespace Fusion.Resources.Api.Tests.FusionMocks { public class TestQueueMessage { public string Path { get; set; } - public Message Message { get; set; } + public ServiceBusMessage Message { get; set; } public bool Processed { get; set; } public string BodyText { get; set; } } diff --git a/src/backend/tests/Fusion.Resources.Api.Tests/IntegrationTests/InternalRequests/DirectRequestTests.cs b/src/backend/tests/Fusion.Resources.Api.Tests/IntegrationTests/InternalRequests/DirectRequestTests.cs index f7bd77aee..167bc77b6 100644 --- a/src/backend/tests/Fusion.Resources.Api.Tests/IntegrationTests/InternalRequests/DirectRequestTests.cs +++ b/src/backend/tests/Fusion.Resources.Api.Tests/IntegrationTests/InternalRequests/DirectRequestTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -609,7 +610,7 @@ public async Task DirectRequest_AutoApproval_ShouldNotSendNotification_WhenAutoA #region assert TestLogger.TryLog($"{JsonConvert.SerializeObject(new { testRequest })}"); - TestLogger.TryLog($"{JsonConvert.SerializeObject(NotificationClientMock.SentMessages)}"); + TestLogger.TryLog($"{JsonConvert.SerializeObject(NotificationClientMock.SentMessages.ToImmutableList())}"); var notificationsForRequest = NotificationClientMock.SentMessages.GetNotificationsForRequestId(testRequest.Id); notificationsForRequest.Should() diff --git a/src/backend/tests/Fusion.Resources.Domain.Tests/Fusion.Resources.Domain.Tests.csproj b/src/backend/tests/Fusion.Resources.Domain.Tests/Fusion.Resources.Domain.Tests.csproj index dfa80949c..ce00a9a7c 100644 --- a/src/backend/tests/Fusion.Resources.Domain.Tests/Fusion.Resources.Domain.Tests.csproj +++ b/src/backend/tests/Fusion.Resources.Domain.Tests/Fusion.Resources.Domain.Tests.csproj @@ -1,7 +1,7 @@ - + - net6.0 + net7.0 false @@ -9,18 +9,15 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/backend/tests/Fusion.Resources.Functions.Tests/Fusion.Resources.Functions.Tests.csproj b/src/backend/tests/Fusion.Resources.Functions.Tests/Fusion.Resources.Functions.Tests.csproj index 3df5d44d8..79d231c49 100644 --- a/src/backend/tests/Fusion.Resources.Functions.Tests/Fusion.Resources.Functions.Tests.csproj +++ b/src/backend/tests/Fusion.Resources.Functions.Tests/Fusion.Resources.Functions.Tests.csproj @@ -1,20 +1,20 @@ - net6.0 + net7.0 false - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/backend/tests/Fusion.Resources.Logic.Tests/Fusion.Resources.Logic.Tests.csproj b/src/backend/tests/Fusion.Resources.Logic.Tests/Fusion.Resources.Logic.Tests.csproj index 98a79dc2b..838131c1e 100644 --- a/src/backend/tests/Fusion.Resources.Logic.Tests/Fusion.Resources.Logic.Tests.csproj +++ b/src/backend/tests/Fusion.Resources.Logic.Tests/Fusion.Resources.Logic.Tests.csproj @@ -1,21 +1,19 @@ - net6.0 + net7.0 false - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs b/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs index d6251449d..998ffef40 100644 --- a/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs +++ b/src/backend/tests/Fusion.Resources.Test.Core/DbTestFixture.cs @@ -34,7 +34,7 @@ public DbTestFixture() protected virtual void ConfigureServices(ServiceCollection services) { - services.AddMediatR(typeof(AddSecondOpinion).Assembly); + services.AddMediatR(c => c.RegisterServicesFromAssemblyContaining()); services.AddDbContext(opts => opts.UseInMemoryDatabase($"unit-test-db-{Guid.NewGuid()}")); var profileService = new Mock(); diff --git a/src/backend/tests/Fusion.Resources.Test.Core/Fusion.Resources.Test.Core.csproj b/src/backend/tests/Fusion.Resources.Test.Core/Fusion.Resources.Test.Core.csproj index 34898f8fe..ffe6f059b 100644 --- a/src/backend/tests/Fusion.Resources.Test.Core/Fusion.Resources.Test.Core.csproj +++ b/src/backend/tests/Fusion.Resources.Test.Core/Fusion.Resources.Test.Core.csproj @@ -1,12 +1,13 @@ - + - net6.0 + net7.0 + - + diff --git a/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj b/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj index 09551d9d4..ca3ff3a4a 100644 --- a/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj +++ b/src/backend/tests/Fusion.Testing.Authentication/Fusion.Testing.Authentication.csproj @@ -1,16 +1,15 @@ - net6.0 + net7.0 - - + - + diff --git a/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj b/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj index 4d64f214c..f530e4eea 100644 --- a/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj +++ b/src/backend/tests/Fusion.Testing.Core/Fusion.Testing.Core.csproj @@ -1,16 +1,15 @@ - net6.0 + net7.0 - - - + + - + diff --git a/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj b/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj index 2008843af..48b12370c 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.ContextService/Fusion.Testing.Mocks.ContextService.csproj @@ -1,14 +1,14 @@ - net6.0 + net7.0 - - - - + + + + diff --git a/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj b/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj index a1b56bda6..321b89581 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.LineOrgService/Fusion.Testing.Mocks.LineOrgService.csproj @@ -1,15 +1,14 @@ - net6.0 + net7.0 - + - diff --git a/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj b/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj index 77773bf97..2315063d9 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.OrgService/Fusion.Testing.Mocks.OrgService.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 @@ -14,13 +14,12 @@ - - - - + + + - - + + diff --git a/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj b/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj index b01ff9ac5..4d62812a8 100644 --- a/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj +++ b/src/backend/tests/Fusion.Testing.Mocks.ProfileService/Fusion.Testing.Mocks.ProfileService.csproj @@ -1,18 +1,17 @@ - net6.0 + net7.0 - - - + + + - - +