From f7b91e0a6627169a56ad63823557d2a137640d96 Mon Sep 17 00:00:00 2001 From: Ivar Nesje Date: Mon, 19 Aug 2024 14:17:20 +0200 Subject: [PATCH] Fix tests and cleanup --- .../Extensions/ServiceCollectionExtensions.cs | 4 +- .../Features/IProcessExclusiveGateway.cs | 2 + src/Altinn.App.Core/Features/IValidator.cs | 11 +- .../Default/DataAnnotationValidator.cs | 2 +- .../Default/DefaultDataElementValidator.cs | 2 +- .../Default/DefaultTaskValidator.cs | 2 +- .../Validation/Default/ExpressionValidator.cs | 51 +++++++-- ...gacyIInstanceValidatorFormDataValidator.cs | 4 +- .../LegacyIInstanceValidatorTaskValidator.cs | 4 +- .../Validation/Default/RequiredValidator.cs | 30 ++--- .../Wrappers/DataElementValidatorWrapper.cs | 4 +- .../Wrappers/FormDataValidatorWrapper.cs | 4 +- .../Wrappers/TaskValidatorWrapper.cs | 4 +- src/Altinn.App.Core/Helpers/ObjectUtils.cs | 13 ++- .../Internal/Data/CachedFormDataAccessor.cs | 14 ++- .../ILayoutEvaluatorStateInitializer.cs | 2 + .../LayoutEvaluatorStateInitializer.cs | 11 +- .../Process/ExpressionsExclusiveGateway.cs | 9 +- .../Internal/Process/ProcessNavigator.cs | 31 +++++- .../Common/ProcessTaskFinalizer.cs | 105 ++++++++++++------ .../Internal/Validation/ValidationService.cs | 4 +- .../Default/ExpressionValidatorTests.cs | 34 ++++-- .../Default/LegacyIValidationFormDataTests.cs | 4 +- .../Validators/ValidationServiceTests.cs | 5 - .../Internal/Patch/PatchServiceTests.cs | 14 +-- .../ExpressionsExclusiveGatewayTests.cs | 62 ++++++----- .../Internal/Process/ProcessNavigatorTests.cs | 48 +++++--- .../StubGatewayFilters/DataValuesFilter.cs | 1 + .../FullTests/LayoutTestUtils.cs | 10 +- 29 files changed, 313 insertions(+), 178 deletions(-) diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 4d0e3dd2e..021949e96 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -209,12 +209,12 @@ private static void AddValidationServices(IServiceCollection services, IConfigur services.AddScoped(); if (configuration.GetSection("AppSettings").Get()?.RequiredValidation == true) { - services.AddTransient(); + services.AddTransient(); } if (configuration.GetSection("AppSettings").Get()?.ExpressionValidation == true) { - services.AddTransient(); + services.AddTransient(); } services.AddTransient(); services.AddTransient(); diff --git a/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs b/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs index e1ad83414..d3d30bfde 100644 --- a/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs +++ b/src/Altinn.App.Core/Features/IProcessExclusiveGateway.cs @@ -19,11 +19,13 @@ public interface IProcessExclusiveGateway /// /// Complete list of defined flows out of gateway /// Instance where process is about to move next + /// Cached accessor for instance.Data /// Information connected with the current gateway under evaluation /// List of possible SequenceFlows to choose out of the gateway public Task> FilterAsync( List outgoingFlows, Instance instance, + IInstanceDataAccessor dataAccessor, ProcessGatewayInformation processGatewayInformation ); } diff --git a/src/Altinn.App.Core/Features/IValidator.cs b/src/Altinn.App.Core/Features/IValidator.cs index 17daffdf3..68f613b06 100644 --- a/src/Altinn.App.Core/Features/IValidator.cs +++ b/src/Altinn.App.Core/Features/IValidator.cs @@ -23,15 +23,15 @@ public interface IValidator /// /// /// The instance to validate + /// Use this to access data from other data elements /// The current task. /// Language for messages, if the messages are too dynamic for the translation system - /// Use this to access data from other data elements /// public Task> Validate( Instance instance, + IInstanceDataAccessor instanceDataAccessor, string taskId, - string? language, - IInstanceDataAccessor instanceDataAccessor + string? language ); /// @@ -77,6 +77,11 @@ public class DataElementChange /// public interface IInstanceDataAccessor { + /// + /// The instance that the accessor can access data for. + /// + Instance Instance { get; } + /// /// Get the actual data represented in the data element. /// diff --git a/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs index 277a18e1b..3461bd88a 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs @@ -14,7 +14,7 @@ namespace Altinn.App.Core.Features.Validation.Default; /// /// Runs validation on the data object. /// -public class DataAnnotationValidator : IFormDataValidator +public class DataAnnotationValidator : IFormDataValidator // TODO: This should be IValidator { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IObjectModelValidator _objectModelValidator; diff --git a/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs index 34c7f34a2..6ef93a865 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs @@ -7,7 +7,7 @@ namespace Altinn.App.Core.Features.Validation.Default; /// /// Default validations that run on all data elements to validate metadata and file scan results. /// -public class DefaultDataElementValidator : IDataElementValidator +public class DefaultDataElementValidator : IDataElementValidator //TODO: This should implemnt IValidator { /// /// Run validations on all data elements diff --git a/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs index 0d31dd24e..b0c220daa 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs @@ -7,7 +7,7 @@ namespace Altinn.App.Core.Features.Validation.Default; /// /// Implement the default validation of DataElements based on the metadata in appMetadata /// -public class DefaultTaskValidator : ITaskValidator +public class DefaultTaskValidator : ITaskValidator //TODO: Implement IValidator { private readonly IAppMetadata _appMetadata; diff --git a/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs index 1f72203c9..e0c091bc7 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs @@ -13,7 +13,7 @@ namespace Altinn.App.Core.Features.Validation.Default; /// /// Validates form data against expression validations /// -public class ExpressionValidator : IFormDataValidator +public class ExpressionValidator : IValidator { private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { ReadCommentHandling = JsonCommentHandling.Skip, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; @@ -21,6 +21,7 @@ public class ExpressionValidator : IFormDataValidator private readonly ILogger _logger; private readonly IAppResources _appResourceService; private readonly ILayoutEvaluatorStateInitializer _layoutEvaluatorStateInitializer; + private readonly IAppMetadata _appMetadata; /// /// Constructor for the expression validator @@ -28,16 +29,18 @@ public class ExpressionValidator : IFormDataValidator public ExpressionValidator( ILogger logger, IAppResources appResourceService, - ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer + ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer, + IAppMetadata appMetadata ) { _logger = logger; _appResourceService = appResourceService; _layoutEvaluatorStateInitializer = layoutEvaluatorStateInitializer; + _appMetadata = appMetadata; } /// - public string DataType => "*"; + public string TaskId => "*"; /// /// This validator has the code "Expression" and this is known by the frontend, who may request this validator to not run for incremental validation. @@ -47,19 +50,48 @@ ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer /// /// We don't have an efficient way to figure out if changes to the model results in different validations, and frontend ignores this anyway /// - public bool HasRelevantChanges(object current, object previous) => true; + public Task HasRelevantChanges( + Instance instance, + string taskId, + List changes, + IInstanceDataAccessor instanceDataAccessor + ) => Task.FromResult(true); /// - public async Task> ValidateFormData( + public async Task> Validate( + Instance instance, + IInstanceDataAccessor instanceDataAccessor, + string taskId, + string? language + ) + { + var dataTypes = (await _appMetadata.GetApplicationMetadata()).DataTypes; + var formDataElementsForTask = instance + .Data.Where(d => + { + var dataType = dataTypes.Find(dt => dt.Id == d.DataType); + return dataType != null && dataType.TaskId == taskId; + }) + .ToList(); + var validationIssues = new List(); + foreach (var dataElement in formDataElementsForTask) + { + var data = instanceDataAccessor.Get(dataElement); + var issues = await ValidateFormData(instance, dataElement, instanceDataAccessor, taskId, language); + validationIssues.AddRange(issues); + } + + return validationIssues; + } + + internal async Task> ValidateFormData( Instance instance, DataElement dataElement, - object data, + IInstanceDataAccessor dataAccessor, + string taskId, string? language ) { - // TODO: Consider not depending on the instance object to get the task - // to follow the same principle as the other validators - var taskId = instance.Process.CurrentTask.ElementId; var rawValidationConfig = _appResourceService.GetValidationConfiguration(dataElement.DataType); if (rawValidationConfig == null) { @@ -71,6 +103,7 @@ public async Task> ValidateFormData( var evaluatorState = await _layoutEvaluatorStateInitializer.Init( instance, + dataAccessor, taskId, gatewayAction: null, language diff --git a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs index 34617f3cd..034612788 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorFormDataValidator.cs @@ -52,9 +52,9 @@ public string ValidationSource /// public async Task> Validate( Instance instance, + IInstanceDataAccessor instanceDataAccessor, string taskId, - string? language, - IInstanceDataAccessor instanceDataAccessor + string? language ) { var issues = new List(); diff --git a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs index 5920289ed..07364a595 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/LegacyIInstanceValidatorTaskValidator.cs @@ -48,9 +48,9 @@ public string ValidationSource /// public async Task> Validate( Instance instance, + IInstanceDataAccessor instanceDataAccessor, string taskId, - string? language, - IInstanceDataAccessor instanceDataAccessor + string? language ) { var modelState = new ModelStateDictionary(); diff --git a/src/Altinn.App.Core/Features/Validation/Default/RequiredValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/RequiredValidator.cs index ff50289d6..c89ebdaf9 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/RequiredValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/RequiredValidator.cs @@ -7,7 +7,7 @@ namespace Altinn.App.Core.Features.Validation.Default; /// /// Validator that runs the required rules in the layout /// -public class RequiredLayoutValidator : IFormDataValidator +public class RequiredLayoutValidator : IValidator { private readonly ILayoutEvaluatorStateInitializer _layoutEvaluatorStateInitializer; @@ -20,32 +20,26 @@ public RequiredLayoutValidator(ILayoutEvaluatorStateInitializer layoutEvaluatorS } /// - /// Run for all data types + /// Run for all tasks /// - public string DataType => "*"; + public string TaskId => "*"; /// /// This validator has the code "Required" and this is known by the frontend, who may request this validator to not run for incremental validation. /// public string ValidationSource => ValidationIssueSources.Required; - /// - /// We don't have an efficient way to figure out if changes to the model results in different validations, and frontend ignores this anyway - /// - public bool HasRelevantChanges(object current, object previous) => true; - /// - public async Task> ValidateFormData( + public async Task> Validate( Instance instance, - DataElement dataElement, - object data, + IInstanceDataAccessor instanceDataAccessor, + string taskId, string? language ) { - var taskId = instance.Process.CurrentTask.ElementId; - var evaluationState = await _layoutEvaluatorStateInitializer.Init( instance, + instanceDataAccessor, taskId, gatewayAction: null, language @@ -53,4 +47,14 @@ public async Task> ValidateFormData( return LayoutEvaluator.RunLayoutValidationsForRequired(evaluationState); } + + /// + /// We don't have an efficient way to figure out if changes to the model results in different validations, and frontend ignores this anyway + /// + public Task HasRelevantChanges( + Instance instance, + string taskId, + List changes, + IInstanceDataAccessor instanceDataAccessor + ) => Task.FromResult(true); } diff --git a/src/Altinn.App.Core/Features/Validation/Wrappers/DataElementValidatorWrapper.cs b/src/Altinn.App.Core/Features/Validation/Wrappers/DataElementValidatorWrapper.cs index 090730823..657b73780 100644 --- a/src/Altinn.App.Core/Features/Validation/Wrappers/DataElementValidatorWrapper.cs +++ b/src/Altinn.App.Core/Features/Validation/Wrappers/DataElementValidatorWrapper.cs @@ -34,9 +34,9 @@ List dataTypes /// public async Task> Validate( Instance instance, + IInstanceDataAccessor instanceDataAccessor, string taskId, - string? language, - IInstanceDataAccessor instanceDataAccessor + string? language ) { var issues = new List(); diff --git a/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs b/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs index 2e85bbd25..8022bc3db 100644 --- a/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs +++ b/src/Altinn.App.Core/Features/Validation/Wrappers/FormDataValidatorWrapper.cs @@ -30,9 +30,9 @@ public FormDataValidatorWrapper(IFormDataValidator formDataValidator, string tas /// public async Task> Validate( Instance instance, + IInstanceDataAccessor instanceDataAccessor, string taskId, - string? language, - IInstanceDataAccessor instanceDataAccessor + string? language ) { var issues = new List(); diff --git a/src/Altinn.App.Core/Features/Validation/Wrappers/TaskValidatorWrapper.cs b/src/Altinn.App.Core/Features/Validation/Wrappers/TaskValidatorWrapper.cs index 3b0221443..9d1c944e7 100644 --- a/src/Altinn.App.Core/Features/Validation/Wrappers/TaskValidatorWrapper.cs +++ b/src/Altinn.App.Core/Features/Validation/Wrappers/TaskValidatorWrapper.cs @@ -27,9 +27,9 @@ public TaskValidatorWrapper(ITaskValidator taskValidator) /// public Task> Validate( Instance instance, + IInstanceDataAccessor instanceDataAccessor, string taskId, - string? language, - IInstanceDataAccessor instanceDataAccessor + string? language ) { return _taskValidator.ValidateTask(instance, taskId, language); diff --git a/src/Altinn.App.Core/Helpers/ObjectUtils.cs b/src/Altinn.App.Core/Helpers/ObjectUtils.cs index 8ac0a1a84..a440dc874 100644 --- a/src/Altinn.App.Core/Helpers/ObjectUtils.cs +++ b/src/Altinn.App.Core/Helpers/ObjectUtils.cs @@ -180,8 +180,10 @@ private static void SetToDefaultIfShouldSerializeFalse(object model, PropertyInf /// /// Set all properties named "AltinnRowId" to Guid.Empty /// - public static void RemoveAltinnRowId(object model, int depth = 64) + /// true if any changes to the data has been performed + public static bool RemoveAltinnRowId(object model, int depth = 64) { + var isModified = false; ArgumentNullException.ThrowIfNull(model); if (depth < 0) { @@ -192,7 +194,7 @@ public static void RemoveAltinnRowId(object model, int depth = 64) var type = model.GetType(); if (type.Namespace?.StartsWith("System", StringComparison.Ordinal) == true) { - return; // System.DateTime.Now causes infinite recursion, and we shuldn't recurse into system types anyway. + return isModified; // System.DateTime.Now causes infinite recursion, and we shuldn't recurse into system types anyway. } foreach (var prop in type.GetProperties()) @@ -200,6 +202,7 @@ public static void RemoveAltinnRowId(object model, int depth = 64) // Handle guid fields named "AltinnRowId" if (PropertyIsAltinRowGuid(prop)) { + isModified = true; prop.SetValue(model, Guid.Empty); } // Recurse into lists @@ -213,7 +216,7 @@ public static void RemoveAltinnRowId(object model, int depth = 64) // Recurse into values of a list if (item is not null) { - RemoveAltinnRowId(item, depth - 1); + isModified |= RemoveAltinnRowId(item, depth - 1); } } } @@ -226,10 +229,12 @@ public static void RemoveAltinnRowId(object model, int depth = 64) // continue recursion over all properties if (value is not null) { - RemoveAltinnRowId(value, depth - 1); + isModified |= RemoveAltinnRowId(value, depth - 1); } } } + + return isModified; } private static bool PropertyIsAltinRowGuid(PropertyInfo prop) diff --git a/src/Altinn.App.Core/Internal/Data/CachedFormDataAccessor.cs b/src/Altinn.App.Core/Internal/Data/CachedFormDataAccessor.cs index 7680f6bba..2fdd71a39 100644 --- a/src/Altinn.App.Core/Internal/Data/CachedFormDataAccessor.cs +++ b/src/Altinn.App.Core/Internal/Data/CachedFormDataAccessor.cs @@ -14,6 +14,7 @@ namespace Altinn.App.Core.Internal.Data; /// internal sealed class CachedInstanceDataAccessor : IInstanceDataAccessor { + private readonly Instance _instance; private readonly string _org; private readonly string _app; private readonly Guid _instanceGuid; @@ -30,15 +31,20 @@ public CachedInstanceDataAccessor( IAppModel appModel ) { - _org = instance.Org; - _app = instance.AppId.Split("/")[1]; - _instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); - _instanceOwnerPartyId = int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture); + var splitApp = instance.AppId.Split("/"); + _org = splitApp[0]; + _app = splitApp[1]; + var splitId = instance.Id.Split("/"); + _instanceOwnerPartyId = int.Parse(splitId[0], CultureInfo.InvariantCulture); + _instanceGuid = Guid.Parse(splitId[1]); + _instance = instance; _dataClient = dataClient; _appMetadata = appMetadata; _appModel = appModel; } + public Instance Instance => _instance; + /// public async Task Get(DataElement dataElement) { diff --git a/src/Altinn.App.Core/Internal/Expressions/ILayoutEvaluatorStateInitializer.cs b/src/Altinn.App.Core/Internal/Expressions/ILayoutEvaluatorStateInitializer.cs index cc1b3f06a..408bdc736 100644 --- a/src/Altinn.App.Core/Internal/Expressions/ILayoutEvaluatorStateInitializer.cs +++ b/src/Altinn.App.Core/Internal/Expressions/ILayoutEvaluatorStateInitializer.cs @@ -1,3 +1,4 @@ +using Altinn.App.Core.Features; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.Core.Internal.Expressions; @@ -14,6 +15,7 @@ public interface ILayoutEvaluatorStateInitializer /// Task Init( Instance instance, + IInstanceDataAccessor dataAccessor, string taskId, string? gatewayAction = null, string? language = null diff --git a/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs b/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs index 2ee87794d..9b8817bee 100644 --- a/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs +++ b/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs @@ -17,19 +17,13 @@ public class LayoutEvaluatorStateInitializer : ILayoutEvaluatorStateInitializer // Dependency injection properties (set in ctor) private readonly IAppResources _appResources; private readonly FrontEndSettings _frontEndSettings; - private readonly IInstanceDataAccessor _dataAccessor; /// /// Constructor with services from dependency injection /// - public LayoutEvaluatorStateInitializer( - IAppResources appResources, - IOptions frontEndSettings, - IInstanceDataAccessor dataAccessor - ) + public LayoutEvaluatorStateInitializer(IAppResources appResources, IOptions frontEndSettings) { _appResources = appResources; - _dataAccessor = dataAccessor; _frontEndSettings = frontEndSettings.Value; } @@ -61,6 +55,7 @@ public Task Init( /// public async Task Init( Instance instance, + IInstanceDataAccessor dataAccessor, string taskId, string? gatewayAction = null, string? language = null @@ -81,7 +76,7 @@ public async Task Init( dataTasks.AddRange( instance .Data.Where(dataElement => dataElement.DataType == dataType) - .Select(async dataElement => KeyValuePair.Create(dataElement, await _dataAccessor.Get(dataElement))) + .Select(async dataElement => KeyValuePair.Create(dataElement, await dataAccessor.Get(dataElement))) ); } diff --git a/src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs b/src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs index 5173eaf56..0909ef11d 100644 --- a/src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs +++ b/src/Altinn.App.Core/Internal/Process/ExpressionsExclusiveGateway.cs @@ -2,6 +2,9 @@ using System.Text; using System.Text.Json; using Altinn.App.Core.Features; +using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.AppModel; +using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Expressions; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Models.Expressions; @@ -28,7 +31,6 @@ public class ExpressionsExclusiveGateway : IProcessExclusiveGateway /// /// Constructor for /// - /// Expressions state initalizer used to create context for expression evaluation public ExpressionsExclusiveGateway(ILayoutEvaluatorStateInitializer layoutEvaluatorStateInitializer) { _layoutStateInit = layoutEvaluatorStateInitializer; @@ -41,11 +43,13 @@ public ExpressionsExclusiveGateway(ILayoutEvaluatorStateInitializer layoutEvalua public async Task> FilterAsync( List outgoingFlows, Instance instance, + IInstanceDataAccessor dataAccessor, ProcessGatewayInformation processGatewayInformation ) { var state = await GetLayoutEvaluatorState( instance, + dataAccessor, instance.Process.CurrentTask.ElementId, processGatewayInformation.Action, language: null @@ -56,12 +60,13 @@ ProcessGatewayInformation processGatewayInformation private async Task GetLayoutEvaluatorState( Instance instance, + IInstanceDataAccessor dataAccessor, string taskId, string? gatewayAction, string? language ) { - var state = await _layoutStateInit.Init(instance, taskId, gatewayAction, language); + var state = await _layoutStateInit.Init(instance, dataAccessor, taskId, gatewayAction, language); return state; } diff --git a/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs index 73dc6f4c1..9f10465e7 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessNavigator.cs @@ -1,4 +1,7 @@ using Altinn.App.Core.Features; +using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.AppModel; +using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.Base; using Altinn.App.Core.Models.Process; @@ -15,22 +18,28 @@ public class ProcessNavigator : IProcessNavigator private readonly IProcessReader _processReader; private readonly ExclusiveGatewayFactory _gatewayFactory; private readonly ILogger _logger; + private readonly IDataClient _dataClient; + private readonly IAppMetadata _appMetadata; + private readonly IAppModel _appModel; /// /// Initialize a new instance of /// - /// The process reader - /// Service to fetch wanted gateway filter implementation - /// The logger public ProcessNavigator( IProcessReader processReader, ExclusiveGatewayFactory gatewayFactory, - ILogger logger + ILogger logger, + IDataClient dataClient, + IAppMetadata appMetadata, + IAppModel appModel ) { _processReader = processReader; _gatewayFactory = gatewayFactory; _logger = logger; + _dataClient = dataClient; + _appMetadata = appMetadata; + _appModel = appModel; } /// @@ -101,8 +110,18 @@ private async Task> NextFollowAndFilterGateways( Action = action, DataTypeId = gateway.ExtensionElements?.GatewayExtension?.ConnectedDataTypeId }; - - filteredList = await gatewayFilter.FilterAsync(outgoingFlows, instance, gatewayInformation); + IInstanceDataAccessor dataAccessor = new CachedInstanceDataAccessor( + instance, + _dataClient, + _appMetadata, + _appModel + ); + filteredList = await gatewayFilter.FilterAsync( + outgoingFlows, + instance, + dataAccessor, + gatewayInformation + ); } var defaultSequenceFlow = filteredList.Find(s => s.Id == gateway.Default); if (defaultSequenceFlow != null) diff --git a/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs b/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs index bd3e0c027..c5baf4b2b 100644 --- a/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs +++ b/src/Altinn.App.Core/Internal/Process/ProcessTasks/Common/ProcessTaskFinalizer.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Text.Json; using Altinn.App.Core.Configuration; +using Altinn.App.Core.Features; using Altinn.App.Core.Helpers; using Altinn.App.Core.Helpers.DataModel; using Altinn.App.Core.Internal.App; @@ -46,72 +47,106 @@ public async Task Finalize(string taskId, Instance instance) ApplicationMetadata applicationMetadata = await _appMetadata.GetApplicationMetadata(); List connectedDataTypes = applicationMetadata.DataTypes.FindAll(dt => dt.TaskId == taskId); - await RunRemoveFieldsInModelOnTaskComplete(instance, taskId, connectedDataTypes, language: null); + var dataAccessor = new CachedInstanceDataAccessor(instance, _dataClient, _appMetadata, _appModel); + var changedDataElements = await RunRemoveFieldsInModelOnTaskComplete( + instance, + dataAccessor, + taskId, + connectedDataTypes, + language: null + ); + + // Save changes to the data elements with app logic that was changed. + await Task.WhenAll( + changedDataElements.Select(async dataElement => + { + var data = await dataAccessor.Get(dataElement); + return _dataClient.UpdateData( + data, + Guid.Parse(instance.Id.Split('/')[1]), + data.GetType(), + instance.Org, + instance.AppId.Split('/')[1], + int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture), + Guid.Parse(dataElement.Id) + ); + }) + ); } - private async Task RunRemoveFieldsInModelOnTaskComplete( + private async Task> RunRemoveFieldsInModelOnTaskComplete( Instance instance, + IInstanceDataAccessor dataAccessor, string taskId, List dataTypesToLock, string? language = null ) { ArgumentNullException.ThrowIfNull(instance.Data); + HashSet modifiedDataElements = new(); - dataTypesToLock = dataTypesToLock.Where(d => !string.IsNullOrEmpty(d.AppLogic?.ClassRef)).ToList(); + var dataTypesWithLogic = dataTypesToLock.Where(d => !string.IsNullOrEmpty(d.AppLogic?.ClassRef)).ToList(); await Task.WhenAll( instance - .Data.Join(dataTypesToLock, de => de.DataType, dt => dt.Id, (de, dt) => (dataElement: de, dataType: dt)) + .Data.Join( + dataTypesWithLogic, + de => de.DataType, + dt => dt.Id, + (de, dt) => (dataElement: de, dataType: dt) + ) .Select( async (d) => { - await RemoveFieldsOnTaskComplete( - instance, - taskId, - dataTypesToLock, - d.dataElement, - d.dataType, - language - ); + if ( + await RemoveFieldsOnTaskComplete( + instance, + dataAccessor, + taskId, + dataTypesWithLogic, + d.dataElement, + d.dataType, + language + ) + ) + { + modifiedDataElements.Add(d.dataElement); + } } ) ); + return modifiedDataElements; } - private async Task RemoveFieldsOnTaskComplete( + private async Task RemoveFieldsOnTaskComplete( Instance instance, + IInstanceDataAccessor dataAccessor, string taskId, - List dataTypesToLock, + List dataTypesWithLogic, DataElement dataElement, DataType dataType, string? language = null ) { - // Download the data - Type modelType = _appModel.GetModelType(dataType.AppLogic.ClassRef); - Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); - Guid dataGuid = Guid.Parse(dataElement.Id); - string app = instance.AppId.Split("/")[1]; - int instanceOwnerPartyId = int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture); - object data = await _dataClient.GetFormData( - instanceGuid, - modelType, - instance.Org, - app, - instanceOwnerPartyId, - dataGuid - ); + bool isModified = false; + var data = await dataAccessor.Get(dataElement); + + // remove AltinnRowIds + ObjectUtils.RemoveAltinnRowId(data); + isModified = true; // Remove hidden data before validation, ignore hidden rows. if (_appSettings.Value?.RemoveHiddenData == true) { LayoutEvaluatorState evaluationState = await _layoutEvaluatorStateInitializer.Init( instance, + dataAccessor, taskId, gatewayAction: null, language ); LayoutEvaluator.RemoveHiddenData(evaluationState, RowRemovalOption.Ignore); + // TODO: + isModified = true; } // Remove shadow fields @@ -121,7 +156,7 @@ private async Task RemoveFieldsOnTaskComplete( if (dataType.AppLogic.ShadowFields.SaveToDataType != null) { // Save the shadow fields to another data type - DataType? saveToDataType = dataTypesToLock.Find(dt => + DataType? saveToDataType = dataTypesWithLogic.Find(dt => dt.Id == dataType.AppLogic.ShadowFields.SaveToDataType ); if (saveToDataType == null) @@ -133,10 +168,14 @@ private async Task RemoveFieldsOnTaskComplete( Type saveToModelType = _appModel.GetModelType(saveToDataType.AppLogic.ClassRef); object? updatedData = JsonSerializer.Deserialize(serializedData, saveToModelType); + // Save a new data element with the cleaned data without shadow fields. + Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); + string app = instance.AppId.Split("/")[1]; + int instanceOwnerPartyId = int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture); await _dataClient.InsertFormData( updatedData, instanceGuid, - saveToModelType ?? modelType, + saveToModelType, instance.Org, app, instanceOwnerPartyId, @@ -147,16 +186,14 @@ await _dataClient.InsertFormData( { // Remove the shadow fields from the data data = - JsonSerializer.Deserialize(serializedData, modelType) + JsonSerializer.Deserialize(serializedData, data.GetType()) ?? throw new JsonException( "Could not deserialize back datamodel after removing shadow fields. Data was \"null\"" ); } } - // remove AltinnRowIds - ObjectUtils.RemoveAltinnRowId(data); // Save the updated data - await _dataClient.UpdateData(data, instanceGuid, modelType, instance.Org, app, instanceOwnerPartyId, dataGuid); + return isModified; } } diff --git a/src/Altinn.App.Core/Internal/Validation/ValidationService.cs b/src/Altinn.App.Core/Internal/Validation/ValidationService.cs index 33601a4b1..cc3573557 100644 --- a/src/Altinn.App.Core/Internal/Validation/ValidationService.cs +++ b/src/Altinn.App.Core/Internal/Validation/ValidationService.cs @@ -54,7 +54,7 @@ public async Task> ValidateInstanceAtTask( using var validatorActivity = _telemetry?.StartRunValidatorActivity(v); try { - var issues = await v.Validate(instance, taskId, language, dataAccessor); + var issues = await v.Validate(instance, dataAccessor, taskId, language); return KeyValuePair.Create( v.ValidationSource, issues.Select(issue => ValidationIssueWithSource.FromIssue(issue, v.ValidationSource)) @@ -112,7 +112,7 @@ public async Task>> ValidateI validatorActivity?.SetTag(Telemetry.InternalLabels.ValidatorRelevantChanges, hasRelevantChanges); if (hasRelevantChanges) { - var issues = await validator.Validate(instance, taskId, language, dataAccessor); + var issues = await validator.Validate(instance, dataAccessor, taskId, language); var issuesWithSource = issues .Select(i => ValidationIssueWithSource.FromIssue(i, validator.ValidationSource)) .ToList(); diff --git a/test/Altinn.App.Core.Tests/Features/Validators/Default/ExpressionValidatorTests.cs b/test/Altinn.App.Core.Tests/Features/Validators/Default/ExpressionValidatorTests.cs index fe05f569e..38d09ead6 100644 --- a/test/Altinn.App.Core.Tests/Features/Validators/Default/ExpressionValidatorTests.cs +++ b/test/Altinn.App.Core.Tests/Features/Validators/Default/ExpressionValidatorTests.cs @@ -1,17 +1,15 @@ -using System.Reflection; using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Altinn.App.Core.Configuration; +using Altinn.App.Core.Features; using Altinn.App.Core.Features.Validation.Default; using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.AppModel; using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Expressions; using Altinn.App.Core.Models; using Altinn.App.Core.Models.Layout; using Altinn.App.Core.Models.Validation; -using Altinn.App.Core.Tests.Helpers; -using Altinn.App.Core.Tests.LayoutExpressions; using Altinn.App.Core.Tests.LayoutExpressions.CommonTests; using Altinn.App.Core.Tests.LayoutExpressions.TestUtilities; using Altinn.App.Core.Tests.TestUtils; @@ -21,7 +19,6 @@ using Microsoft.Extensions.Options; using Moq; using Xunit.Abstractions; -using Xunit.Sdk; namespace Altinn.App.Core.Tests.Features.Validators.Default; @@ -33,6 +30,7 @@ public class ExpressionValidatorTests private readonly Mock _appResources = new(MockBehavior.Strict); private readonly Mock _appMetadata = new(MockBehavior.Strict); private readonly Mock _dataClient = new(MockBehavior.Strict); + private readonly Mock _appModel = new(MockBehavior.Strict); private readonly IOptions _frontendSettings = Microsoft.Extensions.Options.Options.Create( new FrontEndSettings() ); @@ -48,7 +46,12 @@ public ExpressionValidatorTests(ITestOutputHelper output) new ApplicationMetadata("org/app") { DataTypes = new List { new() { Id = "default" } } } ); _appResources.Setup(ar => ar.GetLayoutSetForTask("Task_1")).Returns(new LayoutSet()); - _validator = new ExpressionValidator(_logger.Object, _appResources.Object, _layoutInitializer.Object); + _validator = new ExpressionValidator( + _logger.Object, + _appResources.Object, + _layoutInitializer.Object, + _appMetadata.Object + ); } public ExpressionValidationTestModel LoadData(string fileName, string folder) @@ -75,7 +78,7 @@ private async Task RunExpressionValidationTest(string fileName, string folder) { var testCase = LoadData(fileName, folder); - var instance = new Instance() { Process = new() { CurrentTask = new() { ElementId = "Task_1", } } }; + var instance = new Instance() { Id = "1337/fa0678ad-960d-4307-aba2-ba29c9804c9d", AppId = "org/app", }; var dataElement = new DataElement { DataType = "default", }; var dataModel = DynamicClassBuilder.DataModelFromJsonDocument(testCase.FormData, dataElement); @@ -83,7 +86,13 @@ private async Task RunExpressionValidationTest(string fileName, string folder) var evaluatorState = new LayoutEvaluatorState(dataModel, testCase.Layouts, _frontendSettings.Value, instance); _layoutInitializer .Setup(init => - init.Init(It.Is(i => i == instance), "Task_1", It.IsAny(), It.IsAny()) + init.Init( + It.Is(i => i == instance), + It.IsAny(), + "Task_1", + It.IsAny(), + It.IsAny() + ) ) .ReturnsAsync(evaluatorState); _appResources @@ -91,7 +100,14 @@ private async Task RunExpressionValidationTest(string fileName, string folder) .Returns(JsonSerializer.Serialize(testCase.ValidationConfig)); _appResources.Setup(ar => ar.GetLayoutSetForTask(null!)).Returns(new LayoutSet() { DataType = "default", }); - var validationIssues = await _validator.ValidateFormData(instance, dataElement, null!, null); + var dataAccessor = new CachedInstanceDataAccessor( + instance, + _dataClient.Object, + _appMetadata.Object, + _appModel.Object + ); + + var validationIssues = await _validator.ValidateFormData(instance, dataElement, dataAccessor, "Task_1", null); var result = validationIssues.Select(i => new { diff --git a/test/Altinn.App.Core.Tests/Features/Validators/Default/LegacyIValidationFormDataTests.cs b/test/Altinn.App.Core.Tests/Features/Validators/Default/LegacyIValidationFormDataTests.cs index 00f3268a7..3c7005ef7 100644 --- a/test/Altinn.App.Core.Tests/Features/Validators/Default/LegacyIValidationFormDataTests.cs +++ b/test/Altinn.App.Core.Tests/Features/Validators/Default/LegacyIValidationFormDataTests.cs @@ -75,7 +75,7 @@ public async Task ValidateFormData_WithErrors() _instanceDataAccessor.Setup(ida => ida.Get(_dataElement)).ReturnsAsync(data); // Act - var result = await _validator.Validate(_instance, "Task_1", null, _instanceDataAccessor.Object); + var result = await _validator.Validate(_instance, _instanceDataAccessor.Object, "Task_1", null); // Assert result @@ -154,7 +154,7 @@ public async Task ValidateErrorAndMappingWithCustomModel(string errorKey, string _instanceDataAccessor.Setup(ida => ida.Get(_dataElement)).ReturnsAsync(data).Verifiable(Times.Once); // Act - var result = await _validator.Validate(_instance, "Task_1", null, _instanceDataAccessor.Object); + var result = await _validator.Validate(_instance, _instanceDataAccessor.Object, "Task_1", null); // Assert result.Should().HaveCount(2); diff --git a/test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs b/test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs index fdb010c18..4338e857b 100644 --- a/test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs +++ b/test/Altinn.App.Core.Tests/Features/Validators/ValidationServiceTests.cs @@ -105,8 +105,6 @@ private class MyModel private readonly Mock _formDataValidatorAlwaysMock = new(MockBehavior.Strict) { Name = "alwaysFormDataValidator" }; - private readonly Mock _httpContextAccessorMock = new(); - private readonly ServiceCollection _serviceCollection = new(); public ValidationServiceTests() @@ -126,9 +124,6 @@ public ValidationServiceTests() _appMetadataMock.Setup(a => a.GetApplicationMetadata()).ReturnsAsync(_defaultAppMetadata); _serviceCollection.AddSingleton(); - _httpContextAccessorMock.Setup(h => h.HttpContext!.TraceIdentifier).Returns(Guid.NewGuid().ToString()); - _serviceCollection.AddSingleton(_httpContextAccessorMock.Object); - _serviceCollection.AddSingleton(Microsoft.Extensions.Options.Options.Create(new GeneralSettings())); // NeverUsedValidators diff --git a/test/Altinn.App.Core.Tests/Internal/Patch/PatchServiceTests.cs b/test/Altinn.App.Core.Tests/Internal/Patch/PatchServiceTests.cs index 087d51184..464eef5c1 100644 --- a/test/Altinn.App.Core.Tests/Internal/Patch/PatchServiceTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Patch/PatchServiceTests.cs @@ -92,19 +92,7 @@ public PatchServiceTests() [], _appMetadataMock.Object ); - var validationService = new ValidationService( - validatorFactory, - _dataClientMock.Object, - _appModelMock.Object, - _appMetadataMock.Object, - _vLoggerMock.Object, - new CachedFormDataAccessor( - _dataClientMock.Object, - _appMetadataMock.Object, - _appModelMock.Object, - _httpContextAccessorMock.Object - ) - ); + var validationService = new ValidationService(validatorFactory, _appMetadataMock.Object, _vLoggerMock.Object); _patchService = new PatchService( _appMetadataMock.Object, diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ExpressionsExclusiveGatewayTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ExpressionsExclusiveGatewayTests.cs index 1ec884a46..7fda8b907 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ExpressionsExclusiveGatewayTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ExpressionsExclusiveGatewayTests.cs @@ -14,7 +14,6 @@ using Altinn.App.Core.Models.Process; using Altinn.App.Core.Tests.Internal.Process.TestData; using Altinn.Platform.Storage.Interface.Models; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Moq; @@ -29,7 +28,6 @@ public class ExpressionsExclusiveGatewayTests private readonly Mock _appModel = new(MockBehavior.Strict); private readonly Mock _appMetadata = new(MockBehavior.Strict); private readonly Mock _dataClient = new(MockBehavior.Strict); - private readonly Mock _httpContextAccessor = new(MockBehavior.Strict); private const string Org = "ttd"; private const string App = "test"; @@ -57,7 +55,6 @@ public async Task FilterAsync_NoExpressions_ReturnsAllFlows() var data = new DummyModel(); - var gateway = SetupExpressionsGateway(dataTypes: dataTypes, formData: data); var outgoingFlows = new List { new SequenceFlow { Id = "1", ConditionExpression = null, }, @@ -76,8 +73,10 @@ public async Task FilterAsync_NoExpressions_ReturnsAllFlows() }; var processGatewayInformation = new ProcessGatewayInformation { Action = "confirm", }; + var (gateway, dataAccessor) = SetupExpressionsGateway(instance, dataTypes: dataTypes, formData: data); + // Act - var result = await gateway.FilterAsync(outgoingFlows, instance, processGatewayInformation); + var result = await gateway.FilterAsync(outgoingFlows, instance, dataAccessor, processGatewayInformation); // Assert Assert.Equal(2, result.Count); @@ -99,7 +98,6 @@ public async Task FilterAsync_Expression_filters_based_on_action() }; var data = new DummyModel(); - var gateway = SetupExpressionsGateway(dataTypes: dataTypes, formData: data); var outgoingFlows = new List { new SequenceFlow { Id = "1", ConditionExpression = "[\"equals\", [\"gatewayAction\"], \"confirm\"]", }, @@ -118,8 +116,10 @@ public async Task FilterAsync_Expression_filters_based_on_action() }; var processGatewayInformation = new ProcessGatewayInformation { Action = "confirm", }; + var (gateway, dataAccessor) = SetupExpressionsGateway(instance, dataTypes, formData: data); + // Act - var result = await gateway.FilterAsync(outgoingFlows, instance, processGatewayInformation); + var result = await gateway.FilterAsync(outgoingFlows, instance, dataAccessor, processGatewayInformation); // Assert Assert.Single(result); @@ -156,11 +156,6 @@ public async Task FilterAsync_Expression_filters_based_on_datamodel_set_by_layou } } }; - var gateway = SetupExpressionsGateway( - dataTypes: dataTypes, - formData: formData, - layoutSets: LayoutSetsToString(layoutSets) - ); var outgoingFlows = new List { new SequenceFlow { Id = "1", ConditionExpression = "[\"notEquals\", [\"dataModel\", \"Amount\"], 1000]", }, @@ -179,8 +174,15 @@ public async Task FilterAsync_Expression_filters_based_on_datamodel_set_by_layou }; var processGatewayInformation = new ProcessGatewayInformation { Action = "confirm", }; + var (gateway, dataAccessor) = SetupExpressionsGateway( + instance, + dataTypes: dataTypes, + formData: formData, + layoutSets: LayoutSetsToString(layoutSets) + ); + // Act - var result = await gateway.FilterAsync(outgoingFlows, instance, processGatewayInformation); + var result = await gateway.FilterAsync(outgoingFlows, instance, dataAccessor, processGatewayInformation); // Assert Assert.Single(result); @@ -218,11 +220,6 @@ public async Task FilterAsync_Expression_filters_based_on_datamodel_set_by_gatew } } }; - var gateway = SetupExpressionsGateway( - dataTypes: dataTypes, - formData: formData, - layoutSets: LayoutSetsToString(layoutSets) - ); var outgoingFlows = new List { new SequenceFlow { Id = "1", ConditionExpression = "[\"notEquals\", [\"dataModel\", \"Amount\"], 1000]", }, @@ -241,15 +238,23 @@ public async Task FilterAsync_Expression_filters_based_on_datamodel_set_by_gatew }; var processGatewayInformation = new ProcessGatewayInformation { Action = "confirm", DataTypeId = "aa" }; + var (gateway, dataAccessor) = SetupExpressionsGateway( + instance, + dataTypes, + LayoutSetsToString(layoutSets), + formData + ); + // Act - var result = await gateway.FilterAsync(outgoingFlows, instance, processGatewayInformation); + var result = await gateway.FilterAsync(outgoingFlows, instance, dataAccessor, processGatewayInformation); // Assert Assert.Single(result); Assert.Equal("2", result[0].Id); } - private ExpressionsExclusiveGateway SetupExpressionsGateway( + private (ExpressionsExclusiveGateway gateway, IInstanceDataAccessor dataAccessor) SetupExpressionsGateway( + Instance instance, List dataTypes, string? layoutSets = null, object? formData = null @@ -300,18 +305,15 @@ private ExpressionsExclusiveGateway SetupExpressionsGateway( var frontendSettings = Options.Create(new FrontEndSettings()); - var layoutStateInit = new LayoutEvaluatorStateInitializer( - _resources.Object, - frontendSettings, - new CachedInstanceDataAccessor( - _instance, - _dataClient.Object, - _appMetadata.Object, - _appModel.Object, - _httpContextAccessor.Object - ) + var dataAccessor = new CachedInstanceDataAccessor( + instance, + _dataClient.Object, + _appMetadata.Object, + _appModel.Object ); - return new ExpressionsExclusiveGateway(layoutStateInit); + + var layoutStateInit = new LayoutEvaluatorStateInitializer(_resources.Object, frontendSettings); + return (new ExpressionsExclusiveGateway(layoutStateInit), dataAccessor); } private static string LayoutSetsToString(LayoutSets layoutSets) => diff --git a/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs b/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs index 634e8149b..9f6ead5a4 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/ProcessNavigatorTests.cs @@ -1,5 +1,7 @@ -#nullable disable using Altinn.App.Core.Features; +using Altinn.App.Core.Internal.App; +using Altinn.App.Core.Internal.AppModel; +using Altinn.App.Core.Internal.Data; using Altinn.App.Core.Internal.Process; using Altinn.App.Core.Internal.Process.Elements; using Altinn.App.Core.Internal.Process.Elements.Base; @@ -8,11 +10,16 @@ using Altinn.Platform.Storage.Interface.Models; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; +using Moq; namespace Altinn.App.Core.Tests.Internal.Process; public class ProcessNavigatorTests { + private readonly Mock _dataClient = new(MockBehavior.Strict); + private readonly Mock _appMetadata = new(MockBehavior.Strict); + private readonly Mock _appModel = new(MockBehavior.Strict); + [Fact] public async Task GetNextTask_returns_next_element_if_no_gateway() { @@ -20,7 +27,7 @@ public async Task GetNextTask_returns_next_element_if_no_gateway() "simple-linear.bpmn", new List() ); - ProcessElement nextElements = await processNavigator.GetNextTask(new Instance(), "Task1", null); + ProcessElement? nextElements = await processNavigator.GetNextTask(new Instance(), "Task1", null); nextElements .Should() .BeEquivalentTo( @@ -45,7 +52,7 @@ public async Task NextFollowAndFilterGateways_returns_empty_list_if_no_outgoing_ "simple-linear.bpmn", new List() ); - ProcessElement nextElements = await processNavigator.GetNextTask(new Instance(), "EndEvent", null); + ProcessElement? nextElements = await processNavigator.GetNextTask(new Instance(), "EndEvent", null); nextElements.Should().BeNull(); } @@ -56,7 +63,7 @@ public async Task GetNextTask_returns_default_if_no_filtering_is_implemented_and "simple-gateway-default.bpmn", new List() ); - ProcessElement nextElements = await processNavigator.GetNextTask(new Instance(), "Task1", null); + ProcessElement? nextElements = await processNavigator.GetNextTask(new Instance(), "Task1", null); nextElements .Should() .BeEquivalentTo( @@ -85,9 +92,14 @@ public async Task GetNextTask_runs_custom_filter_and_returns_result() "simple-gateway-with-join-gateway.bpmn", new List() { new DataValuesFilter("Gateway1", "choose") } ); - Instance i = new Instance() { DataValues = new Dictionary() { { "choose", "Flow3" } } }; + Instance i = new Instance() + { + Id = $"123/{Guid.NewGuid()}", + AppId = "org/app", + DataValues = new Dictionary() { { "choose", "Flow3" } } + }; - ProcessElement nextElements = await processNavigator.GetNextTask(i, "Task1", null); + ProcessElement? nextElements = await processNavigator.GetNextTask(i, "Task1", null); nextElements .Should() .BeEquivalentTo( @@ -133,8 +145,13 @@ public async Task GetNextTask_follows_downstream_gateways() "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); + Instance i = new Instance() + { + Id = $"123/{Guid.NewGuid()}", + AppId = "org/app", + DataValues = new Dictionary() { { "choose1", "Flow4" } } + }; + ProcessElement? nextElements = await processNavigator.GetNextTask(i, "Task1", null); nextElements .Should() .BeEquivalentTo( @@ -161,10 +178,12 @@ public async Task GetNextTask_runs_custom_filter_and_returns_empty_list_if_all_f ); Instance i = new Instance() { + Id = $"123/{Guid.NewGuid()}", + AppId = "org/app", DataValues = new Dictionary() { { "choose1", "Flow4" }, { "choose2", "Bar" } } }; - ProcessElement nextElements = await processNavigator.GetNextTask(i, "Task1", null); + ProcessElement? nextElements = await processNavigator.GetNextTask(i, "Task1", null); nextElements.Should().BeNull(); } @@ -175,13 +194,13 @@ public async Task GetNextTask_returns_empty_list_if_element_has_no_next() "simple-gateway-with-join-gateway.bpmn", new List() ); - Instance i = new Instance(); + Instance i = new Instance() { Id = $"123/{Guid.NewGuid()}", AppId = "org/app", }; - ProcessElement nextElements = await processNavigator.GetNextTask(i, "EndEvent", null); + ProcessElement? nextElements = await processNavigator.GetNextTask(i, "EndEvent", null); nextElements.Should().BeNull(); } - private static IProcessNavigator SetupProcessNavigator( + private IProcessNavigator SetupProcessNavigator( string bpmnfile, IEnumerable gatewayFilters ) @@ -190,7 +209,10 @@ IEnumerable gatewayFilters return new ProcessNavigator( pr, new ExclusiveGatewayFactory(gatewayFilters), - new NullLogger() + new NullLogger(), + _dataClient.Object, + _appMetadata.Object, + _appModel.Object ); } } 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 8ad5c7d2f..89f34745d 100644 --- a/test/Altinn.App.Core.Tests/Internal/Process/StubGatewayFilters/DataValuesFilter.cs +++ b/test/Altinn.App.Core.Tests/Internal/Process/StubGatewayFilters/DataValuesFilter.cs @@ -20,6 +20,7 @@ public DataValuesFilter(string gatewayId, string filterOnDataValue) public async Task> FilterAsync( List outgoingFlows, Instance instance, + IInstanceDataAccessor dataAccessor, ProcessGatewayInformation processGatewayInformation ) { diff --git a/test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LayoutTestUtils.cs b/test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LayoutTestUtils.cs index 633358164..750be0051 100644 --- a/test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LayoutTestUtils.cs +++ b/test/Altinn.App.Core.Tests/LayoutExpressions/FullTests/LayoutTestUtils.cs @@ -93,11 +93,6 @@ public static async Task GetLayoutModelTools(object model, services.AddSingleton(appMetadata.Object); services.AddSingleton(appModel.Object); services.AddScoped(); - services.AddScoped(); - - var httpContextAccessorMock = new Mock(); - httpContextAccessorMock.SetupGet(c => c.HttpContext!.TraceIdentifier).Returns(Guid.NewGuid().ToString()); - services.AddSingleton(httpContextAccessorMock.Object); services.AddOptions().Configure(fes => fes.Add("test", "value")); @@ -105,6 +100,9 @@ public static async Task GetLayoutModelTools(object model, using var scope = serviceProvider.CreateScope(); var initializer = scope.ServiceProvider.GetRequiredService(); - return await initializer.Init(_instance, TaskId); + var dataAccessor = new CachedInstanceDataAccessor(_instance, data.Object, appMetadata.Object, appModel.Object); + dataAccessor.Set(_instance.Data[0], model); + + return await initializer.Init(_instance, dataAccessor, TaskId); } }