diff --git a/backend/src/Designer/Controllers/Preview/DataController.cs b/backend/src/Designer/Controllers/Preview/DataController.cs index d4515b42cf9..7db47c2cccc 100644 --- a/backend/src/Designer/Controllers/Preview/DataController.cs +++ b/backend/src/Designer/Controllers/Preview/DataController.cs @@ -1,19 +1,11 @@ using System; using System.Collections.Generic; using System.Text.Json.Nodes; -using System.Threading; -using System.Threading.Tasks; -using System.Web; using Altinn.Platform.Storage.Interface.Models; using Altinn.Studio.Designer.Filters; -using Altinn.Studio.Designer.Helpers; -using Altinn.Studio.Designer.Models; using Altinn.Studio.Designer.Models.Preview; -using Altinn.Studio.Designer.Services.Implementation; -using Altinn.Studio.Designer.Services.Interfaces; using Altinn.Studio.Designer.Services.Interfaces.Preview; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Altinn.Studio.Designer.Controllers.Preview @@ -21,30 +13,61 @@ namespace Altinn.Studio.Designer.Controllers.Preview [Authorize] [AutoValidateAntiforgeryToken] [Route("{org:regex(^(?!designer))}/{app:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/instances/{partyId}/{instanceGuid}/data")] - public class DataController(IHttpContextAccessor httpContextAccessor, - IPreviewService previewService, - ISchemaModelService schemaModelService, + public class DataController( + IInstanceService instanceService, IDataService dataService ) : Controller { [HttpGet("{dataGuid}")] - public ActionResult Get([FromRoute] Guid dataGuid) + [UseSystemTextJson] + public ActionResult Get( + [FromRoute] Guid dataGuid + ) { JsonNode dataItem = dataService.GetDataElement(dataGuid); return Ok(dataItem); } [HttpPost] - public ActionResult Post( + [UseSystemTextJson] + public ActionResult Post( [FromRoute] int partyId, [FromRoute] Guid instanceGuid, [FromQuery] string dataType ) { DataElement dataElement = dataService.CreateDataElement(partyId, instanceGuid, dataType); + instanceService.AddDataElement(instanceGuid, dataElement); return Created("link-to-app-placeholder", dataElement); } + [HttpPatch] + [UseSystemTextJson] + public ActionResult PatchMultiple( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] int partyId, + [FromRoute] Guid instanceGuid, + [FromBody] DataPatchRequestMultiple dataPatch + ) + { + Instance instance = instanceService.GetInstance(instanceGuid); + + List newDataModels = []; + dataPatch.Patches.ForEach(patch => + { + JsonNode dataItem = dataService.PatchDataElement(patch.DataElementId, patch.Patch); + newDataModels.Add(new DataModelPairResponse(patch.DataElementId, dataItem)); + }); + + return Ok(new DataPatchResponseMultiple() + { + ValidationIssues = [], + NewDataModels = newDataModels, + Instance = instance, + }); + } + [HttpPatch("{dataGuid}")] [UseSystemTextJson] public ActionResult Patch( @@ -60,71 +83,30 @@ [FromBody] DataPatchRequest dataPatch }); } - [HttpDelete("{dataTypeId}")] - public ActionResult DeleteAttachment([FromRoute] Guid dataGuid) + [HttpDelete("{dataGuid}")] + public ActionResult Delete( + [FromRoute] Guid instanceGuid, + [FromRoute] Guid dataGuid + ) { + instanceService.RemoveDataElement(instanceGuid, dataGuid); return Ok(); } [HttpGet("{dataGuid}/validate")] - public ActionResult ValidateInstanceForData([FromRoute] Guid dataGuid) - { - return Ok(new List()); - } - - [HttpPost("{dataTypeId}/tags")] - public ActionResult UpdateTagsForAttachment([FromBody] string tag) - { - return Created("link-to-app-placeholder", tag); - } - - [HttpGet(PreviewService.MockDataTaskId)] - public async Task GetDefaultFormData( - [FromRoute] string org, - [FromRoute] string app, - [FromRoute] int partyId, - CancellationToken cancellationToken + public ActionResult ValidateInstanceForData( + [FromRoute] Guid dataGuid ) { - string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); - string refererHeader = Request.Headers.Referer; - string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); - DataType dataType = await previewService.GetDataTypeForLayoutSetName(org, app, developer, layoutSetName, cancellationToken); - // For apps that does not have a datamodel - if (dataType == null) - { - Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); - return Ok(mockInstance.Id); - } - string modelPath = $"/App/models/{dataType.Id}.schema.json"; - string decodedPath = Uri.UnescapeDataString(modelPath); - string formData = await schemaModelService.GetSchema(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer), decodedPath, cancellationToken); - return Ok(formData); + return Ok(new List()); } - [HttpPut(PreviewService.MockDataTaskId)] - public async Task UpdateFormData( - [FromRoute] string org, - [FromRoute] string app, - [FromRoute] int partyId, - CancellationToken cancellationToken + [HttpPost("{dataGuid}/tags")] + public ActionResult UpdateTagsForAttachment( + [FromBody] string tag ) { - return await GetDefaultFormData(org, app, partyId, cancellationToken); - } - - [HttpPatch(PreviewService.MockDataTaskId)] - public ActionResult PatchFormData() - { - return Ok(); - } - - private static string GetSelectedLayoutSetInEditorFromRefererHeader(string refererHeader) - { - Uri refererUri = new(refererHeader); - string layoutSetName = HttpUtility.ParseQueryString(refererUri.Query)["selectedLayoutSet"]; - - return string.IsNullOrEmpty(layoutSetName) ? null : layoutSetName; + return Created("link-to-app-placeholder", tag); } } } diff --git a/backend/src/Designer/Controllers/Preview/InstancesController.cs b/backend/src/Designer/Controllers/Preview/InstancesController.cs index 12bfaa02504..c9bdede77d0 100644 --- a/backend/src/Designer/Controllers/Preview/InstancesController.cs +++ b/backend/src/Designer/Controllers/Preview/InstancesController.cs @@ -2,203 +2,212 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using System.Web; using Altinn.App.Core.Internal.Process.Elements; using Altinn.Platform.Storage.Interface.Models; +using Altinn.Studio.Designer.Filters; using Altinn.Studio.Designer.Helpers; using Altinn.Studio.Designer.Infrastructure.GitRepository; +using Altinn.Studio.Designer.Models.App; using Altinn.Studio.Designer.Services.Interfaces; +using Altinn.Studio.Designer.Services.Interfaces.Preview; using LibGit2Sharp; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -namespace Altinn.Studio.Designer.Controllers.Preview +namespace Altinn.Studio.Designer.Controllers.Preview; + +[Authorize] +[AutoValidateAntiforgeryToken] +[Route("{org:regex(^(?!designer))}/{app:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/instances")] +public class InstancesController(IHttpContextAccessor httpContextAccessor, + IPreviewService previewService, + IAltinnGitRepositoryFactory altinnGitRepositoryFactory, + IInstanceService instanceService +) : Controller { - [Authorize] - [AutoValidateAntiforgeryToken] - [Route("{org:regex(^(?!designer))}/{app:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/instances")] - public class InstancesController(IHttpContextAccessor httpContextAccessor, - IPreviewService previewService, - IAltinnGitRepositoryFactory altinnGitRepositoryFactory - ) : Controller + + /// + /// Get instance data + /// + [HttpGet("{partyId}/{instanceGuid}")] + [UseSystemTextJson] + public ActionResult GetInstance( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] int partyId, + [FromRoute] Guid instanceGuid, + CancellationToken cancellationToken) { - /// - /// Action for creating the mocked instance object - /// - /// Unique identifier of the organisation responsible for the app. - /// Application identifier which is unique within an organisation. - /// - /// A that observes if operation is cancelled. - /// The mocked instance object - [HttpPost] - public async Task> Instances(string org, string app, [FromQuery] int? instanceOwnerPartyId, CancellationToken cancellationToken) - { - string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); - string refererHeader = Request.Headers["Referer"]; - string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); - Instance mockInstance = await previewService.GetMockInstance(org, app, developer, instanceOwnerPartyId, layoutSetName, cancellationToken); - return Ok(mockInstance); - } + Instance instanceData = instanceService.GetInstance(instanceGuid); + return Ok(instanceData); + } - /// - /// Action for getting a mocked response for the current task connected to the instance - /// - /// The processState - [HttpGet("{partyId}/{instanceGuId}/process")] - public async Task> Process(string org, string app, [FromRoute] int partyId, CancellationToken cancellationToken) - { - string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); - string refererHeader = Request.Headers["Referer"]; - string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); - Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); - List tasks = await previewService.GetTasksForAllLayoutSets(org, app, developer, cancellationToken); - AppProcessState processState = new AppProcessState(mockInstance.Process) - { - ProcessTasks = tasks != null - ? new List(tasks?.ConvertAll(task => new AppProcessTaskTypeInfo { ElementId = task, AltinnTaskType = "data" })) - : null - }; + /// + /// Create a new instance + /// + [HttpPost] + [UseSystemTextJson] + public async Task> Post( + [FromRoute] string org, + [FromRoute] string app, + [FromQuery] int instanceOwnerPartyId, + [FromQuery] string taskId, + [FromQuery] string language = null + ) + { + string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); + AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); + ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(); - return Ok(processState); - } + Instance instance = instanceService.CreateInstance(org, app, instanceOwnerPartyId, taskId, applicationMetadata.DataTypes); + return Ok(instance); + } - /// - /// Endpoint to get instance for next process step - /// - /// A mocked instance object - [HttpGet("{partyId}/{instanceGuId}")] - public async Task> InstanceForNextTask(string org, string app, [FromRoute] int partyId, CancellationToken cancellationToken) - { - string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); - string refererHeader = Request.Headers["Referer"]; - string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); - Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); - return Ok(mockInstance); - } + /// + /// Endpoint to get active instances for apps with state/layout sets/multiple processes + /// + /// A list of a single mocked instance + [HttpGet("{partyId}/active")] + public ActionResult> ActiveInstancesForAppsWithLayoutSets( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] int partyId + ) + { + // Simulate never having any active instances + List activeInstances = new(); + return Ok(activeInstances); + } - /// - /// Endpoint to get active instances for apps with state/layout sets/multiple processes - /// - /// A list of a single mocked instance - [HttpGet("{partyId}/active")] - public ActionResult> ActiveInstancesForAppsWithLayoutSets(string org, string app, [FromRoute] int partyId) - { - // Simulate never having any active instances - List activeInstances = new(); - return Ok(activeInstances); - } + /// + /// Endpoint to validate an instance + /// + /// Ok + [HttpGet("{partyId}/{instanceGuId}/validate")] + public ActionResult ValidateInstance() + { + return Ok(); + } - /// - /// Endpoint to validate an instance - /// - /// Ok - [HttpGet("{partyId}/{instanceGuId}/validate")] - public ActionResult ValidateInstance() + /// + /// Action for getting a mocked response for the current task connected to the instance + /// + /// The processState + [HttpGet("{partyId}/{instanceGuid}/process")] + public async Task> Process( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] int partyId, + [FromRoute] Guid instanceGuid, + CancellationToken cancellationToken) + { + string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); + Instance instance = instanceService.GetInstance(instanceGuid); + List tasks = await previewService.GetTasksForAllLayoutSets(org, app, developer, cancellationToken); + AppProcessState processState = new(instance.Process) { - return Ok(); - } + ProcessTasks = tasks != null + ? new List(tasks?.ConvertAll(task => new AppProcessTaskTypeInfo { ElementId = task, AltinnTaskType = "data" })) + : null + }; - /// - /// Action for getting a mocked response for the next task connected to the instance - /// - /// The processState object on the global mockInstance object - [HttpGet("{partyId}/{instanceGuId}/process/next")] - public async Task ProcessNext(string org, string app, [FromRoute] int partyId, CancellationToken cancellationToken) - { - string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); - string refererHeader = Request.Headers["Referer"]; - string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); - Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); - return Ok(mockInstance.Process); - } + return Ok(processState); + } - /// - /// Action for mocking an end to the process in order to get receipt after "send inn" is pressed - /// - /// Process object where ended is set - [HttpPut("{partyId}/{instanceGuId}/process/next")] - public async Task UpdateProcessNext(string org, string app, [FromRoute] int partyId, [FromQuery] string lang, CancellationToken cancellationToken) - { - string refererHeader = Request.Headers["Referer"]; - string layoutSetName = GetSelectedLayoutSetInEditorFromRefererHeader(refererHeader); - if (string.IsNullOrEmpty(layoutSetName)) - { - string endProcess = """{"ended": "ended"}"""; - return Ok(endProcess); - } - string developer = AuthenticationHelper.GetDeveloperUserName(httpContextAccessor.HttpContext); - Instance mockInstance = await previewService.GetMockInstance(org, app, developer, partyId, layoutSetName, cancellationToken); - return Ok(mockInstance.Process); - } + /// + /// Action for getting a mocked response for the next task connected to the instance + /// + /// The processState object on the global mockInstance object + [HttpGet("{partyId}/{instanceGuid}/process/next")] + public ActionResult ProcessNext( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] int partyId, + [FromRoute] Guid instanceGuid, + CancellationToken cancellationToken + ) + { + Instance instance = instanceService.GetInstance(instanceGuid); + return Ok(instance.Process); + } - /// - /// Action for getting options list for a given options list id for a given instance - /// - /// Unique identifier of the organisation responsible for the app. - /// Application identifier which is unique within an organisation. - /// The id of the options list - /// The language for the options list - /// The source of the options list - /// A that observes if operation is cancelled. - /// The options list if it exists, otherwise nothing - [HttpGet("{partyId}/{instanceGuid}/options/{optionListId}")] - public async Task> GetOptionsForInstance(string org, string app, string optionListId, [FromQuery] string language, [FromQuery] string source, CancellationToken cancellationToken) - { - try - { - // TODO: Need code to get dynamic options list based on language and source? - string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); - AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); - string options = await altinnAppGitRepository.GetOptionsList(optionListId, cancellationToken); - return Ok(options); - } - catch (NotFoundException) - { - // Return empty list since app-frontend don't handle a null result - return Ok(new List()); - } - } + /// + /// Action for mocking an end to the process in order to get receipt after "send inn" is pressed + /// + /// Process object where ended is set + [HttpPut("{partyId}/{instanceGuid}/process/next")] + public async Task UpdateProcessNext( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] int partyId, + [FromRoute] Guid instanceGuid, + [FromQuery] string lang, + CancellationToken cancellationToken + ) + { + Instance instance = instanceService.GetInstance(instanceGuid); + return Ok(instance.Process); + } - /// - /// Action for getting data list for a given data list id for a given instance - /// - /// Unique identifier of the organisation responsible for the app. - /// Application identifier which is unique within an organisation. - /// The id of the data list - /// The language for the data list - /// The number of items to return - /// A that observes if operation is cancelled. - /// The options list if it exists, otherwise nothing - [HttpGet("{partyId}/{instanceGuid}/datalists/{dataListId}")] - public ActionResult> GetDataListsForInstance(string org, string app, string dataListId, [FromQuery] string language, [FromQuery] string size, CancellationToken cancellationToken) + /// + /// Action for getting options list for a given options list id for a given instance + /// + [HttpGet("{partyId}/{instanceGuid}/options/{optionListId}")] + public async Task> GetOptionsForInstance( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] string optionListId, + [FromQuery] string language, + [FromQuery] string source, + CancellationToken cancellationToken + ) + { + try { - // TODO: Should look into whether we can get some actual data here, or if we can make an "informed" mock based on the setup. - // For now, we just return an empty list. - return Ok(new List()); + // TODO: Need code to get dynamic options list based on language and source? + string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); + AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); + string options = await altinnAppGitRepository.GetOptionsList(optionListId, cancellationToken); + return Ok(options); } - - /// - /// Action for updating data model with tag for attachment component // TODO: Figure out what actually happens here - /// - /// Unique identifier of the organisation responsible for the app. - /// Application identifier which is unique within an organisation. - /// Current page in running app - /// Current layout set in running app - /// Connected datatype for that process task - /// The options list if it exists, otherwise nothing - [HttpPost("{partyId}/{instanceGuid}/pages/order")] - public IActionResult UpdateAttachmentWithTag(string org, string app, [FromQuery] string currentPage, [FromQuery] string layoutSetId, [FromQuery] string dataTypeId) + catch (NotFoundException) { - return Ok(); + // Return empty list since app-frontend don't handle a null result + return Ok(new List()); } + } - private static string GetSelectedLayoutSetInEditorFromRefererHeader(string refererHeader) - { - Uri refererUri = new(refererHeader); - string layoutSetName = HttpUtility.ParseQueryString(refererUri.Query)["selectedLayoutSet"]; + /// + /// Action for getting data list for a given data list id for a given instance + /// + [HttpGet("{partyId}/{instanceGuid}/datalists/{dataListId}")] + public ActionResult> GetDataListsForInstance( + [FromRoute] string org, + [FromRoute] string app, + [FromRoute] string dataListId, + [FromQuery] string language, + [FromQuery] string size, + CancellationToken cancellationToken + ) + { + // TODO: Should look into whether we can get some actual data here, or if we can make an "informed" mock based on the setup. + // For now, we just return an empty list. + return Ok(new List()); + } - return string.IsNullOrEmpty(layoutSetName) ? null : layoutSetName; - } + /// + /// Action for updating data model with tag for attachment component // TODO: Figure out what actually happens here + /// + [HttpPost("{partyId}/{instanceGuid}/pages/order")] + public IActionResult UpdateAttachmentWithTag( + [FromRoute] string org, + [FromRoute] string app, + [FromQuery] string currentPage, + [FromQuery] string layoutSetId, + [FromQuery] string dataTypeId + ) + { + return Ok(); } } diff --git a/backend/src/Designer/Controllers/PreviewController.cs b/backend/src/Designer/Controllers/PreviewController.cs index 5a3c74245d3..1ca91566189 100644 --- a/backend/src/Designer/Controllers/PreviewController.cs +++ b/backend/src/Designer/Controllers/PreviewController.cs @@ -142,12 +142,6 @@ public async Task> ApplicationMetadata(string string appNugetVersionString = _appDevelopmentService.GetAppLibVersion(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer)).ToString(); // This property is populated at runtime by the apps, so we need to mock it here applicationMetadata.AltinnNugetVersion = GetMockedAltinnNugetBuildFromVersion(appNugetVersionString); - if (altinnAppGitRepository.AppUsesLayoutSets()) - { - LayoutSets layoutSets = await altinnAppGitRepository.GetLayoutSetsFile(cancellationToken); - applicationMetadata = SetMockDataTypeIfMissing(applicationMetadata, layoutSets); - } - applicationMetadata = SetMockedPartyTypesAllowedAsAllFalse(applicationMetadata); return Ok(applicationMetadata); } @@ -192,8 +186,7 @@ public async Task> LayoutSets(string org, string app, C string developer = AuthenticationHelper.GetDeveloperUserName(_httpContextAccessor.HttpContext); AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); LayoutSets layoutSets = await altinnAppGitRepository.GetLayoutSetsFile(cancellationToken); - LayoutSets layoutSetsWithMockedDataTypes = AddDataTypesToReturnedLayoutSetsIfMissing(layoutSets); - return Ok(layoutSetsWithMockedDataTypes); + return Ok(layoutSets); } catch (NotFoundException) { diff --git a/backend/src/Designer/Infrastructure/ServiceRegistration.cs b/backend/src/Designer/Infrastructure/ServiceRegistration.cs index 8aba45e2e11..5d3b0607d8b 100644 --- a/backend/src/Designer/Infrastructure/ServiceRegistration.cs +++ b/backend/src/Designer/Infrastructure/ServiceRegistration.cs @@ -73,6 +73,7 @@ public static IServiceCollection RegisterServiceImplementations(this IServiceCol services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.RegisterDatamodeling(configuration); diff --git a/backend/src/Designer/Models/Preview/DataPatchRequestMultiple.cs b/backend/src/Designer/Models/Preview/DataPatchRequestMultiple.cs new file mode 100644 index 00000000000..bdc0da5eb08 --- /dev/null +++ b/backend/src/Designer/Models/Preview/DataPatchRequestMultiple.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Json.Patch; + +namespace Altinn.Studio.Designer.Models.Preview; + +/// +/// Represents the request to patch data on the . +/// This version allows multiple patches to be applied by the same request. +/// +public class DataPatchRequestMultiple +{ + /// + /// List of patches to apply. + /// + [JsonPropertyName("patches")] + public required List Patches { get; init; } + + /// + /// Item class for the list of Patches + /// + /// The guid of the data element to patch + /// The patch to apply + public record PatchListItem + ( + [property: JsonPropertyName("dataElementId")] Guid DataElementId, + [property: JsonPropertyName("patch")] JsonPatch Patch + ); + + public required List? IgnoredValidators { get; init; } +} diff --git a/backend/src/Designer/Models/Preview/DataPatchResponseMultiple.cs b/backend/src/Designer/Models/Preview/DataPatchResponseMultiple.cs new file mode 100644 index 00000000000..36889b4835e --- /dev/null +++ b/backend/src/Designer/Models/Preview/DataPatchResponseMultiple.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.Studio.Designer.Models.Preview; + +/// +/// Represents the response from a data patch operation on the . +/// +public class DataPatchResponseMultiple +{ + /// + /// The validation issues that were found during the patch operation. + /// + [JsonPropertyName("validationIssues")] + public required List ValidationIssues { get; init; } + + /// + /// The current data in all data models updated by the patch operation. + /// + [JsonPropertyName("newDataModels")] + public required List NewDataModels { get; init; } + + /// + /// The instance with updated dataElement list. + /// + [JsonPropertyName("instance")] + public required Instance Instance { get; init; } +} + +/// +/// Pair of Guid and data object. +/// +/// The guid of the DataElement +/// The form data of the data element +public record DataModelPairResponse( + [property: JsonPropertyName("dataElementId")] Guid DataElementId, + [property: JsonPropertyName("data")] object Data +); diff --git a/backend/src/Designer/Models/Preview/DataPostResponse.cs b/backend/src/Designer/Models/Preview/DataPostResponse.cs new file mode 100644 index 00000000000..d4886d7212b --- /dev/null +++ b/backend/src/Designer/Models/Preview/DataPostResponse.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json.Serialization; +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Studio.Designer.Models.Preview; + +/// +/// Response object for POST and DELETE to /org/app/instances/{instanceOwnerPartyId:int}/{instanceGuid:guid}/data/{dataType} +/// +public class DataPostResponse +{ + /// + /// The Id of the created data element + /// + [JsonPropertyName("newDataElementId")] + public required Guid NewDataElementId { get; init; } + + /// + /// The instance with updated data + /// + [JsonPropertyName("instance")] + public required Instance Instance { get; init; } + + /// + /// List of validation issues that reported to have relevant changes after a new data element was added + /// + [JsonPropertyName("validationIssues")] + public required List ValidationIssues { get; init; } + + /// + /// List of updated DataModels caused by dataProcessing + /// + [JsonPropertyName("newDataModels")] + public required List NewDataModels { get; init; } +} + +/// +/// Extension of ProblemDetails to include Validation issues from the file upload. +/// +public class DataPostErrorResponse : ProblemDetails +{ + /// + /// Constructor for simple initialization from upload validation issues. + /// + public DataPostErrorResponse(string detail, List validationIssues) + { + Title = "File validation failed"; + Detail = detail; + Status = (int)HttpStatusCode.BadRequest; + UploadValidationIssues = validationIssues; + } + + /// + /// List of the validators that reported to have relevant changes after a new data element was added + /// + [JsonPropertyName("uploadValidationIssues")] + public List UploadValidationIssues { get; } +} diff --git a/backend/src/Designer/Models/Preview/ValidationIssueWithSource.cs b/backend/src/Designer/Models/Preview/ValidationIssueWithSource.cs new file mode 100644 index 00000000000..f8996115d7b --- /dev/null +++ b/backend/src/Designer/Models/Preview/ValidationIssueWithSource.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Altinn.App.Core.Models.Validation; + +namespace Altinn.Studio.Designer.Models.Preview; + +/// +/// Represents a detailed message from validation. +/// +public class ValidationIssueWithSource +{ + /// + /// Converter function to create a from a and adding a source. + /// + public static ValidationIssueWithSource FromIssue(ValidationIssue issue, string source, bool noIncrementalUpdates) + { + return new ValidationIssueWithSource + { + Severity = issue.Severity, + DataElementId = issue.DataElementId, + Field = issue.Field, + Code = issue.Code, + Description = issue.Description, + Source = source, + NoIncrementalUpdates = noIncrementalUpdates, + CustomTextKey = issue.CustomTextKey, + CustomTextParams = issue.CustomTextParams, + }; + } + + /// + /// The seriousness of the identified issue. + /// + /// + /// This property is serialized in json as a number + /// 1: Error (something needs to be fixed) + /// 2: Warning (does not prevent submission) + /// 3: Information (hint shown to the user) + /// 4: Fixed (obsolete, only used for v3 of frontend) + /// 5: Success (Inform the user that something was completed with success) + /// + [JsonPropertyName("severity")] + [JsonConverter(typeof(JsonNumberEnumConverter))] + public required ValidationIssueSeverity Severity { get; set; } + + /// + /// The unique id of the data element of a given instance with the identified issue. + /// + [JsonPropertyName("dataElementId")] + public string? DataElementId { get; set; } + + /// + /// A reference to a property the issue is about. + /// + [JsonPropertyName("field")] + public string? Field { get; set; } + + /// + /// A system readable identification of the type of issue. + /// Eg: + /// + [JsonPropertyName("code")] + public required string? Code { get; set; } + + /// + /// A human readable description of the issue. + /// + [JsonPropertyName("description")] + public required string? Description { get; set; } + + /// + /// The short name of the class that crated the message (set automatically after return of list) + /// + [JsonPropertyName("source")] + public required string Source { get; set; } + + /// + /// Weather the issue is from a validator that correctly implements . + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + [JsonPropertyName("noIncrementalUpdates")] + public bool NoIncrementalUpdates { get; set; } + + /// + /// The custom text key to use for the localized text in the frontend. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("customTextKey")] + public string? CustomTextKey { get; set; } + + /// + /// might include some parameters (typically the field value, or some derived value) + /// that should be included in error message. + /// + /// + /// The localized text for the key might be "Date must be between {0} and {1}" + /// and the param will provide the dynamical range of allowable dates (eg teh reporting period) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("customTextParams")] + public List? CustomTextParams { get; set; } +} + +/// +/// API responses that returns validation issues grouped by source, typically return a list of these. +/// +/// The for the Validator that created theese issues +/// List of issues +public record ValidationSourcePair( + [property: JsonPropertyName("source")] string Source, + [property: JsonPropertyName("issues")] List Issues +); diff --git a/backend/src/Designer/Services/Implementation/Preview/DataService.cs b/backend/src/Designer/Services/Implementation/Preview/DataService.cs index 75a00b64155..5f6ebca7fcd 100644 --- a/backend/src/Designer/Services/Implementation/Preview/DataService.cs +++ b/backend/src/Designer/Services/Implementation/Preview/DataService.cs @@ -10,7 +10,7 @@ namespace Altinn.Studio.Designer.Services.Implementation.Preview; public class DataService( IDistributedCache distributedCache - ) : IDataService +) : IDataService { readonly DistributedCacheEntryOptions _cacheOptions = new() { diff --git a/backend/src/Designer/Services/Implementation/Preview/InstanceService.cs b/backend/src/Designer/Services/Implementation/Preview/InstanceService.cs new file mode 100644 index 00000000000..adafb949868 --- /dev/null +++ b/backend/src/Designer/Services/Implementation/Preview/InstanceService.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using Altinn.Platform.Storage.Interface.Models; +using Altinn.Studio.Designer.Services.Interfaces.Preview; +using Microsoft.Extensions.Caching.Distributed; + +namespace Altinn.Studio.Designer.Services.Implementation.Preview; + +public class InstanceService( + IDistributedCache distributedCache, + IDataService dataService +) : IInstanceService +{ + readonly DistributedCacheEntryOptions _cacheOptions = new() + { + SlidingExpiration = TimeSpan.FromMinutes(30), + }; + + public Instance CreateInstance(string org, string app, int partyId, string taskId, List dataTypes) + { + Guid instanceGuid = Guid.NewGuid(); + Instance instance = new() + { + InstanceOwner = new InstanceOwner { PartyId = partyId.ToString() }, + Id = $"{instanceGuid}", + AppId = app, + Data = [], + Org = org, + Process = new ProcessState + { + CurrentTask = new ProcessElementInfo + { + AltinnTaskType = "data", + ElementId = taskId + } + } + }; + dataTypes.ForEach(dataType => + { + if (dataType.AppLogic?.AutoCreate == true) + { + DataElement dataElement = dataService.CreateDataElement(partyId, instanceGuid, dataType.Id); + instance.Data.Add(dataElement); + } + }); + distributedCache.SetString(instance.Id, JsonSerializer.Serialize(instance), _cacheOptions); + return instance; + } + + public Instance GetInstance(Guid instanceGuid) + { + string instanceJson = distributedCache.GetString(instanceGuid.ToString()); + Instance instanceElement = JsonSerializer.Deserialize(instanceJson); + return instanceElement; + } + + public Instance AddDataElement(Guid instanceGuid, DataElement dataElement) + { + string instanceJson = distributedCache.GetString(instanceGuid.ToString()); + Instance instanceElement = JsonSerializer.Deserialize(instanceJson); + instanceElement.Data.Add(dataElement); + distributedCache.SetString(instanceGuid.ToString(), JsonSerializer.Serialize(instanceElement), _cacheOptions); + return instanceElement; + } + + public Instance RemoveDataElement(Guid instanceGuid, Guid dataElementGuid) + { + string instanceJson = distributedCache.GetString(instanceGuid.ToString()); + Instance instanceElement = JsonSerializer.Deserialize(instanceJson); + instanceElement.Data.RemoveAll(dataElement => dataElement.Id == dataElementGuid.ToString()); + distributedCache.SetString(instanceGuid.ToString(), JsonSerializer.Serialize(instanceElement), _cacheOptions); + return instanceElement; + } +} diff --git a/backend/src/Designer/Services/Implementation/PreviewService.cs b/backend/src/Designer/Services/Implementation/PreviewService.cs index aa88aec4e41..45812a02f99 100644 --- a/backend/src/Designer/Services/Implementation/PreviewService.cs +++ b/backend/src/Designer/Services/Implementation/PreviewService.cs @@ -6,7 +6,9 @@ using Altinn.Platform.Storage.Interface.Models; using Altinn.Studio.Designer.Infrastructure.GitRepository; using Altinn.Studio.Designer.Models; +using Altinn.Studio.Designer.Models.App; using Altinn.Studio.Designer.Services.Interfaces; +using Altinn.Studio.Designer.Services.Interfaces.Preview; using LibGit2Sharp; namespace Altinn.Studio.Designer.Services.Implementation; @@ -14,21 +16,17 @@ namespace Altinn.Studio.Designer.Services.Implementation; /// /// Service for handling a mocked instance object for preview mode /// -public class PreviewService : IPreviewService +/// +/// Constructor +/// +/// IAltinnGitRepository +/// +public class PreviewService(IAltinnGitRepositoryFactory altinnGitRepositoryFactory, IDataService dataService) : IPreviewService { - private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory; + private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory = altinnGitRepositoryFactory; public const string MockDataModelIdPrefix = "MockDataModel"; public const string MockDataTaskId = "test-datatask-id"; - /// - /// Constructor - /// - /// IAltinnGitRepository - public PreviewService(IAltinnGitRepositoryFactory altinnGitRepositoryFactory) - { - _altinnGitRepositoryFactory = altinnGitRepositoryFactory; - } - /// public async Task GetMockInstance(string org, string app, string developer, int? instanceOwnerPartyId, string layoutSetName, CancellationToken cancellationToken = default) { @@ -128,6 +126,41 @@ public async Task GetTaskForLayoutSetName(string org, string app, string return task; } + /*/// */ + /*public async Task CreateInstance(string org, string app, string developer, string taskId, int instanceOwnerPartyId)*/ + /*{*/ + /* AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);*/ + /* ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata();*/ + /* Guid guid = Guid.NewGuid();*/ + /* Instance instance = new()*/ + /* {*/ + /* InstanceOwner = new InstanceOwner { PartyId = instanceOwnerPartyId.ToString() },*/ + /* Id = $"{guid}",*/ + /* AppId = applicationMetadata.Id,*/ + /* Data = [],*/ + /* Org = applicationMetadata.Org,*/ + /* Process = new ProcessState*/ + /* {*/ + /* CurrentTask = new ProcessElementInfo*/ + /* {*/ + /* AltinnTaskType = "data",*/ + /* ElementId = taskId*/ + /* }*/ + /* }*/ + /* };*/ + /* applicationMetadata.DataTypes.ForEach(dataType =>*/ + /* {*/ + /* if (dataType.AppLogic?.AutoCreate == true)*/ + /* {*/ + /* Guid dataElementGuid = Guid.NewGuid();*/ + /* DataElement dataElement = dataService.CreateDataElement(instanceOwnerPartyId, dataElementGuid, dataType.Id);*/ + /* instance.Data.Add(dataElement);*/ + /* }*/ + /* });*/ + /* instanceService.CreateInstance(guid, instance);*/ + /* return instance;*/ + /*}*/ + private async Task> GetDataTypesForInstance(string org, string app, string developer, string layoutSetName, bool shouldProcessActAsReceipt) { AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); @@ -146,14 +179,14 @@ private async Task> GetDataTypesForInstance(string org, string }) .ToList(); } - return [ - // All data types attached to the current task in the process model should be added here - new DataElement - { - DataType = dataTypeForDataElement, - Id = MockDataTaskId - } - ]; + + ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(); + List dataElements = applicationMetadata.DataTypes.Select(dataType => new DataElement + { + DataType = dataType.Id, + Id = (dataTypeForDataElement != dataType.Id) ? dataType.Id : MockDataTaskId + }).ToList(); + return dataElements; } private async Task GetDataTypeForCustomReceipt(AltinnAppGitRepository altinnAppGitRepository) diff --git a/backend/src/Designer/Services/Interfaces/Preview/IDataService.cs b/backend/src/Designer/Services/Interfaces/Preview/IDataService.cs index e12c80a05bf..ac60a9db279 100644 --- a/backend/src/Designer/Services/Interfaces/Preview/IDataService.cs +++ b/backend/src/Designer/Services/Interfaces/Preview/IDataService.cs @@ -11,6 +11,7 @@ namespace Altinn.Studio.Designer.Services.Interfaces.Preview; public interface IDataService { public DataElement CreateDataElement(int partyId, Guid instanceGuid, string dataTypeId); + public JsonNode GetDataElement(Guid dataGuid); public JsonNode PatchDataElement(Guid dataGuid, JsonPatch patch); } diff --git a/backend/src/Designer/Services/Interfaces/Preview/IInstanceService.cs b/backend/src/Designer/Services/Interfaces/Preview/IInstanceService.cs new file mode 100644 index 00000000000..9567f420e33 --- /dev/null +++ b/backend/src/Designer/Services/Interfaces/Preview/IInstanceService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.Studio.Designer.Services.Interfaces.Preview; + +/// +/// Interface for handling a mocked datatype object for preview mode +/// +public interface IInstanceService +{ + public Instance CreateInstance(string org, string app, int partyId, string taskId, List dataTypes); + public Instance GetInstance(Guid instanceGuid); + + public Instance AddDataElement(Guid instanceGuid, DataElement dataElement); + public Instance RemoveDataElement(Guid instanceGuid, Guid dataElementGuid); +} diff --git a/backend/tests/Designer.Tests/Controllers/Preview/DataControllerTests.cs b/backend/tests/Designer.Tests/Controllers/Preview/DataControllerTests.cs new file mode 100644 index 00000000000..468c73aa4d3 --- /dev/null +++ b/backend/tests/Designer.Tests/Controllers/Preview/DataControllerTests.cs @@ -0,0 +1,123 @@ +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; +using Altinn.Studio.Designer.Models.Preview; +using Designer.Tests.Controllers.PreviewController; +using Microsoft.AspNetCore.Mvc.Testing; +using Xunit; + +namespace Designer.Tests.Controllers.Preview; + +public class DataControllerTests(WebApplicationFactory factory) : PreviewControllerTestsBase(factory), IClassFixture> +{ + [Fact] + public async Task Post_ReturnsCreated() + { + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, dataPathWithData); + + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + string responseBody = await response.Content.ReadAsStringAsync(); + DataElement dataElement = JsonSerializer.Deserialize(responseBody, JsonSerializerOptions); + Assert.NotNull(dataElement.Id); + Assert.Equal(instance.Id, dataElement.InstanceGuid); + } + + [Fact] + public async Task Get_ReturnsOk() + { + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "datamodel"); + + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}"; + using HttpRequestMessage httpRequestMessageGet = new(HttpMethod.Get, dataPath); + using HttpResponseMessage responseGet = await HttpClient.SendAsync(httpRequestMessageGet); + Assert.Equal(HttpStatusCode.OK, responseGet.StatusCode); + string responseBodyGet = await responseGet.Content.ReadAsStringAsync(); + JsonNode dataItem = JsonSerializer.Deserialize(responseBodyGet, JsonSerializerOptions); + Assert.NotNull(dataItem); + } + + [Fact] + public async Task Patch_ReturnsOk() + { + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "datamodel"); + + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}"; + using HttpRequestMessage httpRequestMessagePatch = new(HttpMethod.Patch, dataPath); + + string patch = "{\"patch\":[{\"op\":\"add\",\"path\":\"/RegNo\",\"value\":\"asdf\"}],\"ignoredValidators\":[\"DataAnnotations\",\"Required\",\"Expression\"]}"; + httpRequestMessagePatch.Content = new StringContent(patch, System.Text.Encoding.UTF8, "application/json"); + using HttpResponseMessage responsePatch = await HttpClient.SendAsync(httpRequestMessagePatch); + Assert.Equal(HttpStatusCode.OK, responsePatch.StatusCode); + string responseBodyPatch = await responsePatch.Content.ReadAsStringAsync(); + JsonNode dataItem = JsonSerializer.Deserialize(responseBodyPatch, JsonSerializerOptions); + Assert.NotNull(dataItem); + Assert.Equal("asdf", dataItem["newDataModel"]["RegNo"].ToString()); + } + + [Fact] + public async Task PatchMultiple_ReturnsOk() + { + Instance instance = await createInstance(); + DataElement dataElement1 = await createDataElement(instance, "datamodel"); + DataElement dataElement2 = await createDataElement(instance, "datamodel"); + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data"; + using HttpRequestMessage httpRequestMessagePatchMultiple = new(HttpMethod.Patch, dataPath); + + string patches = "{\"patches\":[{\"dataElementId\":\"" + dataElement1.Id + "\",\"patch\":[{\"op\":\"add\",\"path\":\"/RegNo\",\"value\":\"dataobj1\"}]},{\"dataElementId\":\"" + dataElement2.Id + "\",\"patch\":[{\"op\":\"add\",\"path\":\"/RegNo\",\"value\":\"dataobj2\"}]}],\"ignoredValidators\":[\"DataAnnotations\",\"Required\",\"Expression\"]}"; + httpRequestMessagePatchMultiple.Content = new StringContent(patches, System.Text.Encoding.UTF8, "application/json"); + using HttpResponseMessage responsePatchMultiple = await HttpClient.SendAsync(httpRequestMessagePatchMultiple); + Assert.Equal(HttpStatusCode.OK, responsePatchMultiple.StatusCode); + string responseBodyPatchMultiple = await responsePatchMultiple.Content.ReadAsStringAsync(); + DataPatchResponseMultiple dataItem = JsonSerializer.Deserialize(responseBodyPatchMultiple, JsonSerializerOptions); + Assert.NotNull(dataItem); + Assert.Equal(2, dataItem.NewDataModels.Count); + object dataItem1 = JsonSerializer.Serialize(dataItem.NewDataModels[0].Data); + Assert.Equal("{\"RegNo\":\"dataobj1\"}", dataItem1.ToString()); + object dataItem2 = JsonSerializer.Serialize(dataItem.NewDataModels[1].Data); + Assert.Equal("{\"RegNo\":\"dataobj2\"}", dataItem2.ToString()); + } + + [Fact] + public async Task Delete_ReturnsOk() + { + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "datamodel"); + + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}"; + using HttpRequestMessage httpRequestMessageDelete = new(HttpMethod.Delete, dataPath); + using HttpResponseMessage responseDelete = await HttpClient.SendAsync(httpRequestMessageDelete); + Assert.Equal(HttpStatusCode.OK, responseDelete.StatusCode); + } + + [Fact] + public async Task Validate_ReturnsOk() + { + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "datamodel"); + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}/validate"; + using HttpRequestMessage httpRequestMessageValidate = new(HttpMethod.Get, dataPath); + using HttpResponseMessage responseValidate = await HttpClient.SendAsync(httpRequestMessageValidate); + Assert.Equal(HttpStatusCode.OK, responseValidate.StatusCode); + } + + [Fact] + public async Task Tag_ReturnsCreateOkd() + { + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "datamodel"); + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}/tags"; + using HttpRequestMessage httpRequestMessageTag = new(HttpMethod.Post, dataPath); + httpRequestMessageTag.Content = new StringContent("{\"tag\":\"test\"}", System.Text.Encoding.UTF8, "application/json"); + using HttpResponseMessage responseTag = await HttpClient.SendAsync(httpRequestMessageTag); + Assert.Equal(HttpStatusCode.Created, responseTag.StatusCode); + } +} diff --git a/backend/tests/Designer.Tests/Controllers/Preview/InstancesControllerTests.cs b/backend/tests/Designer.Tests/Controllers/Preview/InstancesControllerTests.cs new file mode 100644 index 00000000000..716770b3e7d --- /dev/null +++ b/backend/tests/Designer.Tests/Controllers/Preview/InstancesControllerTests.cs @@ -0,0 +1,71 @@ +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; +using Designer.Tests.Controllers.PreviewController; +using Microsoft.AspNetCore.Mvc.Testing; +using Xunit; + +namespace Designer.Tests.Controllers.Preview; + +public class InstancesControllerTests( + WebApplicationFactory factory +) : PreviewControllerTestsBase(factory), IClassFixture> +{ + + [Fact] + public async Task Post_ReturnsCreated() + { + Instance instance = await createInstance(); + Assert.NotNull(instance); + Assert.NotNull(instance.Id); + } + + [Fact] + public async Task GetInstance_ReturnsOk() + { + Instance instance = await createInstance(); + Assert.NotNull(instance); + Assert.NotNull(instance.Id); + + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPath); + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + string responseBody = await response.Content.ReadAsStringAsync(); + Instance responseInstance = JsonSerializer.Deserialize(responseBody, JsonSerializerOptions); + Assert.NotNull(responseInstance); + Assert.Equal(instance.Id, responseInstance.Id); + } + + [Fact] + public async Task Validate_ReturnsOk() + { + Instance instance = await createInstance(); + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/validate"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPath); + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Process_ReturnsOk() + { + Instance instance = await createInstance(); + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/process"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPath); + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task ProcessNext_ReturnsOk() + { + Instance instance = await createInstance(); + string dataPath = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/process/next"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPath); + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } +} diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs index 3671a2b4747..c8ecc70b75d 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/ApplicationMetadataTests.cs @@ -78,42 +78,6 @@ public async Task Get_ApplicationMetadata_With_V8_Altinn_Nuget_Version_Ok() JsonUtils.DeepEquals(expectedJson, responseBody).Should().BeTrue(); } - [Fact] - public async Task Get_ApplicationMetadata_WithLessDataTypesThanLayoutSetsFile_OkWithMockedDataTypes() - { - string expectedApplicationMetadataString = TestDataHelper.GetFileFromRepo(Org, AppV4, Developer, "App/config/applicationmetadata.json"); - _appDevelopmentServiceMock - .Setup(rs => rs.GetAppLibVersion(It.IsAny())) - .Returns(NuGet.Versioning.NuGetVersion.Parse("8.0.0")); - - string dataPathWithData = $"{Org}/{AppV4}/api/v1/applicationmetadata"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - ApplicationMetadata expectedApplicationMetadata = JsonSerializer.Deserialize(expectedApplicationMetadataString, JsonSerializerOptions); - expectedApplicationMetadata.AltinnNugetVersion = "8.0.0.0"; - // Add the mocked data type to expected app metadata - expectedApplicationMetadata.DataTypes.Add(new DataType() - { - Id = $"{PreviewService.MockDataModelIdPrefix}-0", - AppLogic = new ApplicationLogic() - { - ClassRef = $"Altinn.App.Models.model.{PreviewService.MockDataModelIdPrefix}-0" - }, - TaskId = "Task_2" - }); - expectedApplicationMetadata.PartyTypesAllowed.Person = false; - expectedApplicationMetadata.PartyTypesAllowed.Organisation = false; - expectedApplicationMetadata.PartyTypesAllowed.SubUnit = false; - expectedApplicationMetadata.PartyTypesAllowed.BankruptcyEstate = false; - - string expectedJson = JsonSerializer.Serialize(expectedApplicationMetadata, JsonSerializerOptions); - JsonUtils.DeepEquals(expectedJson, responseBody).Should().BeTrue(); - } - [Fact] public async Task Get_ApplicationMetadata_WithAllPartyTypesAllowedSetToFalse() { diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/DeleteAttachmentTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/DeleteAttachmentTests.cs index 3f0ff5e14c1..2a4134d42cd 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/DeleteAttachmentTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/DeleteAttachmentTests.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -17,7 +18,9 @@ public DeleteAttachmentTests(WebApplicationFactory factory) : base(fact [Fact] public async Task Delete_Attachment_Ok() { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}/data/{AttachmentGuId}"; + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "attachment"); + string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Delete, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); @@ -28,7 +31,9 @@ public async Task Delete_Attachment_Ok() [Fact] public async Task Delete_AttachmentForV4App_Ok() { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/data/{AttachmentGuId}"; + Instance instance = await createInstance(); + DataElement dataElement = await createDataElement(instance, "attachment"); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data/{dataElement.Id}"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Delete, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormDataTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormDataTests.cs deleted file mode 100644 index eae40b51eb8..00000000000 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetFormDataTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Designer.Tests.Utils; -using FluentAssertions; -using Microsoft.AspNetCore.Mvc.Testing; -using SharedResources.Tests; -using Xunit; - -namespace Designer.Tests.Controllers.PreviewController -{ - public class GetFormDataTests : PreviewControllerTestsBase, IClassFixture> - { - - public GetFormDataTests(WebApplicationFactory factory) : base(factory) - { - } - - [Fact] - public async Task Get_FormData_Ok() - { - string expectedFormData = TestDataHelper.GetFileFromRepo(Org, PreviewApp, Developer, "App/models/custom-dm-name.schema.json"); - - string dataPathWithData = $"{Org}/{PreviewApp}/instances/{PartyId}/{InstanceGuId}/data/test-datatask-id"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={PreviewApp}&selectedLayoutSet="); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedFormData, responseBody).Should().BeTrue(); - } - - [Fact] - public async Task Get_FormDataForAppWithoutDatamodel_Ok() - { - string dataPathWithData = $"{Org}/empty-app/instances/{PartyId}/{InstanceGuId}/data/test-datatask-id"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - responseBody.Should().Be($"{PartyId}/{InstanceGuId}"); - } - - [Fact] - public async Task Get_FormDataForV4App_Ok() - { - string expectedFormData = TestDataHelper.GetFileFromRepo(Org, AppV4, Developer, "App/models/datamodel.schema.json"); - - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/data/test-datatask-id"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - JsonUtils.DeepEquals(expectedFormData, responseBody).Should().BeTrue(); - } - - [Fact] - public async Task Get_FormDataForV4AppForTaskWithoutDatamodel_Ok() - { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/data/test-datatask-id"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName2}"); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - responseBody.Should().Be($"{PartyId}/{InstanceGuId}"); - } - } -} diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/GetOptionsTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/GetOptionsTests.cs index 22c3bb7d419..b1becc107c4 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/GetOptionsTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/GetOptionsTests.cs @@ -2,6 +2,7 @@ using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -31,7 +32,8 @@ public async Task Get_Options_when_options_exists_Ok() [Fact] public async Task Get_Options_when_options_exists_for_v4_app_Ok() { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/options/test-options"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/options/test-options"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/InstanceForNextTaskTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/InstanceForNextTaskTests.cs index c751bfaa0c9..2cc7ee39927 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/InstanceForNextTaskTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/InstanceForNextTaskTests.cs @@ -20,7 +20,8 @@ public InstanceForNextTaskTests(WebApplicationFactory factory) : base(f [Fact] public async Task Get_InstanceForNextProcess_Ok() { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{instance.Id}"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); @@ -29,15 +30,16 @@ public async Task Get_InstanceForNextProcess_Ok() string responseBody = await response.Content.ReadAsStringAsync(); JsonDocument responseDocument = JsonDocument.Parse(responseBody); - Instance instance = JsonConvert.DeserializeObject(responseDocument.RootElement.ToString()); - Assert.Equal($"{PartyId}/{InstanceGuId}", instance.Id); - Assert.Equal("ttd", instance.Org); + Instance responseInstance = JsonConvert.DeserializeObject(responseDocument.RootElement.ToString()); + Assert.Equal(instance.Id, responseInstance.Id); + Assert.Equal(Org, responseInstance.Org); } [Fact] public async Task Get_InstanceForNextTaskForV4App_Ok_TaskIsIncreased() { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); @@ -46,10 +48,10 @@ public async Task Get_InstanceForNextTaskForV4App_Ok_TaskIsIncreased() string responseBody = await response.Content.ReadAsStringAsync(); JsonDocument responseDocument = JsonDocument.Parse(responseBody); - Instance instance = JsonConvert.DeserializeObject(responseDocument.RootElement.ToString()); - Assert.Equal($"{PartyId}/{InstanceGuId}", instance.Id); - Assert.Equal("ttd", instance.Org); - Assert.Equal("Task_1", instance.Process.CurrentTask.ElementId); + Instance responseInstance = JsonConvert.DeserializeObject(responseDocument.RootElement.ToString()); + Assert.Equal(instance.Id, responseInstance.Id); + Assert.Equal(Org, responseInstance.Org); + Assert.Equal(TaskId, instance.Process.CurrentTask.ElementId); } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/PostAttachmentTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/PostAttachmentTests.cs index c01b4ddbd3f..2afe33ef7fd 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/PostAttachmentTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/PostAttachmentTests.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -18,7 +19,8 @@ public PostAttachmentTests(WebApplicationFactory factory) : base(factor [Fact] public async Task Post_Attachment_Ok() { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}/data?dataType=FileUploadId"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{instance.Id}/data?dataType=FileUploadId"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); @@ -29,7 +31,8 @@ public async Task Post_Attachment_Ok() [Fact] public async Task Post_AttachmentForV4App_Ok() { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/data?dataType=FileUploadId"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data?dataType=FileUploadId"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/PreviewControllerTestsBase.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/PreviewControllerTestsBase.cs index b0ca17543ea..48ca7ac47cc 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/PreviewControllerTestsBase.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/PreviewControllerTestsBase.cs @@ -1,15 +1,18 @@ using System.Linq; -using System.Text.Encodings.Web; +using System.Net; +using System.Net.Http; using System.Text.Json; -using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; using Designer.Tests.Controllers.ApiTests; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; +using Xunit; namespace Designer.Tests.Controllers.PreviewController { - public class PreviewControllerTestsBase : DesignerEndpointsTestsBase + public class PreviewControllerTestsBase(WebApplicationFactory factory) : DesignerEndpointsTestsBase(factory) where TTestClass : class { protected const string Org = "ttd"; @@ -22,8 +25,7 @@ public class PreviewControllerTestsBase : DesignerEndpointsTestsBase protected const string CustomReceiptLayoutSetName = "receipt"; protected const string CustomReceiptDataType = "datamodel"; protected const string PartyId = "51001"; - protected const string InstanceGuId = "f1e23d45-6789-1bcd-8c34-56789abcdef0"; - protected const string AttachmentGuId = "f47ac10b-58cc-4372-a567-0e02b2c3d479"; + protected const string TaskId = "Task_1"; protected const string MockedReferrerUrl = "https://studio-mock-url.no"; protected override void ConfigureTestServices(IServiceCollection services) @@ -39,10 +41,29 @@ protected override void ConfigureTestServices(IServiceCollection services) services.AddDistributedMemoryCache(); } - public PreviewControllerTestsBase(WebApplicationFactory factory) : base(factory) + protected async Task createInstance() { + string dataPath = $"{Org}/{AppV4}/instances?instanceOwnerPartyId={PartyId}&taskId={TaskId}"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, dataPath); + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + string responseBody = await response.Content.ReadAsStringAsync(); + Instance dataItem = JsonSerializer.Deserialize(responseBody, JsonSerializerOptions); + Assert.NotNull(dataItem); + return dataItem; } + protected async Task createDataElement(Instance instance, string dataType) + { + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/data?dataType={dataType}"; + using HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, dataPathWithData); + using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + string responseBody = await response.Content.ReadAsStringAsync(); + DataElement dataElement = JsonSerializer.Deserialize(responseBody, JsonSerializerOptions); + Assert.NotNull(dataElement); + return dataElement; + } } } diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessNextTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessNextTests.cs index 9921c5ad3e4..b210ba8cb9d 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessNextTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessNextTests.cs @@ -20,7 +20,8 @@ public ProcessNextTests(WebApplicationFactory factory) : base(factory) [Fact] public async Task Get_ProcessNext_Ok() { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}/process/next"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{instance.Id}/process/next"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); @@ -37,7 +38,8 @@ public async Task Get_ProcessNext_Ok() [Fact] public async Task Get_ProcessNextForV4App_Ok() { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/process/next"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/process/next"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessTests.cs deleted file mode 100644 index d3de435bb51..00000000000 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/ProcessTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Altinn.App.Core.Internal.Process.Elements; -using Altinn.Platform.Storage.Interface.Models; -using FluentAssertions; -using Microsoft.AspNetCore.Mvc.Testing; -using Xunit; - -namespace Designer.Tests.Controllers.PreviewController -{ - public class ProcessTests : PreviewControllerTestsBase, IClassFixture> - { - - public ProcessTests(WebApplicationFactory factory) : base(factory) - { - } - - [Fact] - public async Task Get_Process_Ok() - { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}/process"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - ProcessState processState = JsonSerializer.Deserialize(responseBody, JsonSerializerOptions); - Assert.Equal("data", processState.CurrentTask.AltinnTaskType); - Assert.Equal("Task_1", processState.CurrentTask.ElementId); - } - - [Fact] - public async Task Get_ProcessForV4App_Ok() - { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/process"; - using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, dataPathWithData); - httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); - - using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - AppProcessState processState = - JsonSerializer.Deserialize(responseBody, JsonSerializerOptions); - var expectedProcessTasks = new List - { - new AppProcessTaskTypeInfo - { - ElementId = "Task_1", - AltinnTaskType = "data" - }, - new AppProcessTaskTypeInfo - { - ElementId = "Task_2", - AltinnTaskType = "data" - }, - new AppProcessTaskTypeInfo - { - ElementId = "Task_3", - AltinnTaskType = "data" - } - }; - Assert.Equal("data", processState.CurrentTask.AltinnTaskType); - Assert.Equal("Task_1", processState.CurrentTask.ElementId); - expectedProcessTasks.Should().BeEquivalentTo(processState.ProcessTasks); - } - } -} diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateFormDataTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateFormDataTests.cs index 1402307492f..38f8298fe43 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateFormDataTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateFormDataTests.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; @@ -17,7 +18,8 @@ public UpdateFormDataTests(WebApplicationFactory factory) : base(factor [Fact] public async Task Put_UpdateFormData_Ok() { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}/data/test-datatask-id"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{instance.Id}/data/test-datatask-id"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Put, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); diff --git a/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateProcessNextTests.cs b/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateProcessNextTests.cs index 430382d3cb6..d534becbfce 100644 --- a/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateProcessNextTests.cs +++ b/backend/tests/Designer.Tests/Controllers/PreviewController/UpdateProcessNextTests.cs @@ -20,21 +20,20 @@ public UpdateProcessNextTests(WebApplicationFactory factory) : base(fac [Fact] public async Task Put_ProcessNext_Ok() { - string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{InstanceGuId}/process/next"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV3}/instances/{PartyId}/{instance.Id}/process/next"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Put, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV3}&selectedLayoutSet="); using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseBody = await response.Content.ReadAsStringAsync(); - Assert.Equal(@"{""ended"": ""ended""}", responseBody); } [Fact] public async Task Put_ProcessNextForV4AppForNonExistingTask_Ok() { - string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{InstanceGuId}/process/next"; + Instance instance = await createInstance(); + string dataPathWithData = $"{Org}/{AppV4}/instances/{PartyId}/{instance.Id}/process/next"; using HttpRequestMessage httpRequestMessage = new(HttpMethod.Put, dataPathWithData); httpRequestMessage.Headers.Referrer = new Uri($"{MockedReferrerUrl}?org={Org}&app={AppV4}&selectedLayoutSet={LayoutSetName}"); @@ -45,7 +44,7 @@ public async Task Put_ProcessNextForV4AppForNonExistingTask_Ok() JsonDocument responseDocument = JsonDocument.Parse(responseBody); ProcessState processState = JsonConvert.DeserializeObject(responseDocument.RootElement.ToString()); Assert.Equal("data", processState.CurrentTask.AltinnTaskType); - Assert.Equal("Task_1", processState.CurrentTask.ElementId); + Assert.Equal(TaskId, processState.CurrentTask.ElementId); } } } diff --git a/frontend/app-preview/src/components/PreviewControlHeader/PreviewControlHeader.test.tsx b/frontend/app-preview/src/components/PreviewControlHeader/PreviewControlHeader.test.tsx index 1ffd699e473..66031450194 100644 --- a/frontend/app-preview/src/components/PreviewControlHeader/PreviewControlHeader.test.tsx +++ b/frontend/app-preview/src/components/PreviewControlHeader/PreviewControlHeader.test.tsx @@ -10,7 +10,6 @@ import { type ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { type QueryClient } from '@tanstack/react-query'; import { QueryKey } from 'app-shared/types/QueryKey'; -import { useInstanceIdQuery } from 'app-shared/hooks/queries'; // Move jest.mock('app-shared/hooks/queries'); @@ -122,7 +121,6 @@ describe('PreviewControlHeader', () => { }); it('should not render the layout sets dropdown if layoutSets is not available', () => { - (useInstanceIdQuery as jest.Mock).mockReturnValue(mockLayoutId); renderPreviewControlHeader(); expect(screen.queryByRole('combobox')).not.toBeInTheDocument(); diff --git a/frontend/app-preview/src/hooks/useBackToEditingHref/useBackToEditingHref.tsx b/frontend/app-preview/src/hooks/useBackToEditingHref/useBackToEditingHref.tsx index fc666e20119..126eb470cbe 100644 --- a/frontend/app-preview/src/hooks/useBackToEditingHref/useBackToEditingHref.tsx +++ b/frontend/app-preview/src/hooks/useBackToEditingHref/useBackToEditingHref.tsx @@ -1,14 +1,10 @@ import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { PackagesRouter } from 'app-shared/navigation/PackagesRouter'; -import { typedLocalStorage } from '@studio/pure-functions'; -import { useInstanceIdQuery } from 'app-shared/hooks/queries'; export const useBackToEditingHref = () => { const { org, app } = useStudioEnvironmentParams(); - const { data: instanceId } = useInstanceIdQuery(org, app); const packagesRouter = new PackagesRouter({ org, app }); - const queryParams: string = `?layout=${typedLocalStorage.getItem(instanceId)}`; - return `${packagesRouter.getPackageNavigationUrl('editorUiEditor')}${queryParams}`; + return `${packagesRouter.getPackageNavigationUrl('editorUiEditor')}`; }; diff --git a/frontend/app-preview/src/views/LandingPage.tsx b/frontend/app-preview/src/views/LandingPage.tsx index a09a2930bff..46353d20239 100644 --- a/frontend/app-preview/src/views/LandingPage.tsx +++ b/frontend/app-preview/src/views/LandingPage.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import classes from './LandingPage.module.css'; import { useTranslation } from 'react-i18next'; import { usePreviewConnection } from 'app-shared/providers/PreviewConnectionContext'; @@ -16,6 +16,7 @@ import { useSelectedFormLayoutName } from 'app-shared/hooks/useSelectedFormLayou import { useSelectedFormLayoutSetName } from 'app-shared/hooks/useSelectedFormLayoutSetName'; import { useSelectedTaskId } from 'app-shared/hooks/useSelectedTaskId'; import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery'; +import { useCreatePreviewInstanceMutation } from 'app-shared/hooks/mutations/useCreatePreviewInstanceMutation'; export type PreviewAsViewSize = 'desktop' | 'mobile'; @@ -26,17 +27,21 @@ export const LandingPage = () => { const previewConnection = usePreviewConnection(); const { data: user, isPending: isPendingUser } = useUserQuery(); const { data: repository } = useRepoMetadataQuery(org, app); - const { data: layoutSets, isPending: pendingLayoutsets } = useLayoutSetsQuery(org, app); const { selectedFormLayoutSetName, setSelectedFormLayoutSetName } = useSelectedFormLayoutSetName(layoutSets); - const { selectedFormLayoutName } = useSelectedFormLayoutName(selectedFormLayoutSetName); const [previewViewSize, setPreviewViewSize] = useLocalStorage( 'viewSize', 'desktop', ); const taskId = useSelectedTaskId(selectedFormLayoutSetName); + const { mutate: createInstance, data: instance } = useCreatePreviewInstanceMutation(org, app); + + useEffect(() => { + if (user && taskId) createInstance({ partyId: user?.id, taskId: taskId }); + }, [createInstance, user, taskId]); + const isIFrame = (input: HTMLElement | null): input is HTMLIFrameElement => input !== null && input.tagName === 'IFRAME'; @@ -59,9 +64,17 @@ export const LandingPage = () => { }); } - if (isPendingUser || pendingLayoutsets) + if (isPendingUser || pendingLayoutsets || !instance) return ; + const previewUrl = previewPage( + org, + app, + getSelectedFormLayoutSetName(selectedFormLayoutSetName), + taskId, + selectedFormLayoutName, + instance?.id, + ); return ( <> @@ -87,13 +100,7 @@ export const LandingPage = () => {