From ef1f00860e6982a446b2d00b865b7a17e7e31887 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Wed, 3 May 2023 17:36:45 +0200 Subject: [PATCH 01/29] New process engine seems to work. Needs more tests and verification --- .../Controllers/InstancesController.cs | 90 +- .../Controllers/ProcessController.cs | 107 +- src/Altinn.App.Api/Models/AppProcessState.cs | 16 + .../Extensions/InstanceEventExtensions.cs | 50 + .../Extensions/ProcessStateExtensions.cs | 34 + .../Extensions/ServiceCollectionExtensions.cs | 8 +- .../Clients/Storage/ProcessClient.cs | 2 +- .../Interface/IAuthorization.cs | 1 + .../Interface/IProcessChangeHandler.cs | 42 +- .../Interface/IProcessEngine.cs | 28 +- .../AltinnExtensionProperties/AltinnAction.cs | 16 + .../AltinnProperties.cs | 24 + .../Process/Elements/AppProcessState.cs | 16 + .../Process/Elements/ConfirmationTask.cs | 13 +- .../Internal/Process/Elements/DataTask.cs | 15 +- .../Internal/Process/Elements/ElementInfo.cs | 5 + .../Process/Elements/ExtensionElements.cs | 17 + .../Internal/Process/Elements/FeedbackTask.cs | 13 +- .../Internal/Process/Elements/ITask.cs | 7 +- .../Internal/Process/Elements/NullTask.cs | 7 +- .../Internal/Process/Elements/ProcessTask.cs | 6 + .../Internal/Process/Elements/TaskBase.cs | 7 +- .../Internal/Process/FlowHydration.cs | 140 +-- .../Internal/Process/IFlowHydration.cs | 34 +- .../Internal/Process/IProcessReader.cs | 242 ++--- .../Internal/Process/ProcessChangeHandler.cs | 846 ++++++++-------- .../Internal/Process/ProcessEngine.cs | 238 ++--- .../Internal/Process/ProcessReader.cs | 441 ++++---- .../Internal/Process/V2/IProcessEngine.cs | 31 + .../Internal/Process/V2/IProcessNavigator.cs | 21 + .../Internal/Process/V2/IProcessReader.cs | 114 +++ .../Internal/Process/V2/ProcessEngine.cs | 409 ++++++++ .../Internal/Process/V2/ProcessNavigator.cs | 71 ++ .../Internal/Process/V2/ProcessNextRequest.cs | 11 + .../Internal/Process/V2/ProcessReader.cs | 177 ++++ .../Process/V2/ProcessStartRequest.cs | 15 + .../Models/ProcessChangeResult.cs | 11 + ...nstancesController_ActiveInstancesTests.cs | 1 + .../InstancesController_CopyInstanceTests.cs | 8 +- .../InstanceEventExtensionsTests.cs | 57 ++ .../Extensions/ProcessStateExtensionTests.cs | 67 ++ .../Clients/EventsSubscriptionClientTests.cs | 8 + .../Internal/Process/FlowHydrationTests.cs | 499 +++++---- .../Internal/Process/ProcessEngineTest.cs | 280 +++--- .../Internal/Process/ProcessReaderTests.cs | 943 +++++++++--------- .../config/process/process.bpmn | 10 +- .../TestData/simple-gateway-default.bpmn | 35 +- .../simple-gateway-with-join-gateway.bpmn | 44 +- .../Process/TestData/simple-gateway.bpmn | 35 +- .../Process/TestData/simple-linear-both.bpmn | 46 + .../Process/TestData/simple-linear-new.bpmn | 46 + .../Process/TestData/simple-no-end.bpmn | 14 +- .../Process/TestUtils/ProcessTestUtils.cs | 7 +- .../Internal/Process/V2/ProcessEngineTest.cs | 70 ++ .../Internal/Process/V2/ProcessReaderTests.cs | 569 +++++++++++ 55 files changed, 4070 insertions(+), 1994 deletions(-) create mode 100644 src/Altinn.App.Api/Models/AppProcessState.cs create mode 100644 src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs create mode 100644 src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs create mode 100644 src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnAction.cs create mode 100644 src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnProperties.cs create mode 100644 src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs create mode 100644 src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs create mode 100644 src/Altinn.App.Core/Models/ProcessChangeResult.cs create mode 100644 test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs create mode 100644 test/Altinn.App.Core.Tests/Extensions/ProcessStateExtensionTests.cs create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-both.bpmn create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-new.bpmn create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index d7e7a1b17..238c3e85c 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -2,7 +2,6 @@ using System.Net; using System.Text; - using Altinn.App.Api.Helpers.RequestHandling; using Altinn.App.Api.Infrastructure.Filters; using Altinn.App.Api.Mappers; @@ -16,6 +15,7 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; +using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; using Altinn.Authorization.ABAC.Xacml.JsonProfile; @@ -25,13 +25,12 @@ using Altinn.Platform.Profile.Models; using Altinn.Platform.Register.Models; using Altinn.Platform.Storage.Interface.Models; - using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; - using Newtonsoft.Json; +using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; namespace Altinn.App.Api.Controllers { @@ -60,8 +59,8 @@ public class InstancesController : ControllerBase private readonly IInstantiationValidator _instantiationValidator; private readonly IPDP _pdp; private readonly IPrefill _prefillService; - private readonly IProcessEngine _processEngine; private readonly AppSettings _appSettings; + private readonly IProcessEngine _processEngineV2; private const long RequestSizeLimit = 2000 * 1024 * 1024; @@ -81,8 +80,8 @@ public InstancesController( IEvents eventsService, IOptions appSettings, IPrefill prefillService, - IProfile profileClient, - IProcessEngine processEngine) + IProfile profileClient, + IProcessEngine processEngineV2) { _logger = logger; _instanceClient = instanceClient; @@ -97,7 +96,7 @@ public InstancesController( _appSettings = appSettings.Value; _prefillService = prefillService; _profileClientClient = profileClient; - _processEngine = processEngine; + _processEngineV2 = processEngineV2; } /// @@ -208,6 +207,7 @@ public async Task> Post( } else { + // create minimum instance template instanceTemplate = new Instance { InstanceOwner = new InstanceOwner { PartyId = instanceOwnerPartyId.Value.ToString() } @@ -266,13 +266,21 @@ public async Task> Post( ConditionallySetReadStatus(instanceTemplate); Instance instance; + ProcessStateChange processResult; instanceTemplate.Process = null; - ProcessChangeContext processChangeContext = new ProcessChangeContext(instanceTemplate, User); + ProcessStateChange? change = null; + try { - // start process - processChangeContext.DontUpdateProcessAndDispatchEvents = true; - processChangeContext = await _processEngine.StartProcess(processChangeContext); + // start process and goto next task + ProcessStartRequest processStartRequest = new ProcessStartRequest + { + Instance = instanceTemplate, + User = User, + Dryrun = true + }; + var result = await _processEngineV2.StartProcess(processStartRequest); + change = result.ProcessStateChange; // create the instance instance = await _instanceClient.CreateInstance(org, app, instanceTemplate); @@ -288,11 +296,16 @@ public async Task> Post( // get the updated instance instance = await _instanceClient.GetInstance(app, org, int.Parse(instance.InstanceOwner.PartyId), Guid.Parse(instance.Id.Split("/")[1])); - + // notify app and store events - processChangeContext.Instance = instance; - processChangeContext.DontUpdateProcessAndDispatchEvents = false; - await _processEngine.StartTask(processChangeContext); + var request = new ProcessStartRequest() + { + Instance = instance, + User = User, + Dryrun = false, + }; + _logger.LogInformation("Events sent to process engine: {Events}", change?.Events); + await _processEngineV2.UpdateInstanceAndRerunEvents(request, change.Events); } catch (Exception exception) { @@ -404,15 +417,21 @@ public async Task> PostSimplified( } Instance instance; + ProcessChangeResult processResult; try { + // start process and goto next task instanceTemplate.Process = null; - // start process - ProcessChangeContext processChangeContext = new ProcessChangeContext(instanceTemplate, User); - processChangeContext.Prefill = instansiationInstance.Prefill; - processChangeContext.DontUpdateProcessAndDispatchEvents = true; - processChangeContext = await _processEngine.StartProcess(processChangeContext); + var request = new ProcessStartRequest() + { + Instance = instanceTemplate, + User = User, + Dryrun = true, + Prefill = instansiationInstance.Prefill + }; + + processResult = await _processEngineV2.StartProcess(request); Instance? source = null; @@ -445,9 +464,14 @@ public async Task> PostSimplified( instance = await _instanceClient.GetInstance(instance); - processChangeContext.Instance = instance; - processChangeContext.DontUpdateProcessAndDispatchEvents = false; - await _processEngine.StartTask(processChangeContext); + var updateRequest = new ProcessStartRequest() + { + Instance = instance, + User = User, + Dryrun = false, + Prefill = instansiationInstance.Prefill + }; + await _processEngineV2.UpdateInstanceAndRerunEvents(updateRequest, processResult.ProcessStateChange.Events); } catch (Exception exception) { @@ -535,12 +559,14 @@ public async Task CopyInstance( { return StatusCode((int)HttpStatusCode.Forbidden, validationResult); } - - ProcessChangeContext processChangeContext = new(targetInstance, User) + + ProcessStartRequest processStartRequest = new() { - DontUpdateProcessAndDispatchEvents = true + Instance = targetInstance, + User = User, + Dryrun = true }; - processChangeContext = await _processEngine.StartProcess(processChangeContext); + var startResult = await _processEngineV2.StartProcess(processStartRequest); targetInstance = await _instanceClient.CreateInstance(org, app, targetInstance); @@ -548,9 +574,13 @@ public async Task CopyInstance( targetInstance = await _instanceClient.GetInstance(targetInstance); - processChangeContext.Instance = targetInstance; - processChangeContext.DontUpdateProcessAndDispatchEvents = false; - await _processEngine.StartTask(processChangeContext); + ProcessStartRequest rerunRequest = new() + { + Instance = targetInstance, + Dryrun = false, + User = User + }; + await _processEngineV2.UpdateInstanceAndRerunEvents(rerunRequest, startResult.ProcessStateChange?.Events); await RegisterEvent("app.instance.created", targetInstance); diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index 6d6926e83..7afda8b4c 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -11,6 +11,7 @@ using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; using Altinn.Authorization.ABAC.Xacml.JsonProfile; @@ -24,6 +25,8 @@ using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; +using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; +using IProcessReader = Altinn.App.Core.Internal.Process.V2.IProcessReader; namespace Altinn.App.Api.Controllers { @@ -43,9 +46,8 @@ public class ProcessController : ControllerBase private readonly IProcess _processService; private readonly IValidation _validationService; private readonly IPDP _pdp; - private readonly IProcessEngine _processEngine; + private readonly IProcessEngine _processEngineV2; private readonly IProcessReader _processReader; - private readonly IFlowHydration _flowHydration; /// /// Initializes a new instance of the @@ -56,18 +58,16 @@ public ProcessController( IProcess processService, IValidation validationService, IPDP pdp, - IProcessEngine processEngine, IProcessReader processReader, - IFlowHydration flowHydration) + IProcessEngine processEngineV2) { _logger = logger; _instanceClient = instanceClient; _processService = processService; _validationService = validationService; _pdp = pdp; - _processEngine = processEngine; _processReader = processReader; - _flowHydration = flowHydration; + _processEngineV2 = processEngineV2; } /// @@ -133,15 +133,20 @@ public async Task> StartProcess( { instance = await _instanceClient.GetInstance(app, org, instanceOwnerPartyId, instanceGuid); - ProcessChangeContext changeContext = new ProcessChangeContext(instance, User); - changeContext.RequestedProcessElementId = startEvent; - changeContext = await _processEngine.StartProcess(changeContext); - if (changeContext.FailedProcessChange) + var request = new ProcessStartRequest() { - return Conflict(changeContext.ProcessMessages[0].Message); + Instance = instance, + StartEventId = startEvent, + User = User, + Dryrun = false + }; + var result = await _processEngineV2.StartProcess(request); + if (!result.Success) + { + return Conflict(result.ErrorMessage); } - return Ok(changeContext.Instance.Process); + return Ok(result.ProcessStateChange?.NewProcessState); } catch (PlatformHttpException e) { @@ -192,8 +197,8 @@ public async Task>> GetNextElements( return Conflict($"Instance does not have valid info about currentTask"); } - List nextElements = await _flowHydration.NextFollowAndFilterGateways(instance, currentTaskId, false); - + // List nextElements = await _flowHydration.NextFollowAndFilterGateways(instance, currentTaskId, false); + List nextElements = new List(); return Ok(nextElements.Select(e => e.Id).ToList()); } catch (PlatformHttpException e) @@ -235,8 +240,7 @@ private async Task CanTaskBeEnded(Instance instance, string currentElement /// application identifier which is unique within an organisation /// unique id of the party that is the owner of the instance /// unique id to identify the instance - /// the id of the next element to move to. Query parameter is optional, - /// but must be specified if more than one element can be reached from the current process ellement. + /// action performed /// Optional parameter to pass on the language used in the form if this differs from the profile language, /// which otherwise is used automatically. The language is picked up when generating the PDF when leaving a step, /// and is not used for anything else. @@ -250,7 +254,7 @@ public async Task> NextElement( [FromRoute] string app, [FromRoute] int instanceOwnerPartyId, [FromRoute] Guid instanceGuid, - [FromQuery] string elementId = null, + [FromQuery] string action = null, [FromQuery] string lang = null) { try @@ -267,15 +271,6 @@ public async Task> NextElement( return Conflict($"Process is ended."); } - if (!string.IsNullOrEmpty(elementId)) - { - ElementInfo elemInfo = _processReader.GetElementInfo(elementId); - if (elemInfo == null) - { - return BadRequest($"Requested element id {elementId} is not found in process definition"); - } - } - string altinnTaskType = instance.Process.CurrentTask?.AltinnTaskType; if (altinnTaskType == null) @@ -283,25 +278,16 @@ public async Task> NextElement( return Conflict($"Instance does not have current altinn task type information!"); } - ProcessSequenceFlowType processSequenceFlowType = ProcessSequenceFlowType.CompleteCurrentMoveToNext; - List possibleNextElements = await _flowHydration.NextFollowAndFilterGateways(instance, instance.Process.CurrentTask?.ElementId, elementId.IsNullOrEmpty()); - string targetElement = ProcessHelper.GetValidNextElementOrError(elementId, possibleNextElements.Select(e => e.Id).ToList(), out ProcessError processError); - - if (!string.IsNullOrEmpty(elementId) && processError == null) - { - List flows = _processReader.GetSequenceFlowsBetween(instance.Process.CurrentTask?.ElementId, targetElement); - processSequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); - } - bool authorized; - if (processSequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext)) + string checkedAction = action; + if (action != null) { - authorized = await AuthorizeAction(altinnTaskType, org, app, instanceOwnerPartyId, instanceGuid); + authorized = await AuthorizeAction(action, org, app, instanceOwnerPartyId, instanceGuid); } else { - ElementInfo elemInfo = _processReader.GetElementInfo(targetElement); - authorized = await AuthorizeAction(elemInfo.AltinnTaskType, org, app, instanceOwnerPartyId, instanceGuid, elemInfo.Id); + authorized = await AuthorizeAction(altinnTaskType, org, app, instanceOwnerPartyId, instanceGuid); + checkedAction = altinnTaskType; } if (!authorized) @@ -309,15 +295,19 @@ public async Task> NextElement( return Forbid(); } - ProcessChangeContext changeContext = new ProcessChangeContext(instance, User); - changeContext.RequestedProcessElementId = elementId; - changeContext = await _processEngine.Next(changeContext); - if (changeContext.FailedProcessChange) + var request = new ProcessNextRequest() { - return Conflict(changeContext.ProcessMessages[0].Message); + Instance = instance, + User = User, + Action = checkedAction + }; + var result = await _processEngineV2.Next(request); + if (!result.Success) + { + return Conflict(result.ErrorMessage); } - return Ok(changeContext.Instance.Process); + return Ok(result.ProcessStateChange?.NewProcessState); } catch (PlatformHttpException e) { @@ -395,27 +385,22 @@ public async Task> CompleteProcess( return Conflict($"Instance is not valid for task {currentTaskId}. Automatic completion of process is stopped"); } - List nextElements = await _flowHydration.NextFollowAndFilterGateways(instance, currentTaskId); - - if (nextElements.Count > 1) - { - return Conflict($"Cannot complete process. Multiple outgoing sequence flows detected from task {currentTaskId}. Please select manually among {nextElements}"); - } - - string nextElement = nextElements.First().Id; - try { - ProcessChangeContext processChange = new ProcessChangeContext(instance, User); - processChange.RequestedProcessElementId = nextElement; - processChange = await _processEngine.Next(processChange); + ProcessNextRequest request = new ProcessNextRequest() + { + Instance = instance, + User = User, + Action = altinnTaskType + }; + var result = await _processEngineV2.Next(request); - if (processChange.FailedProcessChange) + if (!result.Success) { - return Conflict(processChange.ProcessMessages[0].Message); + return Conflict(result.ErrorMessage); } - currentTaskId = instance.Process.CurrentTask?.ElementId; + currentTaskId = result.ProcessStateChange?.NewProcessState.CurrentTask.ElementId; } catch (Exception ex) { diff --git a/src/Altinn.App.Api/Models/AppProcessState.cs b/src/Altinn.App.Api/Models/AppProcessState.cs new file mode 100644 index 000000000..afc1ec4f5 --- /dev/null +++ b/src/Altinn.App.Api/Models/AppProcessState.cs @@ -0,0 +1,16 @@ +#nullable enable +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Api.Models; + +/// +/// Extended representation of a status object that holds the process state of an application instance. +/// The process is defined by the application's process specification BPMN file. +/// +public class AppProcessState: ProcessState +{ + /// + /// Actions that can be performed and if the user is allowed to perform them. + /// + public Dictionary? Actions { get; set; } +} diff --git a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs new file mode 100644 index 000000000..8262982b8 --- /dev/null +++ b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs @@ -0,0 +1,50 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Extensions; + +/// +/// Extension methods for . +/// +public static class InstanceEventExtensions +{ + public static InstanceEvent CopyValues(this InstanceEvent original) + { + return new InstanceEvent + { + Created = original.Created, + DataId = original.DataId, + EventType = original.EventType, + Id = original.Id, + InstanceId = original.InstanceId, + InstanceOwnerPartyId = original.InstanceOwnerPartyId, + ProcessInfo = new ProcessState + { + Started = original.ProcessInfo?.Started, + CurrentTask = new ProcessElementInfo + { + Flow = original.ProcessInfo?.CurrentTask.Flow, + AltinnTaskType = original.ProcessInfo?.CurrentTask.AltinnTaskType, + ElementId = original.ProcessInfo?.CurrentTask.ElementId, + Name = original.ProcessInfo?.CurrentTask.Name, + Started = original.ProcessInfo?.CurrentTask.Started, + Ended = original.ProcessInfo?.CurrentTask.Ended, + Validated = new ValidationStatus + { + CanCompleteTask = original.ProcessInfo?.CurrentTask?.Validated?.CanCompleteTask ?? false, + Timestamp = original.ProcessInfo?.CurrentTask?.Validated?.Timestamp + } + }, + + StartEvent = original.ProcessInfo?.StartEvent + }, + User = new PlatformUser + { + AuthenticationLevel = original.User.AuthenticationLevel, + EndUserSystemId = original.User.EndUserSystemId, + OrgId = original.User.OrgId, + UserId = original.User.UserId, + NationalIdentityNumber = original.User?.NationalIdentityNumber + } + }; + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs b/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs new file mode 100644 index 000000000..13443c71e --- /dev/null +++ b/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs @@ -0,0 +1,34 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Extensions; + +/// +/// Extension methods for +/// +public static class ProcessStateExtensions +{ + public static ProcessState Copy(this ProcessState original) + { + ProcessState copyOfState = new ProcessState(); + + if (original.CurrentTask != null) + { + copyOfState.CurrentTask = new ProcessElementInfo(); + copyOfState.CurrentTask.FlowType = original.CurrentTask.FlowType; + copyOfState.CurrentTask.Name = original.CurrentTask.Name; + copyOfState.CurrentTask.Validated = original.CurrentTask.Validated; + copyOfState.CurrentTask.AltinnTaskType = original.CurrentTask.AltinnTaskType; + copyOfState.CurrentTask.Flow = original.CurrentTask.Flow; + copyOfState.CurrentTask.ElementId = original.CurrentTask.ElementId; + copyOfState.CurrentTask.Started = original.CurrentTask.Started; + copyOfState.CurrentTask.Ended = original.CurrentTask.Ended; + } + + copyOfState.EndEvent = original.EndEvent; + copyOfState.Started = original.Started; + copyOfState.Ended = original.Ended; + copyOfState.StartEvent = original.StartEvent; + + return copyOfState; + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 91f9fb7f9..96756267b 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -23,6 +23,7 @@ using Altinn.App.Core.Internal.Language; using Altinn.App.Core.Internal.Pdf; using Altinn.App.Core.Internal.Process; +using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Internal.Texts; using Altinn.App.Core.Models; using Altinn.Common.AccessTokenClient.Configuration; @@ -36,6 +37,10 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Newtonsoft.Json.Linq; +using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; +using IProcessReader = Altinn.App.Core.Internal.Process.V2.IProcessReader; +using ProcessEngine = Altinn.App.Core.Internal.Process.V2.ProcessEngine; +using ProcessReader = Altinn.App.Core.Internal.Process.V2.ProcessReader; namespace Altinn.App.Core.Extensions { @@ -218,10 +223,9 @@ private static void AddAppOptions(IServiceCollection services) private static void AddProcessServices(IServiceCollection services) { services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); services.TryAddSingleton(); services.TryAddTransient(); - services.TryAddTransient(); } } } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs index 7d3486019..c415a11f6 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs @@ -84,7 +84,7 @@ public async Task GetProcessHistory(string instanceGuid, str } /// - public async Task DispatchProcessEventsToStorage(Instance instance, List events) + public async Task DispatchProcessEventsToStorage(Instance instance, List? events) { string org = instance.Org; string app = instance.AppId.Split("/")[1]; diff --git a/src/Altinn.App.Core/Interface/IAuthorization.cs b/src/Altinn.App.Core/Interface/IAuthorization.cs index 282f1fe02..e53c1d459 100644 --- a/src/Altinn.App.Core/Interface/IAuthorization.cs +++ b/src/Altinn.App.Core/Interface/IAuthorization.cs @@ -1,3 +1,4 @@ +using Altinn.App.Core.Models; using Altinn.Platform.Register.Models; namespace Altinn.App.Core.Interface diff --git a/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs b/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs index 3fc247603..53947ec36 100644 --- a/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs +++ b/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs @@ -7,26 +7,26 @@ namespace Altinn.App.Core.Interface /// public interface IProcessChangeHandler { - /// - /// Handle start of process - /// - Task HandleStart(ProcessChangeContext processChange); - - /// - /// Handle complete task and move to - /// - /// - Task HandleMoveToNext(ProcessChangeContext processChange); - - /// - /// Handle start task - /// - Task HandleStartTask(ProcessChangeContext processChange); - - /// - /// Check if current task can be completed - /// - /// - Task CanTaskBeEnded(ProcessChangeContext processChange); + // /// + // /// Handle start of process + // /// + // Task HandleStart(ProcessChangeContext processChange); + // + // /// + // /// Handle complete task and move to + // /// + // /// + // Task HandleMoveToNext(ProcessChangeContext processChange); + // + // /// + // /// Handle start task + // /// + // Task HandleStartTask(ProcessChangeContext processChange); + // + // /// + // /// Check if current task can be completed + // /// + // /// + // Task CanTaskBeEnded(ProcessChangeContext processChange); } } diff --git a/src/Altinn.App.Core/Interface/IProcessEngine.cs b/src/Altinn.App.Core/Interface/IProcessEngine.cs index ee2af25a1..04fa6f778 100644 --- a/src/Altinn.App.Core/Interface/IProcessEngine.cs +++ b/src/Altinn.App.Core/Interface/IProcessEngine.cs @@ -8,19 +8,19 @@ namespace Altinn.App.Core.Interface /// public interface IProcessEngine { - /// - /// Method to start a new process - /// - Task StartProcess(ProcessChangeContext processChange); - - /// - /// Method to move process to next task/event - /// - Task Next(ProcessChangeContext processChange); - - /// - /// Method to Start Task - /// - Task StartTask(ProcessChangeContext processChange); + // /// + // /// Method to start a new process + // /// + // Task StartProcess(ProcessChangeContext processChange); + // + // /// + // /// Method to move process to next task/event + // /// + // Task Next(ProcessChangeContext processChange); + // + // /// + // /// Method to Start Task + // /// + // Task StartTask(ProcessChangeContext processChange); } } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnAction.cs b/src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnAction.cs new file mode 100644 index 000000000..15f37be3e --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnAction.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties +{ + /// + /// Defines an altinn action for a task + /// + public class AltinnAction + { + /// + /// Gets or sets the ID of the action + /// + [XmlAttribute("id")] + public string Id { get; set; } + } +} diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnProperties.cs b/src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnProperties.cs new file mode 100644 index 000000000..55b776202 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/Elements/AltinnExtensionProperties/AltinnProperties.cs @@ -0,0 +1,24 @@ +using System.Xml.Serialization; + +namespace Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties +{ + /// + /// Defines the altinn properties for a task + /// + public class AltinnProperties + { + /// + /// List of available actions for a task + /// + [XmlArray(ElementName = "actions", Namespace = "http://altinn.no", IsNullable = true)] + [XmlArrayItem(ElementName = "action", Namespace = "http://altinn.no")] + public List? AltinnActions { get; set; } + + /// + /// Gets or sets the task type + /// + //[XmlElement(ElementName = "taskType", Namespace = "http://altinn.no", IsNullable = true)] + [XmlElement("taskType", Namespace = "http://altinn.no")] + public string? TaskType { get; set; } + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs new file mode 100644 index 000000000..04bbe923c --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs @@ -0,0 +1,16 @@ +#nullable enable +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.Elements; + +/// +/// Extended representation of a status object that holds the process state of an application instance. +/// The process is defined by the application's process specification BPMN file. +/// +public class AppProcessState: ProcessState +{ + /// + /// Actions that can be performed and if the user is allowed to perform them. + /// + public Dictionary? Actions { get; set; } +} diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ConfirmationTask.cs b/src/Altinn.App.Core/Internal/Process/Elements/ConfirmationTask.cs index fdcdaca36..743d44f6d 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/ConfirmationTask.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/ConfirmationTask.cs @@ -1,5 +1,6 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements { @@ -19,21 +20,21 @@ public ConfirmationTask(ITaskEvents taskEvents) } /// - public override async Task HandleTaskAbandon(ProcessChangeContext processChangeContext) + public override async Task HandleTaskAbandon(string elementId, Instance instance) { - await _taskEvents.OnAbandonProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance); + await _taskEvents.OnAbandonProcessTask(elementId, instance); } /// - public override async Task HandleTaskComplete(ProcessChangeContext processChangeContext) + public override async Task HandleTaskComplete(string elementId, Instance instance) { - await _taskEvents.OnEndProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance); + await _taskEvents.OnEndProcessTask(elementId, instance); } /// - public override async Task HandleTaskStart(ProcessChangeContext processChangeContext) + public override async Task HandleTaskStart(string elementId, Instance instance, Dictionary prefill) { - await _taskEvents.OnStartProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance, processChangeContext.Prefill); + await _taskEvents.OnStartProcessTask(elementId, instance, prefill); } } } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/DataTask.cs b/src/Altinn.App.Core/Internal/Process/Elements/DataTask.cs index c167e16da..87c93a8e9 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/DataTask.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/DataTask.cs @@ -1,5 +1,6 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements { @@ -19,22 +20,22 @@ public DataTask(ITaskEvents taskEvents) } /// - public override async Task HandleTaskAbandon(ProcessChangeContext processChangeContext) + public override async Task HandleTaskAbandon(string elementId, Instance instance) { - await _taskEvents.OnAbandonProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance); + await _taskEvents.OnAbandonProcessTask(elementId, instance); } /// - public override async Task HandleTaskComplete(ProcessChangeContext processChangeContext) + public override async Task HandleTaskComplete(string elementId, Instance instance) { - await _taskEvents.OnEndProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance); + await _taskEvents.OnEndProcessTask(elementId, instance); } /// - public override async Task HandleTaskStart(ProcessChangeContext processChangeContext) + public override async Task HandleTaskStart(string elementId, Instance instance, Dictionary prefill) { - await _taskEvents.OnStartProcessTask(processChangeContext.ElementToBeProcessed, - processChangeContext.Instance, processChangeContext.Prefill); + await _taskEvents.OnStartProcessTask(elementId, + instance, prefill); } } } \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs b/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs index 2d0c666a1..ac10a93b2 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs @@ -24,5 +24,10 @@ public class ElementInfo /// The altinn specific task type /// public string? AltinnTaskType { get; set; } + + /// + /// The altinn specific task actions + /// + public List? AltinnTaskActions { get; set; } } } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs b/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs new file mode 100644 index 000000000..c44d7114b --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; +using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; + +namespace Altinn.App.Core.Internal.Process.Elements +{ + /// + /// Class representing the extension elements + /// + public class ExtensionElements + { + /// + /// Gets or sets the altinn properties + /// + [XmlElement("properties", Namespace = "http://altinn.no")] + public AltinnProperties AltinnProperties { get; set; } + } +} diff --git a/src/Altinn.App.Core/Internal/Process/Elements/FeedbackTask.cs b/src/Altinn.App.Core/Internal/Process/Elements/FeedbackTask.cs index a4f37c312..49ba7ee07 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/FeedbackTask.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/FeedbackTask.cs @@ -1,5 +1,6 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements { @@ -19,21 +20,21 @@ public FeedbackTask(ITaskEvents taskEvents) } /// - public override async Task HandleTaskAbandon(ProcessChangeContext processChangeContext) + public override async Task HandleTaskAbandon(string elementId, Instance instance) { - await _taskEvents.OnAbandonProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance); + await _taskEvents.OnAbandonProcessTask(elementId, instance); } /// - public override async Task HandleTaskComplete(ProcessChangeContext processChangeContext) + public override async Task HandleTaskComplete(string elementId, Instance instance) { - await _taskEvents.OnEndProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance); + await _taskEvents.OnEndProcessTask(elementId, instance); } /// - public override async Task HandleTaskStart(ProcessChangeContext processChangeContext) + public override async Task HandleTaskStart(string elementId, Instance instance, Dictionary prefill) { - await _taskEvents.OnStartProcessTask(processChangeContext.ElementToBeProcessed, processChangeContext.Instance, processChangeContext.Prefill); + await _taskEvents.OnStartProcessTask(elementId, instance, prefill); } } } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ITask.cs b/src/Altinn.App.Core/Internal/Process/Elements/ITask.cs index 813fa1946..ab07f273e 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/ITask.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/ITask.cs @@ -1,4 +1,5 @@ using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements { @@ -10,16 +11,16 @@ public interface ITask /// /// This operations triggers process logic needed to start the current task. The logic depend on the different types of task /// - Task HandleTaskStart(ProcessChangeContext processChangeContext); + Task HandleTaskStart(string elementId, Instance instance, Dictionary prefill); /// /// This operatin triggers process logic need to complete a given task. The Logic depend on the different types of task. /// - Task HandleTaskComplete(ProcessChangeContext processChangeContext); + Task HandleTaskComplete(string elementId, Instance instance); /// /// This operatin triggers process logic need to abandon a Task without completing it /// - Task HandleTaskAbandon(ProcessChangeContext processChangeContext); + Task HandleTaskAbandon(string elementId, Instance instance); } } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/NullTask.cs b/src/Altinn.App.Core/Internal/Process/Elements/NullTask.cs index f4e151fe5..b8e79271f 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/NullTask.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/NullTask.cs @@ -1,4 +1,5 @@ using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements; @@ -8,19 +9,19 @@ namespace Altinn.App.Core.Internal.Process.Elements; public class NullTask: ITask { /// - public async Task HandleTaskStart(ProcessChangeContext processChangeContext) + public async Task HandleTaskStart(string elementId, Instance instance, Dictionary prefill) { await Task.CompletedTask; } /// - public async Task HandleTaskComplete(ProcessChangeContext processChangeContext) + public async Task HandleTaskComplete(string elementId, Instance instance) { await Task.CompletedTask; } /// - public async Task HandleTaskAbandon(ProcessChangeContext processChangeContext) + public async Task HandleTaskAbandon(string elementId, Instance instance) { await Task.CompletedTask; } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ProcessTask.cs b/src/Altinn.App.Core/Internal/Process/Elements/ProcessTask.cs index e0b32d758..70b84c207 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/ProcessTask.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/ProcessTask.cs @@ -13,6 +13,12 @@ public class ProcessTask: ProcessElement /// [XmlAttribute("tasktype", Namespace = "http://altinn.no")] public string? TaskType { get; set; } + + /// + /// Defines the extension elements + /// + [XmlElement("extensionElements")] + public ExtensionElements? ExtensionElements { get; set; } /// /// String representation of process element type diff --git a/src/Altinn.App.Core/Internal/Process/Elements/TaskBase.cs b/src/Altinn.App.Core/Internal/Process/Elements/TaskBase.cs index 77f3b528e..274a0a013 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/TaskBase.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/TaskBase.cs @@ -1,4 +1,5 @@ using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements { @@ -10,16 +11,16 @@ public abstract class TaskBase: ITask /// /// hallooo asdf /// - public abstract Task HandleTaskComplete(ProcessChangeContext processChangeContext); + public abstract Task HandleTaskComplete(string elementId, Instance instance); /// /// Handle task start /// - public abstract Task HandleTaskStart(ProcessChangeContext processChangeContext); + public abstract Task HandleTaskStart(string elementId, Instance instance, Dictionary prefill); /// /// Handle task abandon /// - public abstract Task HandleTaskAbandon(ProcessChangeContext processChangeContext); + public abstract Task HandleTaskAbandon(string elementId, Instance instance); } } diff --git a/src/Altinn.App.Core/Internal/Process/FlowHydration.cs b/src/Altinn.App.Core/Internal/Process/FlowHydration.cs index 122c94517..c660fff44 100644 --- a/src/Altinn.App.Core/Internal/Process/FlowHydration.cs +++ b/src/Altinn.App.Core/Internal/Process/FlowHydration.cs @@ -10,74 +10,74 @@ namespace Altinn.App.Core.Internal.Process; /// public class FlowHydration: IFlowHydration { - private readonly IProcessReader _processReader; - private readonly ExclusiveGatewayFactory _gatewayFactory; - - /// - /// Initialize a new instance of FlowHydration - /// - /// IProcessReader implementation used to read the process - /// ExclusiveGatewayFactory used to fetch gateway code to be executed - public FlowHydration(IProcessReader processReader, ExclusiveGatewayFactory gatewayFactory) - { - _processReader = processReader; - _gatewayFactory = gatewayFactory; - } - - /// - public async Task> NextFollowAndFilterGateways(Instance instance, string? currentElement, bool followDefaults = true) - { - List directFlowTargets = _processReader.GetNextElements(currentElement); - return await NextFollowAndFilterGateways(instance, directFlowTargets!, followDefaults); - } - - /// - public async Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, bool followDefaults = true) - { - List filteredNext = new List(); - foreach (var directFlowTarget in originNextElements) - { - if (directFlowTarget == null) - { - continue; - } - if (!IsGateway(directFlowTarget)) - { - filteredNext.Add(directFlowTarget); - continue; - } - - var gateway = (ExclusiveGateway)directFlowTarget; - IProcessExclusiveGateway? gatewayFilter = _gatewayFactory.GetProcessExclusiveGateway(directFlowTarget.Id); - List outgoingFlows = _processReader.GetOutgoingSequenceFlows(directFlowTarget); - List filteredList; - if (gatewayFilter == null) - { - filteredList = outgoingFlows; - } - else - { - filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance); - } - - var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); - if (followDefaults && defaultSequenceFlow != null) - { - var defaultTarget = _processReader.GetFlowElement(defaultSequenceFlow.TargetRef); - filteredNext.AddRange(await NextFollowAndFilterGateways(instance, new List { defaultTarget })); - } - else - { - var filteredTargets= filteredList.Select(e => _processReader.GetFlowElement(e.TargetRef)).ToList(); - filteredNext.AddRange(await NextFollowAndFilterGateways(instance, filteredTargets)); - } - } - - return filteredNext; - } - - private static bool IsGateway(ProcessElement processElement) - { - return processElement is ExclusiveGateway; - } + // private readonly IProcessReader _processReader; + // private readonly ExclusiveGatewayFactory _gatewayFactory; + // + // /// + // /// Initialize a new instance of FlowHydration + // /// + // /// IProcessReader implementation used to read the process + // /// ExclusiveGatewayFactory used to fetch gateway code to be executed + // public FlowHydration(IProcessReader processReader, ExclusiveGatewayFactory gatewayFactory) + // { + // _processReader = processReader; + // _gatewayFactory = gatewayFactory; + // } + // + // /// + // public async Task> NextFollowAndFilterGateways(Instance instance, string? currentElement, bool followDefaults = true) + // { + // List directFlowTargets = _processReader.GetNextElements(currentElement); + // return await NextFollowAndFilterGateways(instance, directFlowTargets!, followDefaults); + // } + // + // /// + // public async Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, bool followDefaults = true) + // { + // List filteredNext = new List(); + // foreach (var directFlowTarget in originNextElements) + // { + // if (directFlowTarget == null) + // { + // continue; + // } + // if (!IsGateway(directFlowTarget)) + // { + // filteredNext.Add(directFlowTarget); + // continue; + // } + // + // var gateway = (ExclusiveGateway)directFlowTarget; + // IProcessExclusiveGateway? gatewayFilter = _gatewayFactory.GetProcessExclusiveGateway(directFlowTarget.Id); + // List outgoingFlows = _processReader.GetOutgoingSequenceFlows(directFlowTarget); + // List filteredList; + // if (gatewayFilter == null) + // { + // filteredList = outgoingFlows; + // } + // else + // { + // filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance); + // } + // + // var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); + // if (followDefaults && defaultSequenceFlow != null) + // { + // var defaultTarget = _processReader.GetFlowElement(defaultSequenceFlow.TargetRef); + // filteredNext.AddRange(await NextFollowAndFilterGateways(instance, new List { defaultTarget })); + // } + // else + // { + // var filteredTargets= filteredList.Select(e => _processReader.GetFlowElement(e.TargetRef)).ToList(); + // filteredNext.AddRange(await NextFollowAndFilterGateways(instance, filteredTargets)); + // } + // } + // + // return filteredNext; + // } + // + // private static bool IsGateway(ProcessElement processElement) + // { + // return processElement is ExclusiveGateway; + // } } diff --git a/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs b/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs index e5218dce1..2e84d9a3d 100644 --- a/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs +++ b/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs @@ -9,21 +9,21 @@ namespace Altinn.App.Core.Internal.Process; /// public interface IFlowHydration { - /// - /// Checks next elements of current for gateways and apply custom gateway decisions based on implementations - /// - /// Instance data - /// Current process element id - /// Should follow default path out of gateway if set - /// Filtered list of next elements - public Task> NextFollowAndFilterGateways(Instance instance, string? currentElement, bool followDefaults = true); - - /// - /// Takes a list of flows checks for gateways and apply custom gateway decisions based on implementations - /// - /// Instance data - /// Original list of next elements - /// /// Should follow default path out of gateway if set - /// Filtered list of next elements - public Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, bool followDefaults = true); + // /// + // /// Checks next elements of current for gateways and apply custom gateway decisions based on implementations + // /// + // /// Instance data + // /// Current process element id + // /// Should follow default path out of gateway if set + // /// Filtered list of next elements + // public Task> NextFollowAndFilterGateways(Instance instance, string? currentElement, bool followDefaults = true); + // + // /// + // /// Takes a list of flows checks for gateways and apply custom gateway decisions based on implementations + // /// + // /// Instance data + // /// Original list of next elements + // /// /// Should follow default path out of gateway if set + // /// Filtered list of next elements + // public Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, bool followDefaults = true); } diff --git a/src/Altinn.App.Core/Internal/Process/IProcessReader.cs b/src/Altinn.App.Core/Internal/Process/IProcessReader.cs index 0c5260a76..8bfd6bf22 100644 --- a/src/Altinn.App.Core/Internal/Process/IProcessReader.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessReader.cs @@ -9,126 +9,126 @@ namespace Altinn.App.Core.Internal.Process; public interface IProcessReader { - /// - /// Get all defined StartEvents in the process - /// - /// - public List GetStartEvents(); - - /// - /// Get ids of all defined StartEvents in the process - /// - /// - public List GetStartEventIds(); - - /// - /// Check id element is StartEvent - /// - /// Id of process element - /// true if elementId is of type StartEvent - public bool IsStartEvent(string? elementId); - - /// - /// Get all defined Tasks in the process - /// - /// - public List GetProcessTasks(); - - /// - /// Get ids of all defined Tasks in the process - /// - /// - public List GetProcessTaskIds(); - - /// - /// Check id element is ProcessTask - /// - /// Id of process element - /// true if elementId is of type Task - public bool IsProcessTask(string? elementId); - - /// - /// Get all ExclusiveGateways defined in the process - /// - /// - public List GetExclusiveGateways(); - - /// - /// Get ids of all defined ExclusiveGateways in the process - /// - /// - public List GetExclusiveGatewayIds(); - - /// - /// Get all EndEvents defined in the process - /// - /// - public List GetEndEvents(); - - /// - /// Get ids of all EndEvents defined in the process - /// - /// - public List GetEndEventIds(); - - /// - /// Check id element is EndEvent - /// - /// Id of process element - /// true if elementId is of type EndEvent - public bool IsEndEvent(string? elementId); - - /// - /// Get all SequenceFlows defined in the process - /// - /// - public List GetSequenceFlows(); - - - /// - /// Get ids of all SequenceFlows defined in the process - /// - /// - public List GetSequenceFlowIds(); - - /// - /// Find all possible next elements from current element - /// - /// Current process element id - /// - public List GetNextElements(string? currentElementId); - - /// - /// Find ids of all possible next elements from current element - /// - /// Current ProcessElement Id - /// - public List GetNextElementIds(string? currentElement); - - /// - /// Get SequenceFlows out of the bpmn element - /// - /// Element to get the outgoing sequenceflows from - /// Outgoing sequence flows - public List GetOutgoingSequenceFlows(ProcessElement? flowElement); - - /// - /// Returns a list of sequence flow to be followed between current step and next element - /// - public List GetSequenceFlowsBetween(string? currentStepId, string? nextElementId); - - /// - /// Returns StartEvent, Task or EndEvent with given Id, null if element not found - /// - /// Id of element to look for - /// or null - public ProcessElement? GetFlowElement(string? elementId); - - /// - /// Retuns ElementInfo for StartEvent, Task or EndEvent with given Id, null if element not found - /// - /// Id of element to look for - /// or null - public ElementInfo? GetElementInfo(string? elementId); + // /// + // /// Get all defined StartEvents in the process + // /// + // /// + // public List GetStartEvents(); + // + // /// + // /// Get ids of all defined StartEvents in the process + // /// + // /// + // public List GetStartEventIds(); + // + // /// + // /// Check id element is StartEvent + // /// + // /// Id of process element + // /// true if elementId is of type StartEvent + // public bool IsStartEvent(string? elementId); + // + // /// + // /// Get all defined Tasks in the process + // /// + // /// + // public List GetProcessTasks(); + // + // /// + // /// Get ids of all defined Tasks in the process + // /// + // /// + // public List GetProcessTaskIds(); + // + // /// + // /// Check id element is ProcessTask + // /// + // /// Id of process element + // /// true if elementId is of type Task + // public bool IsProcessTask(string? elementId); + // + // /// + // /// Get all ExclusiveGateways defined in the process + // /// + // /// + // public List GetExclusiveGateways(); + // + // /// + // /// Get ids of all defined ExclusiveGateways in the process + // /// + // /// + // public List GetExclusiveGatewayIds(); + // + // /// + // /// Get all EndEvents defined in the process + // /// + // /// + // public List GetEndEvents(); + // + // /// + // /// Get ids of all EndEvents defined in the process + // /// + // /// + // public List GetEndEventIds(); + // + // /// + // /// Check id element is EndEvent + // /// + // /// Id of process element + // /// true if elementId is of type EndEvent + // public bool IsEndEvent(string? elementId); + // + // /// + // /// Get all SequenceFlows defined in the process + // /// + // /// + // public List GetSequenceFlows(); + // + // + // /// + // /// Get ids of all SequenceFlows defined in the process + // /// + // /// + // public List GetSequenceFlowIds(); + // + // /// + // /// Find all possible next elements from current element + // /// + // /// Current process element id + // /// + // public List GetNextElements(string? currentElementId); + // + // /// + // /// Find ids of all possible next elements from current element + // /// + // /// Current ProcessElement Id + // /// + // public List GetNextElementIds(string? currentElement); + // + // /// + // /// Get SequenceFlows out of the bpmn element + // /// + // /// Element to get the outgoing sequenceflows from + // /// Outgoing sequence flows + // public List GetOutgoingSequenceFlows(ProcessElement? flowElement); + // + // /// + // /// Returns a list of sequence flow to be followed between current step and next element + // /// + // public List GetSequenceFlowsBetween(string? currentStepId, string? nextElementId); + // + // /// + // /// Returns StartEvent, Task or EndEvent with given Id, null if element not found + // /// + // /// Id of element to look for + // /// or null + // public ProcessElement? GetFlowElement(string? elementId); + // + // /// + // /// Retuns ElementInfo for StartEvent, Task or EndEvent with given Id, null if element not found + // /// + // /// Id of element to look for + // /// or null + // public ElementInfo? GetElementInfo(string? elementId); } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs b/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs index ab3442956..e8a6301ed 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs @@ -22,428 +22,428 @@ namespace Altinn.App.Core.Internal.Process /// public class ProcessChangeHandler : IProcessChangeHandler { - private readonly IInstance _instanceClient; - private readonly IProcess _processService; - private readonly IProcessReader _processReader; - private readonly ILogger _logger; - private readonly IValidation _validationService; - private readonly IEvents _eventsService; - private readonly IProfile _profileClient; - private readonly AppSettings _appSettings; - private readonly IAppEvents _appEvents; - private readonly ITaskEvents _taskEvents; - - /// - /// Altinn App specific process change handler - /// - public ProcessChangeHandler( - ILogger logger, - IProcess processService, - IProcessReader processReader, - IInstance instanceClient, - IValidation validationService, - IEvents eventsService, - IProfile profileClient, - IOptions appSettings, - IAppEvents appEvents, - ITaskEvents taskEvents) - { - _logger = logger; - _processService = processService; - _instanceClient = instanceClient; - _processReader = processReader; - _validationService = validationService; - _eventsService = eventsService; - _profileClient = profileClient; - _appSettings = appSettings.Value; - _appEvents = appEvents; - _taskEvents = taskEvents; - } - - /// - public async Task HandleMoveToNext(ProcessChangeContext processChange) - { - processChange.ProcessStateChange = await ProcessNext(processChange.Instance, processChange.RequestedProcessElementId, processChange.User); - if (processChange.ProcessStateChange != null) - { - processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); - - await RegisterEventWithEventsComponent(processChange.Instance); - } - - return processChange; - } - - /// - public async Task HandleStart(ProcessChangeContext processChange) - { - // start process - ProcessStateChange startChange = await ProcessStart(processChange.Instance, processChange.ProcessFlowElements[0], processChange.User); - InstanceEvent startEvent = CopyInstanceEventValue(startChange.Events.First()); - - ProcessStateChange nextChange = await ProcessNext(processChange.Instance, processChange.ProcessFlowElements[1], processChange.User); - InstanceEvent goToNextEvent = CopyInstanceEventValue(nextChange.Events.First()); - - ProcessStateChange processStateChange = new ProcessStateChange - { - OldProcessState = startChange.OldProcessState, - NewProcessState = nextChange.NewProcessState, - Events = new List { startEvent, goToNextEvent } - }; - processChange.ProcessStateChange = processStateChange; - - if (!processChange.DontUpdateProcessAndDispatchEvents) - { - processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); - } - - return processChange; - } - - /// - public async Task HandleStartTask(ProcessChangeContext processChange) - { - processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); - return processChange; - } - - /// - public async Task CanTaskBeEnded(ProcessChangeContext processChange) - { - List validationIssues = new List(); - - bool canEndTask; - - if (processChange.Instance.Process?.CurrentTask?.Validated == null || !processChange.Instance.Process.CurrentTask.Validated.CanCompleteTask) - { - validationIssues = await _validationService.ValidateAndUpdateProcess(processChange.Instance, processChange.Instance.Process.CurrentTask?.ElementId); - - canEndTask = await ProcessHelper.CanEndProcessTask(processChange.Instance, validationIssues); - } - else - { - canEndTask = await ProcessHelper.CanEndProcessTask(processChange.Instance, validationIssues); - } - - return canEndTask; - } - - /// - /// Identify the correct task implementation - /// - /// - private ITask GetProcessTask(string? altinnTaskType) - { - if (string.IsNullOrEmpty(altinnTaskType)) - { - return new NullTask(); - } - - ITask task = new DataTask(_taskEvents); - if (altinnTaskType.Equals("confirmation")) - { - task = new ConfirmationTask(_taskEvents); - } - else if (altinnTaskType.Equals("feedback")) - { - task = new FeedbackTask(_taskEvents); - } - - return task; - } - - /// - /// This - /// - private async Task UpdateProcessAndDispatchEvents(ProcessChangeContext processChangeContext) - { - await HandleProcessChanges(processChangeContext); - - // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived - Instance updatedInstance = await _instanceClient.UpdateProcess(processChangeContext.Instance); - await _processService.DispatchProcessEventsToStorage(updatedInstance, processChangeContext.ProcessStateChange.Events); - - // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. - updatedInstance = await _instanceClient.GetInstance(updatedInstance); - - return updatedInstance; - } - - /// - /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. - /// - /// Each implementation - /// - internal async Task HandleProcessChanges(ProcessChangeContext processChangeContext) - { - foreach (InstanceEvent processEvent in processChangeContext.ProcessStateChange.Events) - { - if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) - { - processChangeContext.ElementToBeProcessed = processEvent.ProcessInfo?.CurrentTask?.ElementId; - ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); - switch (eventType) - { - case InstanceEventType.process_StartEvent: - break; - case InstanceEventType.process_StartTask: - await task.HandleTaskStart(processChangeContext); - break; - case InstanceEventType.process_EndTask: - await task.HandleTaskComplete(processChangeContext); - break; - case InstanceEventType.process_AbandonTask: - await task.HandleTaskAbandon(processChangeContext); - await _instanceClient.UpdateProcess(processChangeContext.Instance); - break; - case InstanceEventType.process_EndEvent: - processChangeContext.ElementToBeProcessed = processEvent.ProcessInfo?.EndEvent; - await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, processChangeContext.Instance); - break; - } - } - } - } - - /// - /// Does not save process. Instance is updated. - /// - private async Task ProcessStart(Instance instance, string startEvent, ClaimsPrincipal user) - { - if (instance.Process == null) - { - DateTime now = DateTime.UtcNow; - - ProcessState startState = new ProcessState - { - Started = now, - StartEvent = startEvent, - CurrentTask = new ProcessElementInfo { Flow = 1 } - }; - - instance.Process = startState; - - List events = new List - { - await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString(), instance, now, user), - }; - - return new ProcessStateChange - { - OldProcessState = null!, - NewProcessState = startState, - Events = events, - }; - } - - return null; - } - - private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) - { - int? userId = user.GetUserIdAsInt(); - InstanceEvent instanceEvent = new InstanceEvent - { - InstanceId = instance.Id, - InstanceOwnerPartyId = instance.InstanceOwner.PartyId, - EventType = eventType, - Created = now, - User = new PlatformUser - { - UserId = userId, - AuthenticationLevel = user.GetAuthenticationLevel(), - OrgId = user.GetOrg() - }, - ProcessInfo = instance.Process, - }; - - if (string.IsNullOrEmpty(instanceEvent.User.OrgId) && userId != null) - { - UserProfile up = await _profileClient.GetUserProfile((int)userId); - instanceEvent.User.NationalIdentityNumber = up.Party.SSN; - } - - return instanceEvent; - } - - private static InstanceEvent CopyInstanceEventValue(InstanceEvent e) - { - return new InstanceEvent - { - Created = e.Created, - DataId = e.DataId, - EventType = e.EventType, - Id = e.Id, - InstanceId = e.InstanceId, - InstanceOwnerPartyId = e.InstanceOwnerPartyId, - ProcessInfo = new ProcessState - { - Started = e.ProcessInfo?.Started, - CurrentTask = new ProcessElementInfo - { - Flow = e.ProcessInfo?.CurrentTask.Flow, - AltinnTaskType = e.ProcessInfo?.CurrentTask.AltinnTaskType, - ElementId = e.ProcessInfo?.CurrentTask.ElementId, - Name = e.ProcessInfo?.CurrentTask.Name, - Started = e.ProcessInfo?.CurrentTask.Started, - Ended = e.ProcessInfo?.CurrentTask.Ended, - Validated = new ValidationStatus - { - CanCompleteTask = e.ProcessInfo?.CurrentTask?.Validated?.CanCompleteTask ?? false, - Timestamp = e.ProcessInfo?.CurrentTask?.Validated?.Timestamp - } - }, - - StartEvent = e.ProcessInfo?.StartEvent - }, - User = new PlatformUser - { - AuthenticationLevel = e.User.AuthenticationLevel, - EndUserSystemId = e.User.EndUserSystemId, - OrgId = e.User.OrgId, - UserId = e.User.UserId, - NationalIdentityNumber = e.User?.NationalIdentityNumber - } - }; - } - - /// - /// Moves instance's process to nextElement id. Returns the instance together with process events. - /// - public async Task ProcessNext(Instance instance, string? nextElementId, ClaimsPrincipal userContext) - { - if (instance.Process != null) - { - ProcessStateChange result = new ProcessStateChange - { - OldProcessState = new ProcessState() - { - Started = instance.Process.Started, - CurrentTask = instance.Process.CurrentTask, - StartEvent = instance.Process.StartEvent - } - }; - - result.Events = await MoveProcessToNext(instance, nextElementId, userContext); - result.NewProcessState = instance.Process; - return result; - } - - return null; - } - - /// - /// Assumes that nextElementId is a valid task/state - /// - private async Task> MoveProcessToNext( - Instance instance, - string? nextElementId, - ClaimsPrincipal user) - { - List events = new List(); - - ProcessState previousState = Copy(instance.Process); - ProcessState currentState = instance.Process; - string? previousElementId = currentState.CurrentTask?.ElementId; - - ElementInfo? nextElementInfo = _processReader.GetElementInfo(nextElementId); - List flows = _processReader.GetSequenceFlowsBetween(previousElementId, nextElementId); - ProcessSequenceFlowType sequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); - DateTime now = DateTime.UtcNow; - bool previousIsProcessTask = _processReader.IsProcessTask(previousElementId); - // ending previous element if task - if (previousIsProcessTask && sequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext)) - { - instance.Process = previousState; - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); - instance.Process = currentState; - } - else if (previousIsProcessTask) - { - instance.Process = previousState; - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_AbandonTask.ToString(), instance, now, user)); - instance.Process = currentState; - } - - // ending process if next element is end event - if (_processReader.IsEndEvent(nextElementId)) - { - currentState.CurrentTask = null; - currentState.Ended = now; - currentState.EndEvent = nextElementId; - - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); - - // add submit event (to support Altinn2 SBL) - events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); - } - else if (_processReader.IsProcessTask(nextElementId)) - { - currentState.CurrentTask = new ProcessElementInfo - { - Flow = currentState.CurrentTask.Flow + 1, - ElementId = nextElementId, - Name = nextElementInfo?.Name, - Started = now, - AltinnTaskType = nextElementInfo?.AltinnTaskType, - Validated = null, - FlowType = sequenceFlowType.ToString(), - }; - - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_StartTask.ToString(), instance, now, user)); - } - - // current state points to the instance's process object. The following statement is unnecessary, but clarifies logic. - instance.Process = currentState; - - return events; - } - - private async Task RegisterEventWithEventsComponent(Instance instance) - { - if (_appSettings.RegisterEventsWithEventsComponent) - { - try - { - if (!string.IsNullOrWhiteSpace(instance.Process.CurrentTask?.ElementId)) - { - await _eventsService.AddEvent($"app.instance.process.movedTo.{instance.Process.CurrentTask.ElementId}", instance); - } - else if (instance.Process.EndEvent != null) - { - await _eventsService.AddEvent("app.instance.process.completed", instance); - } - } - catch (Exception exception) - { - _logger.LogWarning(exception, "Exception when sending event with the Events component"); - } - } - } - - private static ProcessState Copy(ProcessState original) - { - ProcessState processState = new ProcessState(); - - if (original.CurrentTask != null) - { - processState.CurrentTask = new ProcessElementInfo(); - processState.CurrentTask.FlowType = original.CurrentTask.FlowType; - processState.CurrentTask.Name = original.CurrentTask.Name; - processState.CurrentTask.Validated = original.CurrentTask.Validated; - processState.CurrentTask.AltinnTaskType = original.CurrentTask.AltinnTaskType; - processState.CurrentTask.Flow = original.CurrentTask.Flow; - processState.CurrentTask.ElementId = original.CurrentTask.ElementId; - processState.CurrentTask.Started = original.CurrentTask.Started; - processState.CurrentTask.Ended = original.CurrentTask.Ended; - } - - processState.EndEvent = original.EndEvent; - processState.Started = original.Started; - processState.Ended = original.Ended; - processState.StartEvent = original.StartEvent; - - return processState; - } + // private readonly IInstance _instanceClient; + // private readonly IProcess _processService; + // private readonly IProcessReader _processReader; + // private readonly ILogger _logger; + // private readonly IValidation _validationService; + // private readonly IEvents _eventsService; + // private readonly IProfile _profileClient; + // private readonly AppSettings _appSettings; + // private readonly IAppEvents _appEvents; + // private readonly ITaskEvents _taskEvents; + // + // /// + // /// Altinn App specific process change handler + // /// + // public ProcessChangeHandler( + // ILogger logger, + // IProcess processService, + // IProcessReader processReader, + // IInstance instanceClient, + // IValidation validationService, + // IEvents eventsService, + // IProfile profileClient, + // IOptions appSettings, + // IAppEvents appEvents, + // ITaskEvents taskEvents) + // { + // _logger = logger; + // _processService = processService; + // _instanceClient = instanceClient; + // _processReader = processReader; + // _validationService = validationService; + // _eventsService = eventsService; + // _profileClient = profileClient; + // _appSettings = appSettings.Value; + // _appEvents = appEvents; + // _taskEvents = taskEvents; + // } + // + // /// + // public async Task HandleMoveToNext(ProcessChangeContext processChange) + // { + // processChange.ProcessStateChange = await ProcessNext(processChange.Instance, processChange.RequestedProcessElementId, processChange.User); + // if (processChange.ProcessStateChange != null) + // { + // processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); + // + // await RegisterEventWithEventsComponent(processChange.Instance); + // } + // + // return processChange; + // } + // + // /// + // public async Task HandleStart(ProcessChangeContext processChange) + // { + // // start process + // ProcessStateChange startChange = await ProcessStart(processChange.Instance, processChange.ProcessFlowElements[0], processChange.User); + // InstanceEvent startEvent = CopyInstanceEventValue(startChange.Events.First()); + // + // ProcessStateChange nextChange = await ProcessNext(processChange.Instance, processChange.ProcessFlowElements[1], processChange.User); + // InstanceEvent goToNextEvent = CopyInstanceEventValue(nextChange.Events.First()); + // + // ProcessStateChange processStateChange = new ProcessStateChange + // { + // OldProcessState = startChange.OldProcessState, + // NewProcessState = nextChange.NewProcessState, + // Events = new List { startEvent, goToNextEvent } + // }; + // processChange.ProcessStateChange = processStateChange; + // + // if (!processChange.DontUpdateProcessAndDispatchEvents) + // { + // processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); + // } + // + // return processChange; + // } + // + // /// + // public async Task HandleStartTask(ProcessChangeContext processChange) + // { + // processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); + // return processChange; + // } + // + // /// + // public async Task CanTaskBeEnded(ProcessChangeContext processChange) + // { + // List validationIssues = new List(); + // + // bool canEndTask; + // + // if (processChange.Instance.Process?.CurrentTask?.Validated == null || !processChange.Instance.Process.CurrentTask.Validated.CanCompleteTask) + // { + // validationIssues = await _validationService.ValidateAndUpdateProcess(processChange.Instance, processChange.Instance.Process.CurrentTask?.ElementId); + // + // canEndTask = await ProcessHelper.CanEndProcessTask(processChange.Instance, validationIssues); + // } + // else + // { + // canEndTask = await ProcessHelper.CanEndProcessTask(processChange.Instance, validationIssues); + // } + // + // return canEndTask; + // } + // + // /// + // /// Identify the correct task implementation + // /// + // /// + // private ITask GetProcessTask(string? altinnTaskType) + // { + // if (string.IsNullOrEmpty(altinnTaskType)) + // { + // return new NullTask(); + // } + // + // ITask task = new DataTask(_taskEvents); + // if (altinnTaskType.Equals("confirmation")) + // { + // task = new ConfirmationTask(_taskEvents); + // } + // else if (altinnTaskType.Equals("feedback")) + // { + // task = new FeedbackTask(_taskEvents); + // } + // + // return task; + // } + // + // /// + // /// This + // /// + // private async Task UpdateProcessAndDispatchEvents(ProcessChangeContext processChangeContext) + // { + // await HandleProcessChanges(processChangeContext); + // + // // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived + // Instance updatedInstance = await _instanceClient.UpdateProcess(processChangeContext.Instance); + // await _processService.DispatchProcessEventsToStorage(updatedInstance, processChangeContext.ProcessStateChange.Events); + // + // // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. + // updatedInstance = await _instanceClient.GetInstance(updatedInstance); + // + // return updatedInstance; + // } + // + // /// + // /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. + // /// + // /// Each implementation + // /// + // internal async Task HandleProcessChanges(ProcessChangeContext processChangeContext) + // { + // foreach (InstanceEvent processEvent in processChangeContext.ProcessStateChange.Events) + // { + // if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) + // { + // processChangeContext.ElementToBeProcessed = processEvent.ProcessInfo?.CurrentTask?.ElementId; + // ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); + // switch (eventType) + // { + // case InstanceEventType.process_StartEvent: + // break; + // case InstanceEventType.process_StartTask: + // await task.HandleTaskStart(processChangeContext.Instance.Process.CurrentTask.ElementId, processChangeContext.Instance, processChangeContext.Prefill); + // break; + // case InstanceEventType.process_EndTask: + // await task.HandleTaskComplete(processChangeContext.Instance.Process.CurrentTask.ElementId, processChangeContext.Instance); + // break; + // case InstanceEventType.process_AbandonTask: + // await task.HandleTaskAbandon(processChangeContext.Instance.Process.CurrentTask.ElementId, processChangeContext.Instance); + // await _instanceClient.UpdateProcess(processChangeContext.Instance); + // break; + // case InstanceEventType.process_EndEvent: + // processChangeContext.ElementToBeProcessed = processEvent.ProcessInfo?.EndEvent; + // await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, processChangeContext.Instance); + // break; + // } + // } + // } + // } + // + // /// + // /// Does not save process. Instance is updated. + // /// + // private async Task ProcessStart(Instance instance, string startEvent, ClaimsPrincipal user) + // { + // if (instance.Process == null) + // { + // DateTime now = DateTime.UtcNow; + // + // ProcessState startState = new ProcessState + // { + // Started = now, + // StartEvent = startEvent, + // CurrentTask = new ProcessElementInfo { Flow = 1 } + // }; + // + // instance.Process = startState; + // + // List events = new List + // { + // await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString(), instance, now, user), + // }; + // + // return new ProcessStateChange + // { + // OldProcessState = null!, + // NewProcessState = startState, + // Events = events, + // }; + // } + // + // return null; + // } + // + // private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) + // { + // int? userId = user.GetUserIdAsInt(); + // InstanceEvent instanceEvent = new InstanceEvent + // { + // InstanceId = instance.Id, + // InstanceOwnerPartyId = instance.InstanceOwner.PartyId, + // EventType = eventType, + // Created = now, + // User = new PlatformUser + // { + // UserId = userId, + // AuthenticationLevel = user.GetAuthenticationLevel(), + // OrgId = user.GetOrg() + // }, + // ProcessInfo = instance.Process, + // }; + // + // if (string.IsNullOrEmpty(instanceEvent.User.OrgId) && userId != null) + // { + // UserProfile up = await _profileClient.GetUserProfile((int)userId); + // instanceEvent.User.NationalIdentityNumber = up.Party.SSN; + // } + // + // return instanceEvent; + // } + // + // private static InstanceEvent CopyInstanceEventValue(InstanceEvent e) + // { + // return new InstanceEvent + // { + // Created = e.Created, + // DataId = e.DataId, + // EventType = e.EventType, + // Id = e.Id, + // InstanceId = e.InstanceId, + // InstanceOwnerPartyId = e.InstanceOwnerPartyId, + // ProcessInfo = new ProcessState + // { + // Started = e.ProcessInfo?.Started, + // CurrentTask = new ProcessElementInfo + // { + // Flow = e.ProcessInfo?.CurrentTask.Flow, + // AltinnTaskType = e.ProcessInfo?.CurrentTask.AltinnTaskType, + // ElementId = e.ProcessInfo?.CurrentTask.ElementId, + // Name = e.ProcessInfo?.CurrentTask.Name, + // Started = e.ProcessInfo?.CurrentTask.Started, + // Ended = e.ProcessInfo?.CurrentTask.Ended, + // Validated = new ValidationStatus + // { + // CanCompleteTask = e.ProcessInfo?.CurrentTask?.Validated?.CanCompleteTask ?? false, + // Timestamp = e.ProcessInfo?.CurrentTask?.Validated?.Timestamp + // } + // }, + // + // StartEvent = e.ProcessInfo?.StartEvent + // }, + // User = new PlatformUser + // { + // AuthenticationLevel = e.User.AuthenticationLevel, + // EndUserSystemId = e.User.EndUserSystemId, + // OrgId = e.User.OrgId, + // UserId = e.User.UserId, + // NationalIdentityNumber = e.User?.NationalIdentityNumber + // } + // }; + // } + // + // /// + // /// Moves instance's process to nextElement id. Returns the instance together with process events. + // /// + // public async Task ProcessNext(Instance instance, string? nextElementId, ClaimsPrincipal userContext) + // { + // if (instance.Process != null) + // { + // ProcessStateChange result = new ProcessStateChange + // { + // OldProcessState = new ProcessState() + // { + // Started = instance.Process.Started, + // CurrentTask = instance.Process.CurrentTask, + // StartEvent = instance.Process.StartEvent + // } + // }; + // + // result.Events = await MoveProcessToNext(instance, nextElementId, userContext); + // result.NewProcessState = instance.Process; + // return result; + // } + // + // return null; + // } + // + // /// + // /// Assumes that nextElementId is a valid task/state + // /// + // private async Task> MoveProcessToNext( + // Instance instance, + // string? nextElementId, + // ClaimsPrincipal user) + // { + // List events = new List(); + // + // ProcessState previousState = Copy(instance.Process); + // ProcessState currentState = instance.Process; + // string? previousElementId = currentState.CurrentTask?.ElementId; + // + // ElementInfo? nextElementInfo = _processReader.GetElementInfo(nextElementId); + // List flows = _processReader.GetSequenceFlowsBetween(previousElementId, nextElementId); + // ProcessSequenceFlowType sequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); + // DateTime now = DateTime.UtcNow; + // bool previousIsProcessTask = _processReader.IsProcessTask(previousElementId); + // // ending previous element if task + // if (previousIsProcessTask && sequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext)) + // { + // instance.Process = previousState; + // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); + // instance.Process = currentState; + // } + // else if (previousIsProcessTask) + // { + // instance.Process = previousState; + // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_AbandonTask.ToString(), instance, now, user)); + // instance.Process = currentState; + // } + // + // // ending process if next element is end event + // if (_processReader.IsEndEvent(nextElementId)) + // { + // currentState.CurrentTask = null; + // currentState.Ended = now; + // currentState.EndEvent = nextElementId; + // + // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); + // + // // add submit event (to support Altinn2 SBL) + // events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); + // } + // else if (_processReader.IsProcessTask(nextElementId)) + // { + // currentState.CurrentTask = new ProcessElementInfo + // { + // Flow = currentState.CurrentTask.Flow + 1, + // ElementId = nextElementId, + // Name = nextElementInfo?.Name, + // Started = now, + // AltinnTaskType = nextElementInfo?.AltinnTaskType, + // Validated = null, + // FlowType = sequenceFlowType.ToString(), + // }; + // + // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_StartTask.ToString(), instance, now, user)); + // } + // + // // current state points to the instance's process object. The following statement is unnecessary, but clarifies logic. + // instance.Process = currentState; + // + // return events; + // } + // + // private async Task RegisterEventWithEventsComponent(Instance instance) + // { + // if (_appSettings.RegisterEventsWithEventsComponent) + // { + // try + // { + // if (!string.IsNullOrWhiteSpace(instance.Process.CurrentTask?.ElementId)) + // { + // await _eventsService.AddEvent($"app.instance.process.movedTo.{instance.Process.CurrentTask.ElementId}", instance); + // } + // else if (instance.Process.EndEvent != null) + // { + // await _eventsService.AddEvent("app.instance.process.completed", instance); + // } + // } + // catch (Exception exception) + // { + // _logger.LogWarning(exception, "Exception when sending event with the Events component"); + // } + // } + // } + // + // private static ProcessState Copy(ProcessState original) + // { + // ProcessState processState = new ProcessState(); + // + // if (original.CurrentTask != null) + // { + // processState.CurrentTask = new ProcessElementInfo(); + // processState.CurrentTask.FlowType = original.CurrentTask.FlowType; + // processState.CurrentTask.Name = original.CurrentTask.Name; + // processState.CurrentTask.Validated = original.CurrentTask.Validated; + // processState.CurrentTask.AltinnTaskType = original.CurrentTask.AltinnTaskType; + // processState.CurrentTask.Flow = original.CurrentTask.Flow; + // processState.CurrentTask.ElementId = original.CurrentTask.ElementId; + // processState.CurrentTask.Started = original.CurrentTask.Started; + // processState.CurrentTask.Ended = original.CurrentTask.Ended; + // } + // + // processState.EndEvent = original.EndEvent; + // processState.Started = original.Started; + // processState.Ended = original.Ended; + // processState.StartEvent = original.StartEvent; + // + // return processState; + // } } } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs index 2f08f41ca..e7bfb2468 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs @@ -15,124 +15,124 @@ namespace Altinn.App.Core.Internal.Process /// public class ProcessEngine : IProcessEngine { - private readonly IProcessChangeHandler _processChangeHandler; - - private readonly IProcessReader _processReader; - private readonly IFlowHydration _flowHydration; - - /// - /// Initializes a new instance of the class. - /// - public ProcessEngine( - IProcessChangeHandler processChangeHandler, - IProcessReader processReader, - IFlowHydration flowHydration) - { - _processChangeHandler = processChangeHandler; - _processReader = processReader; - _flowHydration = flowHydration; - } - - /// - /// Move process to next element in process - /// - public async Task Next(ProcessChangeContext processChange) - { - string? currentElementId = processChange.Instance.Process.CurrentTask?.ElementId; - - if (currentElementId == null) - { - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Instance does not have current task information!", Type = "Conflict" }); - processChange.FailedProcessChange = true; - return processChange; - } - - if (currentElementId.Equals(processChange.RequestedProcessElementId)) - { - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Requested process element {processChange.RequestedProcessElementId} is same as instance's current task. Cannot change process.", Type = "Conflict" }); - processChange.FailedProcessChange = true; - return processChange; - } - - // Find next valid element. Later this will be dynamic - List possibleNextElements = await _flowHydration.NextFollowAndFilterGateways(processChange.Instance, currentElementId, processChange.RequestedProcessElementId.IsNullOrEmpty()); - processChange.RequestedProcessElementId = ProcessHelper.GetValidNextElementOrError(processChange.RequestedProcessElementId, possibleNextElements.Select(e => e.Id).ToList(),out ProcessError? nextElementError); - if (nextElementError != null) - { - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = nextElementError.Text, Type = "Conflict" }); - processChange.FailedProcessChange = true; - return processChange; - } - - List flows = _processReader.GetSequenceFlowsBetween(currentElementId, processChange.RequestedProcessElementId); - processChange.ProcessSequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); - - if (processChange.ProcessSequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext) && await _processChangeHandler.CanTaskBeEnded(processChange)) - { - return await _processChangeHandler.HandleMoveToNext(processChange); - } - - if (processChange.ProcessSequenceFlowType.Equals(ProcessSequenceFlowType.AbandonCurrentReturnToNext)) - { - return await _processChangeHandler.HandleMoveToNext(processChange); - } - - processChange.FailedProcessChange = true; - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Cannot complete/close current task {currentElementId}. The data element(s) assigned to the task are not valid!", Type = "conflict" }); - return processChange; - } - - /// - /// Start application process and goes to first valid Task - /// - public async Task StartProcess(ProcessChangeContext processChange) - { - if (processChange.Instance.Process != null) - { - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = "Process is already started. Use next.", Type = "Conflict" }); - processChange.FailedProcessChange = true; - return processChange; - } - - string? validStartElement = ProcessHelper.GetValidStartEventOrError(processChange.RequestedProcessElementId, _processReader.GetStartEventIds(),out ProcessError? startEventError); - if (startEventError != null) - { - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = "No matching startevent", Type = "Conflict" }); - processChange.FailedProcessChange = true; - return processChange; - } - - processChange.ProcessFlowElements = new List(); - processChange.ProcessFlowElements.Add(validStartElement!); - - // find next task - List possibleNextElements = (await _flowHydration.NextFollowAndFilterGateways(processChange.Instance, validStartElement)); - string? nextValidElement = ProcessHelper.GetValidNextElementOrError(null, possibleNextElements.Select(e => e.Id).ToList(),out ProcessError? nextElementError); - if (nextElementError != null) - { - processChange.ProcessMessages = new List(); - processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Unable to goto next element due to {nextElementError.Code}-{nextElementError.Text}", Type = "Conflict" }); - processChange.FailedProcessChange = true; - return processChange; - } - - processChange.ProcessFlowElements.Add(nextValidElement!); - - return await _processChangeHandler.HandleStart(processChange); - } - - /// - /// Process Start Current task. The main goal is to trigger the Task related business logic seperate from start process - /// - public async Task StartTask(ProcessChangeContext processChange) - { - return await _processChangeHandler.HandleStartTask(processChange); - } + // private readonly IProcessChangeHandler _processChangeHandler; + // + // private readonly IProcessReader _processReader; + // private readonly IFlowHydration _flowHydration; + // + // /// + // /// Initializes a new instance of the class. + // /// + // public ProcessEngine( + // IProcessChangeHandler processChangeHandler, + // IProcessReader processReader, + // IFlowHydration flowHydration) + // { + // _processChangeHandler = processChangeHandler; + // _processReader = processReader; + // _flowHydration = flowHydration; + // } + // + // /// + // /// Move process to next element in process + // /// + // public async Task Next(ProcessChangeContext processChange) + // { + // string? currentElementId = processChange.Instance.Process.CurrentTask?.ElementId; + // + // if (currentElementId == null) + // { + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Instance does not have current task information!", Type = "Conflict" }); + // processChange.FailedProcessChange = true; + // return processChange; + // } + // + // if (currentElementId.Equals(processChange.RequestedProcessElementId)) + // { + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Requested process element {processChange.RequestedProcessElementId} is same as instance's current task. Cannot change process.", Type = "Conflict" }); + // processChange.FailedProcessChange = true; + // return processChange; + // } + // + // // Find next valid element. Later this will be dynamic + // List possibleNextElements = await _flowHydration.NextFollowAndFilterGateways(processChange.Instance, currentElementId, processChange.RequestedProcessElementId.IsNullOrEmpty()); + // processChange.RequestedProcessElementId = ProcessHelper.GetValidNextElementOrError(processChange.RequestedProcessElementId, possibleNextElements.Select(e => e.Id).ToList(),out ProcessError? nextElementError); + // if (nextElementError != null) + // { + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = nextElementError.Text, Type = "Conflict" }); + // processChange.FailedProcessChange = true; + // return processChange; + // } + // + // List flows = _processReader.GetSequenceFlowsBetween(currentElementId, processChange.RequestedProcessElementId); + // processChange.ProcessSequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); + // + // if (processChange.ProcessSequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext) && await _processChangeHandler.CanTaskBeEnded(processChange)) + // { + // return await _processChangeHandler.HandleMoveToNext(processChange); + // } + // + // if (processChange.ProcessSequenceFlowType.Equals(ProcessSequenceFlowType.AbandonCurrentReturnToNext)) + // { + // return await _processChangeHandler.HandleMoveToNext(processChange); + // } + // + // processChange.FailedProcessChange = true; + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Cannot complete/close current task {currentElementId}. The data element(s) assigned to the task are not valid!", Type = "conflict" }); + // return processChange; + // } + // + // /// + // /// Start application process and goes to first valid Task + // /// + // public async Task StartProcess(ProcessChangeContext processChange) + // { + // if (processChange.Instance.Process != null) + // { + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = "Process is already started. Use next.", Type = "Conflict" }); + // processChange.FailedProcessChange = true; + // return processChange; + // } + // + // string? validStartElement = ProcessHelper.GetValidStartEventOrError(processChange.RequestedProcessElementId, _processReader.GetStartEventIds(),out ProcessError? startEventError); + // if (startEventError != null) + // { + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = "No matching startevent", Type = "Conflict" }); + // processChange.FailedProcessChange = true; + // return processChange; + // } + // + // processChange.ProcessFlowElements = new List(); + // processChange.ProcessFlowElements.Add(validStartElement!); + // + // // find next task + // List possibleNextElements = (await _flowHydration.NextFollowAndFilterGateways(processChange.Instance, validStartElement)); + // string? nextValidElement = ProcessHelper.GetValidNextElementOrError(null, possibleNextElements.Select(e => e.Id).ToList(),out ProcessError? nextElementError); + // if (nextElementError != null) + // { + // processChange.ProcessMessages = new List(); + // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Unable to goto next element due to {nextElementError.Code}-{nextElementError.Text}", Type = "Conflict" }); + // processChange.FailedProcessChange = true; + // return processChange; + // } + // + // processChange.ProcessFlowElements.Add(nextValidElement!); + // + // return await _processChangeHandler.HandleStart(processChange); + // } + // + // /// + // /// Process Start Current task. The main goal is to trigger the Task related business logic seperate from start process + // /// + // public async Task StartTask(ProcessChangeContext processChange) + // { + // return await _processChangeHandler.HandleStartTask(processChange); + // } } } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessReader.cs b/src/Altinn.App.Core/Internal/Process/ProcessReader.cs index 9314102e2..5a5669914 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessReader.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessReader.cs @@ -10,224 +10,225 @@ namespace Altinn.App.Core.Internal.Process; /// public class ProcessReader : IProcessReader { - private readonly Definitions _definitions; - - /// - /// Create instance of ProcessReader where process stream is fetched from - /// - /// Implementation of IProcess used to get stream of BPMN process - /// If BPMN file could not be deserialized - public ProcessReader(IProcess processService) - { - XmlSerializer serializer = new XmlSerializer(typeof(Definitions)); - Definitions? definitions = (Definitions?)serializer.Deserialize(processService.GetProcessDefinition()); - - _definitions = definitions ?? throw new InvalidOperationException("Failed to deserialize BPMN definitions. Definitions was null"); - } - - /// - public List GetStartEvents() - { - return _definitions.Process.StartEvents; - } - - /// - public List GetStartEventIds() - { - return GetStartEvents().Select(s => s.Id).ToList(); - } - - /// - public bool IsStartEvent(string? elementId) - { - return elementId != null && GetStartEventIds().Contains(elementId); - } - - /// - public List GetProcessTasks() - { - return _definitions.Process.Tasks; - } - - /// - public List GetProcessTaskIds() - { - return GetProcessTasks().Select(t => t.Id).ToList(); - } - - /// - public bool IsProcessTask(string? elementId) - { - return elementId != null && GetProcessTaskIds().Contains(elementId); - } - - /// - public List GetExclusiveGateways() - { - return _definitions.Process.ExclusiveGateway; - } - - /// - public List GetExclusiveGatewayIds() - { - return GetExclusiveGateways().Select(g => g.Id).ToList(); - } - - /// - public List GetEndEvents() - { - return _definitions.Process.EndEvents; - } - - /// - public List GetEndEventIds() - { - return GetEndEvents().Select(e => e.Id).ToList(); - } - - /// - public bool IsEndEvent(string? elementId) - { - return elementId != null && GetEndEventIds().Contains(elementId); - } - - /// - public List GetSequenceFlows() - { - return _definitions.Process.SequenceFlow; - } - - /// - public List GetSequenceFlowIds() - { - return GetSequenceFlows().Select(s => s.Id).ToList(); - } - - /// - public List GetNextElements(string? currentElementId) - { - EnsureArgumentNotNull(currentElementId, nameof(currentElementId)); - List nextElements = new List(); - List allElements = GetAllFlowElements(); - if (!allElements.Exists(e => e.Id == currentElementId)) - { - throw new ProcessException($"Unable to find a element using element id {currentElementId}."); - } - - foreach (SequenceFlow sequenceFlow in GetSequenceFlows().FindAll(s => s.SourceRef == currentElementId)) - { - nextElements.AddRange(allElements.FindAll(e => sequenceFlow.TargetRef == e.Id)); - } - - return nextElements; - } - - /// - public List GetNextElementIds(string? currentElement) - { - return GetNextElements(currentElement).Select(e => e.Id).ToList(); - } - - /// - public List GetOutgoingSequenceFlows(ProcessElement? flowElement) - { - if (flowElement == null) - { - return new List(); - } - - return GetSequenceFlows().FindAll(sf => flowElement.Outgoing.Contains(sf.Id)).ToList(); - } - - /// - public List GetSequenceFlowsBetween(string? currentStepId, string? nextElementId) - { - List flowsToReachTarget = new List(); - foreach (SequenceFlow sequenceFlow in _definitions.Process.SequenceFlow.FindAll(s => s.SourceRef == currentStepId)) - { - if (sequenceFlow.TargetRef.Equals(nextElementId)) - { - flowsToReachTarget.Add(sequenceFlow); - return flowsToReachTarget; - } - - if (_definitions.Process.ExclusiveGateway != null && _definitions.Process.ExclusiveGateway.FirstOrDefault(g => g.Id == sequenceFlow.TargetRef) != null) - { - List subGatewayFlows = GetSequenceFlowsBetween(sequenceFlow.TargetRef, nextElementId); - if (subGatewayFlows.Any()) - { - flowsToReachTarget.Add(sequenceFlow); - flowsToReachTarget.AddRange(subGatewayFlows); - return flowsToReachTarget; - } - } - } - - return flowsToReachTarget; - } - - /// - public ProcessElement? GetFlowElement(string? elementId) - { - EnsureArgumentNotNull(elementId, nameof(elementId)); - - ProcessTask? task = _definitions.Process.Tasks.Find(t => t.Id == elementId); - if (task != null) - { - return task; - } - - EndEvent? endEvent = _definitions.Process.EndEvents.Find(e => e.Id == elementId); - if (endEvent != null) - { - return endEvent; - } - - StartEvent? startEvent = _definitions.Process.StartEvents.Find(e => e.Id == elementId); - if (startEvent != null) - { - return startEvent; - } - - return _definitions.Process.ExclusiveGateway.Find(e => e.Id == elementId); - } - - /// - public ElementInfo? GetElementInfo(string? elementId) - { - var e = GetFlowElement(elementId); - if (e == null || e is ExclusiveGateway) - { - return null; - } - - ElementInfo elementInfo = new ElementInfo() - { - Id = e.Id, - Name = e.Name, - ElementType = e.ElementType() - }; - if (e is ProcessTask task) - { - elementInfo.AltinnTaskType = task.TaskType; - } - - return elementInfo; - } - - private List GetAllFlowElements() - { - List flowElements = new List(); - flowElements.AddRange(GetStartEvents()); - flowElements.AddRange(GetProcessTasks()); - flowElements.AddRange(GetExclusiveGateways()); - flowElements.AddRange(GetEndEvents()); - return flowElements; - } - - private static void EnsureArgumentNotNull(object? argument, string paramName) - { - if (argument == null) - throw new ArgumentNullException(paramName); - } + // private readonly Definitions _definitions; + // + // /// + // /// Create instance of ProcessReader where process stream is fetched from + // /// + // /// Implementation of IProcess used to get stream of BPMN process + // /// If BPMN file could not be deserialized + // public ProcessReader(IProcess processService) + // { + // XmlSerializer serializer = new XmlSerializer(typeof(Definitions)); + // Definitions? definitions = (Definitions?)serializer.Deserialize(processService.GetProcessDefinition()); + // + // _definitions = definitions ?? throw new InvalidOperationException("Failed to deserialize BPMN definitions. Definitions was null"); + // } + // + // /// + // public List GetStartEvents() + // { + // return _definitions.Process.StartEvents; + // } + // + // /// + // public List GetStartEventIds() + // { + // return GetStartEvents().Select(s => s.Id).ToList(); + // } + // + // /// + // public bool IsStartEvent(string? elementId) + // { + // return elementId != null && GetStartEventIds().Contains(elementId); + // } + // + // /// + // public List GetProcessTasks() + // { + // return _definitions.Process.Tasks; + // } + // + // /// + // public List GetProcessTaskIds() + // { + // return GetProcessTasks().Select(t => t.Id).ToList(); + // } + // + // /// + // public bool IsProcessTask(string? elementId) + // { + // return elementId != null && GetProcessTaskIds().Contains(elementId); + // } + // + // /// + // public List GetExclusiveGateways() + // { + // return _definitions.Process.ExclusiveGateway; + // } + // + // /// + // public List GetExclusiveGatewayIds() + // { + // return GetExclusiveGateways().Select(g => g.Id).ToList(); + // } + // + // /// + // public List GetEndEvents() + // { + // return _definitions.Process.EndEvents; + // } + // + // /// + // public List GetEndEventIds() + // { + // return GetEndEvents().Select(e => e.Id).ToList(); + // } + // + // /// + // public bool IsEndEvent(string? elementId) + // { + // return elementId != null && GetEndEventIds().Contains(elementId); + // } + // + // /// + // public List GetSequenceFlows() + // { + // return _definitions.Process.SequenceFlow; + // } + // + // /// + // public List GetSequenceFlowIds() + // { + // return GetSequenceFlows().Select(s => s.Id).ToList(); + // } + // + // /// + // public List GetNextElements(string? currentElementId) + // { + // EnsureArgumentNotNull(currentElementId, nameof(currentElementId)); + // List nextElements = new List(); + // List allElements = GetAllFlowElements(); + // if (!allElements.Exists(e => e.Id == currentElementId)) + // { + // throw new ProcessException($"Unable to find a element using element id {currentElementId}."); + // } + // + // foreach (SequenceFlow sequenceFlow in GetSequenceFlows().FindAll(s => s.SourceRef == currentElementId)) + // { + // nextElements.AddRange(allElements.FindAll(e => sequenceFlow.TargetRef == e.Id)); + // } + // + // return nextElements; + // } + // + // /// + // public List GetNextElementIds(string? currentElement) + // { + // return GetNextElements(currentElement).Select(e => e.Id).ToList(); + // } + // + // /// + // public List GetOutgoingSequenceFlows(ProcessElement? flowElement) + // { + // if (flowElement == null) + // { + // return new List(); + // } + // + // return GetSequenceFlows().FindAll(sf => flowElement.Outgoing.Contains(sf.Id)).ToList(); + // } + // + // /// + // public List GetSequenceFlowsBetween(string? currentStepId, string? nextElementId) + // { + // List flowsToReachTarget = new List(); + // foreach (SequenceFlow sequenceFlow in _definitions.Process.SequenceFlow.FindAll(s => s.SourceRef == currentStepId)) + // { + // if (sequenceFlow.TargetRef.Equals(nextElementId)) + // { + // flowsToReachTarget.Add(sequenceFlow); + // return flowsToReachTarget; + // } + // + // if (_definitions.Process.ExclusiveGateway != null && _definitions.Process.ExclusiveGateway.FirstOrDefault(g => g.Id == sequenceFlow.TargetRef) != null) + // { + // List subGatewayFlows = GetSequenceFlowsBetween(sequenceFlow.TargetRef, nextElementId); + // if (subGatewayFlows.Any()) + // { + // flowsToReachTarget.Add(sequenceFlow); + // flowsToReachTarget.AddRange(subGatewayFlows); + // return flowsToReachTarget; + // } + // } + // } + // + // return flowsToReachTarget; + // } + // + // /// + // public ProcessElement? GetFlowElement(string? elementId) + // { + // EnsureArgumentNotNull(elementId, nameof(elementId)); + // + // ProcessTask? task = _definitions.Process.Tasks.Find(t => t.Id == elementId); + // if (task != null) + // { + // return task; + // } + // + // EndEvent? endEvent = _definitions.Process.EndEvents.Find(e => e.Id == elementId); + // if (endEvent != null) + // { + // return endEvent; + // } + // + // StartEvent? startEvent = _definitions.Process.StartEvents.Find(e => e.Id == elementId); + // if (startEvent != null) + // { + // return startEvent; + // } + // + // return _definitions.Process.ExclusiveGateway.Find(e => e.Id == elementId); + // } + // + // /// + // public ElementInfo? GetElementInfo(string? elementId) + // { + // var e = GetFlowElement(elementId); + // if (e == null || e is ExclusiveGateway) + // { + // return null; + // } + // + // ElementInfo elementInfo = new ElementInfo() + // { + // Id = e.Id, + // Name = e.Name, + // ElementType = e.ElementType() + // }; + // if (e is ProcessTask task) + // { + // elementInfo.AltinnTaskType = task.ExtensionElements?.AltinnProperties?.TaskType ?? task.TaskType; + // elementInfo.AltinnTaskActions = task.ExtensionElements?.AltinnProperties?.AltinnActions?.ConvertAll(a => a.Id); + // } + // + // return elementInfo; + // } + // + // private List GetAllFlowElements() + // { + // List flowElements = new List(); + // flowElements.AddRange(GetStartEvents()); + // flowElements.AddRange(GetProcessTasks()); + // flowElements.AddRange(GetExclusiveGateways()); + // flowElements.AddRange(GetEndEvents()); + // return flowElements; + // } + // + // private static void EnsureArgumentNotNull(object? argument, string paramName) + // { + // if (argument == null) + // throw new ArgumentNullException(paramName); + // } } diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs new file mode 100644 index 000000000..b009f41dd --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs @@ -0,0 +1,31 @@ +using System.Security.Claims; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.V2 +{ + /// + /// Process engine interface that defines the Altinn App process engine + /// + public interface IProcessEngine + { + /// + /// Method to start a new process + /// + Task StartProcess(ProcessStartRequest processStartRequest); + + /// + /// Method to move process to next task/event + /// + Task Next(ProcessNextRequest request); + + /// + /// Update Instance and rerun instance events + /// + /// + /// + /// + Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events); + } +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs new file mode 100644 index 000000000..1f41fd645 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs @@ -0,0 +1,21 @@ +using Altinn.App.Core.Features; +using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.V2 +{ + /// + /// Interface used to descipt the process navigator + /// + public interface IProcessNavigator + { + /// + /// Get the next task in the process from the current element based on the action and datadriven gateway decisions + /// + /// Instance data + /// Current process element id + /// Action performed + /// The next process task + public Task GetNextTask(Instance instance, string currentElement, string? action); + } +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs b/src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs new file mode 100644 index 000000000..88f91b326 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs @@ -0,0 +1,114 @@ +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.Base; + +namespace Altinn.App.Core.Internal.Process.V2; + +/// +/// Interface for classes that reads the applications process +/// +public interface IProcessReader +{ + + /// + /// Get all defined StartEvents in the process + /// + /// + public List GetStartEvents(); + + /// + /// Get ids of all defined StartEvents in the process + /// + /// + public List GetStartEventIds(); + + /// + /// Check id element is StartEvent + /// + /// Id of process element + /// true if elementId is of type StartEvent + public bool IsStartEvent(string? elementId); + + /// + /// Get all defined Tasks in the process + /// + /// + public List GetProcessTasks(); + + /// + /// Get ids of all defined Tasks in the process + /// + /// + public List GetProcessTaskIds(); + + /// + /// Check id element is ProcessTask + /// + /// Id of process element + /// true if elementId is of type Task + public bool IsProcessTask(string? elementId); + + /// + /// Get all ExclusiveGateways defined in the process + /// + /// + public List GetExclusiveGateways(); + + /// + /// Get ids of all defined ExclusiveGateways in the process + /// + /// + public List GetExclusiveGatewayIds(); + + /// + /// Get all EndEvents defined in the process + /// + /// + public List GetEndEvents(); + + /// + /// Get ids of all EndEvents defined in the process + /// + /// + public List GetEndEventIds(); + + /// + /// Check id element is EndEvent + /// + /// Id of process element + /// true if elementId is of type EndEvent + public bool IsEndEvent(string? elementId); + + /// + /// Get all SequenceFlows defined in the process + /// + /// + public List GetSequenceFlows(); + + /// + /// Get SequenceFlows out of the bpmn element + /// + /// Element to get the outgoing sequenceflows from + /// Outgoing sequence flows + public List GetOutgoingSequenceFlows(ProcessElement? flowElement); + + /// + /// Get ids of all SequenceFlows defined in the process + /// + /// + public List GetSequenceFlowIds(); + + /// + /// Find all possible next elements from current element + /// + /// Current process element id + /// + public List GetNextElements(string? currentElementId); + + /// + /// Returns StartEvent, Task or EndEvent with given Id, null if element not found + /// + /// Id of element to look for + /// or null + public ProcessElement? GetFlowElement(string? elementId); + +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs new file mode 100644 index 000000000..7b599409e --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs @@ -0,0 +1,409 @@ +using System.Security.Claims; +using Altinn.App.Core.Configuration; +using Altinn.App.Core.Extensions; +using Altinn.App.Core.Helpers; +using Altinn.App.Core.Interface; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.App.Core.Models; +using Altinn.Platform.Profile.Models; +using Altinn.Platform.Storage.Interface.Enums; +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Altinn.App.Core.Internal.Process.V2; + +/// +/// Default implementation of the +/// +public class ProcessEngine : IProcessEngine +{ + private readonly IInstance _instanceService; + private readonly IProcessReader _processReader; + private readonly IProfile _profileService; + private readonly IProcess _processClient; + private readonly IAppEvents _appEvents; + private readonly ITaskEvents _taskEvents; + private readonly IProcessNavigator _processNavigator; + private readonly IEvents _eventsService; + private readonly ILogger _logger; + private readonly bool _registerWithEventSystem; + + /// + /// Initializes a new instance of the class + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public ProcessEngine( + IInstance instanceService, + IProcessReader processReader, + IProfile profileService, + IProcess processClient, + IAppEvents appEvents, + ITaskEvents taskEvents, + IProcessNavigator processNavigator, + IEvents eventsService, + IOptions appSettings, + ILogger logger) + { + _instanceService = instanceService; + _processReader = processReader; + _profileService = profileService; + _processClient = processClient; + _appEvents = appEvents; + _taskEvents = taskEvents; + _processNavigator = processNavigator; + _eventsService = eventsService; + _registerWithEventSystem = appSettings.Value.RegisterEventsWithEventsComponent; + _logger = logger; + } + + /// + public async Task StartProcess(ProcessStartRequest processStartRequest) + { + if (processStartRequest.Instance.Process != null) + { + return new ProcessChangeResult() + { + Success = false, + ErrorMessage = "Process is already started. Use next.", + ErrorType = "Conflict" + }; + } + + string? validStartElement = ProcessHelper.GetValidStartEventOrError(processStartRequest.StartEventId, _processReader.GetStartEventIds(), out ProcessError? startEventError); + if (startEventError != null) + { + return new ProcessChangeResult() + { + Success = false, + ErrorMessage = "No matching startevent", + ErrorType = "Conflict" + }; + } + + // start process + ProcessStateChange? startChange = await ProcessStart(processStartRequest.Instance, validStartElement!, processStartRequest.User); + InstanceEvent? startEvent = startChange?.Events.First().CopyValues(); + ProcessStateChange nextChange = await ProcessNext(processStartRequest.Instance, processStartRequest.User); + //ProcessChangeResult nextChange = await Next(processStartRequest.InstanceIdentifier, processStartRequest.User); + InstanceEvent goToNextEvent = nextChange.Events.First().CopyValues(); + + ProcessStateChange processStateChange = new ProcessStateChange + { + OldProcessState = startChange.OldProcessState, + NewProcessState = nextChange.NewProcessState, + Events = new List { startEvent, goToNextEvent } + }; + + if (!processStartRequest.Dryrun) + { + await UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, new List { startEvent, goToNextEvent }); + } + + return new ProcessChangeResult() + { + Success = true, + ProcessStateChange = processStateChange + }; + } + + /// + public async Task Next(ProcessNextRequest request) + { + var instance = request.Instance; + string? currentElementId = instance.Process.CurrentTask?.ElementId; + + if (currentElementId == null) + { + return new ProcessChangeResult() + { + Success = false, + ErrorMessage = $"Instance does not have current task information!", + ErrorType = "Conflict" + }; + } + + // Find next valid element. Later this will be dynamic + ProcessElement nextElement = await _processNavigator.GetNextTask(instance, currentElementId, request.Action); + + var nextResult = await HandleMoveToNext(instance, request.User, request.Action); + + return new ProcessChangeResult() + { + Success = true, + ProcessStateChange = nextResult + }; + } + + /// + public async Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events) + { + return await UpdateProcessAndDispatchEvents(startRequest.Instance, startRequest.Prefill, events); + } + + /// + /// Does not save process. Instance object is updated. + /// + private async Task ProcessStart(Instance instance, string startEvent, ClaimsPrincipal user) + { + if (instance.Process == null) + { + DateTime now = DateTime.UtcNow; + + ProcessState startState = new ProcessState + { + Started = now, + StartEvent = startEvent, + CurrentTask = new ProcessElementInfo { Flow = 1, ElementId = startEvent} + }; + + instance.Process = startState; + + List events = new List + { + await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString(), instance, now, user), + }; + + return new ProcessStateChange + { + OldProcessState = null!, + NewProcessState = startState, + Events = events, + }; + } + + return null; + } + + /// + /// Moves instance's process to nextElement id. Returns the instance together with process events. + /// + private async Task ProcessNext(Instance instance, ClaimsPrincipal userContext, string? action = null) + { + if (instance.Process != null) + { + ProcessStateChange result = new ProcessStateChange + { + OldProcessState = new ProcessState() + { + Started = instance.Process.Started, + CurrentTask = instance.Process.CurrentTask, + StartEvent = instance.Process.StartEvent + } + }; + + result.Events = await MoveProcessToNext(instance, userContext, action); + result.NewProcessState = instance.Process; + return result; + } + + return null; + } + + /// + /// Assumes that nextElementId is a valid task/state + /// + private async Task> MoveProcessToNext( + Instance instance, + ClaimsPrincipal user, + string? action = null) + { + List events = new List(); + + ProcessState previousState = instance.Process.Copy(); + ProcessState currentState = instance.Process; + string? previousElementId = currentState.CurrentTask?.ElementId; + + ProcessElement nextElement = await _processNavigator.GetNextTask(instance, instance.Process.CurrentTask.ElementId, action); + DateTime now = DateTime.UtcNow; + bool previousIsProcessTask = _processReader.IsProcessTask(previousElementId); + // ending previous element if task + if (previousIsProcessTask) + { + instance.Process = previousState; + events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); + instance.Process = currentState; + } + + // ending process if next element is end event + if (_processReader.IsEndEvent(nextElement.Id)) + { + currentState.CurrentTask = null; + currentState.Ended = now; + currentState.EndEvent = nextElement.Id; + + events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); + + // add submit event (to support Altinn2 SBL) + events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); + } + else if (_processReader.IsProcessTask(nextElement.Id)) + { + var task = nextElement as ProcessTask; + currentState.CurrentTask = new ProcessElementInfo + { + Flow = currentState.CurrentTask?.Flow + 1, + ElementId = nextElement.Id, + Name = nextElement?.Name, + Started = now, + AltinnTaskType = task?.ExtensionElements?.AltinnProperties.TaskType, + Validated = null, + }; + + events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_StartTask.ToString(), instance, now, user)); + } + + // current state points to the instance's process object. The following statement is unnecessary, but clarifies logic. + instance.Process = currentState; + + return events; + } + + /// + /// This + /// + private async Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events) + { + await HandleProcessChanges(instance, events, prefill); + + // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived + Instance updatedInstance = await _instanceService.UpdateProcess(instance); + await _processClient.DispatchProcessEventsToStorage(updatedInstance, events); + + // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. + updatedInstance = await _instanceService.GetInstance(updatedInstance); + + return updatedInstance; + } + + /// + /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. + /// + /// Each implementation + /// + private async Task HandleProcessChanges(Instance instance, List events, Dictionary? prefill) + { + foreach (InstanceEvent processEvent in events) + { + if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) + { + string? elementId = processEvent.ProcessInfo?.CurrentTask?.ElementId; + ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); + switch (eventType) + { + case InstanceEventType.process_StartEvent: + break; + case InstanceEventType.process_StartTask: + await task.HandleTaskStart(elementId, instance, prefill); + break; + case InstanceEventType.process_EndTask: + await task.HandleTaskComplete(elementId, instance); + break; + case InstanceEventType.process_AbandonTask: + await task.HandleTaskAbandon(elementId, instance); + await _instanceService.UpdateProcess(instance); + break; + case InstanceEventType.process_EndEvent: + await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, instance); + break; + } + } + } + } + + /// + /// Identify the correct task implementation + /// + /// + private ITask GetProcessTask(string? altinnTaskType) + { + if (string.IsNullOrEmpty(altinnTaskType)) + { + return new NullTask(); + } + + ITask task = new DataTask(_taskEvents); + if (altinnTaskType.Equals("confirmation")) + { + task = new ConfirmationTask(_taskEvents); + } + else if (altinnTaskType.Equals("feedback")) + { + task = new FeedbackTask(_taskEvents); + } + + return task; + } + + private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) + { + int? userId = user.GetUserIdAsInt(); + InstanceEvent instanceEvent = new InstanceEvent + { + InstanceId = instance.Id, + InstanceOwnerPartyId = instance.InstanceOwner.PartyId, + EventType = eventType, + Created = now, + User = new PlatformUser + { + UserId = userId, + AuthenticationLevel = user.GetAuthenticationLevel(), + OrgId = user.GetOrg() + }, + ProcessInfo = instance.Process, + }; + + if (string.IsNullOrEmpty(instanceEvent.User.OrgId) && userId != null) + { + UserProfile up = await _profileService.GetUserProfile((int)userId); + instanceEvent.User.NationalIdentityNumber = up.Party.SSN; + } + + return instanceEvent; + } + + private async Task HandleMoveToNext(Instance instance, ClaimsPrincipal user, string? action) + { + var processStateChange = await ProcessNext(instance, user, action); + if (processStateChange != null) + { + instance = await UpdateProcessAndDispatchEvents(instance, new Dictionary(), processStateChange.Events); + + await RegisterEventWithEventsComponent(instance); + } + + return processStateChange; + } + + private async Task RegisterEventWithEventsComponent(Instance instance) + { + if (_registerWithEventSystem) + { + try + { + if (!string.IsNullOrWhiteSpace(instance.Process.CurrentTask?.ElementId)) + { + await _eventsService.AddEvent($"app.instance.process.movedTo.{instance.Process.CurrentTask.ElementId}", instance); + } + else if (instance.Process.EndEvent != null) + { + await _eventsService.AddEvent("app.instance.process.completed", instance); + } + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Exception when sending event with the Events component"); + } + } + } +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs new file mode 100644 index 000000000..1c03e6e92 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs @@ -0,0 +1,71 @@ +using Altinn.App.Core.Features; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.V2; + +/// +/// Default implementation of +/// +public class ProcessNavigator : IProcessNavigator +{ + private readonly IProcessReader _processReader; + private readonly ExclusiveGatewayFactory _gatewayFactory; + + /// + /// Initialize a new instance of + /// + /// The process reader + /// Service to fetch wanted gateway filter implementation + public ProcessNavigator(IProcessReader processReader, ExclusiveGatewayFactory gatewayFactory) + { + _processReader = processReader; + _gatewayFactory = gatewayFactory; + } + + + public async Task GetNextTask(Instance instance, string currentElement, string? action) + { + List directFlowTargets = _processReader.GetNextElements(currentElement); + foreach (var directFlowTarget in directFlowTargets) + { + if (!IsGateway(directFlowTarget)) + { + return directFlowTarget; + } + + var gateway = (ExclusiveGateway)directFlowTarget; + IProcessExclusiveGateway? gatewayFilter = _gatewayFactory.GetProcessExclusiveGateway(directFlowTarget.Id); + List outgoingFlows = _processReader.GetOutgoingSequenceFlows(directFlowTarget); + List filteredList; + if (gatewayFilter == null) + { + filteredList = outgoingFlows; + } + else + { + filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance); + } + + if (filteredList.Count == 1) + { + return _processReader.GetFlowElement(filteredList[0].TargetRef); + } + + var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); + if (defaultSequenceFlow != null) + { + return _processReader.GetFlowElement(defaultSequenceFlow.TargetRef); + } + } + + throw new Exception("No able to find next element"); + } + + + private static bool IsGateway(ProcessElement processElement) + { + return processElement is ExclusiveGateway; + } +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs new file mode 100644 index 000000000..0e617c18e --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs @@ -0,0 +1,11 @@ +using System.Security.Claims; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.V2; + +public class ProcessNextRequest +{ + public Instance Instance { get; set; } + public ClaimsPrincipal User { get; set; } + public string? Action { get; set; } +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs new file mode 100644 index 000000000..ae297b952 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs @@ -0,0 +1,177 @@ +using System.Xml.Serialization; +using Altinn.App.Core.Interface; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.Base; + +namespace Altinn.App.Core.Internal.Process.V2; + +/// +/// Implementation of that reads from a +/// +public class ProcessReader : IProcessReader +{ + private readonly Definitions _definitions; + + /// + /// Create instance of ProcessReader where process stream is fetched from + /// + /// Implementation of IProcess used to get stream of BPMN process + /// If BPMN file could not be deserialized + public ProcessReader(IProcess processService) + { + XmlSerializer serializer = new XmlSerializer(typeof(Definitions)); + Definitions? definitions = (Definitions?)serializer.Deserialize(processService.GetProcessDefinition()); + + _definitions = definitions ?? throw new InvalidOperationException("Failed to deserialize BPMN definitions. Definitions was null"); + } + + /// + public List GetStartEvents() + { + return _definitions.Process.StartEvents; + } + + /// + public List GetStartEventIds() + { + return GetStartEvents().Select(s => s.Id).ToList(); + } + + /// + public bool IsStartEvent(string? elementId) + { + return elementId != null && GetStartEventIds().Contains(elementId); + } + + /// + public List GetProcessTasks() + { + return _definitions.Process.Tasks; + } + + /// + public List GetProcessTaskIds() + { + return GetProcessTasks().Select(t => t.Id).ToList(); + } + + /// + public bool IsProcessTask(string? elementId) + { + return elementId != null && GetProcessTaskIds().Contains(elementId); + } + + /// + public List GetExclusiveGateways() + { + return _definitions.Process.ExclusiveGateway; + } + + /// + public List GetExclusiveGatewayIds() + { + return GetExclusiveGateways().Select(g => g.Id).ToList(); + } + + /// + public List GetEndEvents() + { + return _definitions.Process.EndEvents; + } + + /// + public List GetEndEventIds() + { + return GetEndEvents().Select(e => e.Id).ToList(); + } + + /// + public bool IsEndEvent(string? elementId) + { + return elementId != null && GetEndEventIds().Contains(elementId); + } + + /// + public List GetSequenceFlows() + { + return _definitions.Process.SequenceFlow; + } + + /// + public List GetSequenceFlowIds() + { + return GetSequenceFlows().Select(s => s.Id).ToList(); + } + + /// + public ProcessElement? GetFlowElement(string? elementId) + { + EnsureArgumentNotNull(elementId, nameof(elementId)); + + ProcessTask? task = _definitions.Process.Tasks.Find(t => t.Id == elementId); + if (task != null) + { + return task; + } + + EndEvent? endEvent = _definitions.Process.EndEvents.Find(e => e.Id == elementId); + if (endEvent != null) + { + return endEvent; + } + + StartEvent? startEvent = _definitions.Process.StartEvents.Find(e => e.Id == elementId); + if (startEvent != null) + { + return startEvent; + } + + return _definitions.Process.ExclusiveGateway.Find(e => e.Id == elementId); + } + + /// + public List GetNextElements(string? currentElementId) + { + EnsureArgumentNotNull(currentElementId, nameof(currentElementId)); + List nextElements = new List(); + List allElements = GetAllFlowElements(); + if (!allElements.Exists(e => e.Id == currentElementId)) + { + throw new ProcessException($"Unable to find a element using element id {currentElementId}."); + } + + foreach (SequenceFlow sequenceFlow in GetSequenceFlows().FindAll(s => s.SourceRef == currentElementId)) + { + nextElements.AddRange(allElements.FindAll(e => sequenceFlow.TargetRef == e.Id)); + } + + return nextElements; + } + + /// + public List GetOutgoingSequenceFlows(ProcessElement? flowElement) + { + if (flowElement == null) + { + return new List(); + } + + return GetSequenceFlows().FindAll(sf => flowElement.Outgoing.Contains(sf.Id)).ToList(); + } + + private static void EnsureArgumentNotNull(object? argument, string paramName) + { + if (argument == null) + throw new ArgumentNullException(paramName); + } + + private List GetAllFlowElements() + { + List flowElements = new List(); + flowElements.AddRange(GetStartEvents()); + flowElements.AddRange(GetProcessTasks()); + flowElements.AddRange(GetExclusiveGateways()); + flowElements.AddRange(GetEndEvents()); + return flowElements; + } +} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs new file mode 100644 index 000000000..0326fae67 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs @@ -0,0 +1,15 @@ +using System.Security.Claims; +using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.V2 +{ + public class ProcessStartRequest + { + public Instance Instance { get; set; } + public ClaimsPrincipal User { get; set; } + public Dictionary? Prefill { get; set; } + public string? StartEventId { get; set; } + public bool Dryrun { get; set; } + } +} diff --git a/src/Altinn.App.Core/Models/ProcessChangeResult.cs b/src/Altinn.App.Core/Models/ProcessChangeResult.cs new file mode 100644 index 000000000..f92237a29 --- /dev/null +++ b/src/Altinn.App.Core/Models/ProcessChangeResult.cs @@ -0,0 +1,11 @@ +namespace Altinn.App.Core.Models +{ + public class ProcessChangeResult + { + public bool Success { get; set; } + public string? ErrorMessage { get; set; } + public string? ErrorType { get; set; } + + public ProcessStateChange? ProcessStateChange { get; set; } + } +} diff --git a/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs b/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs index 62e43ecbf..4cf62f259 100644 --- a/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs @@ -16,6 +16,7 @@ using Altinn.App.Core.Internal.App; using Altinn.Platform.Profile.Models; using Altinn.Platform.Register.Models; +using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; namespace Altinn.App.Api.Tests.Controllers; diff --git a/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs b/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs index e6d9f3265..376ec623b 100644 --- a/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs @@ -5,6 +5,7 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; +using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; @@ -19,6 +20,7 @@ using Moq; using Xunit; +using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; namespace Altinn.App.Api.Tests.Controllers; @@ -271,9 +273,9 @@ public async Task CopyInstance_EverythingIsFine_ReturnsRedirect() _instanceClient.Setup(i => i.CreateInstance(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(instance); _instanceClient.Setup(i => i.GetInstance(It.IsAny())).ReturnsAsync(instance); _instantiationValidator.Setup(v => v.Validate(It.IsAny())).ReturnsAsync(instantiationValidationResult); - _processEngine.Setup(p => p.StartProcess(It.IsAny())) - .ReturnsAsync((ProcessChangeContext pcc) => { return pcc; }); - _processEngine.Setup(p => p.StartTask(It.IsAny())); + _processEngine.Setup(p => p.StartProcess(It.IsAny())) + .ReturnsAsync(() => { return new ProcessChangeResult(){Success = true}; }); + _processEngine.Setup(p => p.UpdateInstanceAndRerunEvents(It.IsAny(), It.IsAny>())); // Act ActionResult actual = await SUT.CopyInstance(Org, AppName, InstanceOwnerPartyId, instanceGuid); diff --git a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs new file mode 100644 index 000000000..d367c16e0 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs @@ -0,0 +1,57 @@ +using Altinn.App.Core.Extensions; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.Core.Tests.Extensions; + +public class InstanceEventExtensionsTests +{ + [Fact] + public void CopyValues_returns_copy_of_instance_event() + { + InstanceEvent original = new InstanceEvent() + { + Created = DateTime.Now, + DataId = Guid.NewGuid().ToString(), + EventType = "EventType", + Id = Guid.NewGuid(), + InstanceId = Guid.NewGuid().ToString(), + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = DateTime.Now, + CurrentTask = new ProcessElementInfo + { + Flow = 1, + AltinnTaskType = "AltinnTaskType", + ElementId = "ElementId", + Name = "Name", + Started = DateTime.Now, + Ended = DateTime.Now, + Validated = new ValidationStatus + { + CanCompleteTask = true, + Timestamp = DateTime.Now + } + }, + StartEvent = "StartEvent" + }, + User = new PlatformUser + { + AuthenticationLevel = 2, + EndUserSystemId = 1, + OrgId = "OrgId", + UserId = 3, + NationalIdentityNumber = "NationalIdentityNumber" + } + }; + InstanceEvent copy = original.CopyValues(); + copy.Should().NotBeSameAs(original); + copy.ProcessInfo.Should().NotBeSameAs(original.ProcessInfo); + copy.ProcessInfo.CurrentTask.Should().NotBeSameAs(original.ProcessInfo.CurrentTask); + copy.ProcessInfo.CurrentTask.Validated.Should().NotBeSameAs(original.ProcessInfo.CurrentTask.Validated); + copy.User.Should().NotBeSameAs(original.User); + copy.Should().BeEquivalentTo(original); + } +} \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Extensions/ProcessStateExtensionTests.cs b/test/Altinn.App.Core.Tests/Extensions/ProcessStateExtensionTests.cs new file mode 100644 index 000000000..0e388beaf --- /dev/null +++ b/test/Altinn.App.Core.Tests/Extensions/ProcessStateExtensionTests.cs @@ -0,0 +1,67 @@ +using Altinn.App.Core.Extensions; +using Altinn.App.Core.Models.Validation; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.Core.Tests.Extensions; + +public class ProcessStateExtensionTests +{ + [Fact] + public void Copy_returns_copy_of_process_state() + { + ProcessState original = new ProcessState(); + ProcessState copy = original.Copy(); + Assert.NotSame(original, copy); + } + + [Fact] + public void Copy_returns_state_with_fields_set() + { + ProcessState original = new ProcessState() + { + Ended = DateTime.Now, + Started = DateTime.Now, + StartEvent = "StartEvent", + EndEvent = "EndEvent", + CurrentTask = new ProcessElementInfo() + { + Ended = DateTime.Now, + Started = DateTime.Now, + ElementId = "ElementId", + AltinnTaskType = "AltinnTaskType", + Flow = 1, + FlowType = "FlowType", + Name = "Name", + Validated = new ValidationStatus() + { + Timestamp = DateTime.Now, + CanCompleteTask = true + }, + } + }; + ProcessState copy = original.Copy(); + Assert.NotSame(original, copy); + Assert.NotSame(original.CurrentTask, copy.CurrentTask); + Assert.Same(original.CurrentTask.Validated, copy.CurrentTask.Validated); + copy.Should().BeEquivalentTo(original); + } + + [Fact] + public void Copy_returns_state_with_current_null_when_original_null() + { + ProcessState original = new ProcessState() + { + Ended = DateTime.Now, + Started = DateTime.Now, + StartEvent = "StartEvent", + EndEvent = "EndEvent", + CurrentTask = null + }; + ProcessState copy = original.Copy(); + Assert.NotSame(original, copy); + Assert.Same(original.CurrentTask, copy.CurrentTask); + copy.Should().BeEquivalentTo(original); + } +} diff --git a/test/Altinn.App.Core.Tests/Infrastructure/Clients/EventsSubscriptionClientTests.cs b/test/Altinn.App.Core.Tests/Infrastructure/Clients/EventsSubscriptionClientTests.cs index 6add019e6..d281bcb58 100644 --- a/test/Altinn.App.Core.Tests/Infrastructure/Clients/EventsSubscriptionClientTests.cs +++ b/test/Altinn.App.Core.Tests/Infrastructure/Clients/EventsSubscriptionClientTests.cs @@ -12,11 +12,19 @@ using Moq; using Moq.Protected; using Xunit; +using Xunit.Abstractions; namespace Altinn.App.PlatformServices.Tests.Infrastructure.Clients { public class EventsSubscriptionClientTests { + private readonly ITestOutputHelper _testOutputHelper; + + public EventsSubscriptionClientTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + [Fact] public async Task AddSubscription_ShouldReturnOk() { diff --git a/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs index dbb7709da..2ff7f5884 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs @@ -4,7 +4,6 @@ using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.Base; using Altinn.App.PlatformServices.Tests.Internal.Process.StubGatewayFilters; -using Altinn.App.PlatformServices.Tests.Internal.Process.TestUtils; using Altinn.Platform.Storage.Interface.Models; using FluentAssertions; using Xunit; @@ -13,253 +12,253 @@ namespace Altinn.App.PlatformServices.Tests.Internal.Process; public class FlowHydrationTests { - [Fact] - public async void NextFollowAndFilterGateways_returns_next_element_if_no_gateway() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-linear.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = "Bekreft skjemadata", - TaskType = "confirmation", - Incoming = new List { "Flow2" }, - Outgoing = new List { "Flow3" } - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_returns_empty_list_if_no_outgoing_flows() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-linear.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "EndEvent"); - nextElements.Should().BeEmpty(); - } - - [Fact] - public async void NextFollowAndFilterGateways_returns_default_if_no_filtering_is_implemented_and_default_set() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-default.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_returns_all_if_no_filtering_is_implemented_and_default_set_but_followDefaults_false() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-default.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1", false); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - }, - new EndEvent() - { - Id = "EndEvent", - Incoming = new List { "Flow5", "Flow4" }, - Name = null!, - Outgoing = new List() - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_returns_all_gateway_target_tasks_if_no_filter_and_default() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - }, - new ProcessTask() - { - Id = "EndEvent", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow4", "Flow5" }, - Outgoing = new List() - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_returns_all_gateway_target_tasks_if_no_filter_and_default_folowDefaults_false() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1", false); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - }, - new ProcessTask() - { - Id = "EndEvent", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow4", "Flow5" }, - Outgoing = new List() - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_runs_custom_filter_and_returns_result() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() - { - new DataValuesFilter("Gateway1", "choose") - }); - Instance i = new Instance() - { - DataValues = new Dictionary() - { - { "choose", "Flow3" } - } - }; - - List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_does_not_run_filter_with_non_matchin_ids() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() - { - new DataValuesFilter("Foobar", "choose") - }); - Instance i = new Instance() - { - DataValues = new Dictionary() - { - { "choose", "Flow3" } - } - }; - - List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - }, - new ProcessTask() - { - Id = "EndEvent", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow6" }, - Outgoing = new List() - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_follows_downstream_gateways() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List()); - List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - nextElements.Should().BeEquivalentTo(new List() - { - new ProcessTask() - { - Id = "Task2", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow3" }, - Outgoing = new List { "Flow5" } - }, - new ProcessTask() - { - Id = "EndEvent", - Name = null!, - TaskType = null!, - Incoming = new List { "Flow6" }, - Outgoing = new List() - } - }); - } - - [Fact] - public async void NextFollowAndFilterGateways_runs_custom_filter_and_returns_empty_list_if_all_filtered_out() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() - { - new DataValuesFilter("Gateway1", "choose1"), - new DataValuesFilter("Gateway2", "choose2") - }); - Instance i = new Instance() - { - DataValues = new Dictionary() - { - { "choose1", "Flow4" }, - { "choose2", "Foobar" } - } - }; - - List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); - nextElements.Should().BeEmpty(); - } - - [Fact] - public async void NextFollowAndFilterGateways_returns_empty_list_if_element_has_no_next() - { - IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List()); - Instance i = new Instance(); - - List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "EndEvent"); - nextElements.Should().BeEmpty(); - } - - private static IFlowHydration SetupFlowHydration(string bpmnfile, IEnumerable gatewayFilters) - { - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - return new FlowHydration(pr, new ExclusiveGatewayFactory(gatewayFilters)); - } + // [Fact] + // public async void NextFollowAndFilterGateways_returns_next_element_if_no_gateway() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-linear.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = "Bekreft skjemadata", + // TaskType = "confirmation", + // Incoming = new List { "Flow2" }, + // Outgoing = new List { "Flow3" } + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_returns_empty_list_if_no_outgoing_flows() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-linear.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "EndEvent"); + // nextElements.Should().BeEmpty(); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_returns_default_if_no_filtering_is_implemented_and_default_set() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-default.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_returns_all_if_no_filtering_is_implemented_and_default_set_but_followDefaults_false() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-default.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1", false); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // }, + // new EndEvent() + // { + // Id = "EndEvent", + // Incoming = new List { "Flow5", "Flow4" }, + // Name = null!, + // Outgoing = new List() + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_returns_all_gateway_target_tasks_if_no_filter_and_default() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // }, + // new ProcessTask() + // { + // Id = "EndEvent", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow4", "Flow5" }, + // Outgoing = new List() + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_returns_all_gateway_target_tasks_if_no_filter_and_default_folowDefaults_false() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1", false); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // }, + // new ProcessTask() + // { + // Id = "EndEvent", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow4", "Flow5" }, + // Outgoing = new List() + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_runs_custom_filter_and_returns_result() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() + // { + // new DataValuesFilter("Gateway1", "choose") + // }); + // Instance i = new Instance() + // { + // DataValues = new Dictionary() + // { + // { "choose", "Flow3" } + // } + // }; + // + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_does_not_run_filter_with_non_matchin_ids() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() + // { + // new DataValuesFilter("Foobar", "choose") + // }); + // Instance i = new Instance() + // { + // DataValues = new Dictionary() + // { + // { "choose", "Flow3" } + // } + // }; + // + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // }, + // new ProcessTask() + // { + // Id = "EndEvent", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow6" }, + // Outgoing = new List() + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_follows_downstream_gateways() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List()); + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); + // nextElements.Should().BeEquivalentTo(new List() + // { + // new ProcessTask() + // { + // Id = "Task2", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow3" }, + // Outgoing = new List { "Flow5" } + // }, + // new ProcessTask() + // { + // Id = "EndEvent", + // Name = null!, + // TaskType = null!, + // Incoming = new List { "Flow6" }, + // Outgoing = new List() + // } + // }); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_runs_custom_filter_and_returns_empty_list_if_all_filtered_out() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() + // { + // new DataValuesFilter("Gateway1", "choose1"), + // new DataValuesFilter("Gateway2", "choose2") + // }); + // Instance i = new Instance() + // { + // DataValues = new Dictionary() + // { + // { "choose1", "Flow4" }, + // { "choose2", "Foobar" } + // } + // }; + // + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); + // nextElements.Should().BeEmpty(); + // } + // + // [Fact] + // public async void NextFollowAndFilterGateways_returns_empty_list_if_element_has_no_next() + // { + // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List()); + // Instance i = new Instance(); + // + // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "EndEvent"); + // nextElements.Should().BeEmpty(); + // } + // + // private static IFlowHydration SetupFlowHydration(string bpmnfile, IEnumerable gatewayFilters) + // { + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // return new FlowHydration(pr, new ExclusiveGatewayFactory(gatewayFilters)); + // } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs index 62d4a6f56..ff38974e1 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs @@ -17,145 +17,145 @@ namespace Altinn.App.PlatformServices.Tests.Internal.Process /// public class ProcessEngineTest { - [Fact] - public async void MissingCurrentTask() - { - IProcessReader processReader = GetProcessReader(); - - ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - - Instance instance = new Instance - { - Process = new ProcessState() - }; - - ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!); - - processChangeContext = await processEngine.Next(processChangeContext); - - Assert.True(processChangeContext.FailedProcessChange); - Assert.Equal("Instance does not have current task information!", processChangeContext.ProcessMessages[0].Message); - } - - [Fact] - public async void RequestingCurrentTask() - { - IProcessReader processReader = GetProcessReader(); - - ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - - Instance instance = new Instance - { - Process = new ProcessState - { - CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } - } - }; - - ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - { - RequestedProcessElementId = "Task_1" - }; - - processChangeContext = await processEngine.Next(processChangeContext); - - Assert.True(processChangeContext.FailedProcessChange); - Assert.Equal("Requested process element Task_1 is same as instance's current task. Cannot change process.", processChangeContext.ProcessMessages[0].Message); - } - - [Fact] - public async void RequestInvalidTask() - { - IProcessReader processReader = GetProcessReader(); - - ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - - Instance instance = new Instance - { - Process = new ProcessState - { - CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } - } - }; - - ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - { - RequestedProcessElementId = "Task_10" - }; - - processChangeContext = await processEngine.Next(processChangeContext); - - Assert.True(processChangeContext.FailedProcessChange); - Assert.Contains("The proposed next element id 'Task_10' is", processChangeContext.ProcessMessages[0].Message); - } - - [Fact] - public async void StartStartedTask() - { - IProcessReader processReader = GetProcessReader(); - - ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - - Instance instance = new Instance - { - Process = new ProcessState - { - CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } - } - }; - - ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - { - RequestedProcessElementId = "Task_10" - }; - - processChangeContext = await processEngine.StartProcess(processChangeContext); - - Assert.True(processChangeContext.FailedProcessChange); - Assert.Contains("Process is already started. Use next.", processChangeContext.ProcessMessages[0].Message); - } - - [Fact] - public async void InvalidStartEvent() - { - IProcessReader processReader = GetProcessReader(); - - ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - - Instance instance = new Instance(); - - ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - { - RequestedProcessElementId = "Task_10" - }; - - processChangeContext = await processEngine.StartProcess(processChangeContext); - - Assert.True(processChangeContext.FailedProcessChange); - Assert.Contains("No matching startevent", processChangeContext.ProcessMessages[0].Message); - } - - private static IProcessReader GetProcessReader() - { - AppSettings appSettings = new AppSettings - { - AppBasePath = Path.Join("Internal", "Process", "TestData", "ProcessEngineTest") + Path.DirectorySeparatorChar - }; - IOptions appSettingsO = Microsoft.Extensions.Options.Options.Create(appSettings); - - PlatformSettings platformSettings = new PlatformSettings - { - ApiStorageEndpoint = "http://localhost/" - }; - IOptions platformSettings0 = Microsoft.Extensions.Options.Options.Create(platformSettings); - - ProcessClient processClient = new ProcessClient(platformSettings0, appSettingsO, null!, new NullLogger(), null!, new System.Net.Http.HttpClient()); - return new ProcessReader(processClient); - } - - private static IFlowHydration GetFlowHydration(IProcessReader processReader) - { - return new FlowHydration(processReader, new ExclusiveGatewayFactory(new List())); - } + // [Fact] + // public async void MissingCurrentTask() + // { + // IProcessReader processReader = GetProcessReader(); + // + // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); + // + // Instance instance = new Instance + // { + // Process = new ProcessState() + // }; + // + // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!); + // + // processChangeContext = await processEngine.Next(processChangeContext); + // + // Assert.True(processChangeContext.FailedProcessChange); + // Assert.Equal("Instance does not have current task information!", processChangeContext.ProcessMessages[0].Message); + // } + // + // [Fact] + // public async void RequestingCurrentTask() + // { + // IProcessReader processReader = GetProcessReader(); + // + // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); + // + // Instance instance = new Instance + // { + // Process = new ProcessState + // { + // CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } + // } + // }; + // + // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) + // { + // RequestedProcessElementId = "Task_1" + // }; + // + // processChangeContext = await processEngine.Next(processChangeContext); + // + // Assert.True(processChangeContext.FailedProcessChange); + // Assert.Equal("Requested process element Task_1 is same as instance's current task. Cannot change process.", processChangeContext.ProcessMessages[0].Message); + // } + // + // [Fact] + // public async void RequestInvalidTask() + // { + // IProcessReader processReader = GetProcessReader(); + // + // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); + // + // Instance instance = new Instance + // { + // Process = new ProcessState + // { + // CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } + // } + // }; + // + // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) + // { + // RequestedProcessElementId = "Task_10" + // }; + // + // processChangeContext = await processEngine.Next(processChangeContext); + // + // Assert.True(processChangeContext.FailedProcessChange); + // Assert.Contains("The proposed next element id 'Task_10' is", processChangeContext.ProcessMessages[0].Message); + // } + // + // [Fact] + // public async void StartStartedTask() + // { + // IProcessReader processReader = GetProcessReader(); + // + // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); + // + // Instance instance = new Instance + // { + // Process = new ProcessState + // { + // CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } + // } + // }; + // + // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) + // { + // RequestedProcessElementId = "Task_10" + // }; + // + // processChangeContext = await processEngine.StartProcess(processChangeContext); + // + // Assert.True(processChangeContext.FailedProcessChange); + // Assert.Contains("Process is already started. Use next.", processChangeContext.ProcessMessages[0].Message); + // } + // + // [Fact] + // public async void InvalidStartEvent() + // { + // IProcessReader processReader = GetProcessReader(); + // + // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); + // + // Instance instance = new Instance(); + // + // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) + // { + // RequestedProcessElementId = "Task_10" + // }; + // + // processChangeContext = await processEngine.StartProcess(processChangeContext); + // + // Assert.True(processChangeContext.FailedProcessChange); + // Assert.Contains("No matching startevent", processChangeContext.ProcessMessages[0].Message); + // } + // + // private static IProcessReader GetProcessReader() + // { + // AppSettings appSettings = new AppSettings + // { + // AppBasePath = Path.Join("Internal", "Process", "TestData", "ProcessEngineTest") + Path.DirectorySeparatorChar + // }; + // IOptions appSettingsO = Microsoft.Extensions.Options.Options.Create(appSettings); + // + // PlatformSettings platformSettings = new PlatformSettings + // { + // ApiStorageEndpoint = "http://localhost/" + // }; + // IOptions platformSettings0 = Microsoft.Extensions.Options.Options.Create(platformSettings); + // + // ProcessClient processClient = new ProcessClient(platformSettings0, appSettingsO, null!, new NullLogger(), null!, new System.Net.Http.HttpClient()); + // return new ProcessReader(processClient); + // } + // + // private static IFlowHydration GetFlowHydration(IProcessReader processReader) + // { + // return new FlowHydration(processReader, new ExclusiveGatewayFactory(new List())); + // } } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs index befdcb789..ff38e439f 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs @@ -4,7 +4,7 @@ using System.Linq; using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.PlatformServices.Tests.Internal.Process.TestUtils; +using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; using FluentAssertions; using Xunit; @@ -12,447 +12,502 @@ namespace Altinn.App.PlatformServices.Tests.Internal.Process; public class ProcessReaderTests { - [Fact] - public void TestBpmnRead() - { - ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.GetStartEventIds().Should().Equal("StartEvent"); - pr.GetProcessTaskIds().Should().Equal("Task1", "Task2"); - pr.GetEndEventIds().Should().Equal("EndEvent"); - pr.GetSequenceFlowIds().Should().Equal("Flow1", "Flow2", "Flow3", "Flow4", "Flow5"); - pr.GetExclusiveGatewayIds().Should().Equal("Gateway1"); - } - - [Fact] - public void IsStartEvent_returns_true_when_element_is_StartEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsStartEvent("StartEvent").Should().BeTrue(); - } - - [Fact] - public void IsStartEvent_returns_false_when_element_is_not_StartEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsStartEvent("Task1").Should().BeFalse(); - pr.IsStartEvent("EndEvent").Should().BeFalse(); - pr.IsStartEvent("Gateway1").Should().BeFalse(); - pr.IsStartEvent("Foobar").Should().BeFalse(); - pr.IsStartEvent(null).Should().BeFalse(); - } - - [Fact] - public void IsProcessTask_returns_true_when_element_is_ProcessTask() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsProcessTask("Task1").Should().BeTrue(); - } - - [Fact] - public void IsProcessTask_returns_false_when_element_is_not_ProcessTask() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsProcessTask("StartEvent").Should().BeFalse(); - pr.IsProcessTask("EndEvent").Should().BeFalse(); - pr.IsProcessTask("Gateway1").Should().BeFalse(); - pr.IsProcessTask("Foobar").Should().BeFalse(); - pr.IsProcessTask(null).Should().BeFalse(); - } - - [Fact] - public void IsEndEvent_returns_true_when_element_is_EndEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsEndEvent("EndEvent").Should().BeTrue(); - } - - [Fact] - public void IsEndEvent_returns_false_when_element_is_not_EndEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsEndEvent("StartEvent").Should().BeFalse(); - pr.IsEndEvent("Task1").Should().BeFalse(); - pr.IsEndEvent("Gateway1").Should().BeFalse(); - pr.IsEndEvent("Foobar").Should().BeFalse(); - pr.IsEndEvent(null).Should().BeFalse(); - } - - [Fact] - public void GetNextElement_returns_gateway() - { - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().Equal("Gateway1"); - } - - [Fact] - public void GetNextElement_returns_task() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().Equal("Task2"); - } - - [Fact] - public void GetNextElement_returns_all_targets_after_gateway() - { - var bpmnfile = "simple-gateway.bpmn"; - var currentElement = "Gateway1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().Equal("Task2", "EndEvent"); - } - - [Fact] - public void GetNextElement_returns_task1_in_simple_process() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "StartEvent"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().Equal("Task1"); - } - - [Fact] - public void GetNextElement_returns_task2_in_simple_process() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().Equal("Task2"); - } - - [Fact] - public void GetNextElement_returns_endevent_in_simple_process() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task2"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().Equal("EndEvent"); - } - - [Fact] - public void GetNextElement_returns_emptylist_if_task_without_output() - { - var bpmnfile = "simple-no-end.bpmn"; - var currentElement = "Task2"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElementIds(currentElement); - nextElements.Should().HaveCount(0); - } - - [Fact] - public void GetNextElement_currentElement_null() - { - var bpmnfile = "simple-linear.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.Invoking(p => p.GetNextElementIds(null!)).Should().Throw(); - } - - [Fact] - public void GetNextElement_throws_exception_if_step_not_found() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "NoStep"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.Invoking(p => p.GetNextElementIds(currentElement)).Should().Throw(); - } - - [Fact] - public void GetElementInfo_returns_correct_info_for_ProcessTask() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetElementInfo(currentElement); - actual.Should().BeEquivalentTo(new ElementInfo() - { - Id = "Task1", - Name = "Utfylling", - AltinnTaskType = "data", - ElementType = "Task" - }); - } - - [Fact] - public void GetElementInfo_returns_correct_info_for_StartEvent() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "StartEvent"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetElementInfo(currentElement); - actual.Should().BeEquivalentTo(new ElementInfo() - { - Id = "StartEvent", - Name = null!, - AltinnTaskType = null!, - ElementType = "StartEvent" - }); - } - - [Fact] - public void GetElementInfo_returns_correct_info_for_EndEvent() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "EndEvent"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetElementInfo(currentElement); - actual.Should().BeEquivalentTo(new ElementInfo() - { - Id = "EndEvent", - Name = null!, - AltinnTaskType = null!, - ElementType = "EndEvent" - }); - } - - [Fact] - public void GetElementInfo_returns_null_for_ExclusiveGateway() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "Gateway1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetElementInfo(currentElement); - actual.Should().BeNull(); - } - - [Fact] - public void GetElementInfo_throws_argument_null_expcetion_when_elementName_is_null() - { - var bpmnfile = "simple-linear.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.Invoking(p => p.GetElementInfo(null!)).Should().Throw(); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetOutgoingSequenceFlows(null).Should().BeEmpty(); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_ProcessTask() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Task1")); - outgoingFLows.Should().BeEquivalentTo(new List - { - new SequenceFlow() - { - Id = "Flow2", - FlowType = null!, - SourceRef = "Task1", - TargetRef = "Gateway1" - } - }); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_Gateway() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Gateway1")); - outgoingFLows.Should().BeEquivalentTo(new List - { - new SequenceFlow() - { - Id = "Flow3", - FlowType = null!, - SourceRef = "Gateway1", - TargetRef = "Task2" - }, - new SequenceFlow() - { - Id = "Flow4", - FlowType = null!, - SourceRef = "Gateway1", - TargetRef = "EndEvent" - } - }); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("EndEvent")); - outgoingFLows.Should().BeEmpty(); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_StartEvent_and_Task1() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "StartEvent"; - var nextElementId = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEquivalentTo("Flow1"); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_Task2() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "Task1"; - var nextElementId = "Task2"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEquivalentTo("Flow2", "Flow3"); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "Task1"; - var nextElementId = "EndEvent"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEquivalentTo("Flow2", "Flow4"); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent_complex() - { - var bpmnfile = "simple-gateway-with-join-gateway.bpmn"; - var currentElement = "Task1"; - var nextElementId = "EndEvent"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEquivalentTo("Flow2", "Flow4", "Flow6"); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_empty_list_when_unknown_target() - { - var bpmnfile = "simple-gateway-default.bpmn"; - var currentElement = "Task1"; - var nextElementId = "Foobar"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEmpty(); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_empty_list_when_current_is_null() - { - var bpmnfile = "simple-gateway-default.bpmn"; - string? currentElement = null; - var nextElementId = "Foobar"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEmpty(); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_empty_list_when_next_is_null() - { - var bpmnfile = "simple-gateway-default.bpmn"; - string currentElement = "Task1"; - string? nextElementId = null; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEmpty(); - } - - [Fact] - public void GetSequenceFlowsBetween_returns_empty_list_when_current_and_next_is_null() - { - var bpmnfile = "simple-gateway-default.bpmn"; - string? currentElement = null; - string? nextElementId = null; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - var returnedIds = actual.Select(s => s.Id).ToList(); - returnedIds.Should().BeEmpty(); - } - - [Fact] - public void Constructor_Fails_if_invalid_bpmn() - { - Assert.Throws(() => ProcessTestUtils.SetupProcessReader("not-bpmn.bpmn")); - } - - [Fact] - public void GetFlowElement_returns_StartEvent_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("StartEvent").Should().BeOfType().And.BeEquivalentTo(new StartEvent() - { - Id = "StartEvent", - Name = null!, - Incoming = new List(), - Outgoing = new List { "Flow1" } - }); - } - - [Fact] - public void GetFlowElement_returns_ProcessTask_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("Task1").Should().BeOfType().And.BeEquivalentTo(new ProcessTask() - { - Id = "Task1", - Name = null!, - Incoming = new List { "Flow1" }, - Outgoing = new List { "Flow2" } - }); - } - - [Fact] - public void GetFlowElement_returns_EndEvent_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("EndEvent").Should().BeOfType().And.BeEquivalentTo(new EndEvent() - { - Id = "EndEvent", - Name = null!, - Incoming = new List { "Flow4", "Flow5" }, - Outgoing = new List() - }); - } - - [Fact] - public void GetFlowElement_returns_null_when_id_not_found() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("Foobar").Should().BeNull(); - } - - [Fact] - public void GetFlowElement_returns_Gateway_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("Gateway1").Should().BeOfType().And.BeEquivalentTo(new ExclusiveGateway() - { - Id = "Gateway1", - Name = null!, - Default = "Flow3", - Incoming = new List { "Flow2" }, - Outgoing = new List { "Flow3", "Flow4" } - }); - } + // [Fact] + // public void TestBpmnRead() + // { + // ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.GetStartEventIds().Should().Equal("StartEvent"); + // pr.GetProcessTaskIds().Should().Equal("Task1", "Task2"); + // pr.GetEndEventIds().Should().Equal("EndEvent"); + // pr.GetSequenceFlowIds().Should().Equal("Flow1", "Flow2", "Flow3", "Flow4", "Flow5"); + // pr.GetExclusiveGatewayIds().Should().Equal("Gateway1"); + // } + // + // [Fact] + // public void IsStartEvent_returns_true_when_element_is_StartEvent() + // { + // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.IsStartEvent("StartEvent").Should().BeTrue(); + // } + // + // [Fact] + // public void IsStartEvent_returns_false_when_element_is_not_StartEvent() + // { + // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.IsStartEvent("Task1").Should().BeFalse(); + // pr.IsStartEvent("EndEvent").Should().BeFalse(); + // pr.IsStartEvent("Gateway1").Should().BeFalse(); + // pr.IsStartEvent("Foobar").Should().BeFalse(); + // pr.IsStartEvent(null).Should().BeFalse(); + // } + // + // [Fact] + // public void IsProcessTask_returns_true_when_element_is_ProcessTask() + // { + // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.IsProcessTask("Task1").Should().BeTrue(); + // } + // + // [Fact] + // public void IsProcessTask_returns_false_when_element_is_not_ProcessTask() + // { + // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.IsProcessTask("StartEvent").Should().BeFalse(); + // pr.IsProcessTask("EndEvent").Should().BeFalse(); + // pr.IsProcessTask("Gateway1").Should().BeFalse(); + // pr.IsProcessTask("Foobar").Should().BeFalse(); + // pr.IsProcessTask(null).Should().BeFalse(); + // } + // + // [Fact] + // public void IsEndEvent_returns_true_when_element_is_EndEvent() + // { + // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.IsEndEvent("EndEvent").Should().BeTrue(); + // } + // + // [Fact] + // public void IsEndEvent_returns_false_when_element_is_not_EndEvent() + // { + // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // pr.IsEndEvent("StartEvent").Should().BeFalse(); + // pr.IsEndEvent("Task1").Should().BeFalse(); + // pr.IsEndEvent("Gateway1").Should().BeFalse(); + // pr.IsEndEvent("Foobar").Should().BeFalse(); + // pr.IsEndEvent(null).Should().BeFalse(); + // } + // + // [Fact] + // public void GetNextElement_returns_gateway() + // { + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().Equal("Gateway1"); + // } + // + // [Fact] + // public void GetNextElement_returns_task() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().Equal("Task2"); + // } + // + // [Fact] + // public void GetNextElement_returns_all_targets_after_gateway() + // { + // var bpmnfile = "simple-gateway.bpmn"; + // var currentElement = "Gateway1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().Equal("Task2", "EndEvent"); + // } + // + // [Fact] + // public void GetNextElement_returns_task1_in_simple_process() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "StartEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().Equal("Task1"); + // } + // + // [Fact] + // public void GetNextElement_returns_task2_in_simple_process() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().Equal("Task2"); + // } + // + // [Fact] + // public void GetNextElement_returns_endevent_in_simple_process() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "Task2"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().Equal("EndEvent"); + // } + // + // [Fact] + // public void GetNextElement_returns_emptylist_if_task_without_output() + // { + // var bpmnfile = "simple-no-end.bpmn"; + // var currentElement = "Task2"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List nextElements = pr.GetNextElementIds(currentElement); + // nextElements.Should().HaveCount(0); + // } + // + // [Fact] + // public void GetNextElement_currentElement_null() + // { + // var bpmnfile = "simple-linear.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.Invoking(p => p.GetNextElementIds(null!)).Should().Throw(); + // } + // + // [Fact] + // public void GetNextElement_throws_exception_if_step_not_found() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "NoStep"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.Invoking(p => p.GetNextElementIds(currentElement)).Should().Throw(); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_old_ProcessTask() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "Task1", + // Name = "Utfylling", + // AltinnTaskType = "data", + // ElementType = "Task" + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_new_ProcessTask() + // { + // var bpmnfile = "simple-linear-new.bpmn"; + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "Task1", + // Name = "Utfylling", + // AltinnTaskType = "data", + // ElementType = "Task", + // AltinnTaskActions = new List() + // { + // "submit" + // } + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_ProcessTask_prefers_new() + // { + // var bpmnfile = "simple-linear-both.bpmn"; + // var currentElement = "Task2"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "Task2", + // Name = "Bekreft skjemadata", + // AltinnTaskType = "confirmation2", + // ElementType = "Task", + // AltinnTaskActions = new List() + // { + // "confirm", + // "reject" + // } + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_StartEvent() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "StartEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "StartEvent", + // Name = null!, + // AltinnTaskType = null!, + // ElementType = "StartEvent" + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_EndEvent() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "EndEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "EndEvent", + // Name = null!, + // AltinnTaskType = null!, + // ElementType = "EndEvent" + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_null_for_ExclusiveGateway() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Gateway1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeNull(); + // } + // + // [Fact] + // public void GetElementInfo_throws_argument_null_expcetion_when_elementName_is_null() + // { + // var bpmnfile = "simple-linear.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.Invoking(p => p.GetElementInfo(null!)).Should().Throw(); + // } + // + // [Fact] + // public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.GetOutgoingSequenceFlows(null).Should().BeEmpty(); + // } + // + // [Fact] + // public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_ProcessTask() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Task1")); + // outgoingFLows.Should().BeEquivalentTo(new List + // { + // new SequenceFlow() + // { + // Id = "Flow2", + // FlowType = null!, + // SourceRef = "Task1", + // TargetRef = "Gateway1" + // } + // }); + // } + // + // [Fact] + // public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_Gateway() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Gateway1")); + // outgoingFLows.Should().BeEquivalentTo(new List + // { + // new SequenceFlow() + // { + // Id = "Flow3", + // FlowType = null!, + // SourceRef = "Gateway1", + // TargetRef = "Task2" + // }, + // new SequenceFlow() + // { + // Id = "Flow4", + // FlowType = null!, + // SourceRef = "Gateway1", + // TargetRef = "EndEvent" + // } + // }); + // } + // + // [Fact] + // public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("EndEvent")); + // outgoingFLows.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_StartEvent_and_Task1() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "StartEvent"; + // var nextElementId = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow1"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_Task2() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "Task2"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow2", "Flow3"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "EndEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent_complex() + // { + // var bpmnfile = "simple-gateway-with-join-gateway.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "EndEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4", "Flow6"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_unknown_target() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "Foobar"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_current_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // string? currentElement = null; + // var nextElementId = "Foobar"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_next_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // string currentElement = "Task1"; + // string? nextElementId = null; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_current_and_next_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // string? currentElement = null; + // string? nextElementId = null; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void Constructor_Fails_if_invalid_bpmn() + // { + // Assert.Throws(() => ProcessTestUtils.SetupProcessReader("not-bpmn.bpmn")); + // } + // + // [Fact] + // public void GetFlowElement_returns_StartEvent_with_id() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.GetFlowElement("StartEvent").Should().BeOfType().And.BeEquivalentTo(new StartEvent() + // { + // Id = "StartEvent", + // Name = null!, + // Incoming = new List(), + // Outgoing = new List { "Flow1" } + // }); + // } + // + // [Fact] + // public void GetFlowElement_returns_ProcessTask_with_id() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.GetFlowElement("Task1").Should().BeOfType().And.BeEquivalentTo(new ProcessTask() + // { + // Id = "Task1", + // Name = null!, + // Incoming = new List { "Flow1" }, + // Outgoing = new List { "Flow2" }, + // ExtensionElements = new ExtensionElements() + // { + // AltinnProperties = new AltinnProperties() + // { + // AltinnActions = new List() + // { + // new() + // { + // Id = "submit", + // } + // }, + // TaskType = "data" + // } + // } + // }); + // } + // + // [Fact] + // public void GetFlowElement_returns_EndEvent_with_id() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.GetFlowElement("EndEvent").Should().BeOfType().And.BeEquivalentTo(new EndEvent() + // { + // Id = "EndEvent", + // Name = null!, + // Incoming = new List { "Flow4", "Flow5" }, + // Outgoing = new List() + // }); + // } + // + // [Fact] + // public void GetFlowElement_returns_null_when_id_not_found() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.GetFlowElement("Foobar").Should().BeNull(); + // } + // + // [Fact] + // public void GetFlowElement_returns_Gateway_with_id() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.GetFlowElement("Gateway1").Should().BeOfType().And.BeEquivalentTo(new ExclusiveGateway() + // { + // Id = "Gateway1", + // Name = null!, + // Default = "Flow3", + // Incoming = new List { "Flow2" }, + // Outgoing = new List { "Flow3", "Flow4" } + // }); + // } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/ProcessEngineTest/config/process/process.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/ProcessEngineTest/config/process/process.bpmn index f28219543..6c389b3bc 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestData/ProcessEngineTest/config/process/process.bpmn +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/ProcessEngineTest/config/process/process.bpmn @@ -11,9 +11,17 @@ targetNamespace="http://bpmn.io/schema/bpmn" > SequenceFlow_1n56yn5 - + SequenceFlow_1n56yn5 SequenceFlow_1oot28q + + + + + + + + SequenceFlow_1oot28q diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-default.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-default.bpmn index 45af6b661..a9ad1e9cb 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-default.bpmn +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-default.bpmn @@ -1,27 +1,50 @@ - + Flow1 - + Flow1 Flow2 + + + + + + data + + - + Flow2 Flow3 Flow4 - - + + Flow3 Flow5 + + + + + + + confirm + + - + Flow5 Flow4 diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-with-join-gateway.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-with-join-gateway.bpmn index 441a4aa95..2c1f15152 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-with-join-gateway.bpmn +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway-with-join-gateway.bpmn @@ -1,33 +1,55 @@ - + Flow1 - + Flow1 Flow2 + + + + + + data + + - + Flow2 Flow3 Flow4 - - + + Flow3 Flow5 + + + + + + data + + - + - Flow4 - Flow5 - Flow6 - - + Flow4 + Flow5 + Flow6 + + Flow6 diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway.bpmn index ce5be22a4..d5839d503 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway.bpmn +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-gateway.bpmn @@ -1,27 +1,50 @@ - + Flow1 - + Flow1 Flow2 + + + + + + + + - + Flow2 Flow3 Flow4 - - + + Flow3 Flow5 + + + + + + + + + - + Flow5 Flow4 diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-both.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-both.bpmn new file mode 100644 index 000000000..06dc5c065 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-both.bpmn @@ -0,0 +1,46 @@ + + + + + Flow1 + + + + Flow1 + Flow2 + + + + + + data2 + + + + + + Flow2 + Flow3 + + + + + + + confirmation2 + + + + + + Flow3 + + + diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-new.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-new.bpmn new file mode 100644 index 000000000..3422a65c8 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-linear-new.bpmn @@ -0,0 +1,46 @@ + + + + + Flow1 + + + + Flow1 + Flow2 + + + data + + + + + + + + + Flow2 + Flow3 + + + + + + + confirmation + + + + + + Flow3 + + + diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-no-end.bpmn b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-no-end.bpmn index 94cc8a644..66b353c33 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-no-end.bpmn +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestData/simple-no-end.bpmn @@ -12,13 +12,23 @@ xmlns:altinn="http://altinn.no"> Flow1 - + Flow1 Flow2 + + + data + + - + Flow2 + + + confirmation + + diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs b/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs index a0483d88a..75404a422 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs @@ -1,14 +1,13 @@ -using System.IO; using Altinn.App.Core.Interface; -using Altinn.App.Core.Internal.Process; +using Altinn.App.Core.Internal.Process.V2; using Moq; -namespace Altinn.App.PlatformServices.Tests.Internal.Process.TestUtils; +namespace Altinn.App.Core.Tests.Internal.Process.TestUtils; internal static class ProcessTestUtils { private static readonly string TestDataPath = Path.Combine("Internal", "Process", "TestData"); - + internal static ProcessReader SetupProcessReader(string bpmnfile) { Mock processServiceMock = new Mock(); diff --git a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs new file mode 100644 index 000000000..bb2d6868a --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs @@ -0,0 +1,70 @@ +using Altinn.App.Core.Configuration; +using Altinn.App.Core.Features; +using Altinn.App.Core.Infrastructure.Clients.Storage; +using Altinn.App.Core.Interface; +using Altinn.App.Core.Internal.Process.V2; +using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; +using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; + +namespace Altinn.App.Core.Tests.Internal.Process.V2; + +public class ProcessEngineTest +{ + [Fact] + public async Task StartProcess_returns_unsuccessful_when_process_already_started() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() { Process = new ProcessState() { CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } } }; + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("Process is already started. Use next."); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task StartProcess_returns_unsuccessful_when_no_matching_startevent_found() + { + Mock processReaderMock = new(); + processReaderMock.Setup(r => r.GetStartEventIds()).Returns(new List() { "StartEvent_1" }); + IProcessEngine processEngine = GetProcessEngine(processReaderMock); + Instance instance = new Instance(); + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, StartEventId = "NotTheStartEventYouAreLookingFor" }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("No matching startevent"); + result.ErrorType.Should().Be("Conflict"); + } + + private static IProcessEngine GetProcessEngine(Mock? processReaderMock = null) + { + processReaderMock ??= new(); + Mock instanceMock = new(); + Mock profileMock = new(); + Mock processMock = new(); + Mock appEventsMock = new(); + Mock taskEventsMock = new(); + Mock processNavigatorMock = new(); + Mock eventsMock = new(); + IOptions appSettings = Options.Create(new AppSettings() { RegisterEventsWithEventsComponent = true }); + ILogger logger = new NullLogger(); + return new ProcessEngine( + instanceMock.Object, + processReaderMock.Object, + profileMock.Object, + processMock.Object, + appEventsMock.Object, + taskEventsMock.Object, + processNavigatorMock.Object, + eventsMock.Object, + appSettings, + logger); + } +} diff --git a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs new file mode 100644 index 000000000..2c045c140 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs @@ -0,0 +1,569 @@ +#nullable enable +using Altinn.App.Core.Internal.Process; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; +using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.App.Core.Tests.Internal.Process.TestUtils; +using FluentAssertions; +using Xunit; +using IProcessReader = Altinn.App.Core.Internal.Process.V2.IProcessReader; +using ProcessReader = Altinn.App.Core.Internal.Process.V2.ProcessReader; + +namespace Altinn.App.Core.Tests.Internal.Process.V2; + +public class ProcessReaderTests +{ + [Fact] + public void TestBpmnRead() + { + ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.GetStartEventIds().Should().Equal("StartEvent"); + pr.GetProcessTaskIds().Should().Equal("Task1", "Task2"); + pr.GetEndEventIds().Should().Equal("EndEvent"); + pr.GetSequenceFlowIds().Should().Equal("Flow1", "Flow2", "Flow3", "Flow4", "Flow5"); + pr.GetExclusiveGatewayIds().Should().Equal("Gateway1"); + } + + [Fact] + public void IsStartEvent_returns_true_when_element_is_StartEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsStartEvent("StartEvent").Should().BeTrue(); + } + + [Fact] + public void IsStartEvent_returns_false_when_element_is_not_StartEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsStartEvent("Task1").Should().BeFalse(); + pr.IsStartEvent("EndEvent").Should().BeFalse(); + pr.IsStartEvent("Gateway1").Should().BeFalse(); + pr.IsStartEvent("Foobar").Should().BeFalse(); + pr.IsStartEvent(null).Should().BeFalse(); + } + + [Fact] + public void IsProcessTask_returns_true_when_element_is_ProcessTask() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsProcessTask("Task1").Should().BeTrue(); + } + + [Fact] + public void IsProcessTask_returns_false_when_element_is_not_ProcessTask() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsProcessTask("StartEvent").Should().BeFalse(); + pr.IsProcessTask("EndEvent").Should().BeFalse(); + pr.IsProcessTask("Gateway1").Should().BeFalse(); + pr.IsProcessTask("Foobar").Should().BeFalse(); + pr.IsProcessTask(null).Should().BeFalse(); + } + + [Fact] + public void IsEndEvent_returns_true_when_element_is_EndEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsEndEvent("EndEvent").Should().BeTrue(); + } + + [Fact] + public void IsEndEvent_returns_false_when_element_is_not_EndEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsEndEvent("StartEvent").Should().BeFalse(); + pr.IsEndEvent("Task1").Should().BeFalse(); + pr.IsEndEvent("Gateway1").Should().BeFalse(); + pr.IsEndEvent("Foobar").Should().BeFalse(); + pr.IsEndEvent(null).Should().BeFalse(); + } + + [Fact] + public void GetNextElement_returns_gateway() + { + var currentElement = "Task1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo(new List() { new ExclusiveGateway() { Id = "Gateway1", Incoming = new List() { "Flow2" }, Outgoing = new List() { "Flow3", "Flow4" } } }); + } + + [Fact] + public void GetNextElement_returns_task() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "Task1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List + { + new ProcessTask() + { + Id = "Task2", + Incoming = new List { "Flow2" }, + Outgoing = new List { "Flow3" }, + Name = "Bekreft skjemadata", + TaskType = "confirmation" + } + }); + } + + [Fact] + public void GetNextElement_returns_all_targets_after_gateway() + { + var bpmnfile = "simple-gateway.bpmn"; + var currentElement = "Gateway1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List + { + new ProcessTask() + { + Id = "Task2", + Incoming = new List() { "Flow3" }, + Outgoing = new List() { "Flow5" }, + }, + new EndEvent() + { + Id = "EndEvent", + Incoming = new List() { "Flow4", "Flow5" }, + Outgoing = new List() + } + }); + } + + [Fact] + public void GetNextElement_returns_task1_in_simple_process() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "StartEvent"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List() + { + new ProcessTask() + { + Id = "Task1", + Name = "Utfylling", + Incoming = new List() { "Flow1" }, + Outgoing = new List() { "Flow2" }, + } + }); + } + + [Fact] + public void GetNextElement_returns_task2_in_simple_process() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "Task1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List() + { + new ProcessTask() + { + Id = "Task2", + Name = "Bekreft skjemadata", + Incoming = new List() { "Flow2" }, + Outgoing = new List() { "Flow3" }, + } + }); + } + + [Fact] + public void GetNextElement_returns_endevent_in_simple_process() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "Task2"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List() + { + new EndEvent() + { + Id = "EndEvent", + Incoming = new List() { "Flow3" }, + Outgoing = new List() + } + }); + } + + [Fact] + public void GetNextElement_returns_emptylist_if_task_without_output() + { + var bpmnfile = "simple-no-end.bpmn"; + var currentElement = "Task2"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().HaveCount(0); + } + + [Fact] + public void GetNextElement_currentElement_null() + { + var bpmnfile = "simple-linear.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.Invoking(p => p.GetNextElements(null!)).Should().Throw(); + } + + [Fact] + public void GetNextElement_throws_exception_if_step_not_found() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "NoStep"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.Invoking(p => p.GetNextElements(currentElement)).Should().Throw(); + } + + // [Fact] + // public void GetElementInfo_returns_correct_info_for_old_ProcessTask() + // { + // var bpmnfile = "simple-linear.bpmn"; + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "Task1", + // Name = "Utfylling", + // AltinnTaskType = "data", + // ElementType = "Task" + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_new_ProcessTask() + // { + // var bpmnfile = "simple-linear-new.bpmn"; + // var currentElement = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "Task1", + // Name = "Utfylling", + // AltinnTaskType = "data", + // ElementType = "Task", + // AltinnTaskActions = new List() + // { + // "submit" + // } + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_ProcessTask_prefers_new() + // { + // var bpmnfile = "simple-linear-both.bpmn"; + // var currentElement = "Task2"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "Task2", + // Name = "Bekreft skjemadata", + // AltinnTaskType = "confirmation2", + // ElementType = "Task", + // AltinnTaskActions = new List() + // { + // "confirm", + // "reject" + // } + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_StartEvent() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "StartEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "StartEvent", + // Name = null!, + // AltinnTaskType = null!, + // ElementType = "StartEvent" + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_correct_info_for_EndEvent() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "EndEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeEquivalentTo(new ElementInfo() + // { + // Id = "EndEvent", + // Name = null!, + // AltinnTaskType = null!, + // ElementType = "EndEvent" + // }); + // } + // + // [Fact] + // public void GetElementInfo_returns_null_for_ExclusiveGateway() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Gateway1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetElementInfo(currentElement); + // actual.Should().BeNull(); + // } + // + // [Fact] + // public void GetElementInfo_throws_argument_null_expcetion_when_elementName_is_null() + // { + // var bpmnfile = "simple-linear.bpmn"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // pr.Invoking(p => p.GetElementInfo(null!)).Should().Throw(); + // } + + [Fact] + public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetOutgoingSequenceFlows(null).Should().BeEmpty(); + } + + [Fact] + public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_ProcessTask() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Task1")); + outgoingFLows.Should().BeEquivalentTo(new List + { + new SequenceFlow() + { + Id = "Flow2", + FlowType = null!, + SourceRef = "Task1", + TargetRef = "Gateway1" + } + }); + } + + [Fact] + public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_Gateway() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Gateway1")); + outgoingFLows.Should().BeEquivalentTo(new List + { + new SequenceFlow() + { + Id = "Flow3", + FlowType = null!, + SourceRef = "Gateway1", + TargetRef = "Task2" + }, + new SequenceFlow() + { + Id = "Flow4", + FlowType = null!, + SourceRef = "Gateway1", + TargetRef = "EndEvent" + } + }); + } + + [Fact] + public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("EndEvent")); + outgoingFLows.Should().BeEmpty(); + } + + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_StartEvent_and_Task1() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "StartEvent"; + // var nextElementId = "Task1"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow1"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_Task2() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "Task2"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow2", "Flow3"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "EndEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent_complex() + // { + // var bpmnfile = "simple-gateway-with-join-gateway.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "EndEvent"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4", "Flow6"); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_unknown_target() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // var currentElement = "Task1"; + // var nextElementId = "Foobar"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_current_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // string? currentElement = null; + // var nextElementId = "Foobar"; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_next_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // string currentElement = "Task1"; + // string? nextElementId = null; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + // + // [Fact] + // public void GetSequenceFlowsBetween_returns_empty_list_when_current_and_next_is_null() + // { + // var bpmnfile = "simple-gateway-default.bpmn"; + // string? currentElement = null; + // string? nextElementId = null; + // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); + // var returnedIds = actual.Select(s => s.Id).ToList(); + // returnedIds.Should().BeEmpty(); + // } + + [Fact] + public void Constructor_Fails_if_invalid_bpmn() + { + Assert.Throws(() => ProcessTestUtils.SetupProcessReader("not-bpmn.bpmn")); + } + + [Fact] + public void GetFlowElement_returns_StartEvent_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("StartEvent").Should().BeOfType().And.BeEquivalentTo(new StartEvent() + { + Id = "StartEvent", + Name = null!, + Incoming = new List(), + Outgoing = new List { "Flow1" } + }); + } + + [Fact] + public void GetFlowElement_returns_ProcessTask_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("Task1").Should().BeOfType().And.BeEquivalentTo(new ProcessTask() + { + Id = "Task1", + Name = null!, + Incoming = new List { "Flow1" }, + Outgoing = new List { "Flow2" }, + ExtensionElements = new ExtensionElements() + { + AltinnProperties = new AltinnProperties() + { + AltinnActions = new List() + { + new() + { + Id = "submit", + } + }, + TaskType = "data" + } + } + }); + } + + [Fact] + public void GetFlowElement_returns_EndEvent_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("EndEvent").Should().BeOfType().And.BeEquivalentTo(new EndEvent() + { + Id = "EndEvent", + Name = null!, + Incoming = new List { "Flow4", "Flow5" }, + Outgoing = new List() + }); + } + + [Fact] + public void GetFlowElement_returns_null_when_id_not_found() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("Foobar").Should().BeNull(); + } + + [Fact] + public void GetFlowElement_returns_Gateway_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("Gateway1").Should().BeOfType().And.BeEquivalentTo(new ExclusiveGateway() + { + Id = "Gateway1", + Name = null!, + Default = "Flow3", + Incoming = new List { "Flow2" }, + Outgoing = new List { "Flow3", "Flow4" } + }); + } +} From 83106adf9af2bb287342fac73608d9fb4079c4a5 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 08:29:34 +0200 Subject: [PATCH 02/29] refactored to make class more testable --- .../Extensions/ServiceCollectionExtensions.cs | 1 + .../Process/V2/IProcessEventDispatcher.cs | 9 + .../Internal/Process/V2/ProcessEngine.cs | 158 ++---------------- .../Process/V2/ProcessEventDispatcher.cs | 133 +++++++++++++++ 4 files changed, 156 insertions(+), 145 deletions(-) create mode 100644 src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs create mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 96756267b..c9b302bb8 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -225,6 +225,7 @@ private static void AddProcessServices(IServiceCollection services) services.TryAddTransient(); services.TryAddTransient(); services.TryAddSingleton(); + services.TryAddTransient(); services.TryAddTransient(); } } diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs new file mode 100644 index 000000000..acf9c8fd7 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs @@ -0,0 +1,9 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.V2; + +public interface IProcessEventDispatcher +{ + Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events); + Task RegisterEventWithEventsComponent(Instance instance); +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs index 7b599409e..c98a1d2d6 100644 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs @@ -1,5 +1,4 @@ using System.Security.Claims; -using Altinn.App.Core.Configuration; using Altinn.App.Core.Extensions; using Altinn.App.Core.Helpers; using Altinn.App.Core.Interface; @@ -9,8 +8,6 @@ using Altinn.Platform.Profile.Models; using Altinn.Platform.Storage.Interface.Enums; using Altinn.Platform.Storage.Interface.Models; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Altinn.App.Core.Internal.Process.V2; @@ -19,52 +16,28 @@ namespace Altinn.App.Core.Internal.Process.V2; /// public class ProcessEngine : IProcessEngine { - private readonly IInstance _instanceService; private readonly IProcessReader _processReader; private readonly IProfile _profileService; - private readonly IProcess _processClient; - private readonly IAppEvents _appEvents; - private readonly ITaskEvents _taskEvents; private readonly IProcessNavigator _processNavigator; - private readonly IEvents _eventsService; - private readonly ILogger _logger; - private readonly bool _registerWithEventSystem; + private readonly IProcessEventDispatcher _processEventDispatcher; /// /// Initializes a new instance of the class /// - /// /// /// - /// - /// - /// /// - /// - /// - /// + /// public ProcessEngine( - IInstance instanceService, IProcessReader processReader, - IProfile profileService, - IProcess processClient, - IAppEvents appEvents, - ITaskEvents taskEvents, - IProcessNavigator processNavigator, - IEvents eventsService, - IOptions appSettings, - ILogger logger) + IProfile profileService, + IProcessNavigator processNavigator, + IProcessEventDispatcher processEventDispatcher) { - _instanceService = instanceService; _processReader = processReader; _profileService = profileService; - _processClient = processClient; - _appEvents = appEvents; - _taskEvents = taskEvents; _processNavigator = processNavigator; - _eventsService = eventsService; - _registerWithEventSystem = appSettings.Value.RegisterEventsWithEventsComponent; - _logger = logger; + _processEventDispatcher = processEventDispatcher; } /// @@ -107,7 +80,7 @@ public async Task StartProcess(ProcessStartRequest processS if (!processStartRequest.Dryrun) { - await UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, new List { startEvent, goToNextEvent }); + await _processEventDispatcher.UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, new List { startEvent, goToNextEvent }); } return new ProcessChangeResult() @@ -121,7 +94,7 @@ public async Task StartProcess(ProcessStartRequest processS public async Task Next(ProcessNextRequest request) { var instance = request.Instance; - string? currentElementId = instance.Process.CurrentTask?.ElementId; + string? currentElementId = instance.Process?.CurrentTask?.ElementId; if (currentElementId == null) { @@ -133,9 +106,6 @@ public async Task Next(ProcessNextRequest request) }; } - // Find next valid element. Later this will be dynamic - ProcessElement nextElement = await _processNavigator.GetNextTask(instance, currentElementId, request.Action); - var nextResult = await HandleMoveToNext(instance, request.User, request.Action); return new ProcessChangeResult() @@ -148,7 +118,7 @@ public async Task Next(ProcessNextRequest request) /// public async Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events) { - return await UpdateProcessAndDispatchEvents(startRequest.Instance, startRequest.Prefill, events); + return await _processEventDispatcher.UpdateProcessAndDispatchEvents(startRequest.Instance, startRequest.Prefill, events); } /// @@ -209,10 +179,7 @@ private async Task ProcessNext(Instance instance, ClaimsPrin return null; } - - /// - /// Assumes that nextElementId is a valid task/state - /// + private async Task> MoveProcessToNext( Instance instance, ClaimsPrincipal user, @@ -226,9 +193,8 @@ private async Task> MoveProcessToNext( ProcessElement nextElement = await _processNavigator.GetNextTask(instance, instance.Process.CurrentTask.ElementId, action); DateTime now = DateTime.UtcNow; - bool previousIsProcessTask = _processReader.IsProcessTask(previousElementId); // ending previous element if task - if (previousIsProcessTask) + if (_processReader.IsProcessTask(previousElementId)) { instance.Process = previousState; events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); @@ -269,82 +235,6 @@ private async Task> MoveProcessToNext( return events; } - /// - /// This - /// - private async Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events) - { - await HandleProcessChanges(instance, events, prefill); - - // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived - Instance updatedInstance = await _instanceService.UpdateProcess(instance); - await _processClient.DispatchProcessEventsToStorage(updatedInstance, events); - - // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. - updatedInstance = await _instanceService.GetInstance(updatedInstance); - - return updatedInstance; - } - - /// - /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. - /// - /// Each implementation - /// - private async Task HandleProcessChanges(Instance instance, List events, Dictionary? prefill) - { - foreach (InstanceEvent processEvent in events) - { - if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) - { - string? elementId = processEvent.ProcessInfo?.CurrentTask?.ElementId; - ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); - switch (eventType) - { - case InstanceEventType.process_StartEvent: - break; - case InstanceEventType.process_StartTask: - await task.HandleTaskStart(elementId, instance, prefill); - break; - case InstanceEventType.process_EndTask: - await task.HandleTaskComplete(elementId, instance); - break; - case InstanceEventType.process_AbandonTask: - await task.HandleTaskAbandon(elementId, instance); - await _instanceService.UpdateProcess(instance); - break; - case InstanceEventType.process_EndEvent: - await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, instance); - break; - } - } - } - } - - /// - /// Identify the correct task implementation - /// - /// - private ITask GetProcessTask(string? altinnTaskType) - { - if (string.IsNullOrEmpty(altinnTaskType)) - { - return new NullTask(); - } - - ITask task = new DataTask(_taskEvents); - if (altinnTaskType.Equals("confirmation")) - { - task = new ConfirmationTask(_taskEvents); - } - else if (altinnTaskType.Equals("feedback")) - { - task = new FeedbackTask(_taskEvents); - } - - return task; - } - private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) { int? userId = user.GetUserIdAsInt(); @@ -377,33 +267,11 @@ private async Task GenerateProcessChangeEvent(string eventType, I var processStateChange = await ProcessNext(instance, user, action); if (processStateChange != null) { - instance = await UpdateProcessAndDispatchEvents(instance, new Dictionary(), processStateChange.Events); + instance = await _processEventDispatcher.UpdateProcessAndDispatchEvents(instance, new Dictionary(), processStateChange.Events); - await RegisterEventWithEventsComponent(instance); + await _processEventDispatcher.RegisterEventWithEventsComponent(instance); } return processStateChange; } - - private async Task RegisterEventWithEventsComponent(Instance instance) - { - if (_registerWithEventSystem) - { - try - { - if (!string.IsNullOrWhiteSpace(instance.Process.CurrentTask?.ElementId)) - { - await _eventsService.AddEvent($"app.instance.process.movedTo.{instance.Process.CurrentTask.ElementId}", instance); - } - else if (instance.Process.EndEvent != null) - { - await _eventsService.AddEvent("app.instance.process.completed", instance); - } - } - catch (Exception exception) - { - _logger.LogWarning(exception, "Exception when sending event with the Events component"); - } - } - } } diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs new file mode 100644 index 000000000..b273581e0 --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs @@ -0,0 +1,133 @@ +using Altinn.App.Core.Configuration; +using Altinn.App.Core.Interface; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.Platform.Storage.Interface.Enums; +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Altinn.App.Core.Internal.Process.V2; + +class ProcessEventDispatcher : IProcessEventDispatcher +{ + private readonly IInstance _instanceService; + private readonly IProcess _processClient; + private readonly ITaskEvents _taskEvents; + private readonly IAppEvents _appEvents; + private readonly IEvents _eventsService; + private readonly bool _registerWithEventSystem; + private ILogger _logger; + + public ProcessEventDispatcher( + IInstance instanceService, + IProcess processClient, + ITaskEvents taskEvents, + IAppEvents appEvents, + IEvents eventsService, + IOptions appSettings, + ILogger logger) + { + _instanceService = instanceService; + _processClient = processClient; + _taskEvents = taskEvents; + _appEvents = appEvents; + _eventsService = eventsService; + _registerWithEventSystem = appSettings.Value.RegisterEventsWithEventsComponent; + _logger = logger; + } + + public async Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events) + { + await HandleProcessChanges(instance, events, prefill); + + // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived + Instance updatedInstance = await _instanceService.UpdateProcess(instance); + await _processClient.DispatchProcessEventsToStorage(updatedInstance, events); + + // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. + updatedInstance = await _instanceService.GetInstance(updatedInstance); + + return updatedInstance; + } + + public async Task RegisterEventWithEventsComponent(Instance instance) + { + if (_registerWithEventSystem) + { + try + { + if (!string.IsNullOrWhiteSpace(instance.Process.CurrentTask?.ElementId)) + { + await _eventsService.AddEvent($"app.instance.process.movedTo.{instance.Process.CurrentTask.ElementId}", instance); + } + else if (instance.Process.EndEvent != null) + { + await _eventsService.AddEvent("app.instance.process.completed", instance); + } + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Exception when sending event with the Events component"); + } + } + } + + /// + /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. + /// + /// Each implementation + /// + private async Task HandleProcessChanges(Instance instance, List events, Dictionary? prefill) + { + foreach (InstanceEvent processEvent in events) + { + if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) + { + string? elementId = processEvent.ProcessInfo?.CurrentTask?.ElementId; + ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); + switch (eventType) + { + case InstanceEventType.process_StartEvent: + break; + case InstanceEventType.process_StartTask: + await task.HandleTaskStart(elementId, instance, prefill); + break; + case InstanceEventType.process_EndTask: + await task.HandleTaskComplete(elementId, instance); + break; + case InstanceEventType.process_AbandonTask: + await task.HandleTaskAbandon(elementId, instance); + await _instanceService.UpdateProcess(instance); + break; + case InstanceEventType.process_EndEvent: + await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, instance); + break; + } + } + } + } + + /// + /// Identify the correct task implementation + /// + /// + private ITask GetProcessTask(string? altinnTaskType) + { + if (string.IsNullOrEmpty(altinnTaskType)) + { + return new NullTask(); + } + + ITask task = new DataTask(_taskEvents); + if (altinnTaskType.Equals("confirmation")) + { + task = new ConfirmationTask(_taskEvents); + } + else if (altinnTaskType.Equals("feedback")) + { + task = new FeedbackTask(_taskEvents); + } + + return task; + } +} \ No newline at end of file From d9a3a0baf2ab21682051b456d6aee0cadf8db9ea Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 08:29:47 +0200 Subject: [PATCH 03/29] added tests for ProcessEngine --- .../Internal/Process/V2/ProcessEngineTest.cs | 596 +++++++++++++++++- 1 file changed, 567 insertions(+), 29 deletions(-) diff --git a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs index bb2d6868a..daac6239a 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs @@ -1,22 +1,39 @@ -using Altinn.App.Core.Configuration; -using Altinn.App.Core.Features; -using Altinn.App.Core.Infrastructure.Clients.Storage; +#nullable enable +using System.Security.Claims; +using Altinn.App.Core.Extensions; using Altinn.App.Core.Interface; +using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Models; +using Altinn.Platform.Profile.Models; +using Altinn.Platform.Register.Models; +using Altinn.Platform.Storage.Interface.Enums; using Altinn.Platform.Storage.Interface.Models; +using AltinnCore.Authentication.Constants; using FluentAssertions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; +using FluentAssertions.Execution; using Moq; +using Newtonsoft.Json; using Xunit; using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; namespace Altinn.App.Core.Tests.Internal.Process.V2; -public class ProcessEngineTest +public class ProcessEngineTest : IDisposable { + private Mock _processReaderMock; + private Mock _profileMock; + private Mock _processNavigatorMock; + private Mock _processEventDispatcherMock; + + public ProcessEngineTest() + { + _processReaderMock = new(); + _profileMock = new(); + _processNavigatorMock = new(); + _processEventDispatcherMock = new(); + } + [Fact] public async Task StartProcess_returns_unsuccessful_when_process_already_started() { @@ -28,7 +45,7 @@ public async Task StartProcess_returns_unsuccessful_when_process_already_started result.ErrorMessage.Should().Be("Process is already started. Use next."); result.ErrorType.Should().Be("Conflict"); } - + [Fact] public async Task StartProcess_returns_unsuccessful_when_no_matching_startevent_found() { @@ -38,33 +55,554 @@ public async Task StartProcess_returns_unsuccessful_when_no_matching_startevent_ Instance instance = new Instance(); ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, StartEventId = "NotTheStartEventYouAreLookingFor" }; ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); result.Success.Should().BeFalse(); result.ErrorMessage.Should().Be("No matching startevent"); result.ErrorType.Should().Be("Conflict"); } - private static IProcessEngine GetProcessEngine(Mock? processReaderMock = null) + [Fact] + public async Task StartProcess_starts_process_and_moves_to_first_task_without_event_dispatch_when_dryrun() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + } + }; + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user, Dryrun = true }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); + result.Success.Should().BeTrue(); + } + + [Fact] + public async Task StartProcess_starts_process_and_moves_to_first_task() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + } + }; + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Name = "Utfylling" + }, + StartEvent = "StartEvent_1" + } + }; + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_StartEvent.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "StartEvent_1", + Flow = 1, + Validated = new() + { + CanCompleteTask = false + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_StartTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Name = "Utfylling", + AltinnTaskType = "data", + Flow = 2, + Validated = new() + { + CanCompleteTask = false + } + } + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + null, + It.Is>(l => CompareInstanceEvents(l, expectedInstanceEvents)))); + result.Success.Should().BeTrue(); + } + + [Fact] + public async Task Next_returns_unsuccessful_when_process_null() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() { Process = null }; + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("Instance does not have current task information!"); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task Next_returns_unsuccessful_when_process_currenttask_null() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() { Process = new ProcessState() { CurrentTask = null } }; + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("Instance does not have current task information!"); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task Next_moves_instance_to_next_task_and_produces_instanceevents() + { + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_2", + Flow = 3, + AltinnTaskType = "confirmation", + Name = "Bekreft" + }, + StartEvent = "StartEvent_1" + } + }; + IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); + Instance instance = new Instance() + { + InstanceOwner = new() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + AltinnTaskType = "data", + Flow = 2, + Validated = new() + { + CanCompleteTask = true + } + } + } + }; + ProcessState originalProcessState = instance.Process.Copy(); + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_2"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_1", null), Times.Once); + + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_EndTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Validated = new() + { + CanCompleteTask = true + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_StartTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + Name = "Bekreft", + AltinnTaskType = "confirmation", + Flow = 3 + } + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + It.IsAny?>(), + It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); + _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); + result.Success.Should().BeTrue(); + result.ProcessStateChange.Should().BeEquivalentTo( + new ProcessStateChange() + { + Events = expectedInstanceEvents, + NewProcessState = expectedInstance.Process, + OldProcessState = originalProcessState + }); + } + + [Fact] + public async Task Next_moves_instance_to_end_event_and_ends_proces() + { + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = null, + StartEvent = "StartEvent_1", + EndEvent = "EndEvent_1" + } + }; + IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); + Instance instance = new Instance() + { + InstanceOwner = new() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + AltinnTaskType = "confirmation", + Flow = 3, + Validated = new() + { + CanCompleteTask = true + } + } + } + }; + ProcessState originalProcessState = instance.Process.Copy(); + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + })); + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("EndEvent_1"), Times.Once); + _profileMock.Verify(p => p.GetUserProfile(1337), Times.Exactly(3)); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_2", null), Times.Once); + + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_EndTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + AuthenticationLevel = 2, + NationalIdentityNumber = "22927774937" + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + Flow = 3, + AltinnTaskType = "confirmation", + Validated = new() + { + CanCompleteTask = true + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_EndEvent.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + NationalIdentityNumber = "22927774937", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = null, + EndEvent = "EndEvent_1" + } + }, + new() + { + EventType = InstanceEventType.Submited.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + NationalIdentityNumber = "22927774937", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = null, + EndEvent = "EndEvent_1" + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + It.IsAny?>(), + It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); + _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); + result.Success.Should().BeTrue(); + result.ProcessStateChange.Should().BeEquivalentTo( + new ProcessStateChange() + { + Events = expectedInstanceEvents, + NewProcessState = expectedInstance.Process, + OldProcessState = originalProcessState + }); + } + + private IProcessEngine GetProcessEngine(Mock? processReaderMock = null, Instance? updatedInstance = null) { - processReaderMock ??= new(); - Mock instanceMock = new(); - Mock profileMock = new(); - Mock processMock = new(); - Mock appEventsMock = new(); - Mock taskEventsMock = new(); - Mock processNavigatorMock = new(); - Mock eventsMock = new(); - IOptions appSettings = Options.Create(new AppSettings() { RegisterEventsWithEventsComponent = true }); - ILogger logger = new NullLogger(); + if (processReaderMock == null) + { + _processReaderMock = new(); + _processReaderMock.Setup(r => r.GetStartEventIds()).Returns(new List() { "StartEvent_1" }); + _processReaderMock.Setup(r => r.IsProcessTask("StartEvent_1")).Returns(false); + _processReaderMock.Setup(r => r.IsEndEvent("Task_1")).Returns(false); + _processReaderMock.Setup(r => r.IsProcessTask("Task_1")).Returns(true); + _processReaderMock.Setup(r => r.IsProcessTask("Task_2")).Returns(true); + _processReaderMock.Setup(r => r.IsProcessTask("EndEvent_1")).Returns(false); + _processReaderMock.Setup(r => r.IsEndEvent("EndEvent_1")).Returns(true); + _processReaderMock.Setup(r => r.IsProcessTask("EndEvent_1")).Returns(false); + } + else + { + _processReaderMock = processReaderMock; + } + + _profileMock.Setup(p => p.GetUserProfile(1337)).ReturnsAsync(() => new UserProfile() + { + UserId = 1337, + Email = "test@example.com", + Party = new Party() + { + SSN = "22927774937" + } + }); + _processNavigatorMock.Setup( + pn => pn.GetNextTask(It.IsAny(), "StartEvent_1", It.IsAny())) + .ReturnsAsync(() => new ProcessTask() + { + Id = "Task_1", + Incoming = new List { "Flow_1" }, + Outgoing = new List { "Flow_2" }, + Name = "Utfylling", + ExtensionElements = new() + { + AltinnProperties = new() + { + TaskType = "data" + } + } + }); + _processNavigatorMock.Setup( + pn => pn.GetNextTask(It.IsAny(), "Task_1", It.IsAny())) + .ReturnsAsync(() => new ProcessTask() + { + Id = "Task_2", + Incoming = new List { "Flow_2" }, + Outgoing = new List { "Flow_3" }, + Name = "Bekreft", + ExtensionElements = new() + { + AltinnProperties = new() + { + TaskType = "confirmation" + } + } + }); + _processNavigatorMock.Setup( + pn => pn.GetNextTask(It.IsAny(), "Task_2", It.IsAny())) + .ReturnsAsync(() => new EndEvent() + { + Id = "EndEvent_1", + Incoming = new List { "Flow_3" } + }); + if (updatedInstance is not null) + { + _processEventDispatcherMock.Setup(d => d.UpdateProcessAndDispatchEvents(It.IsAny(), It.IsAny?>(), It.IsAny>())) + .ReturnsAsync(() => updatedInstance); + } + return new ProcessEngine( - instanceMock.Object, - processReaderMock.Object, - profileMock.Object, - processMock.Object, - appEventsMock.Object, - taskEventsMock.Object, - processNavigatorMock.Object, - eventsMock.Object, - appSettings, - logger); + _processReaderMock.Object, + _profileMock.Object, + _processNavigatorMock.Object, + _processEventDispatcherMock.Object); + } + + public void Dispose() + { + _processReaderMock.VerifyNoOtherCalls(); + _profileMock.VerifyNoOtherCalls(); + _processNavigatorMock.VerifyNoOtherCalls(); + _processEventDispatcherMock.VerifyNoOtherCalls(); + } + + private static bool CompareInstance(Instance expected, Instance actual) + { + expected.Process.Started = actual.Process.Started; + expected.Process.Ended = actual.Process.Ended; + if (actual.Process.CurrentTask != null) + { + expected.Process.CurrentTask.Started = actual.Process.CurrentTask.Started; + } + + return JsonCompare(expected, actual); + } + + private static bool CompareInstanceEvents(List expected, List actual) + { + for (int i = 0; i < expected.Count; i++) + { + expected[i].Created = actual[i].Created; + expected[i].ProcessInfo.Started = actual[i].ProcessInfo.Started; + expected[i].ProcessInfo.Ended = actual[i].ProcessInfo.Ended; + if(actual[i].ProcessInfo.CurrentTask != null) + { + expected[i].ProcessInfo.CurrentTask.Started = actual[i].ProcessInfo.CurrentTask.Started; + } + } + + return JsonCompare(expected, actual); + } + + public static bool JsonCompare(object expected, object actual) + { + if (ReferenceEquals(expected, actual)) + { + return true; + } + + if ((expected == null) || (actual == null)) + { + return false; + } + + if (expected.GetType() != actual.GetType()) + { + return false; + } + + var expectedJson = JsonConvert.SerializeObject(expected); + var actualJson = JsonConvert.SerializeObject(actual); + + return expectedJson == actualJson; } } From 3e596992621cd60d88fe92bb6ee8b3d9359569e3 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 10:10:39 +0200 Subject: [PATCH 04/29] Refactor and delete old and unused code --- .../Controllers/InstancesController.cs | 4 +- .../Controllers/ProcessController.cs | 5 +- .../Extensions/ServiceCollectionExtensions.cs | 9 +- .../Interface/IProcessChangeHandler.cs | 32 - .../Interface/IProcessEngine.cs | 26 - .../Internal/Process/FlowHydration.cs | 83 -- .../Internal/Process/IFlowHydration.cs | 29 - .../Process/{V2 => }/IProcessEngine.cs | 4 +- .../Process/IProcessEventDispatcher.cs | 23 + .../Process/{V2 => }/IProcessNavigator.cs | 5 +- .../Internal/Process/IProcessReader.cs | 222 +++--- .../Internal/Process/ProcessChangeHandler.cs | 449 ----------- .../Internal/Process/ProcessEngine.cs | 391 ++++++--- .../{V2 => }/ProcessEventDispatcher.cs | 7 +- .../Process/{V2 => }/ProcessNavigator.cs | 3 +- .../Internal/Process/ProcessNextRequest.cs | 23 + .../Internal/Process/ProcessReader.cs | 385 ++++----- .../Internal/Process/ProcessStartRequest.cs | 31 + .../Process/V2/IProcessEventDispatcher.cs | 9 - .../Internal/Process/V2/IProcessReader.cs | 114 --- .../Internal/Process/V2/ProcessEngine.cs | 277 ------- .../Internal/Process/V2/ProcessNextRequest.cs | 11 - .../Internal/Process/V2/ProcessReader.cs | 177 ----- .../Process/V2/ProcessStartRequest.cs | 15 - .../Models/ProcessChangeContext.cs | 76 -- .../Models/ProcessChangeInfo.cs | 18 - ...nstancesController_ActiveInstancesTests.cs | 2 +- .../InstancesController_CopyInstanceTests.cs | 4 +- .../Internal/Process/FlowHydrationTests.cs | 264 ------ .../Internal/Process/ProcessEngineTest.cs | 749 ++++++++++++++---- .../Internal/Process/ProcessReaderTests.cs | 656 ++++++++------- .../Process/TestUtils/ProcessTestUtils.cs | 2 +- .../Internal/Process/V2/ProcessEngineTest.cs | 608 -------------- .../Internal/Process/V2/ProcessReaderTests.cs | 569 ------------- 34 files changed, 1582 insertions(+), 3700 deletions(-) delete mode 100644 src/Altinn.App.Core/Interface/IProcessChangeHandler.cs delete mode 100644 src/Altinn.App.Core/Interface/IProcessEngine.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/FlowHydration.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/IFlowHydration.cs rename src/Altinn.App.Core/Internal/Process/{V2 => }/IProcessEngine.cs (88%) create mode 100644 src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs rename src/Altinn.App.Core/Internal/Process/{V2 => }/IProcessNavigator.cs (78%) delete mode 100644 src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs rename src/Altinn.App.Core/Internal/Process/{V2 => }/ProcessEventDispatcher.cs (96%) rename src/Altinn.App.Core/Internal/Process/{V2 => }/ProcessNavigator.cs (97%) create mode 100644 src/Altinn.App.Core/Internal/Process/ProcessNextRequest.cs create mode 100644 src/Altinn.App.Core/Internal/Process/ProcessStartRequest.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs delete mode 100644 src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs delete mode 100644 src/Altinn.App.Core/Models/ProcessChangeContext.cs delete mode 100644 src/Altinn.App.Core/Models/ProcessChangeInfo.cs delete mode 100644 test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs delete mode 100644 test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs delete mode 100644 test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index 238c3e85c..18be92a44 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -15,7 +15,7 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; -using Altinn.App.Core.Internal.Process.V2; +using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; using Altinn.Authorization.ABAC.Xacml.JsonProfile; @@ -30,7 +30,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; -using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; +using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; namespace Altinn.App.Api.Controllers { diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index 7afda8b4c..80e675da2 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -11,7 +11,6 @@ using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; using Altinn.Authorization.ABAC.Xacml.JsonProfile; @@ -25,8 +24,8 @@ using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; -using IProcessReader = Altinn.App.Core.Internal.Process.V2.IProcessReader; +using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; +using IProcessReader = Altinn.App.Core.Internal.Process.IProcessReader; namespace Altinn.App.Api.Controllers { diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index c9b302bb8..0d33707a6 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -23,7 +23,6 @@ using Altinn.App.Core.Internal.Language; using Altinn.App.Core.Internal.Pdf; using Altinn.App.Core.Internal.Process; -using Altinn.App.Core.Internal.Process.V2; using Altinn.App.Core.Internal.Texts; using Altinn.App.Core.Models; using Altinn.Common.AccessTokenClient.Configuration; @@ -37,10 +36,10 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Newtonsoft.Json.Linq; -using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; -using IProcessReader = Altinn.App.Core.Internal.Process.V2.IProcessReader; -using ProcessEngine = Altinn.App.Core.Internal.Process.V2.ProcessEngine; -using ProcessReader = Altinn.App.Core.Internal.Process.V2.ProcessReader; +using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; +using IProcessReader = Altinn.App.Core.Internal.Process.IProcessReader; +using ProcessEngine = Altinn.App.Core.Internal.Process.ProcessEngine; +using ProcessReader = Altinn.App.Core.Internal.Process.ProcessReader; namespace Altinn.App.Core.Extensions { diff --git a/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs b/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs deleted file mode 100644 index 53947ec36..000000000 --- a/src/Altinn.App.Core/Interface/IProcessChangeHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Altinn.App.Core.Models; - -namespace Altinn.App.Core.Interface -{ - /// - /// Interface for Process Change Handler. Responsible for triggering events - /// - public interface IProcessChangeHandler - { - // /// - // /// Handle start of process - // /// - // Task HandleStart(ProcessChangeContext processChange); - // - // /// - // /// Handle complete task and move to - // /// - // /// - // Task HandleMoveToNext(ProcessChangeContext processChange); - // - // /// - // /// Handle start task - // /// - // Task HandleStartTask(ProcessChangeContext processChange); - // - // /// - // /// Check if current task can be completed - // /// - // /// - // Task CanTaskBeEnded(ProcessChangeContext processChange); - } -} diff --git a/src/Altinn.App.Core/Interface/IProcessEngine.cs b/src/Altinn.App.Core/Interface/IProcessEngine.cs deleted file mode 100644 index 04fa6f778..000000000 --- a/src/Altinn.App.Core/Interface/IProcessEngine.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading.Tasks; -using Altinn.App.Core.Models; - -namespace Altinn.App.Core.Interface -{ - /// - /// Process engine interface that defines the Altinn App process engine - /// - public interface IProcessEngine - { - // /// - // /// Method to start a new process - // /// - // Task StartProcess(ProcessChangeContext processChange); - // - // /// - // /// Method to move process to next task/event - // /// - // Task Next(ProcessChangeContext processChange); - // - // /// - // /// Method to Start Task - // /// - // Task StartTask(ProcessChangeContext processChange); - } -} diff --git a/src/Altinn.App.Core/Internal/Process/FlowHydration.cs b/src/Altinn.App.Core/Internal/Process/FlowHydration.cs deleted file mode 100644 index c660fff44..000000000 --- a/src/Altinn.App.Core/Internal/Process/FlowHydration.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Altinn.App.Core.Features; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Internal.Process; - -/// -/// Class used to get next elements in the process based on the Process and custom implementations of to make gateway decisions. -/// -public class FlowHydration: IFlowHydration -{ - // private readonly IProcessReader _processReader; - // private readonly ExclusiveGatewayFactory _gatewayFactory; - // - // /// - // /// Initialize a new instance of FlowHydration - // /// - // /// IProcessReader implementation used to read the process - // /// ExclusiveGatewayFactory used to fetch gateway code to be executed - // public FlowHydration(IProcessReader processReader, ExclusiveGatewayFactory gatewayFactory) - // { - // _processReader = processReader; - // _gatewayFactory = gatewayFactory; - // } - // - // /// - // public async Task> NextFollowAndFilterGateways(Instance instance, string? currentElement, bool followDefaults = true) - // { - // List directFlowTargets = _processReader.GetNextElements(currentElement); - // return await NextFollowAndFilterGateways(instance, directFlowTargets!, followDefaults); - // } - // - // /// - // public async Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, bool followDefaults = true) - // { - // List filteredNext = new List(); - // foreach (var directFlowTarget in originNextElements) - // { - // if (directFlowTarget == null) - // { - // continue; - // } - // if (!IsGateway(directFlowTarget)) - // { - // filteredNext.Add(directFlowTarget); - // continue; - // } - // - // var gateway = (ExclusiveGateway)directFlowTarget; - // IProcessExclusiveGateway? gatewayFilter = _gatewayFactory.GetProcessExclusiveGateway(directFlowTarget.Id); - // List outgoingFlows = _processReader.GetOutgoingSequenceFlows(directFlowTarget); - // List filteredList; - // if (gatewayFilter == null) - // { - // filteredList = outgoingFlows; - // } - // else - // { - // filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance); - // } - // - // var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); - // if (followDefaults && defaultSequenceFlow != null) - // { - // var defaultTarget = _processReader.GetFlowElement(defaultSequenceFlow.TargetRef); - // filteredNext.AddRange(await NextFollowAndFilterGateways(instance, new List { defaultTarget })); - // } - // else - // { - // var filteredTargets= filteredList.Select(e => _processReader.GetFlowElement(e.TargetRef)).ToList(); - // filteredNext.AddRange(await NextFollowAndFilterGateways(instance, filteredTargets)); - // } - // } - // - // return filteredNext; - // } - // - // private static bool IsGateway(ProcessElement processElement) - // { - // return processElement is ExclusiveGateway; - // } -} diff --git a/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs b/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs deleted file mode 100644 index 2e84d9a3d..000000000 --- a/src/Altinn.App.Core/Internal/Process/IFlowHydration.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Altinn.App.Core.Features; -using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Internal.Process; - -/// -/// Defines method needed for filtering process flows based on application configuration -/// -public interface IFlowHydration -{ - // /// - // /// Checks next elements of current for gateways and apply custom gateway decisions based on implementations - // /// - // /// Instance data - // /// Current process element id - // /// Should follow default path out of gateway if set - // /// Filtered list of next elements - // public Task> NextFollowAndFilterGateways(Instance instance, string? currentElement, bool followDefaults = true); - // - // /// - // /// Takes a list of flows checks for gateways and apply custom gateway decisions based on implementations - // /// - // /// Instance data - // /// Original list of next elements - // /// /// Should follow default path out of gateway if set - // /// Filtered list of next elements - // public Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, bool followDefaults = true); -} diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/IProcessEngine.cs similarity index 88% rename from src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs rename to src/Altinn.App.Core/Internal/Process/IProcessEngine.cs index b009f41dd..b200165e7 100644 --- a/src/Altinn.App.Core/Internal/Process/V2/IProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessEngine.cs @@ -1,9 +1,7 @@ -using System.Security.Claims; -using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Models; using Altinn.Platform.Storage.Interface.Models; -namespace Altinn.App.Core.Internal.Process.V2 +namespace Altinn.App.Core.Internal.Process { /// /// Process engine interface that defines the Altinn App process engine diff --git a/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs new file mode 100644 index 000000000..5ed60e0cf --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs @@ -0,0 +1,23 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process; + +/// +/// Interface for dispatching events that occur during a process +/// +public interface IProcessEventDispatcher +{ + /// + /// Updates the instance process in storage and dispatches instance events + /// + /// The instance with updated process + /// Prefill data + /// Events that should be dispatched + /// Instance from storage after update + Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events); + /// + /// Dispatch events for instance to the events system if AppSettings.RegisterEventsWithEventsComponent is true + /// + /// The instance to dispatch events for + Task RegisterEventWithEventsComponent(Instance instance); +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/IProcessNavigator.cs similarity index 78% rename from src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs rename to src/Altinn.App.Core/Internal/Process/IProcessNavigator.cs index 1f41fd645..75553bdbf 100644 --- a/src/Altinn.App.Core/Internal/Process/V2/IProcessNavigator.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessNavigator.cs @@ -1,8 +1,7 @@ -using Altinn.App.Core.Features; using Altinn.App.Core.Internal.Process.Elements.Base; using Altinn.Platform.Storage.Interface.Models; -namespace Altinn.App.Core.Internal.Process.V2 +namespace Altinn.App.Core.Internal.Process { /// /// Interface used to descipt the process navigator @@ -16,6 +15,6 @@ public interface IProcessNavigator /// Current process element id /// Action performed /// The next process task - public Task GetNextTask(Instance instance, string currentElement, string? action); + public Task GetNextTask(Instance instance, string currentElement, string? action); } } diff --git a/src/Altinn.App.Core/Internal/Process/IProcessReader.cs b/src/Altinn.App.Core/Internal/Process/IProcessReader.cs index 8bfd6bf22..52c281707 100644 --- a/src/Altinn.App.Core/Internal/Process/IProcessReader.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessReader.cs @@ -9,126 +9,106 @@ namespace Altinn.App.Core.Internal.Process; public interface IProcessReader { - // /// - // /// Get all defined StartEvents in the process - // /// - // /// - // public List GetStartEvents(); - // - // /// - // /// Get ids of all defined StartEvents in the process - // /// - // /// - // public List GetStartEventIds(); - // - // /// - // /// Check id element is StartEvent - // /// - // /// Id of process element - // /// true if elementId is of type StartEvent - // public bool IsStartEvent(string? elementId); - // - // /// - // /// Get all defined Tasks in the process - // /// - // /// - // public List GetProcessTasks(); - // - // /// - // /// Get ids of all defined Tasks in the process - // /// - // /// - // public List GetProcessTaskIds(); - // - // /// - // /// Check id element is ProcessTask - // /// - // /// Id of process element - // /// true if elementId is of type Task - // public bool IsProcessTask(string? elementId); - // - // /// - // /// Get all ExclusiveGateways defined in the process - // /// - // /// - // public List GetExclusiveGateways(); - // - // /// - // /// Get ids of all defined ExclusiveGateways in the process - // /// - // /// - // public List GetExclusiveGatewayIds(); - // - // /// - // /// Get all EndEvents defined in the process - // /// - // /// - // public List GetEndEvents(); - // - // /// - // /// Get ids of all EndEvents defined in the process - // /// - // /// - // public List GetEndEventIds(); - // - // /// - // /// Check id element is EndEvent - // /// - // /// Id of process element - // /// true if elementId is of type EndEvent - // public bool IsEndEvent(string? elementId); - // - // /// - // /// Get all SequenceFlows defined in the process - // /// - // /// - // public List GetSequenceFlows(); - // - // - // /// - // /// Get ids of all SequenceFlows defined in the process - // /// - // /// - // public List GetSequenceFlowIds(); - // - // /// - // /// Find all possible next elements from current element - // /// - // /// Current process element id - // /// - // public List GetNextElements(string? currentElementId); - // - // /// - // /// Find ids of all possible next elements from current element - // /// - // /// Current ProcessElement Id - // /// - // public List GetNextElementIds(string? currentElement); - // - // /// - // /// Get SequenceFlows out of the bpmn element - // /// - // /// Element to get the outgoing sequenceflows from - // /// Outgoing sequence flows - // public List GetOutgoingSequenceFlows(ProcessElement? flowElement); - // - // /// - // /// Returns a list of sequence flow to be followed between current step and next element - // /// - // public List GetSequenceFlowsBetween(string? currentStepId, string? nextElementId); - // - // /// - // /// Returns StartEvent, Task or EndEvent with given Id, null if element not found - // /// - // /// Id of element to look for - // /// or null - // public ProcessElement? GetFlowElement(string? elementId); - // - // /// - // /// Retuns ElementInfo for StartEvent, Task or EndEvent with given Id, null if element not found - // /// - // /// Id of element to look for - // /// or null - // public ElementInfo? GetElementInfo(string? elementId); + /// + /// Get all defined StartEvents in the process + /// + /// + public List GetStartEvents(); + + /// + /// Get ids of all defined StartEvents in the process + /// + /// + public List GetStartEventIds(); + + /// + /// Check id element is StartEvent + /// + /// Id of process element + /// true if elementId is of type StartEvent + public bool IsStartEvent(string? elementId); + + /// + /// Get all defined Tasks in the process + /// + /// + public List GetProcessTasks(); + + /// + /// Get ids of all defined Tasks in the process + /// + /// + public List GetProcessTaskIds(); + + /// + /// Check id element is ProcessTask + /// + /// Id of process element + /// true if elementId is of type Task + public bool IsProcessTask(string? elementId); + + /// + /// Get all ExclusiveGateways defined in the process + /// + /// + public List GetExclusiveGateways(); + + /// + /// Get ids of all defined ExclusiveGateways in the process + /// + /// + public List GetExclusiveGatewayIds(); + + /// + /// Get all EndEvents defined in the process + /// + /// + public List GetEndEvents(); + + /// + /// Get ids of all EndEvents defined in the process + /// + /// + public List GetEndEventIds(); + + /// + /// Check id element is EndEvent + /// + /// Id of process element + /// true if elementId is of type EndEvent + public bool IsEndEvent(string? elementId); + + /// + /// Get all SequenceFlows defined in the process + /// + /// + public List GetSequenceFlows(); + + /// + /// Get SequenceFlows out of the bpmn element + /// + /// Element to get the outgoing sequenceflows from + /// Outgoing sequence flows + public List GetOutgoingSequenceFlows(ProcessElement? flowElement); + + /// + /// Get ids of all SequenceFlows defined in the process + /// + /// + public List GetSequenceFlowIds(); + + /// + /// Find all possible next elements from current element + /// + /// Current process element id + /// + public List GetNextElements(string? currentElementId); + + /// + /// Returns StartEvent, Task or EndEvent with given Id, null if element not found + /// + /// Id of element to look for + /// or null + public ProcessElement? GetFlowElement(string? elementId); } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs b/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs deleted file mode 100644 index e8a6301ed..000000000 --- a/src/Altinn.App.Core/Internal/Process/ProcessChangeHandler.cs +++ /dev/null @@ -1,449 +0,0 @@ -using System.Security.Claims; -using Altinn.App.Core.Configuration; -using Altinn.App.Core.Extensions; -using Altinn.App.Core.Features.Validation; -using Altinn.App.Core.Helpers; -using Altinn.App.Core.Interface; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Models; -using Altinn.App.Core.Models.Validation; -using Altinn.Platform.Profile.Models; -using Altinn.Platform.Storage.Interface.Enums; -using Altinn.Platform.Storage.Interface.Models; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Altinn.App.Core.Internal.Process -{ - /// - /// Handler that implements needed logic related to different process changes. Identifies the correct types of tasks and trigger the different task and event - /// - /// While ProcessEngine.cs only understand standard BPMN process this handler fully understand the Altinn App context - /// - public class ProcessChangeHandler : IProcessChangeHandler - { - // private readonly IInstance _instanceClient; - // private readonly IProcess _processService; - // private readonly IProcessReader _processReader; - // private readonly ILogger _logger; - // private readonly IValidation _validationService; - // private readonly IEvents _eventsService; - // private readonly IProfile _profileClient; - // private readonly AppSettings _appSettings; - // private readonly IAppEvents _appEvents; - // private readonly ITaskEvents _taskEvents; - // - // /// - // /// Altinn App specific process change handler - // /// - // public ProcessChangeHandler( - // ILogger logger, - // IProcess processService, - // IProcessReader processReader, - // IInstance instanceClient, - // IValidation validationService, - // IEvents eventsService, - // IProfile profileClient, - // IOptions appSettings, - // IAppEvents appEvents, - // ITaskEvents taskEvents) - // { - // _logger = logger; - // _processService = processService; - // _instanceClient = instanceClient; - // _processReader = processReader; - // _validationService = validationService; - // _eventsService = eventsService; - // _profileClient = profileClient; - // _appSettings = appSettings.Value; - // _appEvents = appEvents; - // _taskEvents = taskEvents; - // } - // - // /// - // public async Task HandleMoveToNext(ProcessChangeContext processChange) - // { - // processChange.ProcessStateChange = await ProcessNext(processChange.Instance, processChange.RequestedProcessElementId, processChange.User); - // if (processChange.ProcessStateChange != null) - // { - // processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); - // - // await RegisterEventWithEventsComponent(processChange.Instance); - // } - // - // return processChange; - // } - // - // /// - // public async Task HandleStart(ProcessChangeContext processChange) - // { - // // start process - // ProcessStateChange startChange = await ProcessStart(processChange.Instance, processChange.ProcessFlowElements[0], processChange.User); - // InstanceEvent startEvent = CopyInstanceEventValue(startChange.Events.First()); - // - // ProcessStateChange nextChange = await ProcessNext(processChange.Instance, processChange.ProcessFlowElements[1], processChange.User); - // InstanceEvent goToNextEvent = CopyInstanceEventValue(nextChange.Events.First()); - // - // ProcessStateChange processStateChange = new ProcessStateChange - // { - // OldProcessState = startChange.OldProcessState, - // NewProcessState = nextChange.NewProcessState, - // Events = new List { startEvent, goToNextEvent } - // }; - // processChange.ProcessStateChange = processStateChange; - // - // if (!processChange.DontUpdateProcessAndDispatchEvents) - // { - // processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); - // } - // - // return processChange; - // } - // - // /// - // public async Task HandleStartTask(ProcessChangeContext processChange) - // { - // processChange.Instance = await UpdateProcessAndDispatchEvents(processChange); - // return processChange; - // } - // - // /// - // public async Task CanTaskBeEnded(ProcessChangeContext processChange) - // { - // List validationIssues = new List(); - // - // bool canEndTask; - // - // if (processChange.Instance.Process?.CurrentTask?.Validated == null || !processChange.Instance.Process.CurrentTask.Validated.CanCompleteTask) - // { - // validationIssues = await _validationService.ValidateAndUpdateProcess(processChange.Instance, processChange.Instance.Process.CurrentTask?.ElementId); - // - // canEndTask = await ProcessHelper.CanEndProcessTask(processChange.Instance, validationIssues); - // } - // else - // { - // canEndTask = await ProcessHelper.CanEndProcessTask(processChange.Instance, validationIssues); - // } - // - // return canEndTask; - // } - // - // /// - // /// Identify the correct task implementation - // /// - // /// - // private ITask GetProcessTask(string? altinnTaskType) - // { - // if (string.IsNullOrEmpty(altinnTaskType)) - // { - // return new NullTask(); - // } - // - // ITask task = new DataTask(_taskEvents); - // if (altinnTaskType.Equals("confirmation")) - // { - // task = new ConfirmationTask(_taskEvents); - // } - // else if (altinnTaskType.Equals("feedback")) - // { - // task = new FeedbackTask(_taskEvents); - // } - // - // return task; - // } - // - // /// - // /// This - // /// - // private async Task UpdateProcessAndDispatchEvents(ProcessChangeContext processChangeContext) - // { - // await HandleProcessChanges(processChangeContext); - // - // // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived - // Instance updatedInstance = await _instanceClient.UpdateProcess(processChangeContext.Instance); - // await _processService.DispatchProcessEventsToStorage(updatedInstance, processChangeContext.ProcessStateChange.Events); - // - // // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. - // updatedInstance = await _instanceClient.GetInstance(updatedInstance); - // - // return updatedInstance; - // } - // - // /// - // /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. - // /// - // /// Each implementation - // /// - // internal async Task HandleProcessChanges(ProcessChangeContext processChangeContext) - // { - // foreach (InstanceEvent processEvent in processChangeContext.ProcessStateChange.Events) - // { - // if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) - // { - // processChangeContext.ElementToBeProcessed = processEvent.ProcessInfo?.CurrentTask?.ElementId; - // ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); - // switch (eventType) - // { - // case InstanceEventType.process_StartEvent: - // break; - // case InstanceEventType.process_StartTask: - // await task.HandleTaskStart(processChangeContext.Instance.Process.CurrentTask.ElementId, processChangeContext.Instance, processChangeContext.Prefill); - // break; - // case InstanceEventType.process_EndTask: - // await task.HandleTaskComplete(processChangeContext.Instance.Process.CurrentTask.ElementId, processChangeContext.Instance); - // break; - // case InstanceEventType.process_AbandonTask: - // await task.HandleTaskAbandon(processChangeContext.Instance.Process.CurrentTask.ElementId, processChangeContext.Instance); - // await _instanceClient.UpdateProcess(processChangeContext.Instance); - // break; - // case InstanceEventType.process_EndEvent: - // processChangeContext.ElementToBeProcessed = processEvent.ProcessInfo?.EndEvent; - // await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, processChangeContext.Instance); - // break; - // } - // } - // } - // } - // - // /// - // /// Does not save process. Instance is updated. - // /// - // private async Task ProcessStart(Instance instance, string startEvent, ClaimsPrincipal user) - // { - // if (instance.Process == null) - // { - // DateTime now = DateTime.UtcNow; - // - // ProcessState startState = new ProcessState - // { - // Started = now, - // StartEvent = startEvent, - // CurrentTask = new ProcessElementInfo { Flow = 1 } - // }; - // - // instance.Process = startState; - // - // List events = new List - // { - // await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString(), instance, now, user), - // }; - // - // return new ProcessStateChange - // { - // OldProcessState = null!, - // NewProcessState = startState, - // Events = events, - // }; - // } - // - // return null; - // } - // - // private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) - // { - // int? userId = user.GetUserIdAsInt(); - // InstanceEvent instanceEvent = new InstanceEvent - // { - // InstanceId = instance.Id, - // InstanceOwnerPartyId = instance.InstanceOwner.PartyId, - // EventType = eventType, - // Created = now, - // User = new PlatformUser - // { - // UserId = userId, - // AuthenticationLevel = user.GetAuthenticationLevel(), - // OrgId = user.GetOrg() - // }, - // ProcessInfo = instance.Process, - // }; - // - // if (string.IsNullOrEmpty(instanceEvent.User.OrgId) && userId != null) - // { - // UserProfile up = await _profileClient.GetUserProfile((int)userId); - // instanceEvent.User.NationalIdentityNumber = up.Party.SSN; - // } - // - // return instanceEvent; - // } - // - // private static InstanceEvent CopyInstanceEventValue(InstanceEvent e) - // { - // return new InstanceEvent - // { - // Created = e.Created, - // DataId = e.DataId, - // EventType = e.EventType, - // Id = e.Id, - // InstanceId = e.InstanceId, - // InstanceOwnerPartyId = e.InstanceOwnerPartyId, - // ProcessInfo = new ProcessState - // { - // Started = e.ProcessInfo?.Started, - // CurrentTask = new ProcessElementInfo - // { - // Flow = e.ProcessInfo?.CurrentTask.Flow, - // AltinnTaskType = e.ProcessInfo?.CurrentTask.AltinnTaskType, - // ElementId = e.ProcessInfo?.CurrentTask.ElementId, - // Name = e.ProcessInfo?.CurrentTask.Name, - // Started = e.ProcessInfo?.CurrentTask.Started, - // Ended = e.ProcessInfo?.CurrentTask.Ended, - // Validated = new ValidationStatus - // { - // CanCompleteTask = e.ProcessInfo?.CurrentTask?.Validated?.CanCompleteTask ?? false, - // Timestamp = e.ProcessInfo?.CurrentTask?.Validated?.Timestamp - // } - // }, - // - // StartEvent = e.ProcessInfo?.StartEvent - // }, - // User = new PlatformUser - // { - // AuthenticationLevel = e.User.AuthenticationLevel, - // EndUserSystemId = e.User.EndUserSystemId, - // OrgId = e.User.OrgId, - // UserId = e.User.UserId, - // NationalIdentityNumber = e.User?.NationalIdentityNumber - // } - // }; - // } - // - // /// - // /// Moves instance's process to nextElement id. Returns the instance together with process events. - // /// - // public async Task ProcessNext(Instance instance, string? nextElementId, ClaimsPrincipal userContext) - // { - // if (instance.Process != null) - // { - // ProcessStateChange result = new ProcessStateChange - // { - // OldProcessState = new ProcessState() - // { - // Started = instance.Process.Started, - // CurrentTask = instance.Process.CurrentTask, - // StartEvent = instance.Process.StartEvent - // } - // }; - // - // result.Events = await MoveProcessToNext(instance, nextElementId, userContext); - // result.NewProcessState = instance.Process; - // return result; - // } - // - // return null; - // } - // - // /// - // /// Assumes that nextElementId is a valid task/state - // /// - // private async Task> MoveProcessToNext( - // Instance instance, - // string? nextElementId, - // ClaimsPrincipal user) - // { - // List events = new List(); - // - // ProcessState previousState = Copy(instance.Process); - // ProcessState currentState = instance.Process; - // string? previousElementId = currentState.CurrentTask?.ElementId; - // - // ElementInfo? nextElementInfo = _processReader.GetElementInfo(nextElementId); - // List flows = _processReader.GetSequenceFlowsBetween(previousElementId, nextElementId); - // ProcessSequenceFlowType sequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); - // DateTime now = DateTime.UtcNow; - // bool previousIsProcessTask = _processReader.IsProcessTask(previousElementId); - // // ending previous element if task - // if (previousIsProcessTask && sequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext)) - // { - // instance.Process = previousState; - // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); - // instance.Process = currentState; - // } - // else if (previousIsProcessTask) - // { - // instance.Process = previousState; - // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_AbandonTask.ToString(), instance, now, user)); - // instance.Process = currentState; - // } - // - // // ending process if next element is end event - // if (_processReader.IsEndEvent(nextElementId)) - // { - // currentState.CurrentTask = null; - // currentState.Ended = now; - // currentState.EndEvent = nextElementId; - // - // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); - // - // // add submit event (to support Altinn2 SBL) - // events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); - // } - // else if (_processReader.IsProcessTask(nextElementId)) - // { - // currentState.CurrentTask = new ProcessElementInfo - // { - // Flow = currentState.CurrentTask.Flow + 1, - // ElementId = nextElementId, - // Name = nextElementInfo?.Name, - // Started = now, - // AltinnTaskType = nextElementInfo?.AltinnTaskType, - // Validated = null, - // FlowType = sequenceFlowType.ToString(), - // }; - // - // events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_StartTask.ToString(), instance, now, user)); - // } - // - // // current state points to the instance's process object. The following statement is unnecessary, but clarifies logic. - // instance.Process = currentState; - // - // return events; - // } - // - // private async Task RegisterEventWithEventsComponent(Instance instance) - // { - // if (_appSettings.RegisterEventsWithEventsComponent) - // { - // try - // { - // if (!string.IsNullOrWhiteSpace(instance.Process.CurrentTask?.ElementId)) - // { - // await _eventsService.AddEvent($"app.instance.process.movedTo.{instance.Process.CurrentTask.ElementId}", instance); - // } - // else if (instance.Process.EndEvent != null) - // { - // await _eventsService.AddEvent("app.instance.process.completed", instance); - // } - // } - // catch (Exception exception) - // { - // _logger.LogWarning(exception, "Exception when sending event with the Events component"); - // } - // } - // } - // - // private static ProcessState Copy(ProcessState original) - // { - // ProcessState processState = new ProcessState(); - // - // if (original.CurrentTask != null) - // { - // processState.CurrentTask = new ProcessElementInfo(); - // processState.CurrentTask.FlowType = original.CurrentTask.FlowType; - // processState.CurrentTask.Name = original.CurrentTask.Name; - // processState.CurrentTask.Validated = original.CurrentTask.Validated; - // processState.CurrentTask.AltinnTaskType = original.CurrentTask.AltinnTaskType; - // processState.CurrentTask.Flow = original.CurrentTask.Flow; - // processState.CurrentTask.ElementId = original.CurrentTask.ElementId; - // processState.CurrentTask.Started = original.CurrentTask.Started; - // processState.CurrentTask.Ended = original.CurrentTask.Ended; - // } - // - // processState.EndEvent = original.EndEvent; - // processState.Started = original.Started; - // processState.Ended = original.Ended; - // processState.StartEvent = original.StartEvent; - // - // return processState; - // } - } -} diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs index e7bfb2468..abce2494d 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs @@ -1,138 +1,277 @@ +using System.Security.Claims; +using Altinn.App.Core.Extensions; using Altinn.App.Core.Helpers; using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.Base; using Altinn.App.Core.Models; -using Microsoft.IdentityModel.Tokens; +using Altinn.Platform.Profile.Models; +using Altinn.Platform.Storage.Interface.Enums; +using Altinn.Platform.Storage.Interface.Models; -namespace Altinn.App.Core.Internal.Process +namespace Altinn.App.Core.Internal.Process; + +/// +/// Default implementation of the +/// +public class ProcessEngine : IProcessEngine { + private readonly IProcessReader _processReader; + private readonly IProfile _profileService; + private readonly IProcessNavigator _processNavigator; + private readonly IProcessEventDispatcher _processEventDispatcher; + + /// + /// Initializes a new instance of the class + /// + /// + /// + /// + /// + public ProcessEngine( + IProcessReader processReader, + IProfile profileService, + IProcessNavigator processNavigator, + IProcessEventDispatcher processEventDispatcher) + { + _processReader = processReader; + _profileService = profileService; + _processNavigator = processNavigator; + _processEventDispatcher = processEventDispatcher; + } + + /// + public async Task StartProcess(ProcessStartRequest processStartRequest) + { + if (processStartRequest.Instance.Process != null) + { + return new ProcessChangeResult() + { + Success = false, + ErrorMessage = "Process is already started. Use next.", + ErrorType = "Conflict" + }; + } + + string? validStartElement = ProcessHelper.GetValidStartEventOrError(processStartRequest.StartEventId, _processReader.GetStartEventIds(), out ProcessError? startEventError); + if (startEventError != null) + { + return new ProcessChangeResult() + { + Success = false, + ErrorMessage = "No matching startevent", + ErrorType = "Conflict" + }; + } + + // start process + ProcessStateChange? startChange = await ProcessStart(processStartRequest.Instance, validStartElement!, processStartRequest.User); + InstanceEvent? startEvent = startChange?.Events.First().CopyValues(); + ProcessStateChange nextChange = await ProcessNext(processStartRequest.Instance, processStartRequest.User); + //ProcessChangeResult nextChange = await Next(processStartRequest.InstanceIdentifier, processStartRequest.User); + InstanceEvent goToNextEvent = nextChange.Events.First().CopyValues(); + + ProcessStateChange processStateChange = new ProcessStateChange + { + OldProcessState = startChange.OldProcessState, + NewProcessState = nextChange.NewProcessState, + Events = new List { startEvent, goToNextEvent } + }; + + if (!processStartRequest.Dryrun) + { + await _processEventDispatcher.UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, new List { startEvent, goToNextEvent }); + } + + return new ProcessChangeResult() + { + Success = true, + ProcessStateChange = processStateChange + }; + } + + /// + public async Task Next(ProcessNextRequest request) + { + var instance = request.Instance; + string? currentElementId = instance.Process?.CurrentTask?.ElementId; + + if (currentElementId == null) + { + return new ProcessChangeResult() + { + Success = false, + ErrorMessage = $"Instance does not have current task information!", + ErrorType = "Conflict" + }; + } + + var nextResult = await HandleMoveToNext(instance, request.User, request.Action); + + return new ProcessChangeResult() + { + Success = true, + ProcessStateChange = nextResult + }; + } + + /// + public async Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events) + { + return await _processEventDispatcher.UpdateProcessAndDispatchEvents(startRequest.Instance, startRequest.Prefill, events); + } + + /// + /// Does not save process. Instance object is updated. + /// + private async Task ProcessStart(Instance instance, string startEvent, ClaimsPrincipal user) + { + if (instance.Process == null) + { + DateTime now = DateTime.UtcNow; + + ProcessState startState = new ProcessState + { + Started = now, + StartEvent = startEvent, + CurrentTask = new ProcessElementInfo { Flow = 1, ElementId = startEvent} + }; + + instance.Process = startState; + + List events = new List + { + await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString(), instance, now, user), + }; + + return new ProcessStateChange + { + OldProcessState = null!, + NewProcessState = startState, + Events = events, + }; + } + + return null; + } + /// - /// The process engine is responsible for all BMPN related functionality - /// - /// It will call processChange handler that is responsible - /// for the business logic happening for any process change. + /// Moves instance's process to nextElement id. Returns the instance together with process events. /// - public class ProcessEngine : IProcessEngine + private async Task ProcessNext(Instance instance, ClaimsPrincipal userContext, string? action = null) + { + if (instance.Process != null) + { + ProcessStateChange result = new ProcessStateChange + { + OldProcessState = new ProcessState() + { + Started = instance.Process.Started, + CurrentTask = instance.Process.CurrentTask, + StartEvent = instance.Process.StartEvent + } + }; + + result.Events = await MoveProcessToNext(instance, userContext, action); + result.NewProcessState = instance.Process; + return result; + } + + return null; + } + + private async Task> MoveProcessToNext( + Instance instance, + ClaimsPrincipal user, + string? action = null) { - // private readonly IProcessChangeHandler _processChangeHandler; - // - // private readonly IProcessReader _processReader; - // private readonly IFlowHydration _flowHydration; - // - // /// - // /// Initializes a new instance of the class. - // /// - // public ProcessEngine( - // IProcessChangeHandler processChangeHandler, - // IProcessReader processReader, - // IFlowHydration flowHydration) - // { - // _processChangeHandler = processChangeHandler; - // _processReader = processReader; - // _flowHydration = flowHydration; - // } - // - // /// - // /// Move process to next element in process - // /// - // public async Task Next(ProcessChangeContext processChange) - // { - // string? currentElementId = processChange.Instance.Process.CurrentTask?.ElementId; - // - // if (currentElementId == null) - // { - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Instance does not have current task information!", Type = "Conflict" }); - // processChange.FailedProcessChange = true; - // return processChange; - // } - // - // if (currentElementId.Equals(processChange.RequestedProcessElementId)) - // { - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Requested process element {processChange.RequestedProcessElementId} is same as instance's current task. Cannot change process.", Type = "Conflict" }); - // processChange.FailedProcessChange = true; - // return processChange; - // } - // - // // Find next valid element. Later this will be dynamic - // List possibleNextElements = await _flowHydration.NextFollowAndFilterGateways(processChange.Instance, currentElementId, processChange.RequestedProcessElementId.IsNullOrEmpty()); - // processChange.RequestedProcessElementId = ProcessHelper.GetValidNextElementOrError(processChange.RequestedProcessElementId, possibleNextElements.Select(e => e.Id).ToList(),out ProcessError? nextElementError); - // if (nextElementError != null) - // { - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = nextElementError.Text, Type = "Conflict" }); - // processChange.FailedProcessChange = true; - // return processChange; - // } - // - // List flows = _processReader.GetSequenceFlowsBetween(currentElementId, processChange.RequestedProcessElementId); - // processChange.ProcessSequenceFlowType = ProcessHelper.GetSequenceFlowType(flows); - // - // if (processChange.ProcessSequenceFlowType.Equals(ProcessSequenceFlowType.CompleteCurrentMoveToNext) && await _processChangeHandler.CanTaskBeEnded(processChange)) - // { - // return await _processChangeHandler.HandleMoveToNext(processChange); - // } - // - // if (processChange.ProcessSequenceFlowType.Equals(ProcessSequenceFlowType.AbandonCurrentReturnToNext)) - // { - // return await _processChangeHandler.HandleMoveToNext(processChange); - // } - // - // processChange.FailedProcessChange = true; - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Cannot complete/close current task {currentElementId}. The data element(s) assigned to the task are not valid!", Type = "conflict" }); - // return processChange; - // } - // - // /// - // /// Start application process and goes to first valid Task - // /// - // public async Task StartProcess(ProcessChangeContext processChange) - // { - // if (processChange.Instance.Process != null) - // { - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = "Process is already started. Use next.", Type = "Conflict" }); - // processChange.FailedProcessChange = true; - // return processChange; - // } - // - // string? validStartElement = ProcessHelper.GetValidStartEventOrError(processChange.RequestedProcessElementId, _processReader.GetStartEventIds(),out ProcessError? startEventError); - // if (startEventError != null) - // { - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = "No matching startevent", Type = "Conflict" }); - // processChange.FailedProcessChange = true; - // return processChange; - // } - // - // processChange.ProcessFlowElements = new List(); - // processChange.ProcessFlowElements.Add(validStartElement!); - // - // // find next task - // List possibleNextElements = (await _flowHydration.NextFollowAndFilterGateways(processChange.Instance, validStartElement)); - // string? nextValidElement = ProcessHelper.GetValidNextElementOrError(null, possibleNextElements.Select(e => e.Id).ToList(),out ProcessError? nextElementError); - // if (nextElementError != null) - // { - // processChange.ProcessMessages = new List(); - // processChange.ProcessMessages.Add(new ProcessChangeInfo() { Message = $"Unable to goto next element due to {nextElementError.Code}-{nextElementError.Text}", Type = "Conflict" }); - // processChange.FailedProcessChange = true; - // return processChange; - // } - // - // processChange.ProcessFlowElements.Add(nextValidElement!); - // - // return await _processChangeHandler.HandleStart(processChange); - // } - // - // /// - // /// Process Start Current task. The main goal is to trigger the Task related business logic seperate from start process - // /// - // public async Task StartTask(ProcessChangeContext processChange) - // { - // return await _processChangeHandler.HandleStartTask(processChange); - // } + List events = new List(); + + ProcessState previousState = instance.Process.Copy(); + ProcessState currentState = instance.Process; + string? previousElementId = currentState.CurrentTask?.ElementId; + + ProcessElement nextElement = await _processNavigator.GetNextTask(instance, instance.Process.CurrentTask.ElementId, action); + DateTime now = DateTime.UtcNow; + // ending previous element if task + if (_processReader.IsProcessTask(previousElementId)) + { + instance.Process = previousState; + events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); + instance.Process = currentState; + } + + // ending process if next element is end event + if (_processReader.IsEndEvent(nextElement.Id)) + { + currentState.CurrentTask = null; + currentState.Ended = now; + currentState.EndEvent = nextElement.Id; + + events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); + + // add submit event (to support Altinn2 SBL) + events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); + } + else if (_processReader.IsProcessTask(nextElement.Id)) + { + var task = nextElement as ProcessTask; + currentState.CurrentTask = new ProcessElementInfo + { + Flow = currentState.CurrentTask?.Flow + 1, + ElementId = nextElement.Id, + Name = nextElement?.Name, + Started = now, + AltinnTaskType = task?.ExtensionElements?.AltinnProperties.TaskType, + Validated = null, + }; + + events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_StartTask.ToString(), instance, now, user)); + } + + // current state points to the instance's process object. The following statement is unnecessary, but clarifies logic. + instance.Process = currentState; + + return events; + } + + private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) + { + int? userId = user.GetUserIdAsInt(); + InstanceEvent instanceEvent = new InstanceEvent + { + InstanceId = instance.Id, + InstanceOwnerPartyId = instance.InstanceOwner.PartyId, + EventType = eventType, + Created = now, + User = new PlatformUser + { + UserId = userId, + AuthenticationLevel = user.GetAuthenticationLevel(), + OrgId = user.GetOrg() + }, + ProcessInfo = instance.Process, + }; + + if (string.IsNullOrEmpty(instanceEvent.User.OrgId) && userId != null) + { + UserProfile up = await _profileService.GetUserProfile((int)userId); + instanceEvent.User.NationalIdentityNumber = up.Party.SSN; + } + + return instanceEvent; + } + + private async Task HandleMoveToNext(Instance instance, ClaimsPrincipal user, string? action) + { + var processStateChange = await ProcessNext(instance, user, action); + if (processStateChange != null) + { + instance = await _processEventDispatcher.UpdateProcessAndDispatchEvents(instance, new Dictionary(), processStateChange.Events); + + await _processEventDispatcher.RegisterEventWithEventsComponent(instance); + } + + return processStateChange; } } diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs similarity index 96% rename from src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs rename to src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs index b273581e0..a70d91b0c 100644 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessEventDispatcher.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs @@ -6,8 +6,11 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace Altinn.App.Core.Internal.Process.V2; +namespace Altinn.App.Core.Internal.Process; +/// +/// Default implementation of the process event dispatcher +/// class ProcessEventDispatcher : IProcessEventDispatcher { private readonly IInstance _instanceService; @@ -36,6 +39,7 @@ public ProcessEventDispatcher( _logger = logger; } + /// public async Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events) { await HandleProcessChanges(instance, events, prefill); @@ -50,6 +54,7 @@ public async Task UpdateProcessAndDispatchEvents(Instance instance, Di return updatedInstance; } + /// public async Task RegisterEventWithEventsComponent(Instance instance) { if (_registerWithEventSystem) diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs similarity index 97% rename from src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs rename to src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs index 1c03e6e92..dc4740ddb 100644 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessNavigator.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs @@ -3,7 +3,7 @@ using Altinn.App.Core.Internal.Process.Elements.Base; using Altinn.Platform.Storage.Interface.Models; -namespace Altinn.App.Core.Internal.Process.V2; +namespace Altinn.App.Core.Internal.Process; /// /// Default implementation of @@ -25,6 +25,7 @@ public ProcessNavigator(IProcessReader processReader, ExclusiveGatewayFactory ga } + /// public async Task GetNextTask(Instance instance, string currentElement, string? action) { List directFlowTargets = _processReader.GetNextElements(currentElement); diff --git a/src/Altinn.App.Core/Internal/Process/ProcessNextRequest.cs b/src/Altinn.App.Core/Internal/Process/ProcessNextRequest.cs new file mode 100644 index 000000000..18ebe02cc --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/ProcessNextRequest.cs @@ -0,0 +1,23 @@ +using System.Security.Claims; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process; + +/// +/// Class that defines the request for moving the process to the next task +/// +public class ProcessNextRequest +{ + /// + /// The instance to be moved to the next task + /// + public Instance Instance { get; set; } + /// + /// The user that is performing the action + /// + public ClaimsPrincipal User { get; set; } + /// + /// The action that is performed + /// + public string? Action { get; set; } +} diff --git a/src/Altinn.App.Core/Internal/Process/ProcessReader.cs b/src/Altinn.App.Core/Internal/Process/ProcessReader.cs index 5a5669914..ac2573cbd 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessReader.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessReader.cs @@ -10,225 +10,168 @@ namespace Altinn.App.Core.Internal.Process; /// public class ProcessReader : IProcessReader { - // private readonly Definitions _definitions; - // - // /// - // /// Create instance of ProcessReader where process stream is fetched from - // /// - // /// Implementation of IProcess used to get stream of BPMN process - // /// If BPMN file could not be deserialized - // public ProcessReader(IProcess processService) - // { - // XmlSerializer serializer = new XmlSerializer(typeof(Definitions)); - // Definitions? definitions = (Definitions?)serializer.Deserialize(processService.GetProcessDefinition()); - // - // _definitions = definitions ?? throw new InvalidOperationException("Failed to deserialize BPMN definitions. Definitions was null"); - // } - // - // /// - // public List GetStartEvents() - // { - // return _definitions.Process.StartEvents; - // } - // - // /// - // public List GetStartEventIds() - // { - // return GetStartEvents().Select(s => s.Id).ToList(); - // } - // - // /// - // public bool IsStartEvent(string? elementId) - // { - // return elementId != null && GetStartEventIds().Contains(elementId); - // } - // - // /// - // public List GetProcessTasks() - // { - // return _definitions.Process.Tasks; - // } - // - // /// - // public List GetProcessTaskIds() - // { - // return GetProcessTasks().Select(t => t.Id).ToList(); - // } - // - // /// - // public bool IsProcessTask(string? elementId) - // { - // return elementId != null && GetProcessTaskIds().Contains(elementId); - // } - // - // /// - // public List GetExclusiveGateways() - // { - // return _definitions.Process.ExclusiveGateway; - // } - // - // /// - // public List GetExclusiveGatewayIds() - // { - // return GetExclusiveGateways().Select(g => g.Id).ToList(); - // } - // - // /// - // public List GetEndEvents() - // { - // return _definitions.Process.EndEvents; - // } - // - // /// - // public List GetEndEventIds() - // { - // return GetEndEvents().Select(e => e.Id).ToList(); - // } - // - // /// - // public bool IsEndEvent(string? elementId) - // { - // return elementId != null && GetEndEventIds().Contains(elementId); - // } - // - // /// - // public List GetSequenceFlows() - // { - // return _definitions.Process.SequenceFlow; - // } - // - // /// - // public List GetSequenceFlowIds() - // { - // return GetSequenceFlows().Select(s => s.Id).ToList(); - // } - // - // /// - // public List GetNextElements(string? currentElementId) - // { - // EnsureArgumentNotNull(currentElementId, nameof(currentElementId)); - // List nextElements = new List(); - // List allElements = GetAllFlowElements(); - // if (!allElements.Exists(e => e.Id == currentElementId)) - // { - // throw new ProcessException($"Unable to find a element using element id {currentElementId}."); - // } - // - // foreach (SequenceFlow sequenceFlow in GetSequenceFlows().FindAll(s => s.SourceRef == currentElementId)) - // { - // nextElements.AddRange(allElements.FindAll(e => sequenceFlow.TargetRef == e.Id)); - // } - // - // return nextElements; - // } - // - // /// - // public List GetNextElementIds(string? currentElement) - // { - // return GetNextElements(currentElement).Select(e => e.Id).ToList(); - // } - // - // /// - // public List GetOutgoingSequenceFlows(ProcessElement? flowElement) - // { - // if (flowElement == null) - // { - // return new List(); - // } - // - // return GetSequenceFlows().FindAll(sf => flowElement.Outgoing.Contains(sf.Id)).ToList(); - // } - // - // /// - // public List GetSequenceFlowsBetween(string? currentStepId, string? nextElementId) - // { - // List flowsToReachTarget = new List(); - // foreach (SequenceFlow sequenceFlow in _definitions.Process.SequenceFlow.FindAll(s => s.SourceRef == currentStepId)) - // { - // if (sequenceFlow.TargetRef.Equals(nextElementId)) - // { - // flowsToReachTarget.Add(sequenceFlow); - // return flowsToReachTarget; - // } - // - // if (_definitions.Process.ExclusiveGateway != null && _definitions.Process.ExclusiveGateway.FirstOrDefault(g => g.Id == sequenceFlow.TargetRef) != null) - // { - // List subGatewayFlows = GetSequenceFlowsBetween(sequenceFlow.TargetRef, nextElementId); - // if (subGatewayFlows.Any()) - // { - // flowsToReachTarget.Add(sequenceFlow); - // flowsToReachTarget.AddRange(subGatewayFlows); - // return flowsToReachTarget; - // } - // } - // } - // - // return flowsToReachTarget; - // } - // - // /// - // public ProcessElement? GetFlowElement(string? elementId) - // { - // EnsureArgumentNotNull(elementId, nameof(elementId)); - // - // ProcessTask? task = _definitions.Process.Tasks.Find(t => t.Id == elementId); - // if (task != null) - // { - // return task; - // } - // - // EndEvent? endEvent = _definitions.Process.EndEvents.Find(e => e.Id == elementId); - // if (endEvent != null) - // { - // return endEvent; - // } - // - // StartEvent? startEvent = _definitions.Process.StartEvents.Find(e => e.Id == elementId); - // if (startEvent != null) - // { - // return startEvent; - // } - // - // return _definitions.Process.ExclusiveGateway.Find(e => e.Id == elementId); - // } - // - // /// - // public ElementInfo? GetElementInfo(string? elementId) - // { - // var e = GetFlowElement(elementId); - // if (e == null || e is ExclusiveGateway) - // { - // return null; - // } - // - // ElementInfo elementInfo = new ElementInfo() - // { - // Id = e.Id, - // Name = e.Name, - // ElementType = e.ElementType() - // }; - // if (e is ProcessTask task) - // { - // elementInfo.AltinnTaskType = task.ExtensionElements?.AltinnProperties?.TaskType ?? task.TaskType; - // elementInfo.AltinnTaskActions = task.ExtensionElements?.AltinnProperties?.AltinnActions?.ConvertAll(a => a.Id); - // } - // - // return elementInfo; - // } - // - // private List GetAllFlowElements() - // { - // List flowElements = new List(); - // flowElements.AddRange(GetStartEvents()); - // flowElements.AddRange(GetProcessTasks()); - // flowElements.AddRange(GetExclusiveGateways()); - // flowElements.AddRange(GetEndEvents()); - // return flowElements; - // } - // - // private static void EnsureArgumentNotNull(object? argument, string paramName) - // { - // if (argument == null) - // throw new ArgumentNullException(paramName); - // } + private readonly Definitions _definitions; + + /// + /// Create instance of ProcessReader where process stream is fetched from + /// + /// Implementation of IProcess used to get stream of BPMN process + /// If BPMN file could not be deserialized + public ProcessReader(IProcess processService) + { + XmlSerializer serializer = new XmlSerializer(typeof(Definitions)); + Definitions? definitions = (Definitions?)serializer.Deserialize(processService.GetProcessDefinition()); + + _definitions = definitions ?? throw new InvalidOperationException("Failed to deserialize BPMN definitions. Definitions was null"); + } + + /// + public List GetStartEvents() + { + return _definitions.Process.StartEvents; + } + + /// + public List GetStartEventIds() + { + return GetStartEvents().Select(s => s.Id).ToList(); + } + + /// + public bool IsStartEvent(string? elementId) + { + return elementId != null && GetStartEventIds().Contains(elementId); + } + + /// + public List GetProcessTasks() + { + return _definitions.Process.Tasks; + } + + /// + public List GetProcessTaskIds() + { + return GetProcessTasks().Select(t => t.Id).ToList(); + } + + /// + public bool IsProcessTask(string? elementId) + { + return elementId != null && GetProcessTaskIds().Contains(elementId); + } + + /// + public List GetExclusiveGateways() + { + return _definitions.Process.ExclusiveGateway; + } + + /// + public List GetExclusiveGatewayIds() + { + return GetExclusiveGateways().Select(g => g.Id).ToList(); + } + + /// + public List GetEndEvents() + { + return _definitions.Process.EndEvents; + } + + /// + public List GetEndEventIds() + { + return GetEndEvents().Select(e => e.Id).ToList(); + } + + /// + public bool IsEndEvent(string? elementId) + { + return elementId != null && GetEndEventIds().Contains(elementId); + } + + /// + public List GetSequenceFlows() + { + return _definitions.Process.SequenceFlow; + } + + /// + public List GetSequenceFlowIds() + { + return GetSequenceFlows().Select(s => s.Id).ToList(); + } + + /// + public ProcessElement? GetFlowElement(string? elementId) + { + EnsureArgumentNotNull(elementId, nameof(elementId)); + + ProcessTask? task = _definitions.Process.Tasks.Find(t => t.Id == elementId); + if (task != null) + { + return task; + } + + EndEvent? endEvent = _definitions.Process.EndEvents.Find(e => e.Id == elementId); + if (endEvent != null) + { + return endEvent; + } + + StartEvent? startEvent = _definitions.Process.StartEvents.Find(e => e.Id == elementId); + if (startEvent != null) + { + return startEvent; + } + + return _definitions.Process.ExclusiveGateway.Find(e => e.Id == elementId); + } + + /// + public List GetNextElements(string? currentElementId) + { + EnsureArgumentNotNull(currentElementId, nameof(currentElementId)); + List nextElements = new List(); + List allElements = GetAllFlowElements(); + if (!allElements.Exists(e => e.Id == currentElementId)) + { + throw new ProcessException($"Unable to find a element using element id {currentElementId}."); + } + + foreach (SequenceFlow sequenceFlow in GetSequenceFlows().FindAll(s => s.SourceRef == currentElementId)) + { + nextElements.AddRange(allElements.FindAll(e => sequenceFlow.TargetRef == e.Id)); + } + + return nextElements; + } + + /// + public List GetOutgoingSequenceFlows(ProcessElement? flowElement) + { + if (flowElement == null) + { + return new List(); + } + + return GetSequenceFlows().FindAll(sf => flowElement.Outgoing.Contains(sf.Id)).ToList(); + } + + private static void EnsureArgumentNotNull(object? argument, string paramName) + { + if (argument == null) + throw new ArgumentNullException(paramName); + } + + private List GetAllFlowElements() + { + List flowElements = new List(); + flowElements.AddRange(GetStartEvents()); + flowElements.AddRange(GetProcessTasks()); + flowElements.AddRange(GetExclusiveGateways()); + flowElements.AddRange(GetEndEvents()); + return flowElements; + } } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessStartRequest.cs b/src/Altinn.App.Core/Internal/Process/ProcessStartRequest.cs new file mode 100644 index 000000000..3303913fa --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/ProcessStartRequest.cs @@ -0,0 +1,31 @@ +using System.Security.Claims; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process; + +/// +/// Class that defines the request for starting a new process +/// +public class ProcessStartRequest +{ + /// + /// The instance to be started + /// + public Instance Instance { get; set; } + /// + /// The user that is starting the process + /// + public ClaimsPrincipal User { get; set; } + /// + /// The prefill data supplied when starting the process + /// + public Dictionary? Prefill { get; set; } + /// + /// The start event id, only needed if multiple start events in process + /// + public string? StartEventId { get; set; } + /// + /// If set to true the instance is not updated and the events are not dispatched + /// + public bool Dryrun { get; set; } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs deleted file mode 100644 index acf9c8fd7..000000000 --- a/src/Altinn.App.Core/Internal/Process/V2/IProcessEventDispatcher.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Internal.Process.V2; - -public interface IProcessEventDispatcher -{ - Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events); - Task RegisterEventWithEventsComponent(Instance instance); -} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs b/src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs deleted file mode 100644 index 88f91b326..000000000 --- a/src/Altinn.App.Core/Internal/Process/V2/IProcessReader.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.Elements.Base; - -namespace Altinn.App.Core.Internal.Process.V2; - -/// -/// Interface for classes that reads the applications process -/// -public interface IProcessReader -{ - - /// - /// Get all defined StartEvents in the process - /// - /// - public List GetStartEvents(); - - /// - /// Get ids of all defined StartEvents in the process - /// - /// - public List GetStartEventIds(); - - /// - /// Check id element is StartEvent - /// - /// Id of process element - /// true if elementId is of type StartEvent - public bool IsStartEvent(string? elementId); - - /// - /// Get all defined Tasks in the process - /// - /// - public List GetProcessTasks(); - - /// - /// Get ids of all defined Tasks in the process - /// - /// - public List GetProcessTaskIds(); - - /// - /// Check id element is ProcessTask - /// - /// Id of process element - /// true if elementId is of type Task - public bool IsProcessTask(string? elementId); - - /// - /// Get all ExclusiveGateways defined in the process - /// - /// - public List GetExclusiveGateways(); - - /// - /// Get ids of all defined ExclusiveGateways in the process - /// - /// - public List GetExclusiveGatewayIds(); - - /// - /// Get all EndEvents defined in the process - /// - /// - public List GetEndEvents(); - - /// - /// Get ids of all EndEvents defined in the process - /// - /// - public List GetEndEventIds(); - - /// - /// Check id element is EndEvent - /// - /// Id of process element - /// true if elementId is of type EndEvent - public bool IsEndEvent(string? elementId); - - /// - /// Get all SequenceFlows defined in the process - /// - /// - public List GetSequenceFlows(); - - /// - /// Get SequenceFlows out of the bpmn element - /// - /// Element to get the outgoing sequenceflows from - /// Outgoing sequence flows - public List GetOutgoingSequenceFlows(ProcessElement? flowElement); - - /// - /// Get ids of all SequenceFlows defined in the process - /// - /// - public List GetSequenceFlowIds(); - - /// - /// Find all possible next elements from current element - /// - /// Current process element id - /// - public List GetNextElements(string? currentElementId); - - /// - /// Returns StartEvent, Task or EndEvent with given Id, null if element not found - /// - /// Id of element to look for - /// or null - public ProcessElement? GetFlowElement(string? elementId); - -} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs deleted file mode 100644 index c98a1d2d6..000000000 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessEngine.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System.Security.Claims; -using Altinn.App.Core.Extensions; -using Altinn.App.Core.Helpers; -using Altinn.App.Core.Interface; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.App.Core.Models; -using Altinn.Platform.Profile.Models; -using Altinn.Platform.Storage.Interface.Enums; -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Internal.Process.V2; - -/// -/// Default implementation of the -/// -public class ProcessEngine : IProcessEngine -{ - private readonly IProcessReader _processReader; - private readonly IProfile _profileService; - private readonly IProcessNavigator _processNavigator; - private readonly IProcessEventDispatcher _processEventDispatcher; - - /// - /// Initializes a new instance of the class - /// - /// - /// - /// - /// - public ProcessEngine( - IProcessReader processReader, - IProfile profileService, - IProcessNavigator processNavigator, - IProcessEventDispatcher processEventDispatcher) - { - _processReader = processReader; - _profileService = profileService; - _processNavigator = processNavigator; - _processEventDispatcher = processEventDispatcher; - } - - /// - public async Task StartProcess(ProcessStartRequest processStartRequest) - { - if (processStartRequest.Instance.Process != null) - { - return new ProcessChangeResult() - { - Success = false, - ErrorMessage = "Process is already started. Use next.", - ErrorType = "Conflict" - }; - } - - string? validStartElement = ProcessHelper.GetValidStartEventOrError(processStartRequest.StartEventId, _processReader.GetStartEventIds(), out ProcessError? startEventError); - if (startEventError != null) - { - return new ProcessChangeResult() - { - Success = false, - ErrorMessage = "No matching startevent", - ErrorType = "Conflict" - }; - } - - // start process - ProcessStateChange? startChange = await ProcessStart(processStartRequest.Instance, validStartElement!, processStartRequest.User); - InstanceEvent? startEvent = startChange?.Events.First().CopyValues(); - ProcessStateChange nextChange = await ProcessNext(processStartRequest.Instance, processStartRequest.User); - //ProcessChangeResult nextChange = await Next(processStartRequest.InstanceIdentifier, processStartRequest.User); - InstanceEvent goToNextEvent = nextChange.Events.First().CopyValues(); - - ProcessStateChange processStateChange = new ProcessStateChange - { - OldProcessState = startChange.OldProcessState, - NewProcessState = nextChange.NewProcessState, - Events = new List { startEvent, goToNextEvent } - }; - - if (!processStartRequest.Dryrun) - { - await _processEventDispatcher.UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, new List { startEvent, goToNextEvent }); - } - - return new ProcessChangeResult() - { - Success = true, - ProcessStateChange = processStateChange - }; - } - - /// - public async Task Next(ProcessNextRequest request) - { - var instance = request.Instance; - string? currentElementId = instance.Process?.CurrentTask?.ElementId; - - if (currentElementId == null) - { - return new ProcessChangeResult() - { - Success = false, - ErrorMessage = $"Instance does not have current task information!", - ErrorType = "Conflict" - }; - } - - var nextResult = await HandleMoveToNext(instance, request.User, request.Action); - - return new ProcessChangeResult() - { - Success = true, - ProcessStateChange = nextResult - }; - } - - /// - public async Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events) - { - return await _processEventDispatcher.UpdateProcessAndDispatchEvents(startRequest.Instance, startRequest.Prefill, events); - } - - /// - /// Does not save process. Instance object is updated. - /// - private async Task ProcessStart(Instance instance, string startEvent, ClaimsPrincipal user) - { - if (instance.Process == null) - { - DateTime now = DateTime.UtcNow; - - ProcessState startState = new ProcessState - { - Started = now, - StartEvent = startEvent, - CurrentTask = new ProcessElementInfo { Flow = 1, ElementId = startEvent} - }; - - instance.Process = startState; - - List events = new List - { - await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString(), instance, now, user), - }; - - return new ProcessStateChange - { - OldProcessState = null!, - NewProcessState = startState, - Events = events, - }; - } - - return null; - } - - /// - /// Moves instance's process to nextElement id. Returns the instance together with process events. - /// - private async Task ProcessNext(Instance instance, ClaimsPrincipal userContext, string? action = null) - { - if (instance.Process != null) - { - ProcessStateChange result = new ProcessStateChange - { - OldProcessState = new ProcessState() - { - Started = instance.Process.Started, - CurrentTask = instance.Process.CurrentTask, - StartEvent = instance.Process.StartEvent - } - }; - - result.Events = await MoveProcessToNext(instance, userContext, action); - result.NewProcessState = instance.Process; - return result; - } - - return null; - } - - private async Task> MoveProcessToNext( - Instance instance, - ClaimsPrincipal user, - string? action = null) - { - List events = new List(); - - ProcessState previousState = instance.Process.Copy(); - ProcessState currentState = instance.Process; - string? previousElementId = currentState.CurrentTask?.ElementId; - - ProcessElement nextElement = await _processNavigator.GetNextTask(instance, instance.Process.CurrentTask.ElementId, action); - DateTime now = DateTime.UtcNow; - // ending previous element if task - if (_processReader.IsProcessTask(previousElementId)) - { - instance.Process = previousState; - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); - instance.Process = currentState; - } - - // ending process if next element is end event - if (_processReader.IsEndEvent(nextElement.Id)) - { - currentState.CurrentTask = null; - currentState.Ended = now; - currentState.EndEvent = nextElement.Id; - - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); - - // add submit event (to support Altinn2 SBL) - events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); - } - else if (_processReader.IsProcessTask(nextElement.Id)) - { - var task = nextElement as ProcessTask; - currentState.CurrentTask = new ProcessElementInfo - { - Flow = currentState.CurrentTask?.Flow + 1, - ElementId = nextElement.Id, - Name = nextElement?.Name, - Started = now, - AltinnTaskType = task?.ExtensionElements?.AltinnProperties.TaskType, - Validated = null, - }; - - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_StartTask.ToString(), instance, now, user)); - } - - // current state points to the instance's process object. The following statement is unnecessary, but clarifies logic. - instance.Process = currentState; - - return events; - } - - private async Task GenerateProcessChangeEvent(string eventType, Instance instance, DateTime now, ClaimsPrincipal user) - { - int? userId = user.GetUserIdAsInt(); - InstanceEvent instanceEvent = new InstanceEvent - { - InstanceId = instance.Id, - InstanceOwnerPartyId = instance.InstanceOwner.PartyId, - EventType = eventType, - Created = now, - User = new PlatformUser - { - UserId = userId, - AuthenticationLevel = user.GetAuthenticationLevel(), - OrgId = user.GetOrg() - }, - ProcessInfo = instance.Process, - }; - - if (string.IsNullOrEmpty(instanceEvent.User.OrgId) && userId != null) - { - UserProfile up = await _profileService.GetUserProfile((int)userId); - instanceEvent.User.NationalIdentityNumber = up.Party.SSN; - } - - return instanceEvent; - } - - private async Task HandleMoveToNext(Instance instance, ClaimsPrincipal user, string? action) - { - var processStateChange = await ProcessNext(instance, user, action); - if (processStateChange != null) - { - instance = await _processEventDispatcher.UpdateProcessAndDispatchEvents(instance, new Dictionary(), processStateChange.Events); - - await _processEventDispatcher.RegisterEventWithEventsComponent(instance); - } - - return processStateChange; - } -} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs deleted file mode 100644 index 0e617c18e..000000000 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessNextRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Security.Claims; -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Internal.Process.V2; - -public class ProcessNextRequest -{ - public Instance Instance { get; set; } - public ClaimsPrincipal User { get; set; } - public string? Action { get; set; } -} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs deleted file mode 100644 index ae297b952..000000000 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessReader.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System.Xml.Serialization; -using Altinn.App.Core.Interface; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.Elements.Base; - -namespace Altinn.App.Core.Internal.Process.V2; - -/// -/// Implementation of that reads from a -/// -public class ProcessReader : IProcessReader -{ - private readonly Definitions _definitions; - - /// - /// Create instance of ProcessReader where process stream is fetched from - /// - /// Implementation of IProcess used to get stream of BPMN process - /// If BPMN file could not be deserialized - public ProcessReader(IProcess processService) - { - XmlSerializer serializer = new XmlSerializer(typeof(Definitions)); - Definitions? definitions = (Definitions?)serializer.Deserialize(processService.GetProcessDefinition()); - - _definitions = definitions ?? throw new InvalidOperationException("Failed to deserialize BPMN definitions. Definitions was null"); - } - - /// - public List GetStartEvents() - { - return _definitions.Process.StartEvents; - } - - /// - public List GetStartEventIds() - { - return GetStartEvents().Select(s => s.Id).ToList(); - } - - /// - public bool IsStartEvent(string? elementId) - { - return elementId != null && GetStartEventIds().Contains(elementId); - } - - /// - public List GetProcessTasks() - { - return _definitions.Process.Tasks; - } - - /// - public List GetProcessTaskIds() - { - return GetProcessTasks().Select(t => t.Id).ToList(); - } - - /// - public bool IsProcessTask(string? elementId) - { - return elementId != null && GetProcessTaskIds().Contains(elementId); - } - - /// - public List GetExclusiveGateways() - { - return _definitions.Process.ExclusiveGateway; - } - - /// - public List GetExclusiveGatewayIds() - { - return GetExclusiveGateways().Select(g => g.Id).ToList(); - } - - /// - public List GetEndEvents() - { - return _definitions.Process.EndEvents; - } - - /// - public List GetEndEventIds() - { - return GetEndEvents().Select(e => e.Id).ToList(); - } - - /// - public bool IsEndEvent(string? elementId) - { - return elementId != null && GetEndEventIds().Contains(elementId); - } - - /// - public List GetSequenceFlows() - { - return _definitions.Process.SequenceFlow; - } - - /// - public List GetSequenceFlowIds() - { - return GetSequenceFlows().Select(s => s.Id).ToList(); - } - - /// - public ProcessElement? GetFlowElement(string? elementId) - { - EnsureArgumentNotNull(elementId, nameof(elementId)); - - ProcessTask? task = _definitions.Process.Tasks.Find(t => t.Id == elementId); - if (task != null) - { - return task; - } - - EndEvent? endEvent = _definitions.Process.EndEvents.Find(e => e.Id == elementId); - if (endEvent != null) - { - return endEvent; - } - - StartEvent? startEvent = _definitions.Process.StartEvents.Find(e => e.Id == elementId); - if (startEvent != null) - { - return startEvent; - } - - return _definitions.Process.ExclusiveGateway.Find(e => e.Id == elementId); - } - - /// - public List GetNextElements(string? currentElementId) - { - EnsureArgumentNotNull(currentElementId, nameof(currentElementId)); - List nextElements = new List(); - List allElements = GetAllFlowElements(); - if (!allElements.Exists(e => e.Id == currentElementId)) - { - throw new ProcessException($"Unable to find a element using element id {currentElementId}."); - } - - foreach (SequenceFlow sequenceFlow in GetSequenceFlows().FindAll(s => s.SourceRef == currentElementId)) - { - nextElements.AddRange(allElements.FindAll(e => sequenceFlow.TargetRef == e.Id)); - } - - return nextElements; - } - - /// - public List GetOutgoingSequenceFlows(ProcessElement? flowElement) - { - if (flowElement == null) - { - return new List(); - } - - return GetSequenceFlows().FindAll(sf => flowElement.Outgoing.Contains(sf.Id)).ToList(); - } - - private static void EnsureArgumentNotNull(object? argument, string paramName) - { - if (argument == null) - throw new ArgumentNullException(paramName); - } - - private List GetAllFlowElements() - { - List flowElements = new List(); - flowElements.AddRange(GetStartEvents()); - flowElements.AddRange(GetProcessTasks()); - flowElements.AddRange(GetExclusiveGateways()); - flowElements.AddRange(GetEndEvents()); - return flowElements; - } -} diff --git a/src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs b/src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs deleted file mode 100644 index 0326fae67..000000000 --- a/src/Altinn.App.Core/Internal/Process/V2/ProcessStartRequest.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Security.Claims; -using Altinn.App.Core.Models; -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Internal.Process.V2 -{ - public class ProcessStartRequest - { - public Instance Instance { get; set; } - public ClaimsPrincipal User { get; set; } - public Dictionary? Prefill { get; set; } - public string? StartEventId { get; set; } - public bool Dryrun { get; set; } - } -} diff --git a/src/Altinn.App.Core/Models/ProcessChangeContext.cs b/src/Altinn.App.Core/Models/ProcessChangeContext.cs deleted file mode 100644 index faa693e0d..000000000 --- a/src/Altinn.App.Core/Models/ProcessChangeContext.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Security.Claims; -using Altinn.App.Core.Internal.Process; -using Altinn.Platform.Storage.Interface.Models; - -namespace Altinn.App.Core.Models -{ - /// - /// Data entity that will floow between Process Api, Process Engine, Process Handlers and the TaskImpl/Gateway implt - /// - public class ProcessChangeContext - { - /// - /// Initializes a new instance of the class. - /// - public ProcessChangeContext(Instance instance, ClaimsPrincipal user) - { - Instance = instance; - User = user; - } - - /// - /// The current instance - /// - public Instance Instance { get; set; } - - /// - /// The request process element Id - /// - public string? RequestedProcessElementId { get; set; } - - /// - /// The process flow - /// - public List ProcessFlowElements { get; set; } = new List(); - - /// - /// Information messages - /// - public List ProcessMessages { get; set; } - - /// - /// Did process change fail? - /// - public bool FailedProcessChange { get; set; } - - /// - /// The identity performing the process change - /// - public ClaimsPrincipal User { get; set; } - - /// - /// ProcessStateChange - /// - public ProcessStateChange ProcessStateChange { get; set; } - - /// - /// The current process element to be processed - /// - public string ElementToBeProcessed { get; set; } - - /// - /// Process prefill - /// - public Dictionary Prefill { get; set; } - - /// - /// The ProcessSequenceFlowType - /// - public ProcessSequenceFlowType ProcessSequenceFlowType { get; set; } - - /// - /// Defines if the process handler should not handle events - /// - public bool DontUpdateProcessAndDispatchEvents { get; set; } - } -} diff --git a/src/Altinn.App.Core/Models/ProcessChangeInfo.cs b/src/Altinn.App.Core/Models/ProcessChangeInfo.cs deleted file mode 100644 index 5a2e3b9e1..000000000 --- a/src/Altinn.App.Core/Models/ProcessChangeInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Altinn.App.Core.Models -{ - /// - /// Process change info containing information passed around between process engine componentes - /// - public class ProcessChangeInfo - { - /// - /// Type message - /// - public string Type { get; set; } - - /// - /// The message itself - /// - public string Message { get; set; } - } -} diff --git a/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs b/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs index 4cf62f259..e86bea762 100644 --- a/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/InstancesController_ActiveInstancesTests.cs @@ -16,7 +16,7 @@ using Altinn.App.Core.Internal.App; using Altinn.Platform.Profile.Models; using Altinn.Platform.Register.Models; -using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; +using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; namespace Altinn.App.Api.Tests.Controllers; diff --git a/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs b/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs index 376ec623b..fe211c1a8 100644 --- a/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs @@ -5,7 +5,7 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.AppModel; -using Altinn.App.Core.Internal.Process.V2; +using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; @@ -20,7 +20,7 @@ using Moq; using Xunit; -using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; +using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; namespace Altinn.App.Api.Tests.Controllers; diff --git a/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs deleted file mode 100644 index 2ff7f5884..000000000 --- a/test/Altinn.App.Core.Tests/Internal/Process/FlowHydrationTests.cs +++ /dev/null @@ -1,264 +0,0 @@ -using System.Collections.Generic; -using Altinn.App.Core.Features; -using Altinn.App.Core.Internal.Process; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.App.PlatformServices.Tests.Internal.Process.StubGatewayFilters; -using Altinn.Platform.Storage.Interface.Models; -using FluentAssertions; -using Xunit; - -namespace Altinn.App.PlatformServices.Tests.Internal.Process; - -public class FlowHydrationTests -{ - // [Fact] - // public async void NextFollowAndFilterGateways_returns_next_element_if_no_gateway() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-linear.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = "Bekreft skjemadata", - // TaskType = "confirmation", - // Incoming = new List { "Flow2" }, - // Outgoing = new List { "Flow3" } - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_returns_empty_list_if_no_outgoing_flows() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-linear.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "EndEvent"); - // nextElements.Should().BeEmpty(); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_returns_default_if_no_filtering_is_implemented_and_default_set() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-default.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_returns_all_if_no_filtering_is_implemented_and_default_set_but_followDefaults_false() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-default.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1", false); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // }, - // new EndEvent() - // { - // Id = "EndEvent", - // Incoming = new List { "Flow5", "Flow4" }, - // Name = null!, - // Outgoing = new List() - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_returns_all_gateway_target_tasks_if_no_filter_and_default() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // }, - // new ProcessTask() - // { - // Id = "EndEvent", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow4", "Flow5" }, - // Outgoing = new List() - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_returns_all_gateway_target_tasks_if_no_filter_and_default_folowDefaults_false() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1", false); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // }, - // new ProcessTask() - // { - // Id = "EndEvent", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow4", "Flow5" }, - // Outgoing = new List() - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_runs_custom_filter_and_returns_result() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() - // { - // new DataValuesFilter("Gateway1", "choose") - // }); - // Instance i = new Instance() - // { - // DataValues = new Dictionary() - // { - // { "choose", "Flow3" } - // } - // }; - // - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_does_not_run_filter_with_non_matchin_ids() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() - // { - // new DataValuesFilter("Foobar", "choose") - // }); - // Instance i = new Instance() - // { - // DataValues = new Dictionary() - // { - // { "choose", "Flow3" } - // } - // }; - // - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // }, - // new ProcessTask() - // { - // Id = "EndEvent", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow6" }, - // Outgoing = new List() - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_follows_downstream_gateways() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List()); - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(new Instance(), "Task1"); - // nextElements.Should().BeEquivalentTo(new List() - // { - // new ProcessTask() - // { - // Id = "Task2", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow3" }, - // Outgoing = new List { "Flow5" } - // }, - // new ProcessTask() - // { - // Id = "EndEvent", - // Name = null!, - // TaskType = null!, - // Incoming = new List { "Flow6" }, - // Outgoing = new List() - // } - // }); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_runs_custom_filter_and_returns_empty_list_if_all_filtered_out() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List() - // { - // new DataValuesFilter("Gateway1", "choose1"), - // new DataValuesFilter("Gateway2", "choose2") - // }); - // Instance i = new Instance() - // { - // DataValues = new Dictionary() - // { - // { "choose1", "Flow4" }, - // { "choose2", "Foobar" } - // } - // }; - // - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "Task1"); - // nextElements.Should().BeEmpty(); - // } - // - // [Fact] - // public async void NextFollowAndFilterGateways_returns_empty_list_if_element_has_no_next() - // { - // IFlowHydration flowHydrator = SetupFlowHydration("simple-gateway-with-join-gateway.bpmn", new List()); - // Instance i = new Instance(); - // - // List nextElements = await flowHydrator.NextFollowAndFilterGateways(i, "EndEvent"); - // nextElements.Should().BeEmpty(); - // } - // - // private static IFlowHydration SetupFlowHydration(string bpmnfile, IEnumerable gatewayFilters) - // { - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // return new FlowHydration(pr, new ExclusiveGatewayFactory(gatewayFilters)); - // } -} diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs index ff38974e1..986ba8083 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs @@ -1,161 +1,606 @@ -using System.Collections.Generic; -using System.IO; -using Altinn.App.Core.Configuration; -using Altinn.App.Core.Features; -using Altinn.App.Core.Infrastructure.Clients.Storage; +#nullable enable +using System.Security.Claims; +using Altinn.App.Core.Extensions; +using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.Process; +using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Models; +using Altinn.Platform.Profile.Models; +using Altinn.Platform.Register.Models; +using Altinn.Platform.Storage.Interface.Enums; using Altinn.Platform.Storage.Interface.Models; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; +using AltinnCore.Authentication.Constants; +using FluentAssertions; +using Moq; +using Newtonsoft.Json; using Xunit; -namespace Altinn.App.PlatformServices.Tests.Internal.Process +namespace Altinn.App.Core.Tests.Internal.Process; + +public class ProcessEngineTest : IDisposable { - /// - /// Test clas for SimpleInstanceMapper - /// - public class ProcessEngineTest + private Mock _processReaderMock; + private Mock _profileMock; + private Mock _processNavigatorMock; + private Mock _processEventDispatcherMock; + + public ProcessEngineTest() + { + _processReaderMock = new(); + _profileMock = new(); + _processNavigatorMock = new(); + _processEventDispatcherMock = new(); + } + + [Fact] + public async Task StartProcess_returns_unsuccessful_when_process_already_started() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() { Process = new ProcessState() { CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } } }; + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("Process is already started. Use next."); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task StartProcess_returns_unsuccessful_when_no_matching_startevent_found() + { + Mock processReaderMock = new(); + processReaderMock.Setup(r => r.GetStartEventIds()).Returns(new List() { "StartEvent_1" }); + IProcessEngine processEngine = GetProcessEngine(processReaderMock); + Instance instance = new Instance(); + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, StartEventId = "NotTheStartEventYouAreLookingFor" }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("No matching startevent"); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task StartProcess_starts_process_and_moves_to_first_task_without_event_dispatch_when_dryrun() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + } + }; + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user, Dryrun = true }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); + result.Success.Should().BeTrue(); + } + + [Fact] + public async Task StartProcess_starts_process_and_moves_to_first_task() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + } + }; + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Name = "Utfylling" + }, + StartEvent = "StartEvent_1" + } + }; + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_StartEvent.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "StartEvent_1", + Flow = 1, + Validated = new() + { + CanCompleteTask = false + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_StartTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Name = "Utfylling", + AltinnTaskType = "data", + Flow = 2, + Validated = new() + { + CanCompleteTask = false + } + } + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + null, + It.Is>(l => CompareInstanceEvents(l, expectedInstanceEvents)))); + result.Success.Should().BeTrue(); + } + + [Fact] + public async Task Next_returns_unsuccessful_when_process_null() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() { Process = null }; + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("Instance does not have current task information!"); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task Next_returns_unsuccessful_when_process_currenttask_null() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() { Process = new ProcessState() { CurrentTask = null } }; + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + result.Success.Should().BeFalse(); + result.ErrorMessage.Should().Be("Instance does not have current task information!"); + result.ErrorType.Should().Be("Conflict"); + } + + [Fact] + public async Task Next_moves_instance_to_next_task_and_produces_instanceevents() + { + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_2", + Flow = 3, + AltinnTaskType = "confirmation", + Name = "Bekreft" + }, + StartEvent = "StartEvent_1" + } + }; + IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); + Instance instance = new Instance() + { + InstanceOwner = new() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + AltinnTaskType = "data", + Flow = 2, + Validated = new() + { + CanCompleteTask = true + } + } + } + }; + ProcessState originalProcessState = instance.Process.Copy(); + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_2"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_1", null), Times.Once); + + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_EndTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Validated = new() + { + CanCompleteTask = true + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_StartTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + Name = "Bekreft", + AltinnTaskType = "confirmation", + Flow = 3 + } + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + It.IsAny?>(), + It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); + _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); + result.Success.Should().BeTrue(); + result.ProcessStateChange.Should().BeEquivalentTo( + new ProcessStateChange() + { + Events = expectedInstanceEvents, + NewProcessState = expectedInstance.Process, + OldProcessState = originalProcessState + }); + } + + [Fact] + public async Task Next_moves_instance_to_end_event_and_ends_proces() + { + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = null, + StartEvent = "StartEvent_1", + EndEvent = "EndEvent_1" + } + }; + IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); + Instance instance = new Instance() + { + InstanceOwner = new() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + AltinnTaskType = "confirmation", + Flow = 3, + Validated = new() + { + CanCompleteTask = true + } + } + } + }; + ProcessState originalProcessState = instance.Process.Copy(); + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + })); + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("EndEvent_1"), Times.Once); + _profileMock.Verify(p => p.GetUserProfile(1337), Times.Exactly(3)); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_2", null), Times.Once); + + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_EndTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + AuthenticationLevel = 2, + NationalIdentityNumber = "22927774937" + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + Flow = 3, + AltinnTaskType = "confirmation", + Validated = new() + { + CanCompleteTask = true + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_EndEvent.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + NationalIdentityNumber = "22927774937", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = null, + EndEvent = "EndEvent_1" + } + }, + new() + { + EventType = InstanceEventType.Submited.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + NationalIdentityNumber = "22927774937", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = null, + EndEvent = "EndEvent_1" + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + It.IsAny?>(), + It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); + _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); + result.Success.Should().BeTrue(); + result.ProcessStateChange.Should().BeEquivalentTo( + new ProcessStateChange() + { + Events = expectedInstanceEvents, + NewProcessState = expectedInstance.Process, + OldProcessState = originalProcessState + }); + } + + private IProcessEngine GetProcessEngine(Mock? processReaderMock = null, Instance? updatedInstance = null) + { + if (processReaderMock == null) + { + _processReaderMock = new(); + _processReaderMock.Setup(r => r.GetStartEventIds()).Returns(new List() { "StartEvent_1" }); + _processReaderMock.Setup(r => r.IsProcessTask("StartEvent_1")).Returns(false); + _processReaderMock.Setup(r => r.IsEndEvent("Task_1")).Returns(false); + _processReaderMock.Setup(r => r.IsProcessTask("Task_1")).Returns(true); + _processReaderMock.Setup(r => r.IsProcessTask("Task_2")).Returns(true); + _processReaderMock.Setup(r => r.IsProcessTask("EndEvent_1")).Returns(false); + _processReaderMock.Setup(r => r.IsEndEvent("EndEvent_1")).Returns(true); + _processReaderMock.Setup(r => r.IsProcessTask("EndEvent_1")).Returns(false); + } + else + { + _processReaderMock = processReaderMock; + } + + _profileMock.Setup(p => p.GetUserProfile(1337)).ReturnsAsync(() => new UserProfile() + { + UserId = 1337, + Email = "test@example.com", + Party = new Party() + { + SSN = "22927774937" + } + }); + _processNavigatorMock.Setup( + pn => pn.GetNextTask(It.IsAny(), "StartEvent_1", It.IsAny())) + .ReturnsAsync(() => new ProcessTask() + { + Id = "Task_1", + Incoming = new List { "Flow_1" }, + Outgoing = new List { "Flow_2" }, + Name = "Utfylling", + ExtensionElements = new() + { + AltinnProperties = new() + { + TaskType = "data" + } + } + }); + _processNavigatorMock.Setup( + pn => pn.GetNextTask(It.IsAny(), "Task_1", It.IsAny())) + .ReturnsAsync(() => new ProcessTask() + { + Id = "Task_2", + Incoming = new List { "Flow_2" }, + Outgoing = new List { "Flow_3" }, + Name = "Bekreft", + ExtensionElements = new() + { + AltinnProperties = new() + { + TaskType = "confirmation" + } + } + }); + _processNavigatorMock.Setup( + pn => pn.GetNextTask(It.IsAny(), "Task_2", It.IsAny())) + .ReturnsAsync(() => new EndEvent() + { + Id = "EndEvent_1", + Incoming = new List { "Flow_3" } + }); + if (updatedInstance is not null) + { + _processEventDispatcherMock.Setup(d => d.UpdateProcessAndDispatchEvents(It.IsAny(), It.IsAny?>(), It.IsAny>())) + .ReturnsAsync(() => updatedInstance); + } + + return new ProcessEngine( + _processReaderMock.Object, + _profileMock.Object, + _processNavigatorMock.Object, + _processEventDispatcherMock.Object); + } + + public void Dispose() + { + _processReaderMock.VerifyNoOtherCalls(); + _profileMock.VerifyNoOtherCalls(); + _processNavigatorMock.VerifyNoOtherCalls(); + _processEventDispatcherMock.VerifyNoOtherCalls(); + } + + private static bool CompareInstance(Instance expected, Instance actual) { - // [Fact] - // public async void MissingCurrentTask() - // { - // IProcessReader processReader = GetProcessReader(); - // - // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - // - // Instance instance = new Instance - // { - // Process = new ProcessState() - // }; - // - // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!); - // - // processChangeContext = await processEngine.Next(processChangeContext); - // - // Assert.True(processChangeContext.FailedProcessChange); - // Assert.Equal("Instance does not have current task information!", processChangeContext.ProcessMessages[0].Message); - // } - // - // [Fact] - // public async void RequestingCurrentTask() - // { - // IProcessReader processReader = GetProcessReader(); - // - // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - // - // Instance instance = new Instance - // { - // Process = new ProcessState - // { - // CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } - // } - // }; - // - // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - // { - // RequestedProcessElementId = "Task_1" - // }; - // - // processChangeContext = await processEngine.Next(processChangeContext); - // - // Assert.True(processChangeContext.FailedProcessChange); - // Assert.Equal("Requested process element Task_1 is same as instance's current task. Cannot change process.", processChangeContext.ProcessMessages[0].Message); - // } - // - // [Fact] - // public async void RequestInvalidTask() - // { - // IProcessReader processReader = GetProcessReader(); - // - // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - // - // Instance instance = new Instance - // { - // Process = new ProcessState - // { - // CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } - // } - // }; - // - // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - // { - // RequestedProcessElementId = "Task_10" - // }; - // - // processChangeContext = await processEngine.Next(processChangeContext); - // - // Assert.True(processChangeContext.FailedProcessChange); - // Assert.Contains("The proposed next element id 'Task_10' is", processChangeContext.ProcessMessages[0].Message); - // } - // - // [Fact] - // public async void StartStartedTask() - // { - // IProcessReader processReader = GetProcessReader(); - // - // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - // - // Instance instance = new Instance - // { - // Process = new ProcessState - // { - // CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } - // } - // }; - // - // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - // { - // RequestedProcessElementId = "Task_10" - // }; - // - // processChangeContext = await processEngine.StartProcess(processChangeContext); - // - // Assert.True(processChangeContext.FailedProcessChange); - // Assert.Contains("Process is already started. Use next.", processChangeContext.ProcessMessages[0].Message); - // } - // - // [Fact] - // public async void InvalidStartEvent() - // { - // IProcessReader processReader = GetProcessReader(); - // - // ProcessEngine processEngine = new ProcessEngine(null!, processReader, GetFlowHydration(processReader)); - // - // Instance instance = new Instance(); - // - // ProcessChangeContext processChangeContext = new ProcessChangeContext(instance, null!) - // { - // RequestedProcessElementId = "Task_10" - // }; - // - // processChangeContext = await processEngine.StartProcess(processChangeContext); - // - // Assert.True(processChangeContext.FailedProcessChange); - // Assert.Contains("No matching startevent", processChangeContext.ProcessMessages[0].Message); - // } - // - // private static IProcessReader GetProcessReader() - // { - // AppSettings appSettings = new AppSettings - // { - // AppBasePath = Path.Join("Internal", "Process", "TestData", "ProcessEngineTest") + Path.DirectorySeparatorChar - // }; - // IOptions appSettingsO = Microsoft.Extensions.Options.Options.Create(appSettings); - // - // PlatformSettings platformSettings = new PlatformSettings - // { - // ApiStorageEndpoint = "http://localhost/" - // }; - // IOptions platformSettings0 = Microsoft.Extensions.Options.Options.Create(platformSettings); - // - // ProcessClient processClient = new ProcessClient(platformSettings0, appSettingsO, null!, new NullLogger(), null!, new System.Net.Http.HttpClient()); - // return new ProcessReader(processClient); - // } - // - // private static IFlowHydration GetFlowHydration(IProcessReader processReader) - // { - // return new FlowHydration(processReader, new ExclusiveGatewayFactory(new List())); - // } + expected.Process.Started = actual.Process.Started; + expected.Process.Ended = actual.Process.Ended; + if (actual.Process.CurrentTask != null) + { + expected.Process.CurrentTask.Started = actual.Process.CurrentTask.Started; + } + + return JsonCompare(expected, actual); + } + + private static bool CompareInstanceEvents(List expected, List actual) + { + for (int i = 0; i < expected.Count; i++) + { + expected[i].Created = actual[i].Created; + expected[i].ProcessInfo.Started = actual[i].ProcessInfo.Started; + expected[i].ProcessInfo.Ended = actual[i].ProcessInfo.Ended; + if(actual[i].ProcessInfo.CurrentTask != null) + { + expected[i].ProcessInfo.CurrentTask.Started = actual[i].ProcessInfo.CurrentTask.Started; + } + } + + return JsonCompare(expected, actual); + } + + public static bool JsonCompare(object expected, object actual) + { + if (ReferenceEquals(expected, actual)) + { + return true; + } + + if ((expected == null) || (actual == null)) + { + return false; + } + + if (expected.GetType() != actual.GetType()) + { + return false; + } + + var expectedJson = JsonConvert.SerializeObject(expected); + var actualJson = JsonConvert.SerializeObject(actual); + + return expectedJson == actualJson; } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs index ff38e439f..d532e99c7 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs @@ -1,168 +1,222 @@ #nullable enable -using System; -using System.Collections.Generic; -using System.Linq; using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; +using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.App.Core.Tests.Internal.Process.TestUtils; using FluentAssertions; using Xunit; -namespace Altinn.App.PlatformServices.Tests.Internal.Process; +namespace Altinn.App.Core.Tests.Internal.Process; public class ProcessReaderTests { - // [Fact] - // public void TestBpmnRead() - // { - // ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.GetStartEventIds().Should().Equal("StartEvent"); - // pr.GetProcessTaskIds().Should().Equal("Task1", "Task2"); - // pr.GetEndEventIds().Should().Equal("EndEvent"); - // pr.GetSequenceFlowIds().Should().Equal("Flow1", "Flow2", "Flow3", "Flow4", "Flow5"); - // pr.GetExclusiveGatewayIds().Should().Equal("Gateway1"); - // } - // - // [Fact] - // public void IsStartEvent_returns_true_when_element_is_StartEvent() - // { - // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.IsStartEvent("StartEvent").Should().BeTrue(); - // } - // - // [Fact] - // public void IsStartEvent_returns_false_when_element_is_not_StartEvent() - // { - // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.IsStartEvent("Task1").Should().BeFalse(); - // pr.IsStartEvent("EndEvent").Should().BeFalse(); - // pr.IsStartEvent("Gateway1").Should().BeFalse(); - // pr.IsStartEvent("Foobar").Should().BeFalse(); - // pr.IsStartEvent(null).Should().BeFalse(); - // } - // - // [Fact] - // public void IsProcessTask_returns_true_when_element_is_ProcessTask() - // { - // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.IsProcessTask("Task1").Should().BeTrue(); - // } - // - // [Fact] - // public void IsProcessTask_returns_false_when_element_is_not_ProcessTask() - // { - // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.IsProcessTask("StartEvent").Should().BeFalse(); - // pr.IsProcessTask("EndEvent").Should().BeFalse(); - // pr.IsProcessTask("Gateway1").Should().BeFalse(); - // pr.IsProcessTask("Foobar").Should().BeFalse(); - // pr.IsProcessTask(null).Should().BeFalse(); - // } - // - // [Fact] - // public void IsEndEvent_returns_true_when_element_is_EndEvent() - // { - // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.IsEndEvent("EndEvent").Should().BeTrue(); - // } - // - // [Fact] - // public void IsEndEvent_returns_false_when_element_is_not_EndEvent() - // { - // IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // pr.IsEndEvent("StartEvent").Should().BeFalse(); - // pr.IsEndEvent("Task1").Should().BeFalse(); - // pr.IsEndEvent("Gateway1").Should().BeFalse(); - // pr.IsEndEvent("Foobar").Should().BeFalse(); - // pr.IsEndEvent(null).Should().BeFalse(); - // } - // - // [Fact] - // public void GetNextElement_returns_gateway() - // { - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().Equal("Gateway1"); - // } - // - // [Fact] - // public void GetNextElement_returns_task() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().Equal("Task2"); - // } - // - // [Fact] - // public void GetNextElement_returns_all_targets_after_gateway() - // { - // var bpmnfile = "simple-gateway.bpmn"; - // var currentElement = "Gateway1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().Equal("Task2", "EndEvent"); - // } - // - // [Fact] - // public void GetNextElement_returns_task1_in_simple_process() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "StartEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().Equal("Task1"); - // } - // - // [Fact] - // public void GetNextElement_returns_task2_in_simple_process() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().Equal("Task2"); - // } - // - // [Fact] - // public void GetNextElement_returns_endevent_in_simple_process() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "Task2"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().Equal("EndEvent"); - // } - // - // [Fact] - // public void GetNextElement_returns_emptylist_if_task_without_output() - // { - // var bpmnfile = "simple-no-end.bpmn"; - // var currentElement = "Task2"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List nextElements = pr.GetNextElementIds(currentElement); - // nextElements.Should().HaveCount(0); - // } - // - // [Fact] - // public void GetNextElement_currentElement_null() - // { - // var bpmnfile = "simple-linear.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.Invoking(p => p.GetNextElementIds(null!)).Should().Throw(); - // } - // - // [Fact] - // public void GetNextElement_throws_exception_if_step_not_found() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "NoStep"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.Invoking(p => p.GetNextElementIds(currentElement)).Should().Throw(); - // } - // + [Fact] + public void TestBpmnRead() + { + ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.GetStartEventIds().Should().Equal("StartEvent"); + pr.GetProcessTaskIds().Should().Equal("Task1", "Task2"); + pr.GetEndEventIds().Should().Equal("EndEvent"); + pr.GetSequenceFlowIds().Should().Equal("Flow1", "Flow2", "Flow3", "Flow4", "Flow5"); + pr.GetExclusiveGatewayIds().Should().Equal("Gateway1"); + } + + [Fact] + public void IsStartEvent_returns_true_when_element_is_StartEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsStartEvent("StartEvent").Should().BeTrue(); + } + + [Fact] + public void IsStartEvent_returns_false_when_element_is_not_StartEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsStartEvent("Task1").Should().BeFalse(); + pr.IsStartEvent("EndEvent").Should().BeFalse(); + pr.IsStartEvent("Gateway1").Should().BeFalse(); + pr.IsStartEvent("Foobar").Should().BeFalse(); + pr.IsStartEvent(null).Should().BeFalse(); + } + + [Fact] + public void IsProcessTask_returns_true_when_element_is_ProcessTask() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsProcessTask("Task1").Should().BeTrue(); + } + + [Fact] + public void IsProcessTask_returns_false_when_element_is_not_ProcessTask() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsProcessTask("StartEvent").Should().BeFalse(); + pr.IsProcessTask("EndEvent").Should().BeFalse(); + pr.IsProcessTask("Gateway1").Should().BeFalse(); + pr.IsProcessTask("Foobar").Should().BeFalse(); + pr.IsProcessTask(null).Should().BeFalse(); + } + + [Fact] + public void IsEndEvent_returns_true_when_element_is_EndEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsEndEvent("EndEvent").Should().BeTrue(); + } + + [Fact] + public void IsEndEvent_returns_false_when_element_is_not_EndEvent() + { + IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + pr.IsEndEvent("StartEvent").Should().BeFalse(); + pr.IsEndEvent("Task1").Should().BeFalse(); + pr.IsEndEvent("Gateway1").Should().BeFalse(); + pr.IsEndEvent("Foobar").Should().BeFalse(); + pr.IsEndEvent(null).Should().BeFalse(); + } + + [Fact] + public void GetNextElement_returns_gateway() + { + var currentElement = "Task1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo(new List() { new ExclusiveGateway() { Id = "Gateway1", Incoming = new List() { "Flow2" }, Outgoing = new List() { "Flow3", "Flow4" } } }); + } + + [Fact] + public void GetNextElement_returns_task() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "Task1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List + { + new ProcessTask() + { + Id = "Task2", + Incoming = new List { "Flow2" }, + Outgoing = new List { "Flow3" }, + Name = "Bekreft skjemadata", + TaskType = "confirmation" + } + }); + } + + [Fact] + public void GetNextElement_returns_all_targets_after_gateway() + { + var bpmnfile = "simple-gateway.bpmn"; + var currentElement = "Gateway1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List + { + new ProcessTask() + { + Id = "Task2", + Incoming = new List() { "Flow3" }, + Outgoing = new List() { "Flow5" }, + }, + new EndEvent() + { + Id = "EndEvent", + Incoming = new List() { "Flow4", "Flow5" }, + Outgoing = new List() + } + }); + } + + [Fact] + public void GetNextElement_returns_task1_in_simple_process() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "StartEvent"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List() + { + new ProcessTask() + { + Id = "Task1", + Name = "Utfylling", + Incoming = new List() { "Flow1" }, + Outgoing = new List() { "Flow2" }, + } + }); + } + + [Fact] + public void GetNextElement_returns_task2_in_simple_process() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "Task1"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List() + { + new ProcessTask() + { + Id = "Task2", + Name = "Bekreft skjemadata", + Incoming = new List() { "Flow2" }, + Outgoing = new List() { "Flow3" }, + } + }); + } + + [Fact] + public void GetNextElement_returns_endevent_in_simple_process() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "Task2"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().BeEquivalentTo( + new List() + { + new EndEvent() + { + Id = "EndEvent", + Incoming = new List() { "Flow3" }, + Outgoing = new List() + } + }); + } + + [Fact] + public void GetNextElement_returns_emptylist_if_task_without_output() + { + var bpmnfile = "simple-no-end.bpmn"; + var currentElement = "Task2"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List nextElements = pr.GetNextElements(currentElement); + nextElements.Should().HaveCount(0); + } + + [Fact] + public void GetNextElement_currentElement_null() + { + var bpmnfile = "simple-linear.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.Invoking(p => p.GetNextElements(null!)).Should().Throw(); + } + + [Fact] + public void GetNextElement_throws_exception_if_step_not_found() + { + var bpmnfile = "simple-linear.bpmn"; + var currentElement = "NoStep"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.Invoking(p => p.GetNextElements(currentElement)).Should().Throw(); + } + // [Fact] // public void GetElementInfo_returns_correct_info_for_old_ProcessTask() // { @@ -269,67 +323,67 @@ public class ProcessReaderTests // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); // pr.Invoking(p => p.GetElementInfo(null!)).Should().Throw(); // } - // - // [Fact] - // public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.GetOutgoingSequenceFlows(null).Should().BeEmpty(); - // } - // - // [Fact] - // public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_ProcessTask() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Task1")); - // outgoingFLows.Should().BeEquivalentTo(new List - // { - // new SequenceFlow() - // { - // Id = "Flow2", - // FlowType = null!, - // SourceRef = "Task1", - // TargetRef = "Gateway1" - // } - // }); - // } - // - // [Fact] - // public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_Gateway() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Gateway1")); - // outgoingFLows.Should().BeEquivalentTo(new List - // { - // new SequenceFlow() - // { - // Id = "Flow3", - // FlowType = null!, - // SourceRef = "Gateway1", - // TargetRef = "Task2" - // }, - // new SequenceFlow() - // { - // Id = "Flow4", - // FlowType = null!, - // SourceRef = "Gateway1", - // TargetRef = "EndEvent" - // } - // }); - // } - // - // [Fact] - // public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("EndEvent")); - // outgoingFLows.Should().BeEmpty(); - // } - // + + [Fact] + public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetOutgoingSequenceFlows(null).Should().BeEmpty(); + } + + [Fact] + public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_ProcessTask() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Task1")); + outgoingFLows.Should().BeEquivalentTo(new List + { + new SequenceFlow() + { + Id = "Flow2", + FlowType = null!, + SourceRef = "Task1", + TargetRef = "Gateway1" + } + }); + } + + [Fact] + public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_Gateway() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Gateway1")); + outgoingFLows.Should().BeEquivalentTo(new List + { + new SequenceFlow() + { + Id = "Flow3", + FlowType = null!, + SourceRef = "Gateway1", + TargetRef = "Task2" + }, + new SequenceFlow() + { + Id = "Flow4", + FlowType = null!, + SourceRef = "Gateway1", + TargetRef = "EndEvent" + } + }); + } + + [Fact] + public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("EndEvent")); + outgoingFLows.Should().BeEmpty(); + } + // [Fact] // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_StartEvent_and_Task1() // { @@ -425,89 +479,89 @@ public class ProcessReaderTests // var returnedIds = actual.Select(s => s.Id).ToList(); // returnedIds.Should().BeEmpty(); // } - // - // [Fact] - // public void Constructor_Fails_if_invalid_bpmn() - // { - // Assert.Throws(() => ProcessTestUtils.SetupProcessReader("not-bpmn.bpmn")); - // } - // - // [Fact] - // public void GetFlowElement_returns_StartEvent_with_id() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.GetFlowElement("StartEvent").Should().BeOfType().And.BeEquivalentTo(new StartEvent() - // { - // Id = "StartEvent", - // Name = null!, - // Incoming = new List(), - // Outgoing = new List { "Flow1" } - // }); - // } - // - // [Fact] - // public void GetFlowElement_returns_ProcessTask_with_id() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.GetFlowElement("Task1").Should().BeOfType().And.BeEquivalentTo(new ProcessTask() - // { - // Id = "Task1", - // Name = null!, - // Incoming = new List { "Flow1" }, - // Outgoing = new List { "Flow2" }, - // ExtensionElements = new ExtensionElements() - // { - // AltinnProperties = new AltinnProperties() - // { - // AltinnActions = new List() - // { - // new() - // { - // Id = "submit", - // } - // }, - // TaskType = "data" - // } - // } - // }); - // } - // - // [Fact] - // public void GetFlowElement_returns_EndEvent_with_id() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.GetFlowElement("EndEvent").Should().BeOfType().And.BeEquivalentTo(new EndEvent() - // { - // Id = "EndEvent", - // Name = null!, - // Incoming = new List { "Flow4", "Flow5" }, - // Outgoing = new List() - // }); - // } - // - // [Fact] - // public void GetFlowElement_returns_null_when_id_not_found() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.GetFlowElement("Foobar").Should().BeNull(); - // } - // - // [Fact] - // public void GetFlowElement_returns_Gateway_with_id() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.GetFlowElement("Gateway1").Should().BeOfType().And.BeEquivalentTo(new ExclusiveGateway() - // { - // Id = "Gateway1", - // Name = null!, - // Default = "Flow3", - // Incoming = new List { "Flow2" }, - // Outgoing = new List { "Flow3", "Flow4" } - // }); - // } + + [Fact] + public void Constructor_Fails_if_invalid_bpmn() + { + Assert.Throws(() => ProcessTestUtils.SetupProcessReader("not-bpmn.bpmn")); + } + + [Fact] + public void GetFlowElement_returns_StartEvent_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("StartEvent").Should().BeOfType().And.BeEquivalentTo(new StartEvent() + { + Id = "StartEvent", + Name = null!, + Incoming = new List(), + Outgoing = new List { "Flow1" } + }); + } + + [Fact] + public void GetFlowElement_returns_ProcessTask_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("Task1").Should().BeOfType().And.BeEquivalentTo(new ProcessTask() + { + Id = "Task1", + Name = null!, + Incoming = new List { "Flow1" }, + Outgoing = new List { "Flow2" }, + ExtensionElements = new ExtensionElements() + { + AltinnProperties = new AltinnProperties() + { + AltinnActions = new List() + { + new() + { + Id = "submit", + } + }, + TaskType = "data" + } + } + }); + } + + [Fact] + public void GetFlowElement_returns_EndEvent_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("EndEvent").Should().BeOfType().And.BeEquivalentTo(new EndEvent() + { + Id = "EndEvent", + Name = null!, + Incoming = new List { "Flow4", "Flow5" }, + Outgoing = new List() + }); + } + + [Fact] + public void GetFlowElement_returns_null_when_id_not_found() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("Foobar").Should().BeNull(); + } + + [Fact] + public void GetFlowElement_returns_Gateway_with_id() + { + var bpmnfile = "simple-gateway-default.bpmn"; + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + pr.GetFlowElement("Gateway1").Should().BeOfType().And.BeEquivalentTo(new ExclusiveGateway() + { + Id = "Gateway1", + Name = null!, + Default = "Flow3", + Incoming = new List { "Flow2" }, + Outgoing = new List { "Flow3", "Flow4" } + }); + } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs b/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs index 75404a422..041bc7a36 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/TestUtils/ProcessTestUtils.cs @@ -1,5 +1,5 @@ using Altinn.App.Core.Interface; -using Altinn.App.Core.Internal.Process.V2; +using Altinn.App.Core.Internal.Process; using Moq; namespace Altinn.App.Core.Tests.Internal.Process.TestUtils; diff --git a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs deleted file mode 100644 index daac6239a..000000000 --- a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessEngineTest.cs +++ /dev/null @@ -1,608 +0,0 @@ -#nullable enable -using System.Security.Claims; -using Altinn.App.Core.Extensions; -using Altinn.App.Core.Interface; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.V2; -using Altinn.App.Core.Models; -using Altinn.Platform.Profile.Models; -using Altinn.Platform.Register.Models; -using Altinn.Platform.Storage.Interface.Enums; -using Altinn.Platform.Storage.Interface.Models; -using AltinnCore.Authentication.Constants; -using FluentAssertions; -using FluentAssertions.Execution; -using Moq; -using Newtonsoft.Json; -using Xunit; -using IProcessEngine = Altinn.App.Core.Internal.Process.V2.IProcessEngine; - -namespace Altinn.App.Core.Tests.Internal.Process.V2; - -public class ProcessEngineTest : IDisposable -{ - private Mock _processReaderMock; - private Mock _profileMock; - private Mock _processNavigatorMock; - private Mock _processEventDispatcherMock; - - public ProcessEngineTest() - { - _processReaderMock = new(); - _profileMock = new(); - _processNavigatorMock = new(); - _processEventDispatcherMock = new(); - } - - [Fact] - public async Task StartProcess_returns_unsuccessful_when_process_already_started() - { - IProcessEngine processEngine = GetProcessEngine(); - Instance instance = new Instance() { Process = new ProcessState() { CurrentTask = new ProcessElementInfo() { ElementId = "Task_1" } } }; - ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance }; - ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); - result.Success.Should().BeFalse(); - result.ErrorMessage.Should().Be("Process is already started. Use next."); - result.ErrorType.Should().Be("Conflict"); - } - - [Fact] - public async Task StartProcess_returns_unsuccessful_when_no_matching_startevent_found() - { - Mock processReaderMock = new(); - processReaderMock.Setup(r => r.GetStartEventIds()).Returns(new List() { "StartEvent_1" }); - IProcessEngine processEngine = GetProcessEngine(processReaderMock); - Instance instance = new Instance(); - ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, StartEventId = "NotTheStartEventYouAreLookingFor" }; - ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); - _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); - result.Success.Should().BeFalse(); - result.ErrorMessage.Should().Be("No matching startevent"); - result.ErrorType.Should().Be("Conflict"); - } - - [Fact] - public async Task StartProcess_starts_process_and_moves_to_first_task_without_event_dispatch_when_dryrun() - { - IProcessEngine processEngine = GetProcessEngine(); - Instance instance = new Instance() - { - InstanceOwner = new InstanceOwner() - { - PartyId = "1337" - } - }; - ClaimsPrincipal user = new(new ClaimsIdentity(new List() - { - new(AltinnCoreClaimTypes.UserId, "1337"), - new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), - new(AltinnCoreClaimTypes.Org, "tdd"), - })); - ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user, Dryrun = true }; - ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); - _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); - _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); - _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); - _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); - _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); - result.Success.Should().BeTrue(); - } - - [Fact] - public async Task StartProcess_starts_process_and_moves_to_first_task() - { - IProcessEngine processEngine = GetProcessEngine(); - Instance instance = new Instance() - { - InstanceOwner = new InstanceOwner() - { - PartyId = "1337" - } - }; - ClaimsPrincipal user = new(new ClaimsIdentity(new List() - { - new(AltinnCoreClaimTypes.UserId, "1337"), - new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), - new(AltinnCoreClaimTypes.Org, "tdd"), - })); - ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user }; - ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); - _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); - _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); - _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); - _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); - _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); - var expectedInstance = new Instance() - { - InstanceOwner = new InstanceOwner() - { - PartyId = "1337" - }, - Process = new ProcessState() - { - CurrentTask = new ProcessElementInfo() - { - ElementId = "Task_1", - Flow = 2, - AltinnTaskType = "data", - Name = "Utfylling" - }, - StartEvent = "StartEvent_1" - } - }; - var expectedInstanceEvents = new List() - { - new() - { - EventType = InstanceEventType.process_StartEvent.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - OrgId = "tdd", - AuthenticationLevel = 2 - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "StartEvent_1", - Flow = 1, - Validated = new() - { - CanCompleteTask = false - } - } - } - }, - - new() - { - EventType = InstanceEventType.process_StartTask.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - OrgId = "tdd", - AuthenticationLevel = 2, - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "Task_1", - Name = "Utfylling", - AltinnTaskType = "data", - Flow = 2, - Validated = new() - { - CanCompleteTask = false - } - } - } - } - }; - _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( - It.Is(i => CompareInstance(expectedInstance, i)), - null, - It.Is>(l => CompareInstanceEvents(l, expectedInstanceEvents)))); - result.Success.Should().BeTrue(); - } - - [Fact] - public async Task Next_returns_unsuccessful_when_process_null() - { - IProcessEngine processEngine = GetProcessEngine(); - Instance instance = new Instance() { Process = null }; - ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance }; - ProcessChangeResult result = await processEngine.Next(processNextRequest); - result.Success.Should().BeFalse(); - result.ErrorMessage.Should().Be("Instance does not have current task information!"); - result.ErrorType.Should().Be("Conflict"); - } - - [Fact] - public async Task Next_returns_unsuccessful_when_process_currenttask_null() - { - IProcessEngine processEngine = GetProcessEngine(); - Instance instance = new Instance() { Process = new ProcessState() { CurrentTask = null } }; - ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance }; - ProcessChangeResult result = await processEngine.Next(processNextRequest); - result.Success.Should().BeFalse(); - result.ErrorMessage.Should().Be("Instance does not have current task information!"); - result.ErrorType.Should().Be("Conflict"); - } - - [Fact] - public async Task Next_moves_instance_to_next_task_and_produces_instanceevents() - { - var expectedInstance = new Instance() - { - InstanceOwner = new InstanceOwner() - { - PartyId = "1337" - }, - Process = new ProcessState() - { - CurrentTask = new ProcessElementInfo() - { - ElementId = "Task_2", - Flow = 3, - AltinnTaskType = "confirmation", - Name = "Bekreft" - }, - StartEvent = "StartEvent_1" - } - }; - IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); - Instance instance = new Instance() - { - InstanceOwner = new() - { - PartyId = "1337" - }, - Process = new ProcessState() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "Task_1", - AltinnTaskType = "data", - Flow = 2, - Validated = new() - { - CanCompleteTask = true - } - } - } - }; - ProcessState originalProcessState = instance.Process.Copy(); - ClaimsPrincipal user = new(new ClaimsIdentity(new List() - { - new(AltinnCoreClaimTypes.UserId, "1337"), - new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), - new(AltinnCoreClaimTypes.Org, "tdd"), - })); - ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user }; - ProcessChangeResult result = await processEngine.Next(processNextRequest); - _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); - _processReaderMock.Verify(r => r.IsEndEvent("Task_2"), Times.Once); - _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); - _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_1", null), Times.Once); - - var expectedInstanceEvents = new List() - { - new() - { - EventType = InstanceEventType.process_EndTask.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - OrgId = "tdd", - AuthenticationLevel = 2 - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "Task_1", - Flow = 2, - AltinnTaskType = "data", - Validated = new() - { - CanCompleteTask = true - } - } - } - }, - - new() - { - EventType = InstanceEventType.process_StartTask.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - OrgId = "tdd", - AuthenticationLevel = 2, - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "Task_2", - Name = "Bekreft", - AltinnTaskType = "confirmation", - Flow = 3 - } - } - } - }; - _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( - It.Is(i => CompareInstance(expectedInstance, i)), - It.IsAny?>(), - It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); - _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); - result.Success.Should().BeTrue(); - result.ProcessStateChange.Should().BeEquivalentTo( - new ProcessStateChange() - { - Events = expectedInstanceEvents, - NewProcessState = expectedInstance.Process, - OldProcessState = originalProcessState - }); - } - - [Fact] - public async Task Next_moves_instance_to_end_event_and_ends_proces() - { - var expectedInstance = new Instance() - { - InstanceOwner = new InstanceOwner() - { - PartyId = "1337" - }, - Process = new ProcessState() - { - CurrentTask = null, - StartEvent = "StartEvent_1", - EndEvent = "EndEvent_1" - } - }; - IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); - Instance instance = new Instance() - { - InstanceOwner = new() - { - PartyId = "1337" - }, - Process = new ProcessState() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "Task_2", - AltinnTaskType = "confirmation", - Flow = 3, - Validated = new() - { - CanCompleteTask = true - } - } - } - }; - ProcessState originalProcessState = instance.Process.Copy(); - ClaimsPrincipal user = new(new ClaimsIdentity(new List() - { - new(AltinnCoreClaimTypes.UserId, "1337"), - new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), - })); - ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user }; - ProcessChangeResult result = await processEngine.Next(processNextRequest); - _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); - _processReaderMock.Verify(r => r.IsEndEvent("EndEvent_1"), Times.Once); - _profileMock.Verify(p => p.GetUserProfile(1337), Times.Exactly(3)); - _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_2", null), Times.Once); - - var expectedInstanceEvents = new List() - { - new() - { - EventType = InstanceEventType.process_EndTask.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - AuthenticationLevel = 2, - NationalIdentityNumber = "22927774937" - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = new() - { - ElementId = "Task_2", - Flow = 3, - AltinnTaskType = "confirmation", - Validated = new() - { - CanCompleteTask = true - } - } - } - }, - - new() - { - EventType = InstanceEventType.process_EndEvent.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - NationalIdentityNumber = "22927774937", - AuthenticationLevel = 2, - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = null, - EndEvent = "EndEvent_1" - } - }, - new() - { - EventType = InstanceEventType.Submited.ToString(), - InstanceOwnerPartyId = "1337", - User = new() - { - UserId = 1337, - NationalIdentityNumber = "22927774937", - AuthenticationLevel = 2, - }, - ProcessInfo = new() - { - StartEvent = "StartEvent_1", - CurrentTask = null, - EndEvent = "EndEvent_1" - } - } - }; - _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( - It.Is(i => CompareInstance(expectedInstance, i)), - It.IsAny?>(), - It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); - _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); - result.Success.Should().BeTrue(); - result.ProcessStateChange.Should().BeEquivalentTo( - new ProcessStateChange() - { - Events = expectedInstanceEvents, - NewProcessState = expectedInstance.Process, - OldProcessState = originalProcessState - }); - } - - private IProcessEngine GetProcessEngine(Mock? processReaderMock = null, Instance? updatedInstance = null) - { - if (processReaderMock == null) - { - _processReaderMock = new(); - _processReaderMock.Setup(r => r.GetStartEventIds()).Returns(new List() { "StartEvent_1" }); - _processReaderMock.Setup(r => r.IsProcessTask("StartEvent_1")).Returns(false); - _processReaderMock.Setup(r => r.IsEndEvent("Task_1")).Returns(false); - _processReaderMock.Setup(r => r.IsProcessTask("Task_1")).Returns(true); - _processReaderMock.Setup(r => r.IsProcessTask("Task_2")).Returns(true); - _processReaderMock.Setup(r => r.IsProcessTask("EndEvent_1")).Returns(false); - _processReaderMock.Setup(r => r.IsEndEvent("EndEvent_1")).Returns(true); - _processReaderMock.Setup(r => r.IsProcessTask("EndEvent_1")).Returns(false); - } - else - { - _processReaderMock = processReaderMock; - } - - _profileMock.Setup(p => p.GetUserProfile(1337)).ReturnsAsync(() => new UserProfile() - { - UserId = 1337, - Email = "test@example.com", - Party = new Party() - { - SSN = "22927774937" - } - }); - _processNavigatorMock.Setup( - pn => pn.GetNextTask(It.IsAny(), "StartEvent_1", It.IsAny())) - .ReturnsAsync(() => new ProcessTask() - { - Id = "Task_1", - Incoming = new List { "Flow_1" }, - Outgoing = new List { "Flow_2" }, - Name = "Utfylling", - ExtensionElements = new() - { - AltinnProperties = new() - { - TaskType = "data" - } - } - }); - _processNavigatorMock.Setup( - pn => pn.GetNextTask(It.IsAny(), "Task_1", It.IsAny())) - .ReturnsAsync(() => new ProcessTask() - { - Id = "Task_2", - Incoming = new List { "Flow_2" }, - Outgoing = new List { "Flow_3" }, - Name = "Bekreft", - ExtensionElements = new() - { - AltinnProperties = new() - { - TaskType = "confirmation" - } - } - }); - _processNavigatorMock.Setup( - pn => pn.GetNextTask(It.IsAny(), "Task_2", It.IsAny())) - .ReturnsAsync(() => new EndEvent() - { - Id = "EndEvent_1", - Incoming = new List { "Flow_3" } - }); - if (updatedInstance is not null) - { - _processEventDispatcherMock.Setup(d => d.UpdateProcessAndDispatchEvents(It.IsAny(), It.IsAny?>(), It.IsAny>())) - .ReturnsAsync(() => updatedInstance); - } - - return new ProcessEngine( - _processReaderMock.Object, - _profileMock.Object, - _processNavigatorMock.Object, - _processEventDispatcherMock.Object); - } - - public void Dispose() - { - _processReaderMock.VerifyNoOtherCalls(); - _profileMock.VerifyNoOtherCalls(); - _processNavigatorMock.VerifyNoOtherCalls(); - _processEventDispatcherMock.VerifyNoOtherCalls(); - } - - private static bool CompareInstance(Instance expected, Instance actual) - { - expected.Process.Started = actual.Process.Started; - expected.Process.Ended = actual.Process.Ended; - if (actual.Process.CurrentTask != null) - { - expected.Process.CurrentTask.Started = actual.Process.CurrentTask.Started; - } - - return JsonCompare(expected, actual); - } - - private static bool CompareInstanceEvents(List expected, List actual) - { - for (int i = 0; i < expected.Count; i++) - { - expected[i].Created = actual[i].Created; - expected[i].ProcessInfo.Started = actual[i].ProcessInfo.Started; - expected[i].ProcessInfo.Ended = actual[i].ProcessInfo.Ended; - if(actual[i].ProcessInfo.CurrentTask != null) - { - expected[i].ProcessInfo.CurrentTask.Started = actual[i].ProcessInfo.CurrentTask.Started; - } - } - - return JsonCompare(expected, actual); - } - - public static bool JsonCompare(object expected, object actual) - { - if (ReferenceEquals(expected, actual)) - { - return true; - } - - if ((expected == null) || (actual == null)) - { - return false; - } - - if (expected.GetType() != actual.GetType()) - { - return false; - } - - var expectedJson = JsonConvert.SerializeObject(expected); - var actualJson = JsonConvert.SerializeObject(actual); - - return expectedJson == actualJson; - } -} diff --git a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs deleted file mode 100644 index 2c045c140..000000000 --- a/test/Altinn.App.Core.Tests/Internal/Process/V2/ProcessReaderTests.cs +++ /dev/null @@ -1,569 +0,0 @@ -#nullable enable -using Altinn.App.Core.Internal.Process; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; -using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.App.Core.Tests.Internal.Process.TestUtils; -using FluentAssertions; -using Xunit; -using IProcessReader = Altinn.App.Core.Internal.Process.V2.IProcessReader; -using ProcessReader = Altinn.App.Core.Internal.Process.V2.ProcessReader; - -namespace Altinn.App.Core.Tests.Internal.Process.V2; - -public class ProcessReaderTests -{ - [Fact] - public void TestBpmnRead() - { - ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.GetStartEventIds().Should().Equal("StartEvent"); - pr.GetProcessTaskIds().Should().Equal("Task1", "Task2"); - pr.GetEndEventIds().Should().Equal("EndEvent"); - pr.GetSequenceFlowIds().Should().Equal("Flow1", "Flow2", "Flow3", "Flow4", "Flow5"); - pr.GetExclusiveGatewayIds().Should().Equal("Gateway1"); - } - - [Fact] - public void IsStartEvent_returns_true_when_element_is_StartEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsStartEvent("StartEvent").Should().BeTrue(); - } - - [Fact] - public void IsStartEvent_returns_false_when_element_is_not_StartEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsStartEvent("Task1").Should().BeFalse(); - pr.IsStartEvent("EndEvent").Should().BeFalse(); - pr.IsStartEvent("Gateway1").Should().BeFalse(); - pr.IsStartEvent("Foobar").Should().BeFalse(); - pr.IsStartEvent(null).Should().BeFalse(); - } - - [Fact] - public void IsProcessTask_returns_true_when_element_is_ProcessTask() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsProcessTask("Task1").Should().BeTrue(); - } - - [Fact] - public void IsProcessTask_returns_false_when_element_is_not_ProcessTask() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsProcessTask("StartEvent").Should().BeFalse(); - pr.IsProcessTask("EndEvent").Should().BeFalse(); - pr.IsProcessTask("Gateway1").Should().BeFalse(); - pr.IsProcessTask("Foobar").Should().BeFalse(); - pr.IsProcessTask(null).Should().BeFalse(); - } - - [Fact] - public void IsEndEvent_returns_true_when_element_is_EndEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsEndEvent("EndEvent").Should().BeTrue(); - } - - [Fact] - public void IsEndEvent_returns_false_when_element_is_not_EndEvent() - { - IProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - pr.IsEndEvent("StartEvent").Should().BeFalse(); - pr.IsEndEvent("Task1").Should().BeFalse(); - pr.IsEndEvent("Gateway1").Should().BeFalse(); - pr.IsEndEvent("Foobar").Should().BeFalse(); - pr.IsEndEvent(null).Should().BeFalse(); - } - - [Fact] - public void GetNextElement_returns_gateway() - { - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader("simple-gateway.bpmn"); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().BeEquivalentTo(new List() { new ExclusiveGateway() { Id = "Gateway1", Incoming = new List() { "Flow2" }, Outgoing = new List() { "Flow3", "Flow4" } } }); - } - - [Fact] - public void GetNextElement_returns_task() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().BeEquivalentTo( - new List - { - new ProcessTask() - { - Id = "Task2", - Incoming = new List { "Flow2" }, - Outgoing = new List { "Flow3" }, - Name = "Bekreft skjemadata", - TaskType = "confirmation" - } - }); - } - - [Fact] - public void GetNextElement_returns_all_targets_after_gateway() - { - var bpmnfile = "simple-gateway.bpmn"; - var currentElement = "Gateway1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().BeEquivalentTo( - new List - { - new ProcessTask() - { - Id = "Task2", - Incoming = new List() { "Flow3" }, - Outgoing = new List() { "Flow5" }, - }, - new EndEvent() - { - Id = "EndEvent", - Incoming = new List() { "Flow4", "Flow5" }, - Outgoing = new List() - } - }); - } - - [Fact] - public void GetNextElement_returns_task1_in_simple_process() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "StartEvent"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().BeEquivalentTo( - new List() - { - new ProcessTask() - { - Id = "Task1", - Name = "Utfylling", - Incoming = new List() { "Flow1" }, - Outgoing = new List() { "Flow2" }, - } - }); - } - - [Fact] - public void GetNextElement_returns_task2_in_simple_process() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task1"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().BeEquivalentTo( - new List() - { - new ProcessTask() - { - Id = "Task2", - Name = "Bekreft skjemadata", - Incoming = new List() { "Flow2" }, - Outgoing = new List() { "Flow3" }, - } - }); - } - - [Fact] - public void GetNextElement_returns_endevent_in_simple_process() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "Task2"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().BeEquivalentTo( - new List() - { - new EndEvent() - { - Id = "EndEvent", - Incoming = new List() { "Flow3" }, - Outgoing = new List() - } - }); - } - - [Fact] - public void GetNextElement_returns_emptylist_if_task_without_output() - { - var bpmnfile = "simple-no-end.bpmn"; - var currentElement = "Task2"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List nextElements = pr.GetNextElements(currentElement); - nextElements.Should().HaveCount(0); - } - - [Fact] - public void GetNextElement_currentElement_null() - { - var bpmnfile = "simple-linear.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.Invoking(p => p.GetNextElements(null!)).Should().Throw(); - } - - [Fact] - public void GetNextElement_throws_exception_if_step_not_found() - { - var bpmnfile = "simple-linear.bpmn"; - var currentElement = "NoStep"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.Invoking(p => p.GetNextElements(currentElement)).Should().Throw(); - } - - // [Fact] - // public void GetElementInfo_returns_correct_info_for_old_ProcessTask() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "Task1", - // Name = "Utfylling", - // AltinnTaskType = "data", - // ElementType = "Task" - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_new_ProcessTask() - // { - // var bpmnfile = "simple-linear-new.bpmn"; - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "Task1", - // Name = "Utfylling", - // AltinnTaskType = "data", - // ElementType = "Task", - // AltinnTaskActions = new List() - // { - // "submit" - // } - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_ProcessTask_prefers_new() - // { - // var bpmnfile = "simple-linear-both.bpmn"; - // var currentElement = "Task2"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "Task2", - // Name = "Bekreft skjemadata", - // AltinnTaskType = "confirmation2", - // ElementType = "Task", - // AltinnTaskActions = new List() - // { - // "confirm", - // "reject" - // } - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_StartEvent() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "StartEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "StartEvent", - // Name = null!, - // AltinnTaskType = null!, - // ElementType = "StartEvent" - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_EndEvent() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "EndEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "EndEvent", - // Name = null!, - // AltinnTaskType = null!, - // ElementType = "EndEvent" - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_null_for_ExclusiveGateway() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Gateway1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeNull(); - // } - // - // [Fact] - // public void GetElementInfo_throws_argument_null_expcetion_when_elementName_is_null() - // { - // var bpmnfile = "simple-linear.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.Invoking(p => p.GetElementInfo(null!)).Should().Throw(); - // } - - [Fact] - public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetOutgoingSequenceFlows(null).Should().BeEmpty(); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_ProcessTask() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Task1")); - outgoingFLows.Should().BeEquivalentTo(new List - { - new SequenceFlow() - { - Id = "Flow2", - FlowType = null!, - SourceRef = "Task1", - TargetRef = "Gateway1" - } - }); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_SequenceFlow_objects_for_outgoing_flows_from_Gateway() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("Gateway1")); - outgoingFLows.Should().BeEquivalentTo(new List - { - new SequenceFlow() - { - Id = "Flow3", - FlowType = null!, - SourceRef = "Gateway1", - TargetRef = "Task2" - }, - new SequenceFlow() - { - Id = "Flow4", - FlowType = null!, - SourceRef = "Gateway1", - TargetRef = "EndEvent" - } - }); - } - - [Fact] - public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - List outgoingFLows = pr.GetOutgoingSequenceFlows(pr.GetFlowElement("EndEvent")); - outgoingFLows.Should().BeEmpty(); - } - - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_StartEvent_and_Task1() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "StartEvent"; - // var nextElementId = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow1"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_Task2() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "Task2"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow2", "Flow3"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "EndEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent_complex() - // { - // var bpmnfile = "simple-gateway-with-join-gateway.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "EndEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4", "Flow6"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_unknown_target() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "Foobar"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_current_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // string? currentElement = null; - // var nextElementId = "Foobar"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_next_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // string currentElement = "Task1"; - // string? nextElementId = null; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_current_and_next_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // string? currentElement = null; - // string? nextElementId = null; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - - [Fact] - public void Constructor_Fails_if_invalid_bpmn() - { - Assert.Throws(() => ProcessTestUtils.SetupProcessReader("not-bpmn.bpmn")); - } - - [Fact] - public void GetFlowElement_returns_StartEvent_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("StartEvent").Should().BeOfType().And.BeEquivalentTo(new StartEvent() - { - Id = "StartEvent", - Name = null!, - Incoming = new List(), - Outgoing = new List { "Flow1" } - }); - } - - [Fact] - public void GetFlowElement_returns_ProcessTask_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("Task1").Should().BeOfType().And.BeEquivalentTo(new ProcessTask() - { - Id = "Task1", - Name = null!, - Incoming = new List { "Flow1" }, - Outgoing = new List { "Flow2" }, - ExtensionElements = new ExtensionElements() - { - AltinnProperties = new AltinnProperties() - { - AltinnActions = new List() - { - new() - { - Id = "submit", - } - }, - TaskType = "data" - } - } - }); - } - - [Fact] - public void GetFlowElement_returns_EndEvent_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("EndEvent").Should().BeOfType().And.BeEquivalentTo(new EndEvent() - { - Id = "EndEvent", - Name = null!, - Incoming = new List { "Flow4", "Flow5" }, - Outgoing = new List() - }); - } - - [Fact] - public void GetFlowElement_returns_null_when_id_not_found() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("Foobar").Should().BeNull(); - } - - [Fact] - public void GetFlowElement_returns_Gateway_with_id() - { - var bpmnfile = "simple-gateway-default.bpmn"; - ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - pr.GetFlowElement("Gateway1").Should().BeOfType().And.BeEquivalentTo(new ExclusiveGateway() - { - Id = "Gateway1", - Name = null!, - Default = "Flow3", - Incoming = new List { "Flow2" }, - Outgoing = new List { "Flow3", "Flow4" } - }); - } -} From d8a9f5c062274d0ea807527093efce113189c8f1 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 12:32:20 +0200 Subject: [PATCH 05/29] added tests for ProcessEventDispatcher --- .../Process/ProcessEventDispatcher.cs | 2 +- .../Process/ProcessEventDispatcherTests.cs | 722 ++++++++++++++++++ 2 files changed, 723 insertions(+), 1 deletion(-) create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs index a70d91b0c..9b662afc3 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs @@ -19,7 +19,7 @@ class ProcessEventDispatcher : IProcessEventDispatcher private readonly IAppEvents _appEvents; private readonly IEvents _eventsService; private readonly bool _registerWithEventSystem; - private ILogger _logger; + private readonly ILogger _logger; public ProcessEventDispatcher( IInstance instanceService, diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs new file mode 100644 index 000000000..d1b70a073 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs @@ -0,0 +1,722 @@ +using Altinn.App.Core.Configuration; +using Altinn.App.Core.Interface; +using Altinn.App.Core.Internal.Process; +using Altinn.Platform.Storage.Interface.Enums; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Altinn.App.Core.Tests.Internal.Process; + +public class ProcessEventDispatcherTests +{ + [Fact] + public async Task UpdateProcessAndDispatchEvents_StartEvent_instance_updated_and_events_sent_to_storage_nothing_sent_to_ITask() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString() + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_1" + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2 + } + } + }; + List events = new List() + { + new InstanceEvent() + { + EventType = InstanceEventType.process_StartEvent.ToString(), + ProcessInfo = new() + { + CurrentTask = new() + { + ElementId = "StartEvent", + AltinnTaskType = "start", + Name = "Start" + } + } + } + }; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); + processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task UpdateProcessAndDispatchEvents_StartTask_instance_updated_and_events_sent_to_storage_nothing_sent_to_ITask_when_tasktype_missing() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString() + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_1" + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2 + } + } + }; + List events = new List() + { + new InstanceEvent() + { + EventType = InstanceEventType.process_StartTask.ToString(), + ProcessInfo = new() + { + CurrentTask = new() + { + ElementId = "StartEvent", + Name = "Start" + } + } + } + }; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); + processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated_and_events_sent_to_storage_and_trigger_ITask() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_1", + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2 + } + } + }; + List events = new List() + { + new InstanceEvent() + { + EventType = InstanceEventType.process_StartTask.ToString(), + ProcessInfo = new() + { + CurrentTask = new() + { + ElementId = "Task_1", + AltinnTaskType = "data", + Name = "Utfylling", + Flow = 2 + } + } + } + }; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + taskEvents.Verify(t => t.OnStartProcessTask("Task_1", instance, prefill), Times.Once); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); + processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_updated_and_events_sent_to_storage_and_trigger_ITask() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + Flow = 3 + } + } + }; + List events = new List() + { + new InstanceEvent() + { + EventType = InstanceEventType.process_EndTask.ToString(), + ProcessInfo = new() + { + CurrentTask = new() + { + ElementId = "Task_2", + AltinnTaskType = "confirmation", + Name = "Bekreft", + Flow = 2 + } + } + } + }; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + taskEvents.Verify(t => t.OnEndProcessTask("Task_2", instance), Times.Once); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); + processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_updated_and_events_sent_to_storage_and_trigger_ITask() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + Flow = 4 + } + } + }; + List events = new List() + { + new InstanceEvent() + { + EventType = InstanceEventType.process_AbandonTask.ToString(), + ProcessInfo = new() + { + CurrentTask = new() + { + ElementId = "Task_2", + AltinnTaskType = "feedback", + Name = "Bekreft", + Flow = 4 + } + } + } + }; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + taskEvents.Verify(t => t.OnAbandonProcessTask("Task_2", instance), Times.Once); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Exactly(2)); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); + processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_updated_and_events_sent_to_storage_and_trigger_ITask() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + Flow = 3 + } + } + }; + List events = new List() + { + new InstanceEvent() + { + EventType = InstanceEventType.process_EndEvent.ToString(), + ProcessInfo = new() + { + CurrentTask = new() + { + ElementId = "Task_2", + AltinnTaskType = "confirmation", + Name = "Bekreft", + Flow = 2 + }, + EndEvent = "EndEvent" + } + } + }; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + appEvents.Verify(a => a.OnEndAppEvent("EndEvent", instance), Times.Once); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); + processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RegisterEventWithEventsComponent_sends_movedTo_event_to_events_system_when_enabled_and_current_task_set() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings() + { + RegisterEventsWithEventsComponent = true + }); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + Process = new() + { + CurrentTask = new() + { + ElementId = "Task_1" + } + } + }; + + // Act + await dispatcher.RegisterEventWithEventsComponent(instance); + + // Assert + eventsService.Verify(e => e.AddEvent("app.instance.process.movedTo.Task_1", instance), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RegisterEventWithEventsComponent_sends_complete_event_to_events_system_when_currentTask_null_and_endevent_set() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings() + { + RegisterEventsWithEventsComponent = true + }); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + Process = new() + { + CurrentTask = null, + EndEvent = "EndEvent" + } + }; + + // Act + await dispatcher.RegisterEventWithEventsComponent(instance); + + // Assert + eventsService.Verify(e => e.AddEvent("app.instance.process.completed", instance), Times.Once); + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RegisterEventWithEventsComponent_sends_no_events_when_process_is_null() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings() + { + RegisterEventsWithEventsComponent = true + }); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + Process = null + }; + + // Act + await dispatcher.RegisterEventWithEventsComponent(instance); + + // Assert + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RegisterEventWithEventsComponent_sends_no_events_when_current_and_endevent_is_null() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings() + { + RegisterEventsWithEventsComponent = true + }); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + Process = new() + }; + + // Act + await dispatcher.RegisterEventWithEventsComponent(instance); + + // Assert + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RegisterEventWithEventsComponent_sends_no_events_when_registereventswitheventscomponent_false() + { + // Arrange + var instanceService = new Mock(); + var processClient = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings() + { + RegisterEventsWithEventsComponent = false + }); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + processClient.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + Process = new() + { + CurrentTask = new() + { + ElementId = "Task_1" + } + } + }; + + // Act + await dispatcher.RegisterEventWithEventsComponent(instance); + + // Assert + instanceService.VerifyNoOtherCalls(); + processClient.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } +} From 9547df8c8504927b7e4f598b5a88401968f639d3 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 14:55:50 +0200 Subject: [PATCH 06/29] Add tests and fix ProcessNavigator --- .../Internal/Process/Elements/ElementInfo.cs | 33 --- .../Internal/Process/ProcessException.cs | 17 -- .../Internal/Process/ProcessNavigator.cs | 42 +++- .../Internal/Process/ProcessNavigatorTests.cs | 190 ++++++++++++++++++ 4 files changed, 223 insertions(+), 59 deletions(-) delete mode 100644 src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs b/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs deleted file mode 100644 index ac10a93b2..000000000 --- a/src/Altinn.App.Core/Internal/Process/Elements/ElementInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Altinn.App.Core.Internal.Process.Elements -{ - /// - /// Represents information about an element in a BPMN description. - /// - public class ElementInfo - { - /// - /// The unique id of a specific element in the BPMN - /// - public string Id { get; set; } - - /// - /// The type of BPMN element - /// - public string ElementType { get; set; } - - /// - /// The name of the BPMN element - /// - public string Name { get; set; } - - /// - /// The altinn specific task type - /// - public string? AltinnTaskType { get; set; } - - /// - /// The altinn specific task actions - /// - public List? AltinnTaskActions { get; set; } - } -} diff --git a/src/Altinn.App.Core/Internal/Process/ProcessException.cs b/src/Altinn.App.Core/Internal/Process/ProcessException.cs index 189898063..9c90c3fd2 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessException.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessException.cs @@ -5,13 +5,6 @@ namespace Altinn.App.Core.Internal.Process /// public class ProcessException : Exception { - /// - /// Initializes a new instance of the class. - /// - public ProcessException() - { - } - /// /// Initializes a new instance of the class with a specified error message. /// @@ -19,15 +12,5 @@ public ProcessException() public ProcessException(string message) : base(message) { } - - /// - /// Initializes a new instance of the class with a specified error - /// message and a reference to the inner exception that is the cause of this exception. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified - public ProcessException(string message, Exception inner) : base(message, inner) - { - } } } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs index dc4740ddb..90b251f22 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs @@ -29,11 +29,34 @@ public ProcessNavigator(IProcessReader processReader, ExclusiveGatewayFactory ga public async Task GetNextTask(Instance instance, string currentElement, string? action) { List directFlowTargets = _processReader.GetNextElements(currentElement); - foreach (var directFlowTarget in directFlowTargets) + List filteredNext = await NextFollowAndFilterGateways(instance, directFlowTargets, action); + if (filteredNext.Count == 0) { + return null; + } + + if (filteredNext.Count == 1) + { + return filteredNext[0]; + } + + throw new ProcessException($"Multiple next elements found from {currentElement}. Please supply action and filters or define a default flow."); + } + + private async Task> NextFollowAndFilterGateways(Instance instance, List originNextElements, string? action) + { + List filteredNext = new List(); + foreach (var directFlowTarget in originNextElements) + { + if (directFlowTarget == null) + { + continue; + } + if (!IsGateway(directFlowTarget)) { - return directFlowTarget; + filteredNext.Add(directFlowTarget); + continue; } var gateway = (ExclusiveGateway)directFlowTarget; @@ -49,19 +72,20 @@ public ProcessNavigator(IProcessReader processReader, ExclusiveGatewayFactory ga filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance); } - if (filteredList.Count == 1) - { - return _processReader.GetFlowElement(filteredList[0].TargetRef); - } - var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); if (defaultSequenceFlow != null) { - return _processReader.GetFlowElement(defaultSequenceFlow.TargetRef); + var defaultTarget = _processReader.GetFlowElement(defaultSequenceFlow.TargetRef); + filteredNext.AddRange(await NextFollowAndFilterGateways(instance, new List { defaultTarget }, action)); + } + else + { + var filteredTargets = filteredList.Select(e => _processReader.GetFlowElement(e.TargetRef)).ToList(); + filteredNext.AddRange(await NextFollowAndFilterGateways(instance, filteredTargets, action)); } } - throw new Exception("No able to find next element"); + return filteredNext; } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs new file mode 100644 index 000000000..e8540c303 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs @@ -0,0 +1,190 @@ +using Altinn.App.Core.Features; +using Altinn.App.Core.Internal.Process; +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.Base; +using Altinn.App.Core.Tests.Internal.Process.TestUtils; +using Altinn.App.PlatformServices.Tests.Internal.Process.StubGatewayFilters; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.Core.Tests.Internal.Process; + +public class ProcessNavigatorTests +{ + [Fact] + public async void GetNextTask_returns_next_element_if_no_gateway() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-linear.bpmn", new List()); + ProcessElement nextElements = await processNavigator.GetNextTask(new Instance(), "Task1", null); + nextElements.Should().BeEquivalentTo(new ProcessTask() + { + Id = "Task2", + Name = "Bekreft skjemadata", + TaskType = "confirmation", + Incoming = new List { "Flow2" }, + Outgoing = new List { "Flow3" } + }); + } + + [Fact] + public async void NextFollowAndFilterGateways_returns_empty_list_if_no_outgoing_flows() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-linear.bpmn", new List()); + ProcessElement nextElements = await processNavigator.GetNextTask(new Instance(), "EndEvent", null); + nextElements.Should().BeNull(); + } + + [Fact] + public async void GetNextTask_returns_default_if_no_filtering_is_implemented_and_default_set() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-gateway-default.bpmn", new List()); + ProcessElement nextElements = await processNavigator.GetNextTask(new Instance(), "Task1", null); + nextElements.Should().BeEquivalentTo(new ProcessTask() + { + Id = "Task2", + Name = null!, + TaskType = null!, + ExtensionElements = new() + { + AltinnProperties = new() + { + TaskType = "confirm", + AltinnActions = new() + { + new() + { + Id = "confirm" + }, + new() + { + Id= "reject" + } + } + } + }, + Incoming = new List { "Flow3" }, + Outgoing = new List { "Flow5" } + }); + } + + [Fact] + public async void GetNextTask_runs_custom_filter_and_returns_result() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-gateway-with-join-gateway.bpmn", new List() + { + new DataValuesFilter("Gateway1", "choose") + }); + Instance i = new Instance() + { + DataValues = new Dictionary() + { + { "choose", "Flow3" } + } + }; + + ProcessElement nextElements = await processNavigator.GetNextTask(i, "Task1", null); + nextElements.Should().BeEquivalentTo(new ProcessTask() + { + Id = "Task2", + Name = null!, + TaskType = null!, + ExtensionElements = new() + { + AltinnProperties = new() + { + TaskType = "data", + AltinnActions = new() + { + new() + { + Id = "submit" + } + } + } + }, + Incoming = new List { "Flow3" }, + Outgoing = new List { "Flow5" } + }); + } + + [Fact] + public async void GetNextTask_throws_ProcessException_if_multiple_targets_found() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-gateway-with-join-gateway.bpmn", new List() + { + new DataValuesFilter("Foobar", "choose") + }); + Instance i = new Instance() + { + DataValues = new Dictionary() + { + { "choose", "Flow3" } + } + }; + + var result = await Assert.ThrowsAsync(async () => await processNavigator.GetNextTask(i, "Task1", null)); + result.Message.Should().Be("Multiple next elements found from Task1. Please supply action and filters or define a default flow."); + } + + [Fact] + public async void GetNextTask_follows_downstream_gateways() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-gateway-with-join-gateway.bpmn", new List() + { + new DataValuesFilter("Gateway1", "choose1") + }); + Instance i = new Instance() + { + DataValues = new Dictionary() + { + { "choose1", "Flow4" } + } + }; + ProcessElement nextElements = await processNavigator.GetNextTask(i, "Task1", null); + nextElements.Should().BeEquivalentTo(new EndEvent() + { + Id = "EndEvent", + Name = null!, + Incoming = new List { "Flow6" }, + Outgoing = new List() + }); + } + + [Fact] + public async void GetNextTask_runs_custom_filter_and_returns_empty_list_if_all_filtered_out() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-gateway-with-join-gateway.bpmn", new List() + { + new DataValuesFilter("Gateway1", "choose1"), + new DataValuesFilter("Gateway2", "choose2") + }); + Instance i = new Instance() + { + DataValues = new Dictionary() + { + { "choose1", "Flow4" }, + { "choose2", "Bar" } + } + }; + + ProcessElement nextElements = await processNavigator.GetNextTask(i, "Task1", null); + nextElements.Should().BeNull(); + } + + [Fact] + public async void GetNextTask_returns_empty_list_if_element_has_no_next() + { + IProcessNavigator processNavigator = SetupProcessNavigator("simple-gateway-with-join-gateway.bpmn", new List()); + Instance i = new Instance(); + + ProcessElement nextElements = await processNavigator.GetNextTask(i, "EndEvent", null); + nextElements.Should().BeNull(); + } + + private static IProcessNavigator SetupProcessNavigator(string bpmnfile, IEnumerable gatewayFilters) + { + ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); + return new ProcessNavigator(pr, new ExclusiveGatewayFactory(gatewayFilters)); + } +} From f8d1444edd1029c0185ca3466ad08c8dc30de0de Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 15:59:42 +0200 Subject: [PATCH 07/29] add available actions to currentTask and perform authcheck --- .../Controllers/InstancesController.cs | 19 ++++---- .../Controllers/ProcessController.cs | 48 +++++++++++-------- .../Process/Elements/AppProcessElementInfo.cs | 28 +++++++++++ .../Process/Elements/AppProcessState.cs | 24 +++++++++- 4 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index 18be92a44..f4e5bde74 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -30,7 +30,6 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; -using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; namespace Altinn.App.Api.Controllers { @@ -60,7 +59,7 @@ public class InstancesController : ControllerBase private readonly IPDP _pdp; private readonly IPrefill _prefillService; private readonly AppSettings _appSettings; - private readonly IProcessEngine _processEngineV2; + private readonly IProcessEngine _processEngine; private const long RequestSizeLimit = 2000 * 1024 * 1024; @@ -81,7 +80,7 @@ public InstancesController( IOptions appSettings, IPrefill prefillService, IProfile profileClient, - IProcessEngine processEngineV2) + IProcessEngine processEngine) { _logger = logger; _instanceClient = instanceClient; @@ -96,7 +95,7 @@ public InstancesController( _appSettings = appSettings.Value; _prefillService = prefillService; _profileClientClient = profileClient; - _processEngineV2 = processEngineV2; + _processEngine = processEngine; } /// @@ -279,7 +278,7 @@ public async Task> Post( User = User, Dryrun = true }; - var result = await _processEngineV2.StartProcess(processStartRequest); + var result = await _processEngine.StartProcess(processStartRequest); change = result.ProcessStateChange; // create the instance @@ -305,7 +304,7 @@ public async Task> Post( Dryrun = false, }; _logger.LogInformation("Events sent to process engine: {Events}", change?.Events); - await _processEngineV2.UpdateInstanceAndRerunEvents(request, change.Events); + await _processEngine.UpdateInstanceAndRerunEvents(request, change.Events); } catch (Exception exception) { @@ -431,7 +430,7 @@ public async Task> PostSimplified( Prefill = instansiationInstance.Prefill }; - processResult = await _processEngineV2.StartProcess(request); + processResult = await _processEngine.StartProcess(request); Instance? source = null; @@ -471,7 +470,7 @@ public async Task> PostSimplified( Dryrun = false, Prefill = instansiationInstance.Prefill }; - await _processEngineV2.UpdateInstanceAndRerunEvents(updateRequest, processResult.ProcessStateChange.Events); + await _processEngine.UpdateInstanceAndRerunEvents(updateRequest, processResult.ProcessStateChange.Events); } catch (Exception exception) { @@ -566,7 +565,7 @@ public async Task CopyInstance( User = User, Dryrun = true }; - var startResult = await _processEngineV2.StartProcess(processStartRequest); + var startResult = await _processEngine.StartProcess(processStartRequest); targetInstance = await _instanceClient.CreateInstance(org, app, targetInstance); @@ -580,7 +579,7 @@ public async Task CopyInstance( Dryrun = false, User = User }; - await _processEngineV2.UpdateInstanceAndRerunEvents(rerunRequest, startResult.ProcessStateChange?.Events); + await _processEngine.UpdateInstanceAndRerunEvents(rerunRequest, startResult.ProcessStateChange?.Events); await RegisterEvent("app.instance.created", targetInstance); diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index 80e675da2..e2babb7fe 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Threading.Tasks; using Altinn.App.Api.Infrastructure.Filters; using Altinn.App.Core.Features.Validation; @@ -10,8 +6,8 @@ using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; +using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; using Altinn.App.Core.Internal.Process.Elements.Base; -using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; using Altinn.Authorization.ABAC.Xacml.JsonProfile; using Altinn.Common.PEP.Helpers; @@ -19,13 +15,8 @@ using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; -using IProcessEngine = Altinn.App.Core.Internal.Process.IProcessEngine; -using IProcessReader = Altinn.App.Core.Internal.Process.IProcessReader; namespace Altinn.App.Api.Controllers { @@ -45,7 +36,7 @@ public class ProcessController : ControllerBase private readonly IProcess _processService; private readonly IValidation _validationService; private readonly IPDP _pdp; - private readonly IProcessEngine _processEngineV2; + private readonly IProcessEngine _processEngine; private readonly IProcessReader _processReader; /// @@ -58,7 +49,7 @@ public ProcessController( IValidation validationService, IPDP pdp, IProcessReader processReader, - IProcessEngine processEngineV2) + IProcessEngine processEngine) { _logger = logger; _instanceClient = instanceClient; @@ -66,7 +57,7 @@ public ProcessController( _validationService = validationService; _pdp = pdp; _processReader = processReader; - _processEngineV2 = processEngineV2; + _processEngine = processEngine; } /// @@ -81,7 +72,7 @@ public ProcessController( [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [Authorize(Policy = "InstanceRead")] - public async Task> GetProcessState( + public async Task> GetProcessState( [FromRoute] string org, [FromRoute] string app, [FromRoute] int instanceOwnerPartyId, @@ -90,9 +81,21 @@ public async Task> GetProcessState( try { Instance instance = await _instanceClient.GetInstance(app, org, instanceOwnerPartyId, instanceGuid); - ProcessState processState = instance.Process; - - return Ok(processState); + AppProcessState appProcessState = new AppProcessState(instance.Process); + if (appProcessState.CurrentTask?.ElementId != null) + { + var flowElement = _processReader.GetFlowElement(appProcessState.CurrentTask.ElementId); + if (flowElement is ProcessTask processTask) + { + appProcessState.CurrentTask.Actions = new Dictionary(); + foreach (AltinnAction action in processTask.ExtensionElements?.AltinnProperties.AltinnActions ?? new List()) + { + appProcessState.CurrentTask.Actions.Add(action.Id, await AuthorizeAction(action.Id, org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id)); + } + } + } + + return Ok(appProcessState); } catch (PlatformHttpException e) { @@ -139,7 +142,7 @@ public async Task> StartProcess( User = User, Dryrun = false }; - var result = await _processEngineV2.StartProcess(request); + var result = await _processEngine.StartProcess(request); if (!result.Success) { return Conflict(result.ErrorMessage); @@ -239,6 +242,7 @@ private async Task CanTaskBeEnded(Instance instance, string currentElement /// application identifier which is unique within an organisation /// unique id of the party that is the owner of the instance /// unique id to identify the instance + /// obsolete: alias for action /// action performed /// Optional parameter to pass on the language used in the form if this differs from the profile language, /// which otherwise is used automatically. The language is picked up when generating the PDF when leaving a step, @@ -253,6 +257,7 @@ public async Task> NextElement( [FromRoute] string app, [FromRoute] int instanceOwnerPartyId, [FromRoute] Guid instanceGuid, + [FromQuery] string elementId = null, [FromQuery] string action = null, [FromQuery] string lang = null) { @@ -300,7 +305,7 @@ public async Task> NextElement( User = User, Action = checkedAction }; - var result = await _processEngineV2.Next(request); + var result = await _processEngine.Next(request); if (!result.Success) { return Conflict(result.ErrorMessage); @@ -392,7 +397,7 @@ public async Task> CompleteProcess( User = User, Action = altinnTaskType }; - var result = await _processEngineV2.Next(request); + var result = await _processEngine.Next(request); if (!result.Success) { @@ -480,7 +485,8 @@ private async Task AuthorizeAction(string currentTaskType, string org, str actionType = currentTaskType; break; } - + + _logger.LogInformation("About to authorize action {ActionType}", actionType); XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(org, app, HttpContext.User, actionType, instanceOwnerPartyId, instanceGuid, taskId); XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request); if (response?.Response == null) diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs new file mode 100644 index 000000000..a5e36a4da --- /dev/null +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs @@ -0,0 +1,28 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Internal.Process.Elements; + +public class AppProcessElementInfo: ProcessElementInfo +{ + public AppProcessElementInfo() + { + Actions = new Dictionary(); + } + + public AppProcessElementInfo(ProcessElementInfo processElementInfo) + { + Flow = processElementInfo.Flow; + Started = processElementInfo.Started; + ElementId = processElementInfo.ElementId; + Name = processElementInfo.Name; + AltinnTaskType = processElementInfo.AltinnTaskType; + Ended = processElementInfo.Ended; + Validated = processElementInfo.Validated; + FlowType = processElementInfo.FlowType; + Actions = new Dictionary(); + } + /// + /// Actions that can be performed and if the user is allowed to perform them. + /// + public Dictionary? Actions { get; set; } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs index 04bbe923c..e738b8d3e 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs @@ -10,7 +10,27 @@ namespace Altinn.App.Core.Internal.Process.Elements; public class AppProcessState: ProcessState { /// - /// Actions that can be performed and if the user is allowed to perform them. + /// Default constructor /// - public Dictionary? Actions { get; set; } + public AppProcessState() + { + } + + /// + /// Constructor that takes a ProcessState object and copies the values. + /// + /// + public AppProcessState(ProcessState processState) + { + Started = processState.Started; + StartEvent = processState.StartEvent; + CurrentTask = new AppProcessElementInfo(processState.CurrentTask); + Ended = processState.Ended; + EndEvent = processState.EndEvent; + } + + /// + /// Gets or sets a status object containing the task info of the currentTask of an ongoing process. + /// + public new AppProcessElementInfo CurrentTask { get; set; } } From edac5cff20896078b85152c7e62166a87437b5d6 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 16:45:00 +0200 Subject: [PATCH 08/29] action passed along from PUT process/next to gateway filters --- src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs | 5 +++-- src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs b/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs index 32c8d5095..4ea37a384 100644 --- a/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs +++ b/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs @@ -9,7 +9,7 @@ namespace Altinn.App.Core.Features; public interface IProcessExclusiveGateway { /// - /// + /// Id of the gateway in the BPMN process this filter applies to /// string GatewayId { get; } @@ -18,6 +18,7 @@ public interface IProcessExclusiveGateway /// /// Complete list of defined flows out of gateway /// Instance where process is about to move next + /// Action performed by the requester /// - public Task> FilterAsync(List outgoingFlows, Instance instance); + public Task> FilterAsync(List outgoingFlows, Instance instance, string? action); } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs index 90b251f22..6f82781fe 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs @@ -69,7 +69,7 @@ private async Task> NextFollowAndFilterGateways(Instance in } else { - filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance); + filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance, action); } var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); From e7b4dedde8c4e4e10fd3b89c75dda3df935d4846 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 16:45:41 +0200 Subject: [PATCH 09/29] fix bug in AppProcessState ctor --- .../Internal/Process/Elements/AppProcessState.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs index e738b8d3e..1f87920ad 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs @@ -24,7 +24,10 @@ public AppProcessState(ProcessState processState) { Started = processState.Started; StartEvent = processState.StartEvent; - CurrentTask = new AppProcessElementInfo(processState.CurrentTask); + if (processState.CurrentTask != null) + { + CurrentTask = new AppProcessElementInfo(processState.CurrentTask); + } Ended = processState.Ended; EndEvent = processState.EndEvent; } From 99658854d11d54bc4422dbbcb5d2adbc2ced183f Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 5 May 2023 17:05:22 +0200 Subject: [PATCH 10/29] add fields for read/write and check users permissions --- .../Controllers/ProcessController.cs | 3 +++ .../Process/Elements/AppProcessElementInfo.cs | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index e2babb7fe..7122f6bf0 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -92,6 +92,9 @@ public async Task> GetProcessState( { appProcessState.CurrentTask.Actions.Add(action.Id, await AuthorizeAction(action.Id, org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id)); } + + appProcessState.CurrentTask.HasWriteAccess = await AuthorizeAction("write", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); + appProcessState.CurrentTask.HasReadAccess = await AuthorizeAction("read", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); } } diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs index a5e36a4da..b2b86b9c5 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs @@ -1,3 +1,4 @@ +using System.Text.Json.Serialization; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Process.Elements; @@ -24,5 +25,18 @@ public AppProcessElementInfo(ProcessElementInfo processElementInfo) /// /// Actions that can be performed and if the user is allowed to perform them. /// + [JsonPropertyName(name:"actions")] public Dictionary? Actions { get; set; } + + /// + /// Indicates if the user has read access to the task. + /// + [JsonPropertyName(name:"read")] + public bool HasReadAccess { get; set; } + + /// + /// Indicates if the user has write access to the task. + /// + [JsonPropertyName(name:"write")] + public bool HasWriteAccess { get; set; } } \ No newline at end of file From 7120616b0bf13851c90c5a5444ebc709d73c1ce0 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 08:53:41 +0200 Subject: [PATCH 11/29] Fix test stub implementation of IProcessExclusiveGateway --- .../Internal/Process/StubGatewayFilters/DataValuesFilter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Altinn.App.Core.Tests/Internal/Process/StubGatewayFilters/DataValuesFilter.cs b/test/Altinn.App.Core.Tests/Internal/Process/StubGatewayFilters/DataValuesFilter.cs index 9565fc8db..50bd024f7 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/StubGatewayFilters/DataValuesFilter.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/StubGatewayFilters/DataValuesFilter.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using System.Threading.Tasks; using Altinn.App.Core.Features; @@ -18,7 +19,7 @@ public DataValuesFilter(string gatewayId, string filterOnDataValue) _filterOnDataValue = filterOnDataValue; } - public async Task> FilterAsync(List outgoingFlows, Instance instance) + public async Task> FilterAsync(List outgoingFlows, Instance instance, string? action) { var targetFlow = instance.DataValues[_filterOnDataValue]; return await Task.FromResult(outgoingFlows.FindAll(e => e.Id == targetFlow)); From 19dea2d6b4cf18b0d9155b515739a5d5351f08dc Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 09:25:50 +0200 Subject: [PATCH 12/29] Fixing some reported code smells --- .../Controllers/InstancesController.cs | 2 +- .../Internal/Process/IProcessEngine.cs | 2 +- .../Process/IProcessEventDispatcher.cs | 2 +- .../Internal/Process/ProcessEngine.cs | 30 +++++++----- .../Process/ProcessEventDispatcher.cs | 47 ++++++++++--------- .../Models/ProcessStateChange.cs | 6 +-- .../Internal/Process/ProcessEngineTest.cs | 6 +-- 7 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index f4e5bde74..9b1db27a4 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -304,7 +304,7 @@ public async Task> Post( Dryrun = false, }; _logger.LogInformation("Events sent to process engine: {Events}", change?.Events); - await _processEngine.UpdateInstanceAndRerunEvents(request, change.Events); + await _processEngine.UpdateInstanceAndRerunEvents(request, change?.Events); } catch (Exception exception) { diff --git a/src/Altinn.App.Core/Internal/Process/IProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/IProcessEngine.cs index b200165e7..05ef332e1 100644 --- a/src/Altinn.App.Core/Internal/Process/IProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessEngine.cs @@ -24,6 +24,6 @@ public interface IProcessEngine /// /// /// - Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events); + Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List? events); } } diff --git a/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs index 5ed60e0cf..b8aa8305d 100644 --- a/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs @@ -14,7 +14,7 @@ public interface IProcessEventDispatcher /// Prefill data /// Events that should be dispatched /// Instance from storage after update - Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events); + Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List? events); /// /// Dispatch events for instance to the events system if AppSettings.RegisterEventsWithEventsComponent is true /// diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs index abce2494d..d8e447256 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs @@ -66,16 +66,24 @@ public async Task StartProcess(ProcessStartRequest processS // start process ProcessStateChange? startChange = await ProcessStart(processStartRequest.Instance, validStartElement!, processStartRequest.User); - InstanceEvent? startEvent = startChange?.Events.First().CopyValues(); + InstanceEvent? startEvent = startChange?.Events?.First().CopyValues(); ProcessStateChange nextChange = await ProcessNext(processStartRequest.Instance, processStartRequest.User); //ProcessChangeResult nextChange = await Next(processStartRequest.InstanceIdentifier, processStartRequest.User); - InstanceEvent goToNextEvent = nextChange.Events.First().CopyValues(); - + InstanceEvent? goToNextEvent = nextChange.Events?.First().CopyValues(); + List events = new List(); + if (startEvent is not null) + { + events.Add(startEvent); + } + if (goToNextEvent is not null) + { + events.Add(goToNextEvent); + } ProcessStateChange processStateChange = new ProcessStateChange { - OldProcessState = startChange.OldProcessState, + OldProcessState = startChange?.OldProcessState, NewProcessState = nextChange.NewProcessState, - Events = new List { startEvent, goToNextEvent } + Events = events }; if (!processStartRequest.Dryrun) @@ -116,7 +124,7 @@ public async Task Next(ProcessNextRequest request) } /// - public async Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List events) + public async Task UpdateInstanceAndRerunEvents(ProcessStartRequest startRequest, List? events) { return await _processEventDispatcher.UpdateProcessAndDispatchEvents(startRequest.Instance, startRequest.Prefill, events); } @@ -191,7 +199,7 @@ private async Task> MoveProcessToNext( ProcessState currentState = instance.Process; string? previousElementId = currentState.CurrentTask?.ElementId; - ProcessElement nextElement = await _processNavigator.GetNextTask(instance, instance.Process.CurrentTask.ElementId, action); + ProcessElement? nextElement = await _processNavigator.GetNextTask(instance, instance.Process.CurrentTask.ElementId, action); DateTime now = DateTime.UtcNow; // ending previous element if task if (_processReader.IsProcessTask(previousElementId)) @@ -202,24 +210,24 @@ private async Task> MoveProcessToNext( } // ending process if next element is end event - if (_processReader.IsEndEvent(nextElement.Id)) + if (_processReader.IsEndEvent(nextElement?.Id)) { currentState.CurrentTask = null; currentState.Ended = now; - currentState.EndEvent = nextElement.Id; + currentState.EndEvent = nextElement!.Id; events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndEvent.ToString(), instance, now, user)); // add submit event (to support Altinn2 SBL) events.Add(await GenerateProcessChangeEvent(InstanceEventType.Submited.ToString(), instance, now, user)); } - else if (_processReader.IsProcessTask(nextElement.Id)) + else if (_processReader.IsProcessTask(nextElement?.Id)) { var task = nextElement as ProcessTask; currentState.CurrentTask = new ProcessElementInfo { Flow = currentState.CurrentTask?.Flow + 1, - ElementId = nextElement.Id, + ElementId = nextElement!.Id, Name = nextElement?.Name, Started = now, AltinnTaskType = task?.ExtensionElements?.AltinnProperties.TaskType, diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs index 9b662afc3..a42006d99 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs @@ -40,7 +40,7 @@ public ProcessEventDispatcher( } /// - public async Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List events) + public async Task UpdateProcessAndDispatchEvents(Instance instance, Dictionary? prefill, List? events) { await HandleProcessChanges(instance, events, prefill); @@ -82,31 +82,34 @@ public async Task RegisterEventWithEventsComponent(Instance instance) /// /// Each implementation /// - private async Task HandleProcessChanges(Instance instance, List events, Dictionary? prefill) + private async Task HandleProcessChanges(Instance instance, List? events, Dictionary? prefill) { - foreach (InstanceEvent processEvent in events) + if (events != null) { - if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) + foreach (InstanceEvent processEvent in events) { - string? elementId = processEvent.ProcessInfo?.CurrentTask?.ElementId; - ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); - switch (eventType) + if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) { - case InstanceEventType.process_StartEvent: - break; - case InstanceEventType.process_StartTask: - await task.HandleTaskStart(elementId, instance, prefill); - break; - case InstanceEventType.process_EndTask: - await task.HandleTaskComplete(elementId, instance); - break; - case InstanceEventType.process_AbandonTask: - await task.HandleTaskAbandon(elementId, instance); - await _instanceService.UpdateProcess(instance); - break; - case InstanceEventType.process_EndEvent: - await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, instance); - break; + string? elementId = processEvent.ProcessInfo?.CurrentTask?.ElementId; + ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); + switch (eventType) + { + case InstanceEventType.process_StartEvent: + break; + case InstanceEventType.process_StartTask: + await task.HandleTaskStart(elementId, instance, prefill); + break; + case InstanceEventType.process_EndTask: + await task.HandleTaskComplete(elementId, instance); + break; + case InstanceEventType.process_AbandonTask: + await task.HandleTaskAbandon(elementId, instance); + await _instanceService.UpdateProcess(instance); + break; + case InstanceEventType.process_EndEvent: + await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, instance); + break; + } } } } diff --git a/src/Altinn.App.Core/Models/ProcessStateChange.cs b/src/Altinn.App.Core/Models/ProcessStateChange.cs index f5f59e64f..8232857fc 100644 --- a/src/Altinn.App.Core/Models/ProcessStateChange.cs +++ b/src/Altinn.App.Core/Models/ProcessStateChange.cs @@ -12,16 +12,16 @@ public class ProcessStateChange /// /// Gets or sets the old process state /// - public ProcessState OldProcessState { get; set; } + public ProcessState? OldProcessState { get; set; } /// /// Gets or sets the new process state /// - public ProcessState NewProcessState { get; set; } + public ProcessState? NewProcessState { get; set; } /// /// Gets or sets a list of events to be registered. /// - public List Events { get; set; } + public List? Events { get; set; } } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs index 986ba8083..f825e8252 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs @@ -20,9 +20,9 @@ namespace Altinn.App.Core.Tests.Internal.Process; public class ProcessEngineTest : IDisposable { private Mock _processReaderMock; - private Mock _profileMock; - private Mock _processNavigatorMock; - private Mock _processEventDispatcherMock; + private readonly Mock _profileMock; + private readonly Mock _processNavigatorMock; + private readonly Mock _processEventDispatcherMock; public ProcessEngineTest() { From ea04c41913722e06a0845f63aaaf3864792595ce Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 12:34:54 +0200 Subject: [PATCH 13/29] Some code smell fixes. Added logic to dispatch abandon event if action is reject --- .../Controllers/InstancesController.cs | 2 +- .../Controllers/ProcessController.cs | 7 +- .../Extensions/InstanceEventExtensions.cs | 5 + .../Extensions/ProcessStateExtensions.cs | 5 + .../Clients/Storage/ProcessClient.cs | 9 +- src/Altinn.App.Core/Interface/IProcess.cs | 2 +- .../Process/Elements/AppProcessElementInfo.cs | 10 ++ .../Process/Elements/ExtensionElements.cs | 2 +- .../Internal/Process/ProcessEngine.cs | 22 ++-- .../Clients/ProcessClientTests.cs | 7 + .../Internal/Process/ProcessEngineTest.cs | 123 ++++++++++++++++++ 11 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index 9b1db27a4..34c283ee1 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -470,7 +470,7 @@ public async Task> PostSimplified( Dryrun = false, Prefill = instansiationInstance.Prefill }; - await _processEngine.UpdateInstanceAndRerunEvents(updateRequest, processResult.ProcessStateChange.Events); + await _processEngine.UpdateInstanceAndRerunEvents(updateRequest, processResult.ProcessStateChange?.Events); } catch (Exception exception) { diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index 7122f6bf0..318f294dc 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -88,7 +88,7 @@ public async Task> GetProcessState( if (flowElement is ProcessTask processTask) { appProcessState.CurrentTask.Actions = new Dictionary(); - foreach (AltinnAction action in processTask.ExtensionElements?.AltinnProperties.AltinnActions ?? new List()) + foreach (AltinnAction action in processTask.ExtensionElements?.AltinnProperties?.AltinnActions ?? new List()) { appProcessState.CurrentTask.Actions.Add(action.Id, await AuthorizeAction(action.Id, org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id)); } @@ -177,6 +177,7 @@ public async Task> StartProcess( [HttpGet("next")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] + [Obsolete("From v8 of nuget package navigation is done by sending performed action to the next api. Available actions are returned in the GET /process endpoint")] public async Task>> GetNextElements( [FromRoute] string org, [FromRoute] string app, @@ -202,9 +203,7 @@ public async Task>> GetNextElements( return Conflict($"Instance does not have valid info about currentTask"); } - // List nextElements = await _flowHydration.NextFollowAndFilterGateways(instance, currentTaskId, false); - List nextElements = new List(); - return Ok(nextElements.Select(e => e.Id).ToList()); + return Ok(new List()); } catch (PlatformHttpException e) { diff --git a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs index 8262982b8..1491bcece 100644 --- a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs +++ b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs @@ -7,6 +7,11 @@ namespace Altinn.App.Core.Extensions; /// public static class InstanceEventExtensions { + /// + /// Copies the values of the original to a new instance. + /// + /// The original . + /// New object with copies of values form original public static InstanceEvent CopyValues(this InstanceEvent original) { return new InstanceEvent diff --git a/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs b/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs index 13443c71e..561e97505 100644 --- a/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ProcessStateExtensions.cs @@ -7,6 +7,11 @@ namespace Altinn.App.Core.Extensions; /// public static class ProcessStateExtensions { + /// + /// Copies the values of the original to a new instance. + /// + /// The original . + /// New object with copies of values form original public static ProcessState Copy(this ProcessState original) { ProcessState copyOfState = new ProcessState(); diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs index c415a11f6..dccacf841 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs @@ -89,10 +89,13 @@ public async Task DispatchProcessEventsToStorage(Instance instance, Listthe instance /// process events /// - public Task DispatchProcessEventsToStorage(Instance instance, List events); + public Task DispatchProcessEventsToStorage(Instance instance, List? events); /// /// Gets the instance process events related to the instance matching the instance id. diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs index b2b86b9c5..3efb36de2 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessElementInfo.cs @@ -3,13 +3,23 @@ namespace Altinn.App.Core.Internal.Process.Elements; +/// +/// Extended representation of a status object that holds the process state of an application instance. +/// public class AppProcessElementInfo: ProcessElementInfo { + /// + /// Create a new instance of with no fields set. + /// public AppProcessElementInfo() { Actions = new Dictionary(); } + /// + /// Create a new instance of with values copied from . + /// + /// The to copy values from. public AppProcessElementInfo(ProcessElementInfo processElementInfo) { Flow = processElementInfo.Flow; diff --git a/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs b/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs index c44d7114b..1f624b611 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/ExtensionElements.cs @@ -12,6 +12,6 @@ public class ExtensionElements /// Gets or sets the altinn properties /// [XmlElement("properties", Namespace = "http://altinn.no")] - public AltinnProperties AltinnProperties { get; set; } + public AltinnProperties? AltinnProperties { get; set; } } } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs index d8e447256..e1d072d28 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs @@ -67,9 +67,8 @@ public async Task StartProcess(ProcessStartRequest processS // start process ProcessStateChange? startChange = await ProcessStart(processStartRequest.Instance, validStartElement!, processStartRequest.User); InstanceEvent? startEvent = startChange?.Events?.First().CopyValues(); - ProcessStateChange nextChange = await ProcessNext(processStartRequest.Instance, processStartRequest.User); - //ProcessChangeResult nextChange = await Next(processStartRequest.InstanceIdentifier, processStartRequest.User); - InstanceEvent? goToNextEvent = nextChange.Events?.First().CopyValues(); + ProcessStateChange? nextChange = await ProcessNext(processStartRequest.Instance, processStartRequest.User); + InstanceEvent? goToNextEvent = nextChange?.Events?.First().CopyValues(); List events = new List(); if (startEvent is not null) { @@ -82,13 +81,13 @@ public async Task StartProcess(ProcessStartRequest processS ProcessStateChange processStateChange = new ProcessStateChange { OldProcessState = startChange?.OldProcessState, - NewProcessState = nextChange.NewProcessState, + NewProcessState = nextChange?.NewProcessState, Events = events }; if (!processStartRequest.Dryrun) { - await _processEventDispatcher.UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, new List { startEvent, goToNextEvent }); + await _processEventDispatcher.UpdateProcessAndDispatchEvents(processStartRequest.Instance, processStartRequest.Prefill, events); } return new ProcessChangeResult() @@ -166,7 +165,7 @@ await GenerateProcessChangeEvent(InstanceEventType.process_StartEvent.ToString() /// /// Moves instance's process to nextElement id. Returns the instance together with process events. /// - private async Task ProcessNext(Instance instance, ClaimsPrincipal userContext, string? action = null) + private async Task ProcessNext(Instance instance, ClaimsPrincipal userContext, string? action = null) { if (instance.Process != null) { @@ -205,7 +204,12 @@ private async Task> MoveProcessToNext( if (_processReader.IsProcessTask(previousElementId)) { instance.Process = previousState; - events.Add(await GenerateProcessChangeEvent(InstanceEventType.process_EndTask.ToString(), instance, now, user)); + string eventType = InstanceEventType.process_EndTask.ToString(); + if (action is "reject") + { + eventType = InstanceEventType.process_AbandonTask.ToString(); + } + events.Add(await GenerateProcessChangeEvent(eventType, instance, now, user)); instance.Process = currentState; } @@ -228,9 +232,9 @@ private async Task> MoveProcessToNext( { Flow = currentState.CurrentTask?.Flow + 1, ElementId = nextElement!.Id, - Name = nextElement?.Name, + Name = nextElement.Name, Started = now, - AltinnTaskType = task?.ExtensionElements?.AltinnProperties.TaskType, + AltinnTaskType = task?.ExtensionElements?.AltinnProperties?.TaskType, Validated = null, }; diff --git a/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs b/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs new file mode 100644 index 000000000..47e80e31b --- /dev/null +++ b/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs @@ -0,0 +1,7 @@ +namespace Altinn.App.Core.Tests.Infrastructure.Clients +{ + public class ProcessClientTests + { + + } +} diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs index f825e8252..022eeb762 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs @@ -336,6 +336,129 @@ public async Task Next_moves_instance_to_next_task_and_produces_instanceevents() }); } + [Fact] + public async Task Next_moves_instance_to_next_task_and_produces_abandon_instanceevent_when_action_reject() + { + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_2", + Flow = 3, + AltinnTaskType = "confirmation", + Name = "Bekreft" + }, + StartEvent = "StartEvent_1" + } + }; + IProcessEngine processEngine = GetProcessEngine(null, expectedInstance); + Instance instance = new Instance() + { + InstanceOwner = new() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + AltinnTaskType = "data", + Flow = 2, + Validated = new() + { + CanCompleteTask = true + } + } + } + }; + ProcessState originalProcessState = instance.Process.Copy(); + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + ProcessNextRequest processNextRequest = new ProcessNextRequest() { Instance = instance, User = user, Action = "reject" }; + ProcessChangeResult result = await processEngine.Next(processNextRequest); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_2"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_2"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "Task_1", "reject"), Times.Once); + + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_AbandonTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Validated = new() + { + CanCompleteTask = true + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_StartTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_2", + Name = "Bekreft", + AltinnTaskType = "confirmation", + Flow = 3 + } + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + It.IsAny?>(), + It.Is>(l => CompareInstanceEvents(expectedInstanceEvents, l)))); + _processEventDispatcherMock.Verify(d => d.RegisterEventWithEventsComponent(It.Is(i => CompareInstance(expectedInstance, i)))); + result.Success.Should().BeTrue(); + result.ProcessStateChange.Should().BeEquivalentTo( + new ProcessStateChange() + { + Events = expectedInstanceEvents, + NewProcessState = expectedInstance.Process, + OldProcessState = originalProcessState + }); + } + [Fact] public async Task Next_moves_instance_to_end_event_and_ends_proces() { From fc627eb3686bce1d55b29b7ebde5c0503342fa4b Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 12:42:07 +0200 Subject: [PATCH 14/29] remove unfinished test file --- .../Infrastructure/Clients/ProcessClientTests.cs | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs diff --git a/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs b/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs deleted file mode 100644 index 47e80e31b..000000000 --- a/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Altinn.App.Core.Tests.Infrastructure.Clients -{ - public class ProcessClientTests - { - - } -} From 0327a6cd7ab1766c63df0dc819f4839cdbfdcfdb Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 13:08:04 +0200 Subject: [PATCH 15/29] add tests for method in ProcessClient --- .../Internal/Process/ProcessEngine.cs | 2 +- .../Clients/ProcessClientTests.cs | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs index e1d072d28..104913388 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs @@ -232,7 +232,7 @@ private async Task> MoveProcessToNext( { Flow = currentState.CurrentTask?.Flow + 1, ElementId = nextElement!.Id, - Name = nextElement.Name, + Name = nextElement!.Name, Started = now, AltinnTaskType = task?.ExtensionElements?.AltinnProperties?.TaskType, Validated = null, diff --git a/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs b/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs new file mode 100644 index 000000000..b80d145fb --- /dev/null +++ b/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs @@ -0,0 +1,65 @@ +using Altinn.App.Core.Configuration; +using Altinn.App.Core.Infrastructure.Clients.Storage; +using Altinn.App.Core.Interface; +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Altinn.App.Core.Tests.Infrastructure.Clients; + +public class ProcessClientTests: IDisposable +{ + private readonly Mock _instanceEventClientMock = new Mock(); + private readonly Mock _httpClientMock = new Mock(); + + [Fact] + public async Task DispatchProcessEventsToStorage_does_not_save_events_if_list_is_null() + { + IProcess processClient = GetProcessClient(); + await processClient.DispatchProcessEventsToStorage( + new Instance() + { + Org = "ttd", + AppId = "1337/aaaa-bbbbb-dddd-eeee" + }, + null); + } + + [Fact] + public async Task DispatchProcessEventsToStorage_sends_events_to_instanceEventClient() + { + IProcess processClient = GetProcessClient(); + var instanceEvent = new InstanceEvent(); + await processClient.DispatchProcessEventsToStorage( + new Instance() + { + Org = "ttd", + AppId = "ttd/demo-app" + }, + new List() + { + instanceEvent + }); + _instanceEventClientMock.Verify(i => i.SaveInstanceEvent(instanceEvent, "ttd", "demo-app")); + } + + private ProcessClient GetProcessClient() + { + var platformSettings = Options.Create(new PlatformSettings()); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + var httpContextAccessor = new Mock(); + + return new ProcessClient(platformSettings, appSettings, _instanceEventClientMock.Object, logger, httpContextAccessor.Object, _httpClientMock.Object); + } + + public void Dispose() + { + _instanceEventClientMock.VerifyNoOtherCalls(); + _httpClientMock.VerifyNoOtherCalls(); + } +} From fd0cf1d70dbe329d5addd15961bb8cb3a0e83725 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 14:38:34 +0200 Subject: [PATCH 16/29] add test for classes extending storage classes --- .../Process/Elements/AppProcessState.cs | 2 +- .../Process/Elements/AppProcessStateTests.cs | 132 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 test/Altinn.App.Core.Tests/Internal/Process/Elements/AppProcessStateTests.cs diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs index 1f87920ad..d3215b5a1 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs @@ -35,5 +35,5 @@ public AppProcessState(ProcessState processState) /// /// Gets or sets a status object containing the task info of the currentTask of an ongoing process. /// - public new AppProcessElementInfo CurrentTask { get; set; } + public new AppProcessElementInfo? CurrentTask { get; set; } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/Elements/AppProcessStateTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/Elements/AppProcessStateTests.cs new file mode 100644 index 000000000..693d9b09a --- /dev/null +++ b/test/Altinn.App.Core.Tests/Internal/Process/Elements/AppProcessStateTests.cs @@ -0,0 +1,132 @@ +using Altinn.App.Core.Internal.Process.Elements; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.Core.Tests.Internal.Process.Elements; + +public class AppProcessStateTests +{ + [Fact] + public void Constructor_with_ProcessState_copies_values() + { + ProcessState input = new ProcessState() + { + Started = DateTime.Now, + StartEvent = "StartEvent", + Ended = DateTime.Now, + EndEvent = "EndEvent", + CurrentTask = new() + { + Started = DateTime.Now, + Ended = DateTime.Now, + Flow = 2, + Name = "Task_1", + Validated = new() + { + Timestamp = DateTime.Now, + CanCompleteTask = false + }, + ElementId = "Task_1", + FlowType = "FlowType", + AltinnTaskType = "data", + } + }; + AppProcessState expected = new AppProcessState() + { + Started = input.Started, + StartEvent = input.StartEvent, + Ended = input.Ended, + EndEvent = input.EndEvent, + CurrentTask = new() + { + Started = input.CurrentTask.Started, + Ended = input.CurrentTask.Ended, + Flow = input.CurrentTask.Flow, + Name = input.CurrentTask.Name, + Validated = new() + { + Timestamp = input.CurrentTask.Validated.Timestamp, + CanCompleteTask = input.CurrentTask.Validated.CanCompleteTask + }, + ElementId = input.CurrentTask.ElementId, + FlowType = input.CurrentTask.FlowType, + AltinnTaskType = input.CurrentTask.AltinnTaskType, + Actions = new Dictionary(), + HasReadAccess = false, + HasWriteAccess = false + } + }; + AppProcessState actual = new(input); + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void Constructor_with_ProcessState_copies_values_validated_null() + { + ProcessState input = new ProcessState() + { + Started = DateTime.Now, + StartEvent = "StartEvent", + Ended = DateTime.Now, + EndEvent = "EndEvent", + CurrentTask = new() + { + Started = DateTime.Now, + Ended = DateTime.Now, + Flow = 2, + Name = "Task_1", + Validated = null, + ElementId = "Task_1", + FlowType = "FlowType", + AltinnTaskType = "data" + } + }; + AppProcessState expected = new AppProcessState() + { + Started = input.Started, + StartEvent = input.StartEvent, + Ended = input.Ended, + EndEvent = input.EndEvent, + CurrentTask = new() + { + Started = input.CurrentTask.Started, + Ended = input.CurrentTask.Ended, + Flow = input.CurrentTask.Flow, + Name = input.CurrentTask.Name, + Validated = null, + ElementId = input.CurrentTask.ElementId, + FlowType = input.CurrentTask.FlowType, + AltinnTaskType = input.CurrentTask.AltinnTaskType, + Actions = new Dictionary(), + HasReadAccess = false, + HasWriteAccess = false + } + }; + AppProcessState actual = new(input); + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void Constructor_with_ProcessState_copies_values_currenttask_null() + { + ProcessState input = new ProcessState() + { + Started = DateTime.Now, + StartEvent = "StartEvent", + Ended = DateTime.Now, + EndEvent = "EndEvent", + CurrentTask = null + }; + AppProcessState expected = new AppProcessState() + { + Started = input.Started, + StartEvent = input.StartEvent, + Ended = input.Ended, + EndEvent = input.EndEvent, + CurrentTask = null + }; + AppProcessState actual = new(input); + actual.Should().BeEquivalentTo(expected); + } +} \ No newline at end of file From 62cee902abe3acb868e04164512e9959a67f31fa Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 15:19:22 +0200 Subject: [PATCH 17/29] add test for null values in extensions --- .../Extensions/InstanceEventExtensions.cs | 20 +- .../InstanceEventExtensionsTests.cs | 239 ++++++++++++++++++ 2 files changed, 249 insertions(+), 10 deletions(-) diff --git a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs index 1491bcece..4c1d40169 100644 --- a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs +++ b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs @@ -27,12 +27,12 @@ public static InstanceEvent CopyValues(this InstanceEvent original) Started = original.ProcessInfo?.Started, CurrentTask = new ProcessElementInfo { - Flow = original.ProcessInfo?.CurrentTask.Flow, - AltinnTaskType = original.ProcessInfo?.CurrentTask.AltinnTaskType, - ElementId = original.ProcessInfo?.CurrentTask.ElementId, - Name = original.ProcessInfo?.CurrentTask.Name, - Started = original.ProcessInfo?.CurrentTask.Started, - Ended = original.ProcessInfo?.CurrentTask.Ended, + Flow = original.ProcessInfo?.CurrentTask?.Flow, + AltinnTaskType = original.ProcessInfo?.CurrentTask?.AltinnTaskType, + ElementId = original.ProcessInfo?.CurrentTask?.ElementId, + Name = original.ProcessInfo?.CurrentTask?.Name, + Started = original.ProcessInfo?.CurrentTask?.Started, + Ended = original.ProcessInfo?.CurrentTask?.Ended, Validated = new ValidationStatus { CanCompleteTask = original.ProcessInfo?.CurrentTask?.Validated?.CanCompleteTask ?? false, @@ -44,10 +44,10 @@ public static InstanceEvent CopyValues(this InstanceEvent original) }, User = new PlatformUser { - AuthenticationLevel = original.User.AuthenticationLevel, - EndUserSystemId = original.User.EndUserSystemId, - OrgId = original.User.OrgId, - UserId = original.User.UserId, + AuthenticationLevel = original.User?.AuthenticationLevel ?? 0, + EndUserSystemId = original.User?.EndUserSystemId, + OrgId = original.User?.OrgId, + UserId = original.User?.UserId, NationalIdentityNumber = original.User?.NationalIdentityNumber } }; diff --git a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs index d367c16e0..ec278bcdf 100644 --- a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs +++ b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs @@ -54,4 +54,243 @@ public void CopyValues_returns_copy_of_instance_event() copy.User.Should().NotBeSameAs(original.User); copy.Should().BeEquivalentTo(original); } + + [Fact] + public void CopyValues_returns_copy_of_instance_event_Validated_null() + { + Guid id = Guid.NewGuid(); + string dataGuid = Guid.NewGuid().ToString(); + string instanceGuid = Guid.NewGuid().ToString(); + DateTime now = DateTime.Now; + InstanceEvent original = new InstanceEvent() + { + Created = now, + DataId = dataGuid, + EventType = "EventType", + Id = id, + InstanceId = instanceGuid, + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = now, + CurrentTask = new ProcessElementInfo + { + Flow = 1, + AltinnTaskType = "AltinnTaskType", + ElementId = "ElementId", + Name = "Name", + Started = now, + Ended = now, + Validated = null + }, + StartEvent = "StartEvent" + }, + User = new PlatformUser + { + AuthenticationLevel = 2, + EndUserSystemId = 1, + OrgId = "OrgId", + UserId = 3, + NationalIdentityNumber = "NationalIdentityNumber" + } + }; + InstanceEvent expected = new InstanceEvent() + { + Created = now, + DataId = dataGuid, + EventType = "EventType", + Id = id, + InstanceId = instanceGuid, + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = now, + CurrentTask = new ProcessElementInfo + { + Flow = 1, + AltinnTaskType = "AltinnTaskType", + ElementId = "ElementId", + Name = "Name", + Started = now, + Ended = now, + Validated = new() + { + Timestamp = null, + CanCompleteTask = false + } + }, + StartEvent = "StartEvent" + }, + User = new PlatformUser + { + AuthenticationLevel = 2, + EndUserSystemId = 1, + OrgId = "OrgId", + UserId = 3, + NationalIdentityNumber = "NationalIdentityNumber" + } + }; + InstanceEvent copy = original.CopyValues(); + copy.Should().NotBeSameAs(original); + copy.ProcessInfo.Should().NotBeSameAs(original.ProcessInfo); + copy.ProcessInfo.CurrentTask.Should().NotBeSameAs(original.ProcessInfo.CurrentTask); + copy.ProcessInfo.CurrentTask.Validated.Should().NotBeSameAs(original.ProcessInfo.CurrentTask.Validated); + copy.User.Should().NotBeSameAs(original.User); + copy.Should().BeEquivalentTo(expected); + } + + [Fact] + public void CopyValues_returns_copy_of_instance_event_CurrentTask_null() + { + Guid id = Guid.NewGuid(); + string dataGuid = Guid.NewGuid().ToString(); + string instanceGuid = Guid.NewGuid().ToString(); + DateTime now = DateTime.Now; + InstanceEvent original = new InstanceEvent() + { + Created = now, + DataId = dataGuid, + EventType = "EventType", + Id = id, + InstanceId = instanceGuid, + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = now, + CurrentTask = null, + StartEvent = "StartEvent" + }, + User = new PlatformUser + { + AuthenticationLevel = 2, + EndUserSystemId = 1, + OrgId = "OrgId", + UserId = 3, + NationalIdentityNumber = "NationalIdentityNumber" + } + }; + InstanceEvent expected = new InstanceEvent() + { + Created = now, + DataId = dataGuid, + EventType = "EventType", + Id = id, + InstanceId = instanceGuid, + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = now, + CurrentTask = new ProcessElementInfo + { + Flow = null, + AltinnTaskType = null, + ElementId = null, + Name = null, + Started = null, + Ended = null, + Validated = new() + { + Timestamp = null, + CanCompleteTask = false + } + }, + StartEvent = "StartEvent" + }, + User = new PlatformUser + { + AuthenticationLevel = 2, + EndUserSystemId = 1, + OrgId = "OrgId", + UserId = 3, + NationalIdentityNumber = "NationalIdentityNumber" + } + }; + InstanceEvent copy = original.CopyValues(); + copy.Should().NotBeSameAs(original); + copy.ProcessInfo.Should().NotBeSameAs(original.ProcessInfo); + copy.ProcessInfo.CurrentTask.Should().NotBeSameAs(original.ProcessInfo.CurrentTask); + copy.User.Should().NotBeSameAs(original.User); + copy.Should().BeEquivalentTo(expected); + } + + [Fact] + public void CopyValues_returns_copy_of_instance_event_User_null() + { + Guid id = Guid.NewGuid(); + string dataGuid = Guid.NewGuid().ToString(); + string instanceGuid = Guid.NewGuid().ToString(); + DateTime now = DateTime.Now; + InstanceEvent original = new InstanceEvent() + { + Created = now, + DataId = dataGuid, + EventType = "EventType", + Id = id, + InstanceId = instanceGuid, + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = now, + CurrentTask = new ProcessElementInfo + { + Flow = 1, + AltinnTaskType = "AltinnTaskType", + ElementId = "ElementId", + Name = "Name", + Started = now, + Ended = now, + Validated = new() + { + CanCompleteTask = true, + Timestamp = now + } + }, + StartEvent = "StartEvent" + }, + User = null + }; + InstanceEvent expected = new InstanceEvent() + { + Created = now, + DataId = dataGuid, + EventType = "EventType", + Id = id, + InstanceId = instanceGuid, + InstanceOwnerPartyId = "1", + ProcessInfo = new ProcessState + { + Started = now, + CurrentTask = new ProcessElementInfo + { + Flow = 1, + AltinnTaskType = "AltinnTaskType", + ElementId = "ElementId", + Name = "Name", + Started = now, + Ended = now, + Validated = new() + { + Timestamp = now, + CanCompleteTask = true + } + }, + StartEvent = "StartEvent" + }, + User = new PlatformUser + { + AuthenticationLevel = 0, + EndUserSystemId = null, + OrgId = null, + UserId = null, + NationalIdentityNumber = null + } + }; + InstanceEvent copy = original.CopyValues(); + copy.Should().NotBeSameAs(original); + copy.ProcessInfo.Should().NotBeSameAs(original.ProcessInfo); + copy.ProcessInfo.CurrentTask.Should().NotBeSameAs(original.ProcessInfo.CurrentTask); + copy.ProcessInfo.CurrentTask.Validated.Should().NotBeSameAs(original.ProcessInfo.CurrentTask.Validated); + copy.User.Should().NotBeSameAs(original.User); + copy.Should().BeEquivalentTo(expected); + } } \ No newline at end of file From 40086cfa8ff47aa2e73db1ae2e787b9fe441a17e Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 8 May 2023 15:20:36 +0200 Subject: [PATCH 18/29] revert code changes due to test --- .../Extensions/InstanceEventExtensions.cs | 8 +- .../InstanceEventExtensionsTests.cs | 81 ------------------- 2 files changed, 4 insertions(+), 85 deletions(-) diff --git a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs index 4c1d40169..bc6a54bc9 100644 --- a/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs +++ b/src/Altinn.App.Core/Extensions/InstanceEventExtensions.cs @@ -44,10 +44,10 @@ public static InstanceEvent CopyValues(this InstanceEvent original) }, User = new PlatformUser { - AuthenticationLevel = original.User?.AuthenticationLevel ?? 0, - EndUserSystemId = original.User?.EndUserSystemId, - OrgId = original.User?.OrgId, - UserId = original.User?.UserId, + AuthenticationLevel = original.User.AuthenticationLevel, + EndUserSystemId = original.User.EndUserSystemId, + OrgId = original.User.OrgId, + UserId = original.User.UserId, NationalIdentityNumber = original.User?.NationalIdentityNumber } }; diff --git a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs index ec278bcdf..09abfa59b 100644 --- a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs +++ b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs @@ -212,85 +212,4 @@ public void CopyValues_returns_copy_of_instance_event_CurrentTask_null() copy.User.Should().NotBeSameAs(original.User); copy.Should().BeEquivalentTo(expected); } - - [Fact] - public void CopyValues_returns_copy_of_instance_event_User_null() - { - Guid id = Guid.NewGuid(); - string dataGuid = Guid.NewGuid().ToString(); - string instanceGuid = Guid.NewGuid().ToString(); - DateTime now = DateTime.Now; - InstanceEvent original = new InstanceEvent() - { - Created = now, - DataId = dataGuid, - EventType = "EventType", - Id = id, - InstanceId = instanceGuid, - InstanceOwnerPartyId = "1", - ProcessInfo = new ProcessState - { - Started = now, - CurrentTask = new ProcessElementInfo - { - Flow = 1, - AltinnTaskType = "AltinnTaskType", - ElementId = "ElementId", - Name = "Name", - Started = now, - Ended = now, - Validated = new() - { - CanCompleteTask = true, - Timestamp = now - } - }, - StartEvent = "StartEvent" - }, - User = null - }; - InstanceEvent expected = new InstanceEvent() - { - Created = now, - DataId = dataGuid, - EventType = "EventType", - Id = id, - InstanceId = instanceGuid, - InstanceOwnerPartyId = "1", - ProcessInfo = new ProcessState - { - Started = now, - CurrentTask = new ProcessElementInfo - { - Flow = 1, - AltinnTaskType = "AltinnTaskType", - ElementId = "ElementId", - Name = "Name", - Started = now, - Ended = now, - Validated = new() - { - Timestamp = now, - CanCompleteTask = true - } - }, - StartEvent = "StartEvent" - }, - User = new PlatformUser - { - AuthenticationLevel = 0, - EndUserSystemId = null, - OrgId = null, - UserId = null, - NationalIdentityNumber = null - } - }; - InstanceEvent copy = original.CopyValues(); - copy.Should().NotBeSameAs(original); - copy.ProcessInfo.Should().NotBeSameAs(original.ProcessInfo); - copy.ProcessInfo.CurrentTask.Should().NotBeSameAs(original.ProcessInfo.CurrentTask); - copy.ProcessInfo.CurrentTask.Validated.Should().NotBeSameAs(original.ProcessInfo.CurrentTask.Validated); - copy.User.Should().NotBeSameAs(original.User); - copy.Should().BeEquivalentTo(expected); - } } \ No newline at end of file From f139582c60e557e48f94bbdce5f22f5ef618e01a Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Tue, 9 May 2023 09:31:40 +0200 Subject: [PATCH 19/29] add frontend feature and parse request body on process/next if present --- test/Altinn.App.Core.Tests/Internal/App/AppMedataTest.cs | 6 ++++-- .../Internal/App/FrontendFeaturesTest.cs | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/Altinn.App.Core.Tests/Internal/App/AppMedataTest.cs b/test/Altinn.App.Core.Tests/Internal/App/AppMedataTest.cs index 9f93c3b00..1a88da473 100644 --- a/test/Altinn.App.Core.Tests/Internal/App/AppMedataTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/App/AppMedataTest.cs @@ -58,7 +58,8 @@ public async void GetApplicationMetadata_desrializes_file_from_disk() }, Features = new Dictionary() { - { "footer", true } + { "footer", true }, + { "processActions", true } } }; var actual = await appMetadata.GetApplicationMetadata(); @@ -127,7 +128,8 @@ public async void GetApplicationMetadata_eformidling_desrializes_file_from_disk( }, Features = new Dictionary() { - { "footer", true } + { "footer", true }, + { "processActions", true } } }; var actual = await appMetadata.GetApplicationMetadata(); diff --git a/test/Altinn.App.Core.Tests/Internal/App/FrontendFeaturesTest.cs b/test/Altinn.App.Core.Tests/Internal/App/FrontendFeaturesTest.cs index ec0b8df6e..e97c1e619 100644 --- a/test/Altinn.App.Core.Tests/Internal/App/FrontendFeaturesTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/App/FrontendFeaturesTest.cs @@ -11,7 +11,8 @@ public async void GetFeatures_returns_list_of_enabled_features() { Dictionary expected = new Dictionary() { - { "footer", true } + { "footer", true }, + { "processActions", true }, }; IFrontendFeatures frontendFeatures = new FrontendFeatures(); var actual = await frontendFeatures.GetFrontendFeatures(); From 570a14adf32c7ef6942d4d61bbcbcbb8d614272d Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Tue, 9 May 2023 09:32:07 +0200 Subject: [PATCH 20/29] add frontend feature and parse request body on process/next if present --- .../Controllers/InstancesController.cs | 1 - .../Controllers/ProcessController.cs | 40 ++++++++++--------- src/Altinn.App.Api/Models/ProcessNext.cs | 15 +++++++ .../Internal/App/FrontendFeatures.cs | 1 + 4 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 src/Altinn.App.Api/Models/ProcessNext.cs diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index 34c283ee1..9b975b251 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -265,7 +265,6 @@ public async Task> Post( ConditionallySetReadStatus(instanceTemplate); Instance instance; - ProcessStateChange processResult; instanceTemplate.Process = null; ProcessStateChange? change = null; diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index 318f294dc..b0ded3f96 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -1,22 +1,21 @@ using System.Net; - using Altinn.App.Api.Infrastructure.Filters; +using Altinn.App.Api.Models; using Altinn.App.Core.Features.Validation; using Altinn.App.Core.Helpers; using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; -using Altinn.App.Core.Internal.Process.Elements.Base; using Altinn.App.Core.Models.Validation; using Altinn.Authorization.ABAC.Xacml.JsonProfile; using Altinn.Common.PEP.Helpers; using Altinn.Common.PEP.Interfaces; using Altinn.Platform.Storage.Interface.Models; - using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; +using AppProcessState = Altinn.App.Core.Internal.Process.Elements.AppProcessState; namespace Altinn.App.Api.Controllers { @@ -92,12 +91,12 @@ public async Task> GetProcessState( { appProcessState.CurrentTask.Actions.Add(action.Id, await AuthorizeAction(action.Id, org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id)); } - + appProcessState.CurrentTask.HasWriteAccess = await AuthorizeAction("write", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); appProcessState.CurrentTask.HasReadAccess = await AuthorizeAction("read", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); } } - + return Ok(appProcessState); } catch (PlatformHttpException e) @@ -202,7 +201,7 @@ public async Task>> GetNextElements( { return Conflict($"Instance does not have valid info about currentTask"); } - + return Ok(new List()); } catch (PlatformHttpException e) @@ -245,7 +244,6 @@ private async Task CanTaskBeEnded(Instance instance, string currentElement /// unique id of the party that is the owner of the instance /// unique id to identify the instance /// obsolete: alias for action - /// action performed /// Optional parameter to pass on the language used in the form if this differs from the profile language, /// which otherwise is used automatically. The language is picked up when generating the PDF when leaving a step, /// and is not used for anything else. @@ -260,11 +258,16 @@ public async Task> NextElement( [FromRoute] int instanceOwnerPartyId, [FromRoute] Guid instanceGuid, [FromQuery] string elementId = null, - [FromQuery] string action = null, [FromQuery] string lang = null) { try { + ProcessNext? processNext = null; + if (Request.Body != null && Request.Body.CanRead) + { + processNext = await DeserializeFromStream(Request.Body); + } + Instance instance = await _instanceClient.GetInstance(app, org, instanceOwnerPartyId, instanceGuid); if (instance.Process == null) @@ -285,16 +288,8 @@ public async Task> NextElement( } bool authorized; - string checkedAction = action; - if (action != null) - { - authorized = await AuthorizeAction(action, org, app, instanceOwnerPartyId, instanceGuid); - } - else - { - authorized = await AuthorizeAction(altinnTaskType, org, app, instanceOwnerPartyId, instanceGuid); - checkedAction = altinnTaskType; - } + string checkedAction = processNext?.Action ?? altinnTaskType; + authorized = await AuthorizeAction(checkedAction, org, app, instanceOwnerPartyId, instanceGuid); if (!authorized) { @@ -487,7 +482,7 @@ private async Task AuthorizeAction(string currentTaskType, string org, str actionType = currentTaskType; break; } - + _logger.LogInformation("About to authorize action {ActionType}", actionType); XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(org, app, HttpContext.User, actionType, instanceOwnerPartyId, instanceGuid, taskId); XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request); @@ -520,5 +515,12 @@ private ActionResult HandlePlatformHttpException(PlatformHttpException e, string return ExceptionResponse(e, defaultMessage); } } + + private static async Task DeserializeFromStream(Stream stream) + { + StreamReader reader = new StreamReader(stream); + string text = await reader.ReadToEndAsync(); + return JsonConvert.DeserializeObject(text); + } } } diff --git a/src/Altinn.App.Api/Models/ProcessNext.cs b/src/Altinn.App.Api/Models/ProcessNext.cs new file mode 100644 index 000000000..675e9f841 --- /dev/null +++ b/src/Altinn.App.Api/Models/ProcessNext.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Altinn.App.Api.Models; + +/// +/// Model for process next body +/// +public class ProcessNext +{ + /// + /// Action performed + /// + [JsonPropertyName("action")] + public string? Action { get; set; } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/App/FrontendFeatures.cs b/src/Altinn.App.Core/Internal/App/FrontendFeatures.cs index 399cdb9fa..f6d3826f9 100644 --- a/src/Altinn.App.Core/Internal/App/FrontendFeatures.cs +++ b/src/Altinn.App.Core/Internal/App/FrontendFeatures.cs @@ -13,6 +13,7 @@ public class FrontendFeatures : IFrontendFeatures public FrontendFeatures() { features.Add("footer", true); + features.Add("processActions", true); } /// From 711b21ea8c7c15b57bd14c80d082788734b573a2 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Tue, 9 May 2023 09:44:46 +0200 Subject: [PATCH 21/29] fix codeql warning --- src/Altinn.App.Api/Controllers/ProcessController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index b0ded3f96..39a698e00 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -518,7 +518,7 @@ private ActionResult HandlePlatformHttpException(PlatformHttpException e, string private static async Task DeserializeFromStream(Stream stream) { - StreamReader reader = new StreamReader(stream); + using StreamReader reader = new StreamReader(stream); string text = await reader.ReadToEndAsync(); return JsonConvert.DeserializeObject(text); } From e6dd6c55851cf06b79e1aeac6e4eb2213668e022 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Tue, 9 May 2023 10:40:50 +0200 Subject: [PATCH 22/29] add v8 as target of github workflows in addition to main --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/dotnet-test.yml | 4 ++-- .github/workflows/test-and-analyze-fork.yml | 2 +- .github/workflows/test-and-analyze.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e9357d43d..9128935e1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ "main" ] + branches: [ "main", "v8" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: [ "main", "v8" ] schedule: - cron: '37 20 * * 3' diff --git a/.github/workflows/dotnet-test.yml b/.github/workflows/dotnet-test.yml index 56849711f..4b89aa166 100644 --- a/.github/workflows/dotnet-test.yml +++ b/.github/workflows/dotnet-test.yml @@ -1,9 +1,9 @@ name: Build and Test on windows, macos and ubuntu on: push: - branches: [ main ] + branches: [ main, v8 ] pull_request: - branches: [ main ] + branches: [ main, v8 ] types: [opened, synchronize, reopened] workflow_dispatch: jobs: diff --git a/.github/workflows/test-and-analyze-fork.yml b/.github/workflows/test-and-analyze-fork.yml index 8c6b65433..d68ce6cc3 100644 --- a/.github/workflows/test-and-analyze-fork.yml +++ b/.github/workflows/test-and-analyze-fork.yml @@ -1,7 +1,7 @@ name: Code test and analysis (fork) on: pull_request: - branches: [ main ] + branches: [ main, v8 ] types: [opened, synchronize, reopened, ready_for_review] jobs: test: diff --git a/.github/workflows/test-and-analyze.yml b/.github/workflows/test-and-analyze.yml index 6d5714280..7dcd477f4 100644 --- a/.github/workflows/test-and-analyze.yml +++ b/.github/workflows/test-and-analyze.yml @@ -1,9 +1,9 @@ name: Code test and analysis on: push: - branches: [ main ] + branches: [ main, v8 ] pull_request: - branches: [ main ] + branches: [ main, v8 ] types: [opened, synchronize, reopened] workflow_dispatch: jobs: From ba8430f53c816c71711861f82158a888f7c69f3d Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Wed, 10 May 2023 08:02:09 +0200 Subject: [PATCH 23/29] Fix return type of all methods in ProcessController returning ProcessState --- .../Controllers/ProcessController.cs | 57 +++++++++++-------- .../Process/Elements/AppProcessState.cs | 7 ++- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index 39a698e00..f3173a059 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -80,22 +80,7 @@ public async Task> GetProcessState( try { Instance instance = await _instanceClient.GetInstance(app, org, instanceOwnerPartyId, instanceGuid); - AppProcessState appProcessState = new AppProcessState(instance.Process); - if (appProcessState.CurrentTask?.ElementId != null) - { - var flowElement = _processReader.GetFlowElement(appProcessState.CurrentTask.ElementId); - if (flowElement is ProcessTask processTask) - { - appProcessState.CurrentTask.Actions = new Dictionary(); - foreach (AltinnAction action in processTask.ExtensionElements?.AltinnProperties?.AltinnActions ?? new List()) - { - appProcessState.CurrentTask.Actions.Add(action.Id, await AuthorizeAction(action.Id, org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id)); - } - - appProcessState.CurrentTask.HasWriteAccess = await AuthorizeAction("write", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); - appProcessState.CurrentTask.HasReadAccess = await AuthorizeAction("read", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); - } - } + AppProcessState appProcessState = await ConvertAndAuthorizeActions(org, app, instanceOwnerPartyId, instanceGuid, instance.Process); return Ok(appProcessState); } @@ -124,7 +109,7 @@ public async Task> GetProcessState( [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [Authorize(Policy = "InstanceInstantiate")] - public async Task> StartProcess( + public async Task> StartProcess( [FromRoute] string org, [FromRoute] string app, [FromRoute] int instanceOwnerPartyId, @@ -149,8 +134,9 @@ public async Task> StartProcess( { return Conflict(result.ErrorMessage); } - - return Ok(result.ProcessStateChange?.NewProcessState); + + AppProcessState appProcessState = await ConvertAndAuthorizeActions(org, app, instanceOwnerPartyId, instanceGuid, result.ProcessStateChange?.NewProcessState); + return Ok(appProcessState); } catch (PlatformHttpException e) { @@ -252,7 +238,7 @@ private async Task CanTaskBeEnded(Instance instance, string currentElement [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> NextElement( + public async Task> NextElement( [FromRoute] string org, [FromRoute] string app, [FromRoute] int instanceOwnerPartyId, @@ -308,7 +294,9 @@ public async Task> NextElement( return Conflict(result.ErrorMessage); } - return Ok(result.ProcessStateChange?.NewProcessState); + AppProcessState appProcessState = await ConvertAndAuthorizeActions(org, app, instanceOwnerPartyId, instanceGuid, result.ProcessStateChange?.NewProcessState); + + return Ok(appProcessState); } catch (PlatformHttpException e) { @@ -333,7 +321,7 @@ public async Task> NextElement( [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task> CompleteProcess( + public async Task> CompleteProcess( [FromRoute] string org, [FromRoute] string app, [FromRoute] int instanceOwnerPartyId, @@ -418,7 +406,8 @@ public async Task> CompleteProcess( return StatusCode(500, $"More than {counter} iterations detected in process. Possible loop. Fix app process definition!"); } - return Ok(instance.Process); + AppProcessState appProcessState = await ConvertAndAuthorizeActions(org, app, instanceOwnerPartyId, instanceGuid, instance.Process); + return Ok(appProcessState); } /// @@ -446,6 +435,28 @@ public async Task GetProcessHistory( return ExceptionResponse(processException, $"Unable to find retrieve process history for instance {instanceOwnerPartyId}/{instanceGuid}. Exception: {processException}"); } } + + private async Task ConvertAndAuthorizeActions(string org, string app, int instanceOwnerPartyId, Guid instanceGuid, ProcessState? processState) + { + AppProcessState appProcessState = new AppProcessState(processState); + if (appProcessState.CurrentTask?.ElementId != null) + { + var flowElement = _processReader.GetFlowElement(appProcessState.CurrentTask.ElementId); + if (flowElement is ProcessTask processTask) + { + appProcessState.CurrentTask.Actions = new Dictionary(); + foreach (AltinnAction action in processTask.ExtensionElements?.AltinnProperties?.AltinnActions ?? new List()) + { + appProcessState.CurrentTask.Actions.Add(action.Id, await AuthorizeAction(action.Id, org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id)); + } + + appProcessState.CurrentTask.HasWriteAccess = await AuthorizeAction("write", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); + appProcessState.CurrentTask.HasReadAccess = await AuthorizeAction("read", org, app, instanceOwnerPartyId, instanceGuid, flowElement.Id); + } + } + + return appProcessState; + } private ActionResult ExceptionResponse(Exception exception, string message) { diff --git a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs index d3215b5a1..ecfc689c1 100644 --- a/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs +++ b/src/Altinn.App.Core/Internal/Process/Elements/AppProcessState.cs @@ -20,8 +20,13 @@ public AppProcessState() /// Constructor that takes a ProcessState object and copies the values. /// /// - public AppProcessState(ProcessState processState) + public AppProcessState(ProcessState? processState) { + if(processState == null) + { + return; + } + Started = processState.Started; StartEvent = processState.StartEvent; if (processState.CurrentTask != null) From 958be0655f56d10add9f1c66801f084508b0f29d Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Thu, 11 May 2023 14:23:41 +0200 Subject: [PATCH 24/29] Authorize action moved to AuthorizationClient TaskType is substituted with corresponding action earlier Resolvs #207 --- .../Controllers/ProcessController.cs | 57 +++++++------------ .../Authorization/AuthorizationClient.cs | 25 ++++++++ .../Interface/IAuthorization.cs | 12 ++++ .../Mocks/AuthorizationMock.cs | 14 +++-- 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/Altinn.App.Api/Controllers/ProcessController.cs b/src/Altinn.App.Api/Controllers/ProcessController.cs index f3173a059..6d6bdba3e 100644 --- a/src/Altinn.App.Api/Controllers/ProcessController.cs +++ b/src/Altinn.App.Api/Controllers/ProcessController.cs @@ -7,10 +7,8 @@ using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties; +using Altinn.App.Core.Models; using Altinn.App.Core.Models.Validation; -using Altinn.Authorization.ABAC.Xacml.JsonProfile; -using Altinn.Common.PEP.Helpers; -using Altinn.Common.PEP.Interfaces; using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -34,7 +32,7 @@ public class ProcessController : ControllerBase private readonly IInstance _instanceClient; private readonly IProcess _processService; private readonly IValidation _validationService; - private readonly IPDP _pdp; + private readonly IAuthorization _authorization; private readonly IProcessEngine _processEngine; private readonly IProcessReader _processReader; @@ -46,7 +44,7 @@ public ProcessController( IInstance instanceClient, IProcess processService, IValidation validationService, - IPDP pdp, + IAuthorization authorization, IProcessReader processReader, IProcessEngine processEngine) { @@ -54,7 +52,7 @@ public ProcessController( _instanceClient = instanceClient; _processService = processService; _validationService = validationService; - _pdp = pdp; + _authorization = authorization; _processReader = processReader; _processEngine = processEngine; } @@ -274,7 +272,7 @@ public async Task> NextElement( } bool authorized; - string checkedAction = processNext?.Action ?? altinnTaskType; + string checkedAction = EnsureActionNotTaskType(processNext?.Action ?? altinnTaskType); authorized = await AuthorizeAction(checkedAction, org, app, instanceOwnerPartyId, instanceGuid); if (!authorized) @@ -361,7 +359,7 @@ public async Task> CompleteProcess( int counter = 0; do { - string altinnTaskType = instance.Process.CurrentTask?.AltinnTaskType; + string altinnTaskType = EnsureActionNotTaskType(instance.Process.CurrentTask?.AltinnTaskType); bool authorized = await AuthorizeAction(altinnTaskType, org, app, instanceOwnerPartyId, instanceGuid); if (!authorized) @@ -476,35 +474,24 @@ private ActionResult ExceptionResponse(Exception exception, string message) return StatusCode(500, $"{message}"); } - private async Task AuthorizeAction(string currentTaskType, string org, string app, int instanceOwnerPartyId, Guid instanceGuid, string taskId = null) + private async Task AuthorizeAction(string action, string org, string app, int instanceOwnerPartyId, Guid instanceGuid, string taskId = null) { - string actionType; + return await _authorization.AuthorizeAction(new AppIdentifier(org, app), new InstanceIdentifier(instanceOwnerPartyId, instanceGuid), HttpContext.User, action, taskId); + } - switch (currentTaskType) + private static string EnsureActionNotTaskType(string actionOrTaskType) + { + switch (actionOrTaskType) { case "data": case "feedback": - actionType = "write"; - break; + return "write"; case "confirmation": - actionType = "confirm"; - break; + return "confirm"; default: - actionType = currentTaskType; - break; + // Not any known task type, so assume it is an action type + return actionOrTaskType; } - - _logger.LogInformation("About to authorize action {ActionType}", actionType); - XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(org, app, HttpContext.User, actionType, instanceOwnerPartyId, instanceGuid, taskId); - XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request); - if (response?.Response == null) - { - _logger.LogInformation($"// Process Controller // Authorization of moving process forward failed with request: {JsonConvert.SerializeObject(request)}."); - return false; - } - - bool authorized = DecisionHelper.ValidatePdpDecision(response.Response, HttpContext.User); - return authorized; } private ActionResult HandlePlatformHttpException(PlatformHttpException e, string defaultMessage) @@ -513,18 +500,18 @@ private ActionResult HandlePlatformHttpException(PlatformHttpException e, string { return Forbid(); } - else if (e.Response.StatusCode == HttpStatusCode.NotFound) + + if (e.Response.StatusCode == HttpStatusCode.NotFound) { return NotFound(); } - else if (e.Response.StatusCode == HttpStatusCode.Conflict) + + if (e.Response.StatusCode == HttpStatusCode.Conflict) { return Conflict(); } - else - { - return ExceptionResponse(e, defaultMessage); - } + + return ExceptionResponse(e, defaultMessage); } private static async Task DeserializeFromStream(Stream stream) diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs index 03d7a37a9..467ec7b2d 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs @@ -1,8 +1,13 @@ using System.Net.Http.Headers; +using System.Security.Claims; using Altinn.App.Core.Configuration; using Altinn.App.Core.Constants; using Altinn.App.Core.Extensions; using Altinn.App.Core.Interface; +using Altinn.App.Core.Models; +using Altinn.Authorization.ABAC.Xacml.JsonProfile; +using Altinn.Common.PEP.Helpers; +using Altinn.Common.PEP.Interfaces; using Altinn.Platform.Register.Models; using AltinnCore.Authentication.Utils; @@ -23,6 +28,7 @@ public class AuthorizationClient : IAuthorization private readonly IHttpContextAccessor _httpContextAccessor; private readonly AppSettings _settings; private readonly HttpClient _client; + private readonly IPDP _pdp; private readonly ILogger _logger; /// @@ -32,16 +38,19 @@ public class AuthorizationClient : IAuthorization /// the http context accessor. /// A Http client from the HttpClientFactory. /// The application settings. + /// /// the handler for logger service public AuthorizationClient( IOptions platformSettings, IHttpContextAccessor httpContextAccessor, HttpClient httpClient, IOptionsMonitor settings, + IPDP pdp, ILogger logger) { _httpContextAccessor = httpContextAccessor; _settings = settings.CurrentValue; + _pdp = pdp; _logger = logger; httpClient.BaseAddress = new Uri(platformSettings.Value.ApiAuthorizationEndpoint); httpClient.DefaultRequestHeaders.Add(General.SubscriptionKeyHeaderName, platformSettings.Value.SubscriptionKey); @@ -95,5 +104,21 @@ public AuthorizationClient( return result; } + + /// + public async Task AuthorizeAction(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier, ClaimsPrincipal user, string action, string? taskId = null) + { + _logger.LogInformation("About to authorize action {Action}", action); + XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(appIdentifier.Org, appIdentifier.App, user, action, instanceIdentifier.InstanceOwnerPartyId, instanceIdentifier.InstanceGuid, taskId); + XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request); + if (response?.Response == null) + { + _logger.LogInformation("Failed to get decision from pdp: {SerializeObject}", JsonConvert.SerializeObject(request)); + return false; + } + + bool authorized = DecisionHelper.ValidatePdpDecision(response.Response, user); + return authorized; + } } } diff --git a/src/Altinn.App.Core/Interface/IAuthorization.cs b/src/Altinn.App.Core/Interface/IAuthorization.cs index e53c1d459..64dd95d89 100644 --- a/src/Altinn.App.Core/Interface/IAuthorization.cs +++ b/src/Altinn.App.Core/Interface/IAuthorization.cs @@ -1,3 +1,4 @@ +using System.Security.Claims; using Altinn.App.Core.Models; using Altinn.Platform.Register.Models; @@ -22,5 +23,16 @@ public interface IAuthorization /// The party id. /// Boolean indicating whether or not the user can represent the selected party. Task ValidateSelectedParty(int userId, int partyId); + + /// + /// Check if the user is authorized to perform the given action on the given instance. + /// + /// + /// + /// + /// + /// + /// + Task AuthorizeAction(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier, ClaimsPrincipal user, string action, string? taskId = null); } } diff --git a/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs b/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs index d0c40b1cb..4cb2638aa 100644 --- a/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs +++ b/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs @@ -2,7 +2,9 @@ using Altinn.Platform.Register.Models; using System; using System.Collections.Generic; +using System.Security.Claims; using System.Threading.Tasks; +using Altinn.App.Core.Models; namespace Altinn.App.Api.Tests.Mocks { @@ -15,14 +17,14 @@ public class AuthorizationMock : IAuthorization public Task ValidateSelectedParty(int userId, int partyId) { - bool? isvalid = true; - - if (userId == 1) - { - isvalid = false; - } + bool? isvalid = userId != 1; return Task.FromResult(isvalid); } + + public async Task AuthorizeAction(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier, ClaimsPrincipal user, string action, string? taskId = null) + { + throw new NotImplementedException(); + } } } From aa71607de89e82400acd2c9bd674a29945f3ca2a Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 12 May 2023 12:38:50 +0200 Subject: [PATCH 25/29] Fixed some issues after review and added some more tests --- .../Controllers/InstancesController.cs | 5 + .../Authorization/AuthorizationClient.cs | 3 +- .../Clients/Storage/ProcessClient.cs | 19 -- src/Altinn.App.Core/Interface/IProcess.cs | 8 - .../Process/IProcessEventDispatcher.cs | 2 +- .../Internal/Process/ProcessEngine.cs | 6 +- .../Process/ProcessEventDispatcher.cs | 34 +++- .../Models/ProcessChangeResult.cs | 28 ++- .../Clients/ProcessClientTests.cs | 65 ------ .../Internal/Process/ProcessEngineTest.cs | 189 +++++++++++++++++- .../Process/ProcessEventDispatcherTests.cs | 183 +++++++++++++---- 11 files changed, 385 insertions(+), 157 deletions(-) delete mode 100644 test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index 80eabb044..cc710ec01 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -278,6 +278,11 @@ public async Task> Post( Dryrun = true }; var result = await _processEngine.StartProcess(processStartRequest); + if (!result.Success) + { + return Conflict(result.ErrorMessage); + } + change = result.ProcessStateChange; // create the instance diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs index 467ec7b2d..9d3146a16 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs @@ -108,12 +108,11 @@ public AuthorizationClient( /// public async Task AuthorizeAction(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier, ClaimsPrincipal user, string action, string? taskId = null) { - _logger.LogInformation("About to authorize action {Action}", action); XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(appIdentifier.Org, appIdentifier.App, user, action, instanceIdentifier.InstanceOwnerPartyId, instanceIdentifier.InstanceGuid, taskId); XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request); if (response?.Response == null) { - _logger.LogInformation("Failed to get decision from pdp: {SerializeObject}", JsonConvert.SerializeObject(request)); + _logger.LogWarning("Failed to get decision from pdp: {SerializeObject}", JsonConvert.SerializeObject(request)); return false; } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs index dccacf841..df905700f 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Storage/ProcessClient.cs @@ -20,7 +20,6 @@ public class ProcessClient : IProcess { private readonly AppSettings _appSettings; private readonly ILogger _logger; - private readonly IInstanceEvent _instanceEventClient; private readonly HttpClient _client; private readonly IHttpContextAccessor _httpContextAccessor; @@ -30,13 +29,11 @@ public class ProcessClient : IProcess public ProcessClient( IOptions platformSettings, IOptions appSettings, - IInstanceEvent instanceEventClient, ILogger logger, IHttpContextAccessor httpContextAccessor, HttpClient httpClient) { _appSettings = appSettings.Value; - _instanceEventClient = instanceEventClient; _httpContextAccessor = httpContextAccessor; _logger = logger; httpClient.BaseAddress = new Uri(platformSettings.Value.ApiStorageEndpoint); @@ -82,21 +79,5 @@ public async Task GetProcessHistory(string instanceGuid, str throw await PlatformHttpException.CreateAsync(response); } - - /// - public async Task DispatchProcessEventsToStorage(Instance instance, List? events) - { - string org = instance.Org; - string app = instance.AppId.Split("/")[1]; - - if (events != null) - { - foreach (InstanceEvent instanceEvent in events) - { - instanceEvent.InstanceId = instance.Id; - await _instanceEventClient.SaveInstanceEvent(instanceEvent, org, app); - } - } - } } } diff --git a/src/Altinn.App.Core/Interface/IProcess.cs b/src/Altinn.App.Core/Interface/IProcess.cs index 51a30629a..582fa4ef4 100644 --- a/src/Altinn.App.Core/Interface/IProcess.cs +++ b/src/Altinn.App.Core/Interface/IProcess.cs @@ -13,14 +13,6 @@ public interface IProcess /// the stream Stream GetProcessDefinition(); - /// - /// Dispatches process events to storage. - /// - /// the instance - /// process events - /// - public Task DispatchProcessEventsToStorage(Instance instance, List? events); - /// /// Gets the instance process events related to the instance matching the instance id. /// diff --git a/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs index b8aa8305d..119fedf29 100644 --- a/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs +++ b/src/Altinn.App.Core/Internal/Process/IProcessEventDispatcher.cs @@ -20,4 +20,4 @@ public interface IProcessEventDispatcher /// /// The instance to dispatch events for Task RegisterEventWithEventsComponent(Instance instance); -} \ No newline at end of file +} diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs index 104913388..eb8b7e022 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEngine.cs @@ -49,7 +49,7 @@ public async Task StartProcess(ProcessStartRequest processS { Success = false, ErrorMessage = "Process is already started. Use next.", - ErrorType = "Conflict" + ErrorType = ProcessErrorType.Conflict }; } @@ -60,7 +60,7 @@ public async Task StartProcess(ProcessStartRequest processS { Success = false, ErrorMessage = "No matching startevent", - ErrorType = "Conflict" + ErrorType = ProcessErrorType.Conflict }; } @@ -109,7 +109,7 @@ public async Task Next(ProcessNextRequest request) { Success = false, ErrorMessage = $"Instance does not have current task information!", - ErrorType = "Conflict" + ErrorType = ProcessErrorType.Conflict }; } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs index a42006d99..81d4c0b28 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessEventDispatcher.cs @@ -14,7 +14,7 @@ namespace Altinn.App.Core.Internal.Process; class ProcessEventDispatcher : IProcessEventDispatcher { private readonly IInstance _instanceService; - private readonly IProcess _processClient; + private readonly IInstanceEvent _instanceEventClient; private readonly ITaskEvents _taskEvents; private readonly IAppEvents _appEvents; private readonly IEvents _eventsService; @@ -23,7 +23,7 @@ class ProcessEventDispatcher : IProcessEventDispatcher public ProcessEventDispatcher( IInstance instanceService, - IProcess processClient, + IInstanceEvent instanceEventClient, ITaskEvents taskEvents, IAppEvents appEvents, IEvents eventsService, @@ -31,7 +31,7 @@ public ProcessEventDispatcher( ILogger logger) { _instanceService = instanceService; - _processClient = processClient; + _instanceEventClient = instanceEventClient; _taskEvents = taskEvents; _appEvents = appEvents; _eventsService = eventsService; @@ -46,7 +46,7 @@ public async Task UpdateProcessAndDispatchEvents(Instance instance, Di // need to update the instance process and then the instance in case appbase has changed it, e.g. endEvent sets status.archived Instance updatedInstance = await _instanceService.UpdateProcess(instance); - await _processClient.DispatchProcessEventsToStorage(updatedInstance, events); + await DispatchProcessEventsToStorage(updatedInstance, events); // remember to get the instance anew since AppBase can have updated a data element or stored something in the database. updatedInstance = await _instanceService.GetInstance(updatedInstance); @@ -76,6 +76,22 @@ public async Task RegisterEventWithEventsComponent(Instance instance) } } } + + + private async Task DispatchProcessEventsToStorage(Instance instance, List? events) + { + string org = instance.Org; + string app = instance.AppId.Split("/")[1]; + + if (events != null) + { + foreach (InstanceEvent instanceEvent in events) + { + instanceEvent.InstanceId = instance.Id; + await _instanceEventClient.SaveInstanceEvent(instanceEvent, org, app); + } + } + } /// /// Will for each process change trigger relevant Process Elements to perform the relevant change actions. @@ -86,12 +102,12 @@ private async Task HandleProcessChanges(Instance instance, List? { if (events != null) { - foreach (InstanceEvent processEvent in events) + foreach (InstanceEvent instanceEvent in events) { - if (Enum.TryParse(processEvent.EventType, true, out InstanceEventType eventType)) + if (Enum.TryParse(instanceEvent.EventType, true, out InstanceEventType eventType)) { - string? elementId = processEvent.ProcessInfo?.CurrentTask?.ElementId; - ITask task = GetProcessTask(processEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); + string? elementId = instanceEvent.ProcessInfo?.CurrentTask?.ElementId; + ITask task = GetProcessTask(instanceEvent.ProcessInfo?.CurrentTask?.AltinnTaskType); switch (eventType) { case InstanceEventType.process_StartEvent: @@ -107,7 +123,7 @@ private async Task HandleProcessChanges(Instance instance, List? await _instanceService.UpdateProcess(instance); break; case InstanceEventType.process_EndEvent: - await _appEvents.OnEndAppEvent(processEvent.ProcessInfo?.EndEvent, instance); + await _appEvents.OnEndAppEvent(instanceEvent.ProcessInfo?.EndEvent, instance); break; } } diff --git a/src/Altinn.App.Core/Models/ProcessChangeResult.cs b/src/Altinn.App.Core/Models/ProcessChangeResult.cs index f92237a29..68aa1736c 100644 --- a/src/Altinn.App.Core/Models/ProcessChangeResult.cs +++ b/src/Altinn.App.Core/Models/ProcessChangeResult.cs @@ -1,11 +1,37 @@ namespace Altinn.App.Core.Models { + /// + /// Class representing the result of a process change + /// public class ProcessChangeResult { + /// + /// Gets or sets a value indicating whether the process change was successful + /// public bool Success { get; set; } + /// + /// Gets or sets the error message if the process change was not successful + /// public string? ErrorMessage { get; set; } - public string? ErrorType { get; set; } + /// + /// Gets or sets the error type if the process change was not successful + /// + public ProcessErrorType? ErrorType { get; set; } + /// + /// Gets or sets the process state change if the process change was successful + /// public ProcessStateChange? ProcessStateChange { get; set; } } + + /// + /// Types of errors that can occur during a process change + /// + public enum ProcessErrorType + { + /// + /// The process change was not allowed due to the current state of the process + /// + Conflict + } } diff --git a/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs b/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs deleted file mode 100644 index b80d145fb..000000000 --- a/test/Altinn.App.Core.Tests/Infrastructure/Clients/ProcessClientTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Altinn.App.Core.Configuration; -using Altinn.App.Core.Infrastructure.Clients.Storage; -using Altinn.App.Core.Interface; -using Altinn.Platform.Storage.Interface.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using Xunit; - -namespace Altinn.App.Core.Tests.Infrastructure.Clients; - -public class ProcessClientTests: IDisposable -{ - private readonly Mock _instanceEventClientMock = new Mock(); - private readonly Mock _httpClientMock = new Mock(); - - [Fact] - public async Task DispatchProcessEventsToStorage_does_not_save_events_if_list_is_null() - { - IProcess processClient = GetProcessClient(); - await processClient.DispatchProcessEventsToStorage( - new Instance() - { - Org = "ttd", - AppId = "1337/aaaa-bbbbb-dddd-eeee" - }, - null); - } - - [Fact] - public async Task DispatchProcessEventsToStorage_sends_events_to_instanceEventClient() - { - IProcess processClient = GetProcessClient(); - var instanceEvent = new InstanceEvent(); - await processClient.DispatchProcessEventsToStorage( - new Instance() - { - Org = "ttd", - AppId = "ttd/demo-app" - }, - new List() - { - instanceEvent - }); - _instanceEventClientMock.Verify(i => i.SaveInstanceEvent(instanceEvent, "ttd", "demo-app")); - } - - private ProcessClient GetProcessClient() - { - var platformSettings = Options.Create(new PlatformSettings()); - var appSettings = Options.Create(new AppSettings()); - var logger = new NullLogger(); - var httpContextAccessor = new Mock(); - - return new ProcessClient(platformSettings, appSettings, _instanceEventClientMock.Object, logger, httpContextAccessor.Object, _httpClientMock.Object); - } - - public void Dispose() - { - _instanceEventClientMock.VerifyNoOtherCalls(); - _httpClientMock.VerifyNoOtherCalls(); - } -} diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs index 022eeb762..24d50609f 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs @@ -41,7 +41,7 @@ public async Task StartProcess_returns_unsuccessful_when_process_already_started ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); result.Success.Should().BeFalse(); result.ErrorMessage.Should().Be("Process is already started. Use next."); - result.ErrorType.Should().Be("Conflict"); + result.ErrorType.Should().Be(ProcessErrorType.Conflict); } [Fact] @@ -56,7 +56,7 @@ public async Task StartProcess_returns_unsuccessful_when_no_matching_startevent_ _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); result.Success.Should().BeFalse(); result.ErrorMessage.Should().Be("No matching startevent"); - result.ErrorType.Should().Be("Conflict"); + result.ErrorType.Should().Be(ProcessErrorType.Conflict); } [Fact] @@ -189,6 +189,110 @@ public async Task StartProcess_starts_process_and_moves_to_first_task() result.Success.Should().BeTrue(); } + [Fact] + public async Task StartProcess_starts_process_and_moves_to_first_task_with_prefill() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + } + }; + ClaimsPrincipal user = new(new ClaimsIdentity(new List() + { + new(AltinnCoreClaimTypes.UserId, "1337"), + new(AltinnCoreClaimTypes.AuthenticationLevel, "2"), + new(AltinnCoreClaimTypes.Org, "tdd"), + })); + var prefill = new Dictionary() { { "test", "test" } }; + ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, User = user, Prefill = prefill }; + ProcessChangeResult result = await processEngine.StartProcess(processStartRequest); + _processReaderMock.Verify(r => r.GetStartEventIds(), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("StartEvent_1"), Times.Once); + _processReaderMock.Verify(r => r.IsEndEvent("Task_1"), Times.Once); + _processReaderMock.Verify(r => r.IsProcessTask("Task_1"), Times.Once); + _processNavigatorMock.Verify(n => n.GetNextTask(It.IsAny(), "StartEvent_1", null), Times.Once); + var expectedInstance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Name = "Utfylling" + }, + StartEvent = "StartEvent_1" + } + }; + var expectedInstanceEvents = new List() + { + new() + { + EventType = InstanceEventType.process_StartEvent.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "StartEvent_1", + Flow = 1, + Validated = new() + { + CanCompleteTask = false + } + } + } + }, + + new() + { + EventType = InstanceEventType.process_StartTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2, + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Name = "Utfylling", + AltinnTaskType = "data", + Flow = 2, + Validated = new() + { + CanCompleteTask = false + } + } + } + } + }; + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(expectedInstance, i)), + prefill, + It.Is>(l => CompareInstanceEvents(l, expectedInstanceEvents)))); + result.Success.Should().BeTrue(); + } + [Fact] public async Task Next_returns_unsuccessful_when_process_null() { @@ -198,7 +302,7 @@ public async Task Next_returns_unsuccessful_when_process_null() ProcessChangeResult result = await processEngine.Next(processNextRequest); result.Success.Should().BeFalse(); result.ErrorMessage.Should().Be("Instance does not have current task information!"); - result.ErrorType.Should().Be("Conflict"); + result.ErrorType.Should().Be(ProcessErrorType.Conflict); } [Fact] @@ -210,7 +314,7 @@ public async Task Next_returns_unsuccessful_when_process_currenttask_null() ProcessChangeResult result = await processEngine.Next(processNextRequest); result.Success.Should().BeFalse(); result.ErrorMessage.Should().Be("Instance does not have current task information!"); - result.ErrorType.Should().Be("Conflict"); + result.ErrorType.Should().Be(ProcessErrorType.Conflict); } [Fact] @@ -335,7 +439,7 @@ public async Task Next_moves_instance_to_next_task_and_produces_instanceevents() OldProcessState = originalProcessState }); } - + [Fact] public async Task Next_moves_instance_to_next_task_and_produces_abandon_instanceevent_when_action_reject() { @@ -458,7 +562,7 @@ public async Task Next_moves_instance_to_next_task_and_produces_abandon_instance OldProcessState = originalProcessState }); } - + [Fact] public async Task Next_moves_instance_to_end_event_and_ends_proces() { @@ -588,6 +692,75 @@ public async Task Next_moves_instance_to_end_event_and_ends_proces() }); } + [Fact] + public async Task UpdateInstanceAndRerunEvents_sends_instance_and_events_to_eventdispatcher() + { + IProcessEngine processEngine = GetProcessEngine(); + Instance instance = new Instance() + { + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_1", + Flow = 3, + AltinnTaskType = "confirmation", + Validated = new() + { + CanCompleteTask = true + } + } + } + }; + Dictionary prefill = new Dictionary() + { + { "test", "test" } + }; + List events = new List() + { + new() + { + EventType = InstanceEventType.process_AbandonTask.ToString(), + InstanceOwnerPartyId = "1337", + User = new() + { + UserId = 1337, + OrgId = "tdd", + AuthenticationLevel = 2 + }, + ProcessInfo = new() + { + StartEvent = "StartEvent_1", + CurrentTask = new() + { + ElementId = "Task_1", + Flow = 2, + AltinnTaskType = "data", + Validated = new() + { + CanCompleteTask = true + } + } + } + } + }; + ProcessStartRequest processStartRequest = new ProcessStartRequest() + { + Instance = instance, + Prefill = prefill, + }; + Instance result = await processEngine.UpdateInstanceAndRerunEvents(processStartRequest, events); + _processEventDispatcherMock.Verify(d => d.UpdateProcessAndDispatchEvents( + It.Is(i => CompareInstance(instance, i)), + prefill, + It.Is>(l => CompareInstanceEvents(events, l)))); + } + private IProcessEngine GetProcessEngine(Mock? processReaderMock = null, Instance? updatedInstance = null) { if (processReaderMock == null) @@ -684,7 +857,7 @@ private static bool CompareInstance(Instance expected, Instance actual) { expected.Process.CurrentTask.Started = actual.Process.CurrentTask.Started; } - + return JsonCompare(expected, actual); } @@ -695,7 +868,7 @@ private static bool CompareInstanceEvents(List expected, List(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -26,7 +26,7 @@ public async Task UpdateProcessAndDispatchEvents_StartEvent_instance_updated_and var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -34,11 +34,15 @@ public async Task UpdateProcessAndDispatchEvents_StartEvent_instance_updated_and logger); Instance instance = new Instance() { - Id = Guid.NewGuid().ToString() + Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", }; Instance updateInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -50,6 +54,8 @@ public async Task UpdateProcessAndDispatchEvents_StartEvent_instance_updated_and Instance getInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -86,9 +92,9 @@ public async Task UpdateProcessAndDispatchEvents_StartEvent_instance_updated_and result.Should().Be(getInstanceResponse); instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); - processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceEvent.Verify(p => p.SaveInstanceEvent(events[0], instance.Org, "test-app"), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -99,7 +105,7 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_instance_updated_and_ { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -107,7 +113,7 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_instance_updated_and_ var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -115,11 +121,15 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_instance_updated_and_ logger); Instance instance = new Instance() { - Id = Guid.NewGuid().ToString() + Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", }; Instance updateInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -166,9 +176,9 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_instance_updated_and_ result.Should().Be(getInstanceResponse); instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); - processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceEvent.Verify(p => p.SaveInstanceEvent(events[0], instance.Org, "test-app"), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -179,7 +189,7 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -187,7 +197,7 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -196,10 +206,14 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated Instance instance = new Instance() { Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", }; Instance updateInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -211,6 +225,8 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated Instance getInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -249,9 +265,9 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated taskEvents.Verify(t => t.OnStartProcessTask("Task_1", instance, prefill), Times.Once); instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); - processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceEvent.Verify(p => p.SaveInstanceEvent(events[0], instance.Org, "test-app"), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -262,7 +278,7 @@ public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_u { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -270,7 +286,7 @@ public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_u var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -279,10 +295,14 @@ public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_u Instance instance = new Instance() { Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", }; Instance updateInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -294,6 +314,8 @@ public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_u Instance getInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -332,9 +354,9 @@ public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_u taskEvents.Verify(t => t.OnEndProcessTask("Task_2", instance), Times.Once); instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); - processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceEvent.Verify(p => p.SaveInstanceEvent(events[0], instance.Org, "test-app"), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -345,7 +367,7 @@ public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_u { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -353,7 +375,7 @@ public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_u var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -362,10 +384,14 @@ public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_u Instance instance = new Instance() { Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", }; Instance updateInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -377,6 +403,8 @@ public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_u Instance getInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -415,9 +443,9 @@ public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_u taskEvents.Verify(t => t.OnAbandonProcessTask("Task_2", instance), Times.Once); instanceService.Verify(i => i.UpdateProcess(instance), Times.Exactly(2)); instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); - processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceEvent.Verify(p => p.SaveInstanceEvent(events[0], instance.Org, "test-app"), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -428,7 +456,7 @@ public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_ { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -436,7 +464,7 @@ public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_ var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -445,10 +473,14 @@ public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_ Instance instance = new Instance() { Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", }; Instance updateInstanceResponse = new Instance() { Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", Process = new ProcessState() { CurrentTask = new() @@ -499,9 +531,78 @@ public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_ appEvents.Verify(a => a.OnEndAppEvent("EndEvent", instance), Times.Once); instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); - processClient.Verify(p => p.DispatchProcessEventsToStorage(updateInstanceResponse, events), Times.Once); + instanceEvent.Verify(p => p.SaveInstanceEvent(events[0], instance.Org, "test-app"), Times.Once); + instanceService.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); + taskEvents.VerifyNoOtherCalls(); + appEvents.VerifyNoOtherCalls(); + eventsService.VerifyNoOtherCalls(); + } + + [Fact] + public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_updated_and_dispatches_no_events_when_events_null() + { + // Arrange + var instanceService = new Mock(); + var instanceEvent = new Mock(); + var taskEvents = new Mock(); + var appEvents = new Mock(); + var eventsService = new Mock(); + var appSettings = Options.Create(new AppSettings()); + var logger = new NullLogger(); + IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( + instanceService.Object, + instanceEvent.Object, + taskEvents.Object, + appEvents.Object, + eventsService.Object, + appSettings, + logger); + Instance instance = new Instance() + { + Id = Guid.NewGuid().ToString(), + Org = "ttd", + AppId = "ttd/test-app", + }; + Instance updateInstanceResponse = new Instance() + { + Id = instance.Id, + Org = "ttd", + AppId = "ttd/test-app", + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + } + } + }; + Instance getInstanceResponse = new Instance() + { + Id = instance.Id, + Process = new ProcessState() + { + CurrentTask = new() + { + ElementId = "Task_2", + Flow = 3 + } + } + }; + List events = null; + instanceService.Setup(i => i.UpdateProcess(instance)).ReturnsAsync(updateInstanceResponse); + instanceService.Setup(i => i.GetInstance(updateInstanceResponse)).ReturnsAsync(getInstanceResponse); + Dictionary prefill = new Dictionary(); + + // Act + var result = await dispatcher.UpdateProcessAndDispatchEvents(instance, prefill, events); + + // Assert + result.Should().Be(getInstanceResponse); + instanceService.Verify(i => i.UpdateProcess(instance), Times.Once); + instanceService.Verify(i => i.GetInstance(updateInstanceResponse), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -512,7 +613,7 @@ public async Task RegisterEventWithEventsComponent_sends_movedTo_event_to_events { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -523,7 +624,7 @@ public async Task RegisterEventWithEventsComponent_sends_movedTo_event_to_events var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -547,7 +648,7 @@ public async Task RegisterEventWithEventsComponent_sends_movedTo_event_to_events // Assert eventsService.Verify(e => e.AddEvent("app.instance.process.movedTo.Task_1", instance), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -558,7 +659,7 @@ public async Task RegisterEventWithEventsComponent_sends_complete_event_to_event { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -569,7 +670,7 @@ public async Task RegisterEventWithEventsComponent_sends_complete_event_to_event var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -591,7 +692,7 @@ public async Task RegisterEventWithEventsComponent_sends_complete_event_to_event // Assert eventsService.Verify(e => e.AddEvent("app.instance.process.completed", instance), Times.Once); instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -602,7 +703,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_process_ { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -613,7 +714,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_process_ var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -630,7 +731,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_process_ // Assert instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -641,7 +742,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_current_ { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -652,7 +753,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_current_ var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -669,7 +770,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_current_ // Assert instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); @@ -680,7 +781,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_register { // Arrange var instanceService = new Mock(); - var processClient = new Mock(); + var instanceEvent = new Mock(); var taskEvents = new Mock(); var appEvents = new Mock(); var eventsService = new Mock(); @@ -691,7 +792,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_register var logger = new NullLogger(); IProcessEventDispatcher dispatcher = new ProcessEventDispatcher( instanceService.Object, - processClient.Object, + instanceEvent.Object, taskEvents.Object, appEvents.Object, eventsService.Object, @@ -714,7 +815,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_register // Assert instanceService.VerifyNoOtherCalls(); - processClient.VerifyNoOtherCalls(); + instanceEvent.VerifyNoOtherCalls(); taskEvents.VerifyNoOtherCalls(); appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); From 86975f16a8521c19ddfe4b99285d774907cf6147 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Fri, 12 May 2023 12:52:03 +0200 Subject: [PATCH 26/29] fix codeQL warning and improve test --- .../Internal/Process/ProcessEngineTest.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs index 24d50609f..30f4cf6a6 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEngineTest.cs @@ -695,7 +695,6 @@ public async Task Next_moves_instance_to_end_event_and_ends_proces() [Fact] public async Task UpdateInstanceAndRerunEvents_sends_instance_and_events_to_eventdispatcher() { - IProcessEngine processEngine = GetProcessEngine(); Instance instance = new Instance() { InstanceOwner = new InstanceOwner() @@ -717,6 +716,28 @@ public async Task UpdateInstanceAndRerunEvents_sends_instance_and_events_to_even } } }; + Instance updatedInstance = new Instance() + { + Org = "ttd", + InstanceOwner = new InstanceOwner() + { + PartyId = "1337" + }, + Process = new ProcessState() + { + StartEvent = "StartEvent_1", + CurrentTask = new ProcessElementInfo() + { + ElementId = "Task_1", + Flow = 3, + AltinnTaskType = "confirmation", + Validated = new() + { + CanCompleteTask = true + } + } + } + }; Dictionary prefill = new Dictionary() { { "test", "test" } @@ -749,6 +770,7 @@ public async Task UpdateInstanceAndRerunEvents_sends_instance_and_events_to_even } } }; + IProcessEngine processEngine = GetProcessEngine(null, updatedInstance); ProcessStartRequest processStartRequest = new ProcessStartRequest() { Instance = instance, @@ -759,6 +781,7 @@ public async Task UpdateInstanceAndRerunEvents_sends_instance_and_events_to_even It.Is(i => CompareInstance(instance, i)), prefill, It.Is>(l => CompareInstanceEvents(events, l)))); + result.Should().Be(updatedInstance); } private IProcessEngine GetProcessEngine(Mock? processReaderMock = null, Instance? updatedInstance = null) From 81761587a17859c771e309f8d6d76c3068a3f085 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 15 May 2023 08:05:39 +0200 Subject: [PATCH 27/29] Fix some code smells --- .../InstanceEventExtensionsTests.cs | 8 +- .../Process/ProcessEventDispatcherTests.cs | 20 +- .../Internal/Process/ProcessNavigatorTests.cs | 2 +- .../Internal/Process/ProcessReaderTests.cs | 203 ------------------ 4 files changed, 15 insertions(+), 218 deletions(-) diff --git a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs index 09abfa59b..08640c8d4 100644 --- a/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs +++ b/test/Altinn.App.Core.Tests/Extensions/InstanceEventExtensionsTests.cs @@ -54,7 +54,7 @@ public void CopyValues_returns_copy_of_instance_event() copy.User.Should().NotBeSameAs(original.User); copy.Should().BeEquivalentTo(original); } - + [Fact] public void CopyValues_returns_copy_of_instance_event_Validated_null() { @@ -138,8 +138,8 @@ public void CopyValues_returns_copy_of_instance_event_Validated_null() copy.User.Should().NotBeSameAs(original.User); copy.Should().BeEquivalentTo(expected); } - - [Fact] + + [Fact] public void CopyValues_returns_copy_of_instance_event_CurrentTask_null() { Guid id = Guid.NewGuid(); @@ -212,4 +212,4 @@ public void CopyValues_returns_copy_of_instance_event_CurrentTask_null() copy.User.Should().NotBeSameAs(original.User); copy.Should().BeEquivalentTo(expected); } -} \ No newline at end of file +} diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs index 1ebc7182f..db1856666 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessEventDispatcherTests.cs @@ -99,7 +99,7 @@ public async Task UpdateProcessAndDispatchEvents_StartEvent_instance_updated_and appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task UpdateProcessAndDispatchEvents_StartTask_instance_updated_and_events_sent_to_storage_nothing_sent_to_ITask_when_tasktype_missing() { @@ -272,7 +272,7 @@ public async Task UpdateProcessAndDispatchEvents_StartTask_data_instance_updated appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_updated_and_events_sent_to_storage_and_trigger_ITask() { @@ -361,7 +361,7 @@ public async Task UpdateProcessAndDispatchEvents_EndTask_confirmation_instance_u appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_updated_and_events_sent_to_storage_and_trigger_ITask() { @@ -450,7 +450,7 @@ public async Task UpdateProcessAndDispatchEvents_AbandonTask_feedback_instance_u appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_updated_and_events_sent_to_storage_and_trigger_ITask() { @@ -538,8 +538,8 @@ public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_ appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - - [Fact] + + [Fact] public async Task UpdateProcessAndDispatchEvents_EndEvent_confirmation_instance_updated_and_dispatches_no_events_when_events_null() { // Arrange @@ -653,7 +653,7 @@ public async Task RegisterEventWithEventsComponent_sends_movedTo_event_to_events appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task RegisterEventWithEventsComponent_sends_complete_event_to_events_system_when_currentTask_null_and_endevent_set() { @@ -697,7 +697,7 @@ public async Task RegisterEventWithEventsComponent_sends_complete_event_to_event appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task RegisterEventWithEventsComponent_sends_no_events_when_process_is_null() { @@ -736,7 +736,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_process_ appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task RegisterEventWithEventsComponent_sends_no_events_when_current_and_endevent_is_null() { @@ -775,7 +775,7 @@ public async Task RegisterEventWithEventsComponent_sends_no_events_when_current_ appEvents.VerifyNoOtherCalls(); eventsService.VerifyNoOtherCalls(); } - + [Fact] public async Task RegisterEventWithEventsComponent_sends_no_events_when_registereventswitheventscomponent_false() { diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs index e8540c303..22fdc9519 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs @@ -58,7 +58,7 @@ public async void GetNextTask_returns_default_if_no_filtering_is_implemented_and }, new() { - Id= "reject" + Id = "reject" } } } diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs index d532e99c7..e49a3d3ca 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessReaderTests.cs @@ -217,113 +217,6 @@ public void GetNextElement_throws_exception_if_step_not_found() pr.Invoking(p => p.GetNextElements(currentElement)).Should().Throw(); } - // [Fact] - // public void GetElementInfo_returns_correct_info_for_old_ProcessTask() - // { - // var bpmnfile = "simple-linear.bpmn"; - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "Task1", - // Name = "Utfylling", - // AltinnTaskType = "data", - // ElementType = "Task" - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_new_ProcessTask() - // { - // var bpmnfile = "simple-linear-new.bpmn"; - // var currentElement = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "Task1", - // Name = "Utfylling", - // AltinnTaskType = "data", - // ElementType = "Task", - // AltinnTaskActions = new List() - // { - // "submit" - // } - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_ProcessTask_prefers_new() - // { - // var bpmnfile = "simple-linear-both.bpmn"; - // var currentElement = "Task2"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "Task2", - // Name = "Bekreft skjemadata", - // AltinnTaskType = "confirmation2", - // ElementType = "Task", - // AltinnTaskActions = new List() - // { - // "confirm", - // "reject" - // } - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_StartEvent() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "StartEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "StartEvent", - // Name = null!, - // AltinnTaskType = null!, - // ElementType = "StartEvent" - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_correct_info_for_EndEvent() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "EndEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeEquivalentTo(new ElementInfo() - // { - // Id = "EndEvent", - // Name = null!, - // AltinnTaskType = null!, - // ElementType = "EndEvent" - // }); - // } - // - // [Fact] - // public void GetElementInfo_returns_null_for_ExclusiveGateway() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Gateway1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetElementInfo(currentElement); - // actual.Should().BeNull(); - // } - // - // [Fact] - // public void GetElementInfo_throws_argument_null_expcetion_when_elementName_is_null() - // { - // var bpmnfile = "simple-linear.bpmn"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // pr.Invoking(p => p.GetElementInfo(null!)).Should().Throw(); - // } - [Fact] public void GetOutgoingSequenceFlows_returns_empty_list_if_input_is_null() { @@ -384,102 +277,6 @@ public void GetOutgoingSequenceFlows_returns_empty_list_when_no_outgoing() outgoingFLows.Should().BeEmpty(); } - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_StartEvent_and_Task1() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "StartEvent"; - // var nextElementId = "Task1"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow1"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_Task2() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "Task2"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow2", "Flow3"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "EndEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_all_sequenceflows_between_Task1_and_EndEvent_complex() - // { - // var bpmnfile = "simple-gateway-with-join-gateway.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "EndEvent"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEquivalentTo("Flow2", "Flow4", "Flow6"); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_unknown_target() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // var currentElement = "Task1"; - // var nextElementId = "Foobar"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_current_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // string? currentElement = null; - // var nextElementId = "Foobar"; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_next_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // string currentElement = "Task1"; - // string? nextElementId = null; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - // - // [Fact] - // public void GetSequenceFlowsBetween_returns_empty_list_when_current_and_next_is_null() - // { - // var bpmnfile = "simple-gateway-default.bpmn"; - // string? currentElement = null; - // string? nextElementId = null; - // ProcessReader pr = ProcessTestUtils.SetupProcessReader(bpmnfile); - // var actual = pr.GetSequenceFlowsBetween(currentElement, nextElementId); - // var returnedIds = actual.Select(s => s.Id).ToList(); - // returnedIds.Should().BeEmpty(); - // } - [Fact] public void Constructor_Fails_if_invalid_bpmn() { From 0cf44866cd578a0875d3cb1f0f1eeaae3d97a5d8 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 15 May 2023 14:09:35 +0200 Subject: [PATCH 28/29] Update src/Altinn.App.Api/Controllers/InstancesController.cs Co-authored-by: Ronny Birkeli --- src/Altinn.App.Api/Controllers/InstancesController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index cc710ec01..93b506f96 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -299,7 +299,6 @@ public async Task> Post( // get the updated instance instance = await _instanceClient.GetInstance(app, org, int.Parse(instance.InstanceOwner.PartyId), Guid.Parse(instance.Id.Split("/")[1])); - // notify app and store events var request = new ProcessStartRequest() { From 9af5185dfae85edc83d7e2cd970fdff8b6bd2ee4 Mon Sep 17 00:00:00 2001 From: Vemund Gaukstad Date: Mon, 15 May 2023 14:25:08 +0200 Subject: [PATCH 29/29] fix build error --- src/Altinn.App.Api/Controllers/InstancesController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index 93b506f96..2de5fffc0 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -299,6 +299,7 @@ public async Task> Post( // get the updated instance instance = await _instanceClient.GetInstance(app, org, int.Parse(instance.InstanceOwner.PartyId), Guid.Parse(instance.Id.Split("/")[1])); + // notify app and store events var request = new ProcessStartRequest() {