Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for data element Post and fix bug with AbandonChanges #875

Merged
merged 2 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Altinn.App.Api/Controllers/ActionsController.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Altinn.App.Api.Extensions;
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Models;
using Altinn.App.Core.Extensions;
Expand Down Expand Up @@ -172,7 +173,7 @@ await _appMetadata.GetApplicationMetadata(),
);
}

if (dataMutator.AbandonIssues.Count > 0)
if (dataMutator.GetAbandonResponse() is not null)
{
throw new NotImplementedException(
"return an error response from UserActions instead of abandoning the dataMutator"
Expand Down
63 changes: 11 additions & 52 deletions src/Altinn.App.Api/Controllers/DataController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Globalization;
using System.Net;
using System.Text.Json;
using Altinn.App.Api.Extensions;
using Altinn.App.Api.Helpers;
using Altinn.App.Api.Helpers.Patch;
using Altinn.App.Api.Helpers.RequestHandling;
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Models;
Expand All @@ -16,7 +18,6 @@
using Altinn.App.Core.Internal.AppModel;
using Altinn.App.Core.Internal.Data;
using Altinn.App.Core.Internal.Instances;
using Altinn.App.Core.Internal.Patch;
using Altinn.App.Core.Internal.Prefill;
using Altinn.App.Core.Internal.Validation;
using Altinn.App.Core.Models;
Expand Down Expand Up @@ -52,7 +53,7 @@ public class DataController : ControllerBase
private readonly IFileAnalysisService _fileAnalyserService;
private readonly IFileValidationService _fileValidationService;
private readonly IFeatureManager _featureManager;
private readonly IPatchService _patchService;
private readonly InternalPatchService _patchService;
private readonly ModelSerializationService _modelDeserializer;

private const long REQUEST_SIZE_LIMIT = 2000 * 1024 * 1024;
Expand Down Expand Up @@ -85,7 +86,7 @@ public DataController(
IFileValidationService fileValidationService,
IAppMetadata appMetadata,
IFeatureManager featureManager,
IPatchService patchService,
InternalPatchService patchService,
ModelSerializationService modelDeserializer
)
{
Expand Down Expand Up @@ -366,14 +367,9 @@ await _patchService.RunDataProcessors(
language
);

if (dataMutator.AbandonIssues.Count > 1)
if (dataMutator.GetAbandonResponse() is { } abandonResponse)
{
return new DataPostErrorResponse(
"Data Processing abandoned",
dataMutator
.AbandonIssues.Select(i => ValidationIssueWithSource.FromIssue(i, "dataProcessing", true))
.ToList()
);
return abandonResponse;
}

var finalChanges = dataMutator.GetDataElementChanges(initializeAltinnRowId: true);
Expand Down Expand Up @@ -715,7 +711,7 @@ public async Task<ActionResult<DataPatchResponseMultiple>> PatchFormDataMultiple
}
}

ServiceResult<DataPatchResult, DataPatchError> res = await _patchService.ApplyPatches(
ServiceResult<DataPatchResult, ProblemDetails> res = await _patchService.ApplyPatches(
instance,
dataPatchRequest.Patches.ToDictionary(i => i.DataElementId, i => i.Patch),
language,
Expand Down Expand Up @@ -800,16 +796,9 @@ await _appMetadata.GetApplicationMetadata(),
var changes = dataMutator.GetDataElementChanges(initializeAltinnRowId: false);
await _patchService.RunDataProcessors(dataMutator, changes, taskId, language);

if (dataMutator.AbandonIssues.Count > 1)
if (dataMutator.GetAbandonResponse() is { } abandonResponse)
{
return BadRequest(
new DataPostErrorResponse(
"DataProcessing abandoned",
dataMutator
.AbandonIssues.Select(i => ValidationIssueWithSource.FromIssue(i, "dataProcessing", true))
.ToList()
)
);
return Problem(abandonResponse);
}
// Get the updated changes for saving
changes = dataMutator.GetDataElementChanges(initializeAltinnRowId: false);
Expand Down Expand Up @@ -1105,16 +1094,9 @@ await _patchService.RunDataProcessors(
);
var jsonAfterDataProcessors = JsonSerializer.Serialize(serviceModel);

if (dataMutator.AbandonIssues.Count > 1)
if (dataMutator.GetAbandonResponse() is { } abandonResponse)
{
return BadRequest(
new DataPostErrorResponse(
"DataProcessing abandoned",
dataMutator
.AbandonIssues.Select(i => ValidationIssueWithSource.FromIssue(i, "dataProcessing", true))
.ToList()
)
);
return Problem(abandonResponse);
}

// Save changes
Expand Down Expand Up @@ -1153,29 +1135,6 @@ private ActionResult HandlePlatformHttpException(PlatformHttpException e, string
};
}

private ObjectResult Problem(DataPatchError error)
{
var code = error.ErrorType switch
{
DataPatchErrorType.PatchTestFailed => HttpStatusCode.Conflict,
DataPatchErrorType.DeserializationFailed => HttpStatusCode.UnprocessableContent,
DataPatchErrorType.AbandonedRequest => HttpStatusCode.BadRequest,
_ => HttpStatusCode.InternalServerError
};

return StatusCode(
(int)code,
new ProblemDetails()
{
Title = error.Title,
Detail = error.Detail,
Type = "https://datatracker.ietf.org/doc/html/rfc6902/",
Status = (int)code,
Extensions = error.Extensions ?? new Dictionary<string, object?>(StringComparer.Ordinal)
}
);
}

private ObjectResult Problem(ProblemDetails error)
{
return StatusCode(error.Status ?? (int)HttpStatusCode.InternalServerError, error);
Expand Down
27 changes: 11 additions & 16 deletions src/Altinn.App.Api/Controllers/InstancesController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Globalization;
using System.Net;
using System.Text.Json;
using Altinn.App.Api.Extensions;
using Altinn.App.Api.Helpers.Patch;
using Altinn.App.Api.Helpers.RequestHandling;
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Mappers;
Expand All @@ -16,7 +18,6 @@
using Altinn.App.Core.Internal.Data;
using Altinn.App.Core.Internal.Events;
using Altinn.App.Core.Internal.Instances;
using Altinn.App.Core.Internal.Patch;
using Altinn.App.Core.Internal.Prefill;
using Altinn.App.Core.Internal.Process;
using Altinn.App.Core.Internal.Profile;
Expand Down Expand Up @@ -69,7 +70,7 @@ public class InstancesController : ControllerBase
private readonly IOrganizationClient _orgClient;
private readonly IHostEnvironment _env;
private readonly ModelSerializationService _serializationService;
private readonly IPatchService _patchService;
private readonly InternalPatchService _patchService;
private static readonly JsonSerializerOptions _jsonSerializerOptionsWeb = new(JsonSerializerDefaults.Web);
private const long RequestSizeLimit = 2000 * 1024 * 1024;

Expand All @@ -94,7 +95,7 @@ public InstancesController(
IOrganizationClient orgClient,
IHostEnvironment env,
ModelSerializationService serializationService,
IPatchService patchService
InternalPatchService patchService
)
{
_logger = logger;
Expand Down Expand Up @@ -348,6 +349,11 @@ public async Task<ActionResult<Instance>> Post(
var prefillProblem = await StorePrefillParts(instance, application, requestParts, language);
if (prefillProblem is not null)
{
await _instanceClient.DeleteInstance(
int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture),
Guid.Parse(instance.Id.Split("/")[1]),
hard: true
);
return StatusCode(prefillProblem.Status ?? 500, prefillProblem);
}

Expand Down Expand Up @@ -1193,23 +1199,12 @@ string action
var changes = dataMutator.GetDataElementChanges(initializeAltinnRowId: true);
await _patchService.RunDataProcessors(dataMutator, changes, taskId, language);

if (dataMutator.AbandonIssues.Count > 0)
if (dataMutator.GetAbandonResponse() is { } abandonResponse)
{
_logger.LogWarning(
"Data processing failed for one or more data elements, the instance was created, but we try to delete the instance"
);
await _instanceClient.DeleteInstance(
int.Parse(instance.Id.Split("/")[0], CultureInfo.InvariantCulture),
Guid.Parse(instance.Id.Split("/")[1]),
hard: true
);
return new ProblemDetails
{
Title = "Data processing failed",
Detail =
"Data processing failed for one or more data elements, the instance was created, but the data was ignored",
Extensions = { { "issues", dataMutator.AbandonIssues }, { "instance", instance } }
};
return abandonResponse;
}

// Update the changes list if it changed in data processors
Expand Down
34 changes: 34 additions & 0 deletions src/Altinn.App.Api/Extensions/InstanceDataUnitOfWorkExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Altinn.App.Core.Internal.Data;
using Altinn.App.Core.Models.Validation;
using Microsoft.AspNetCore.Mvc;

namespace Altinn.App.Api.Extensions;

internal static class InstanceDataUnitOfWorkExtensions
{
internal class FileValidationIssueProblemDetails : ProblemDetails
{
public List<ValidationIssueWithSource>? UploadValidationIssues { get; set; }
}

public static ProblemDetails? GetAbandonResponse(this InstanceDataUnitOfWork instanceDataUnitOfWork)
{
if (instanceDataUnitOfWork.HasAbandonIssues)
{
var issues = instanceDataUnitOfWork
.AbandonIssues.Select(issue =>
ValidationIssueWithSource.FromIssue(issue, "DataProcessorAbandon", noIncrementalUpdates: false)
)
.ToList();
return new FileValidationIssueProblemDetails
{
Title = "File validation failed",
Detail = "Validation issues were found in the file upload",
Status = 400,
UploadValidationIssues = issues,
};
}

return null;
}
}
2 changes: 2 additions & 0 deletions src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Altinn.App.Api.Controllers.Attributes;
using Altinn.App.Api.Controllers.Conventions;
using Altinn.App.Api.Helpers;
using Altinn.App.Api.Helpers.Patch;
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Infrastructure.Health;
using Altinn.App.Api.Infrastructure.Middleware;
Expand Down Expand Up @@ -101,6 +102,7 @@ IWebHostEnvironment env
AddAntiforgery(services);

services.AddSingleton<IAuthorizationHandler, AppAccessHandler>();
services.AddTransient<InternalPatchService>();

services.Configure<KestrelServerOptions>(options =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
using Altinn.App.Core.Models.Validation;
using Altinn.Platform.Storage.Interface.Models;

namespace Altinn.App.Core.Internal.Patch;
namespace Altinn.App.Api.Helpers.Patch;

/// <summary>
/// Result of a data patch operation in the <see cref="IPatchService"/>.
/// Result of a data patch operation in the <see cref="InternalPatchService"/>.
/// </summary>
public class DataPatchResult
{
/// <summary>
/// The updated instance after the patch and dataProcessing operations.
/// </summary>
public required Instance Instance { get; set; }
public required Instance Instance { get; init; }

/// <summary>
/// The validation issues that were found during the patch operation.
Expand All @@ -23,11 +23,4 @@ public class DataPatchResult
/// Get updated data elements that have app logic in a dictionary with the data element id as key.
/// </summary>
public required DataElementChanges FormDataChanges { get; init; }

/// <summary>
/// Store a pair with Id and Data
/// </summary>
/// <param name="Identifier">The data element id</param>
/// <param name="Data">The deserialized data</param>
public record DataModelPair(DataElementIdentifier Identifier, object Data);
}
Loading
Loading