From 411bce50932b5f8152339666462b7a394b747e7a Mon Sep 17 00:00:00 2001 From: Ivar Nesje Date: Wed, 10 Jan 2024 23:47:35 +0100 Subject: [PATCH] Cleanup and more tests --- .../Controllers/DataController.cs | 23 ++- .../Controllers/ValidateController.cs | 1 - .../Features/IDataElementValidator.cs | 5 + .../Features/IFormDataValidator.cs | 2 +- .../Features/ITaskValidator.cs | 5 + .../Default/DataAnnotationValidator.cs | 4 +- .../Default/DefaultDataElementValidator.cs | 5 - .../Default/DefaultTaskValidator.cs | 2 - .../Validation/Default/ExpressionValidator.cs | 4 +- .../Internal/Expressions/LayoutEvaluator.cs | 1 - .../Models/Validation/ValidationIssue.cs | 26 +++- .../Validation/ValidationIssueSeverity.cs | 1 + ...aController_PatchFormDataImplementation.cs | 3 +- .../Controllers/DataController_PatchTests.cs | 147 +++++++++++------- .../ValidateControllerValidateDataTests.cs | 1 - ...3-fe31-4ef5-8fb9-dd3f479354cd.pretest.json | 30 ++++ ...121812-0336-45fb-a75c-490df3ad5109.pretest | 6 + ...2-0336-45fb-a75c-490df3ad5109.pretest.json | 22 +++ test/Altinn.App.Api.Tests/Data/TestData.cs | 6 +- .../ui/layouts/page.json | 8 + 20 files changed, 210 insertions(+), 92 deletions(-) create mode 100644 test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd.pretest.json create mode 100644 test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/blob/fc121812-0336-45fb-a75c-490df3ad5109.pretest create mode 100644 test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/fc121812-0336-45fb-a75c-490df3ad5109.pretest.json diff --git a/src/Altinn.App.Api/Controllers/DataController.cs b/src/Altinn.App.Api/Controllers/DataController.cs index af8add84f..1b70331f4 100644 --- a/src/Altinn.App.Api/Controllers/DataController.cs +++ b/src/Altinn.App.Api/Controllers/DataController.cs @@ -367,6 +367,9 @@ public async Task Put( /// A response object with the new full model and validation issues from all the groups that run [Authorize(Policy = AuthzConstants.POLICY_INSTANCE_WRITE)] [HttpPatch("{dataGuid:guid}")] + [ProducesResponseType(typeof(DataPatchResponse), 200)] + [ProducesResponseType(typeof(ProblemDetails), 412)] + [ProducesResponseType(typeof(ProblemDetails), 422)] public async Task> PatchFormData( [FromRoute] string org, [FromRoute] string app, @@ -409,9 +412,14 @@ public async Task> PatchFormData( var oldModel = await _dataClient.GetFormData(instanceGuid, modelType, org, app, instanceOwnerPartyId, dataGuid); - var response = + var (response, problemDetails) = await PatchFormDataImplementation(dataType, dataElement, dataPatchRequest, oldModel, instance); + if (problemDetails is not null) + { + return StatusCode(problemDetails.Status ?? 500, problemDetails); + } + await UpdatePresentationTextsOnInstance(instance, dataType.Id, response.NewDataModel); await UpdateDataValuesOnInstance(instance, dataType.Id, response.NewDataModel); @@ -442,13 +450,20 @@ await _dataClient.UpdateData( /// The old state of the form data /// The instance /// DataPatchResponse after this patch operation - internal async Task PatchFormDataImplementation(DataType dataType, DataElement dataElement, DataPatchRequest dataPatchRequest, object oldModel, Instance instance) + internal async Task<(DataPatchResponse, ProblemDetails?)> PatchFormDataImplementation(DataType dataType, DataElement dataElement, DataPatchRequest dataPatchRequest, object oldModel, Instance instance) { var oldModelNode = JsonSerializer.SerializeToNode(oldModel, oldModel.GetType()); var patchResult = dataPatchRequest.Patch.Apply(oldModelNode); if (!patchResult.IsSuccess) { - throw new Exception(patchResult.Error); // TODO: Let DataPatchResponse have an error state + bool testOperationFailed = dataPatchRequest.Patch.Operations[patchResult.Operation].Op == OperationType.Test; + return (null!, new ProblemDetails() + { + Title = testOperationFailed ? "Precondition in patch failed": "PatchOperationFailed", + Detail = patchResult.Error, + Type = "https://datatracker.ietf.org/doc/html/rfc6902/", + Status = testOperationFailed ? (int)HttpStatusCode.PreconditionFailed : (int)HttpStatusCode.UnprocessableContent, + }); } var model = patchResult.Result.Deserialize(oldModel.GetType())!; @@ -466,7 +481,7 @@ internal async Task PatchFormDataImplementation(DataType data NewDataModel = model, ValidationIssues = validationIssues }; - return response; + return (response, null); } /// diff --git a/src/Altinn.App.Api/Controllers/ValidateController.cs b/src/Altinn.App.Api/Controllers/ValidateController.cs index 4da14bbde..28f08183c 100644 --- a/src/Altinn.App.Api/Controllers/ValidateController.cs +++ b/src/Altinn.App.Api/Controllers/ValidateController.cs @@ -141,7 +141,6 @@ public async Task ValidateData( ValidationIssue message = new ValidationIssue { Code = ValidationIssueCodes.DataElementCodes.DataElementValidatedAtWrongTask, - InstanceId = instance.Id, Severity = ValidationIssueSeverity.Warning, DataElementId = element.Id, Description = AppTextHelper.GetAppText( diff --git a/src/Altinn.App.Core/Features/IDataElementValidator.cs b/src/Altinn.App.Core/Features/IDataElementValidator.cs index 95e5a1c37..800dd9d97 100644 --- a/src/Altinn.App.Core/Features/IDataElementValidator.cs +++ b/src/Altinn.App.Core/Features/IDataElementValidator.cs @@ -20,6 +20,11 @@ public interface IDataElementValidator /// string DataType { get; } + /// + /// Override this if you want a different name for the validation source + /// + string ValidationSource => $"{this.GetType().FullName}-{DataType}"; + /// /// Run validations for a data element. This is supposed to run quickly /// diff --git a/src/Altinn.App.Core/Features/IFormDataValidator.cs b/src/Altinn.App.Core/Features/IFormDataValidator.cs index 91a2e2594..369cb19c1 100644 --- a/src/Altinn.App.Core/Features/IFormDataValidator.cs +++ b/src/Altinn.App.Core/Features/IFormDataValidator.cs @@ -30,7 +30,7 @@ public interface IFormDataValidator /// /// The default implementation should work for most cases. /// - public string Code => $"{this.GetType().FullName}-{DataType}"; + public string ValidationSource => $"{this.GetType().FullName}-{DataType}"; /// /// The actual validation function diff --git a/src/Altinn.App.Core/Features/ITaskValidator.cs b/src/Altinn.App.Core/Features/ITaskValidator.cs index f2137fe34..2bcb02d3b 100644 --- a/src/Altinn.App.Core/Features/ITaskValidator.cs +++ b/src/Altinn.App.Core/Features/ITaskValidator.cs @@ -25,6 +25,11 @@ public interface ITaskValidator /// string TaskId { get; } + /// + /// Override this if you want a different name for the validation source + /// + string ValidationSource => $"{this.GetType().FullName}-{TaskId}"; + /// /// Actual validation logic for the task /// diff --git a/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs index 1c4bd5c9e..5b8948e64 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/DataAnnotationValidator.cs @@ -38,12 +38,12 @@ public DataAnnotationValidator(IHttpContextAccessor httpContextAccessor, IObject /// /// This validator has the code "dataannotations" and this is known by the frontend, who requests this validator to not run for incremental validation. /// - public string Code => "dataannotations"; + public string ValidationSource => "dataannotations"; /// /// We don't know which fields are relevant for data annotation validation, so we always run it. /// - public bool ShouldRunForIncrementalValidation(List changedFields) => true; + public bool ShouldRun(List changedFields) => true; /// public Task> ValidateFormData(Instance instance, DataElement dataElement, object data) diff --git a/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs index 841a2c16b..28a94a041 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/DefaultDataElementValidator.cs @@ -22,7 +22,6 @@ public Task> ValidateDataElement(Instance instance, DataEl { issues.Add( new ValidationIssue { - InstanceId = instance.Id, Code = ValidationIssueCodes.DataElementCodes.MissingContentType, DataElementId = dataElement.Id, Severity = ValidationIssueSeverity.Error, @@ -39,7 +38,6 @@ public Task> ValidateDataElement(Instance instance, DataEl { issues.Add( new ValidationIssue { - InstanceId = instance.Id, DataElementId = dataElement.Id, Code = ValidationIssueCodes.DataElementCodes.ContentTypeNotAllowed, Severity = ValidationIssueSeverity.Error, @@ -54,7 +52,6 @@ public Task> ValidateDataElement(Instance instance, DataEl { issues.Add( new ValidationIssue { - InstanceId = instance.Id, DataElementId = dataElement.Id, Code = ValidationIssueCodes.DataElementCodes.DataElementTooLarge, Severity = ValidationIssueSeverity.Error, @@ -67,7 +64,6 @@ public Task> ValidateDataElement(Instance instance, DataEl { issues.Add( new ValidationIssue { - InstanceId = instance.Id, DataElementId = dataElement.Id, Code = ValidationIssueCodes.DataElementCodes.DataElementFileInfected, Severity = ValidationIssueSeverity.Error, @@ -81,7 +77,6 @@ public Task> ValidateDataElement(Instance instance, DataEl { issues.Add( new ValidationIssue { - InstanceId = instance.Id, DataElementId = dataElement.Id, Code = ValidationIssueCodes.DataElementCodes.DataElementFileScanPending, Severity = ValidationIssueSeverity.Error, diff --git a/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs index 629c9f4db..a01b98bd6 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/DefaultTaskValidator.cs @@ -38,7 +38,6 @@ public async Task> ValidateTask(Instance instance, string { var message = new ValidationIssue { - InstanceId = instance.Id, Code = ValidationIssueCodes.InstanceCodes.TooManyDataElementsOfType, Severity = ValidationIssueSeverity.Error, Description = ValidationIssueCodes.InstanceCodes.TooManyDataElementsOfType, @@ -51,7 +50,6 @@ public async Task> ValidateTask(Instance instance, string { var message = new ValidationIssue { - InstanceId = instance.Id, Code = ValidationIssueCodes.InstanceCodes.TooFewDataElementsOfType, Severity = ValidationIssueSeverity.Error, Description = ValidationIssueCodes.InstanceCodes.TooFewDataElementsOfType, diff --git a/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs b/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs index 714e5c78b..3e76654f9 100644 --- a/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs +++ b/src/Altinn.App.Core/Features/Validation/Default/ExpressionValidator.cs @@ -38,12 +38,12 @@ public ExpressionValidator(ILogger logger, IAppResources ap /// /// This validator has the code "expression" and this is known by the frontend, who requests this validator to not run for incremental validation. /// - public string Code => "expression"; + public string ValidationSource => "expression"; /// /// Expression validations should always run (it is way to complex to figure out if it should run or not) /// - public bool ShouldRunForIncrementalValidation(List changedFields) => true; + public bool ShouldRun(List changedFields) => true; /// public async Task> ValidateFormData(Instance instance, DataElement dataElement, object data) diff --git a/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluator.cs b/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluator.cs index 8d191936d..a736adb55 100644 --- a/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluator.cs +++ b/src/Altinn.App.Core/Internal/Expressions/LayoutEvaluator.cs @@ -134,7 +134,6 @@ private static void RunLayoutValidationsForRequiredRecurs(List validationIssues.Add(new ValidationIssue() { Severity = ValidationIssueSeverity.Error, - InstanceId = state.GetInstanceContext("instanceId").ToString(), DataElementId = dataElementId, Field = field, Description = $"{field} is required in component with id {context.Component.Id}", diff --git a/src/Altinn.App.Core/Models/Validation/ValidationIssue.cs b/src/Altinn.App.Core/Models/Validation/ValidationIssue.cs index 2d516a073..76d6ed308 100644 --- a/src/Altinn.App.Core/Models/Validation/ValidationIssue.cs +++ b/src/Altinn.App.Core/Models/Validation/ValidationIssue.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using Altinn.App.Core.Features.Validation; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -16,24 +17,25 @@ public class ValidationIssue [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] [JsonPropertyName("severity")] [System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))] - public ValidationIssueSeverity Severity { get; set; } + public required ValidationIssueSeverity Severity { get; set; } /// /// The unique id of the specific element with the identified issue. /// - [JsonProperty(PropertyName = "instanceId")] - [JsonPropertyName("instanceId")] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] + [Obsolete("Not in use", error: true)] public string? InstanceId { get; set; } /// - /// The uniqe id of the data element of a given instance with the identified issue. + /// The unique id of the data element of a given instance with the identified issue. /// [JsonProperty(PropertyName = "dataElementId")] [JsonPropertyName("dataElementId")] public string? DataElementId { get; set; } /// - /// A reference to a property the issue is a bout. + /// A reference to a property the issue is about. /// [JsonProperty(PropertyName = "field")] [JsonPropertyName("field")] @@ -54,11 +56,14 @@ public class ValidationIssue public string? Description { get; set; } /// - /// The validation source of the issue eg. File, Schema, Component + /// The short name of the class that crated the message (set automatically after return of list) /// + /// + /// Intentionally not marked as "required", because it is set in + /// [JsonProperty(PropertyName = "source")] [JsonPropertyName("source")] - public string? Source { get; set; } + public string Source { get; set; } = default!; /// /// The custom text key to use for the localized text in the frontend. @@ -66,5 +71,12 @@ public class ValidationIssue [JsonProperty(PropertyName = "customTextKey")] [JsonPropertyName("customTextKey")] public string? CustomTextKey { get; set; } + + /// + /// The custom text key to use for the localized text in the frontend. + /// + [JsonProperty(PropertyName = "customTextParams")] + [JsonPropertyName("customTextParams")] + public List? CustomTextParams { get; set; } } } diff --git a/src/Altinn.App.Core/Models/Validation/ValidationIssueSeverity.cs b/src/Altinn.App.Core/Models/Validation/ValidationIssueSeverity.cs index 5661998d7..2b2264492 100644 --- a/src/Altinn.App.Core/Models/Validation/ValidationIssueSeverity.cs +++ b/src/Altinn.App.Core/Models/Validation/ValidationIssueSeverity.cs @@ -28,6 +28,7 @@ public enum ValidationIssueSeverity /// /// The issue has been corrected. /// + [Obsolete("We run all validations from frontend version 4, so we don't need info about fixed issues")] Fixed = 4, /// diff --git a/test/Altinn.App.Api.Tests/Controllers/DataController_PatchFormDataImplementation.cs b/test/Altinn.App.Api.Tests/Controllers/DataController_PatchFormDataImplementation.cs index af7bf6d3d..c418a576e 100644 --- a/test/Altinn.App.Api.Tests/Controllers/DataController_PatchFormDataImplementation.cs +++ b/test/Altinn.App.Api.Tests/Controllers/DataController_PatchFormDataImplementation.cs @@ -60,7 +60,7 @@ public class DataController_PatchFormDataImplementation : IAsyncDisposable public DataController_PatchFormDataImplementation() { _formDataValidator.Setup(fdv => fdv.DataType).Returns(_dataType.Id); - _formDataValidator.Setup(fdv => fdv.Code).Returns("formDataValidator"); + _formDataValidator.Setup(fdv => fdv.ValidationSource).Returns("formDataValidator"); _formDataValidator.Setup(fdv => fdv.ShouldRunForIncrementalValidation(It.IsAny>())).Returns(true); // _dataElementValidator.Setup(ev => ev.DataType).Returns(_dataType.Id); _serviceCollection.AddSingleton(_formDataValidator.Object); @@ -129,6 +129,7 @@ public async Task Test() { new () { + Severity = ValidationIssueSeverity.Error, Description = "First error", } }; diff --git a/test/Altinn.App.Api.Tests/Controllers/DataController_PatchTests.cs b/test/Altinn.App.Api.Tests/Controllers/DataController_PatchTests.cs index 8f47f6e9c..bce889420 100644 --- a/test/Altinn.App.Api.Tests/Controllers/DataController_PatchTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/DataController_PatchTests.cs @@ -3,94 +3,121 @@ using System.Net.Http.Headers; using System.Net; using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using Altinn.App.Api.Models; +using Altinn.App.Api.Tests.Data; using Altinn.App.Api.Tests.Data.apps.tdd.contributer_restriction.models; using Altinn.App.Core.Features; using Xunit; using Altinn.Platform.Storage.Interface.Models; using FluentAssertions; +using Json.Patch; +using Json.Pointer; using Microsoft.Extensions.DependencyInjection; using Moq; +using Xunit.Abstractions; namespace Altinn.App.Api.Tests.Controllers; -public class DataController_PatchTests : ApiTestBase, IClassFixture> +public class DataControllerPatchTests : ApiTestBase, IClassFixture> { - private readonly Mock _dataProcessor = new(); + private const string Org = "tdd"; + private const string App = "contributer-restriction"; + private const int InstanceOwnerPartyId = 500600; + private static readonly Guid InstanceGuid = new("0fc98a23-fe31-4ef5-8fb9-dd3f479354cd"); + private static readonly string InstanceId = $"{InstanceOwnerPartyId}/{InstanceGuid}"; + private static readonly Guid DataGuid = new("fc121812-0336-45fb-a75c-490df3ad5109"); + + private readonly Mock _dataProcessorMock = new(); + private readonly Mock _formDataValidatorMock = new(); private static readonly JsonSerializerOptions JsonSerializerOptions = new () { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true, + UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement, }; - public DataController_PatchTests(WebApplicationFactory factory) : base(factory) + private readonly ITestOutputHelper _outputHelper; + + public DataControllerPatchTests(WebApplicationFactory factory, ITestOutputHelper outputHelper) : base(factory) { + _formDataValidatorMock.Setup(v => v.DataType).Returns("Not a valid data type"); + _outputHelper = outputHelper; OverrideServicesForAllTests = (services) => { - services.AddSingleton(_dataProcessor.Object); + services.AddSingleton(_dataProcessorMock.Object); + services.AddSingleton(_formDataValidatorMock.Object); }; + } - [Fact] - public async Task PutDataElement_TestSinglePartUpdate_ReturnsOk() + private async Task CallPatchApi(JsonPatch patch, List? ignoredValidators) { - // Setup test data - string org = "tdd"; - string app = "contributer-restriction"; - int instanceOwnerPartyId = 501337; - HttpClient client = GetRootedClient(org, app); + using var httpClient = GetRootedClient(Org, App); string token = PrincipalUtil.GetToken(1337, null); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - // Create instance - var createResponse = await client.PostAsync($"{org}/{app}/instances/?instanceOwnerPartyId={instanceOwnerPartyId}", null); - var createResponseContent = await createResponse.Content.ReadAsStringAsync(); - createResponse.StatusCode.Should().Be(HttpStatusCode.Created); - var createResponseParsed = JsonSerializer.Deserialize(createResponseContent, JsonSerializerOptions)!; - var instanceId = createResponseParsed.Id; - - // Create data element (not sure why it isn't created when the instance is created, autoCreate is true) - using var createDataElementContent = - new StringContent("""{"melding":{"name": "Ivar"}}""", System.Text.Encoding.UTF8, "application/json"); - var createDataElementResponse = - await client.PostAsync($"/{org}/{app}/instances/{instanceId}/data?dataType=default", createDataElementContent); - var createDataElementResponseContent = await createDataElementResponse.Content.ReadAsStringAsync(); - createDataElementResponse.StatusCode.Should().Be(HttpStatusCode.Created); - var createDataElementResponseParsed = - JsonSerializer.Deserialize(createDataElementResponseContent, JsonSerializerOptions)!; - var dataGuid = createDataElementResponseParsed.Id; - - // Update data element + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); using var updateDataElementContent = new StringContent( - """ + JsonSerializer.Serialize(new DataPatchRequest() { - "patch": [ - { - "op": "replace", - "path": "/melding/name", - "value": "Ivar Nesje" - } - ], - "ignoredValidators": [ - "required" - ] - } - """, System.Text.Encoding.UTF8, "application/json"); - var response = await client.PatchAsync($"/{org}/{app}/instances/{instanceId}/data/{dataGuid}", updateDataElementContent); - response.StatusCode.Should().Be(HttpStatusCode.OK); + Patch = patch, + IgnoredValidators = ignoredValidators, + }, JsonSerializerOptions), System.Text.Encoding.UTF8, "application/json"); + return await httpClient.PatchAsync($"/{Org}/{App}/instances/{InstanceId}/data/{DataGuid}", updateDataElementContent); + } + + private async Task ParseResponse(HttpResponseMessage response) + { var responseString = await response.Content.ReadAsStringAsync(); - responseString.Should().Be("""{"validationIssues":{},"newDataModel":{"melding":{"name":"Ivar Nesje","random":null,"tags":null,"simple_list":null,"nested_list":[],"toggle":false}}}"""); - - // Verify stored data - var readDataElementResponse = await client.GetAsync($"/{org}/{app}/instances/{instanceId}/data/{dataGuid}"); - readDataElementResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var readDataElementResponseContent = await readDataElementResponse.Content.ReadAsStringAsync(); - var readDataElementResponseParsed = - JsonSerializer.Deserialize(readDataElementResponseContent)!; - readDataElementResponseParsed.Melding.Name.Should().Be("Ivar Nesje"); - - _dataProcessor.Verify(p => p.ProcessDataRead(It.IsAny(), It.Is(dataId => dataId == Guid.Parse(dataGuid)), It.IsAny()), Times.Exactly(1)); - _dataProcessor.Verify(p => p.ProcessDataWrite(It.IsAny(), It.Is(dataId => dataId == Guid.Parse(dataGuid)), It.IsAny(), It.IsAny()), Times.Exactly(1)); // TODO: Shouldn't this be 2 because of the first write? - _dataProcessor.VerifyNoOtherCalls(); + using var responseParsedRaw = JsonDocument.Parse(responseString); + _outputHelper.WriteLine(JsonSerializer.Serialize(responseParsedRaw, JsonSerializerOptions)); + return JsonSerializer.Deserialize(responseString, JsonSerializerOptions)!; + } + + [Fact] + public async Task PatchDataElement_ValidName_ReturnsOk() + { + // Update data element + var patch = new JsonPatch( + PatchOperation.Replace(JsonPointer.Create("melding", "name"), JsonNode.Parse("\"Ivar Nesje\""))); + + var response = await CallPatchApi(patch, null); + + response.Should().HaveStatusCode(HttpStatusCode.OK); + var parsedResponse = await ParseResponse(response); + parsedResponse.ValidationIssues.Should().ContainKey("required").WhoseValue.Should().BeEmpty(); + + var newModelElement = parsedResponse.NewDataModel.Should().BeOfType().Which; + var newModel = newModelElement.Deserialize()!; + newModel.Melding.Name.Should().Be("Ivar Nesje"); + + _dataProcessorMock.Verify(p => p.ProcessDataWrite(It.IsAny(), It.Is(dataId => dataId == DataGuid), It.IsAny(), It.IsAny()), Times.Exactly(1)); + _dataProcessorMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task PatchDataElement_NullName_ReturnsOk() + { + // Update data element + var patch = new JsonPatch( + PatchOperation.Replace(JsonPointer.Create("melding", "name"), JsonNode.Parse("null"))); + + var response = await CallPatchApi(patch, null); + + response.Should().HaveStatusCode(HttpStatusCode.OK); + var parsedResponse = await ParseResponse(response); + var requiredList = parsedResponse.ValidationIssues.Should().ContainKey("required").WhoseValue; + var requiredName = requiredList.Should().ContainSingle().Which; + requiredName.Field.Should().Be("melding.name"); + requiredName.Description.Should().Be("melding.name is required in component with id name"); + + var newModelElement = parsedResponse.NewDataModel.Should().BeOfType().Which; + var newModel = newModelElement.Deserialize()!; + newModel.Melding.Name.Should().BeNull(); + + _dataProcessorMock.Verify(p => p.ProcessDataWrite(It.IsAny(), It.Is(dataId => dataId == DataGuid), It.IsAny(), It.IsAny()), Times.Exactly(1)); + _dataProcessorMock.VerifyNoOtherCalls(); } } \ No newline at end of file diff --git a/test/Altinn.App.Api.Tests/Controllers/ValidateControllerValidateDataTests.cs b/test/Altinn.App.Api.Tests/Controllers/ValidateControllerValidateDataTests.cs index 16a50bd7e..b09672457 100644 --- a/test/Altinn.App.Api.Tests/Controllers/ValidateControllerValidateDataTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/ValidateControllerValidateDataTests.cs @@ -125,7 +125,6 @@ public class TestScenariosData : IEnumerable new ValidationIssue { Code = ValidationIssueCodes.DataElementCodes.DataElementValidatedAtWrongTask, - InstanceId = "0fc98a23-fe31-4ef5-8fb9-dd3f479354ef", Severity = ValidationIssueSeverity.Warning, DataElementId = "0fc98a23-fe31-4ef5-8fb9-dd3f479354cd", Description = AppTextHelper.GetAppText( diff --git a/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd.pretest.json b/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd.pretest.json new file mode 100644 index 000000000..1cc0c5045 --- /dev/null +++ b/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd.pretest.json @@ -0,0 +1,30 @@ +{ + "id": "500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd", + "instanceOwner": { + "partyId": "500600", + "organisationNumber": "897069631" + }, + "appId": "tdd/contributer-restriction", + "org": "tdd", + "process": { + "started": "2019-12-05T13:24:34.8412179Z", + "startEvent": "StartEvent_1", + "currentTask": { + "flow": 2, + "started": "2019-12-05T13:24:34.9196661Z", + "elementId": "Task_1", + "name": "Utfylling", + "altinnTaskType": "data", + "validated": { + "timestamp": "2020-02-07T10:46:36.985894Z", + "canCompleteTask": false + } + } + }, + "status": { + "isArchived": false, + "isSoftDeleted": false, + "isHardDeleted": false, + "readStatus": "Read" + } +} \ No newline at end of file diff --git a/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/blob/fc121812-0336-45fb-a75c-490df3ad5109.pretest b/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/blob/fc121812-0336-45fb-a75c-490df3ad5109.pretest new file mode 100644 index 000000000..903c3e28a --- /dev/null +++ b/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/blob/fc121812-0336-45fb-a75c-490df3ad5109.pretest @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/fc121812-0336-45fb-a75c-490df3ad5109.pretest.json b/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/fc121812-0336-45fb-a75c-490df3ad5109.pretest.json new file mode 100644 index 000000000..bc6bc73e5 --- /dev/null +++ b/test/Altinn.App.Api.Tests/Data/Instances/tdd/contributer-restriction/500600/0fc98a23-fe31-4ef5-8fb9-dd3f479354cd/fc121812-0336-45fb-a75c-490df3ad5109.pretest.json @@ -0,0 +1,22 @@ +{ + "id": "fc121812-0336-45fb-a75c-490df3ad5109", + "instanceGuid": "0fc98a23-fe31-4ef5-8fb9-dd3f479354cd", + "dataType": "default", + "filename": null, + "contentType": "application/xml", + "blobStoragePath": null, + "selfLinks": null, + "size": 0, + "contentHash": null, + "locked": false, + "refs": null, + "isRead": true, + "tags": [], + "deleteStatus": null, + "fileScanResult": "NotApplicable", + "references": null, + "created": null, + "createdBy": null, + "lastChanged": "2024-01-10T22:04:31.511965Z", + "lastChangedBy": null +} \ No newline at end of file diff --git a/test/Altinn.App.Api.Tests/Data/TestData.cs b/test/Altinn.App.Api.Tests/Data/TestData.cs index 1f74ef50c..9b6513367 100644 --- a/test/Altinn.App.Api.Tests/Data/TestData.cs +++ b/test/Altinn.App.Api.Tests/Data/TestData.cs @@ -119,12 +119,8 @@ public static void PrepareInstance(string org, string app, int instanceOwnerId, // Handling all data elements File.Copy(filePath, filePath.Replace(".pretest.json", ".json"), true); } - else if (filePath.EndsWith(".pretest")) - { - // Handling all data blobs - File.Copy(filePath, filePath.Replace(".pretest", string.Empty), true); - } } + } } diff --git a/test/Altinn.App.Api.Tests/Data/apps/tdd/contributer-restriction/ui/layouts/page.json b/test/Altinn.App.Api.Tests/Data/apps/tdd/contributer-restriction/ui/layouts/page.json index 8a940965e..ca66ac17f 100644 --- a/test/Altinn.App.Api.Tests/Data/apps/tdd/contributer-restriction/ui/layouts/page.json +++ b/test/Altinn.App.Api.Tests/Data/apps/tdd/contributer-restriction/ui/layouts/page.json @@ -9,6 +9,14 @@ "textResourceBindings": { "title": "Brukeropp-side-overskrift" } + }, + { + "id": "name", + "type": "Input", + "required": true, + "dataModelBindings": { + "simpleBinding": "melding.name" + } } ] }