Skip to content

Commit

Permalink
feat: add endpoint for updating option list id (#14180)
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored Dec 10, 2024
1 parent 37d92b5 commit fc6bfbb
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 8 deletions.
31 changes: 31 additions & 0 deletions backend/src/Designer/Controllers/OptionsController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -129,6 +130,36 @@ public async Task<ActionResult<Dictionary<string, List<Option>>>> CreateOrOverwr
return Ok(newOptionsList);
}

/// <summary>
/// Updates the name of an options list by changing file name in repo.
/// </summary>
/// <param name="org">Unique identifier of the organisation responsible for the app.</param>
/// <param name="repo">Application identifier which is unique within an organisation.</param>
/// <param name="optionsListId">Name of the options list.</param>
/// <param name="newOptionsListId">New name of options list file.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param>
[HttpPut]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("change-name/{optionsListId}")]
public ActionResult UpdateOptionsListId(string org, string repo, [FromRoute] string optionsListId, [FromBody] string newOptionsListId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
var editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repo, developer);
try
{
_optionsService.UpdateOptionsListId(editingContext, optionsListId, newOptionsListId, cancellationToken);
}
catch (IOException exception)
{
return BadRequest(exception.Message);
}

return Ok();
}

/// <summary>
/// Create new options list.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -712,13 +712,8 @@ public string[] GetOptionsListIds()
throw new NotFoundException("Options folder not found.");
}

string[] fileNames = GetFilesByRelativeDirectory(optionsFolder, "*.json");
List<string> optionsListIds = [];
foreach (string fileName in fileNames.Select(Path.GetFileNameWithoutExtension))
{
optionsListIds.Add(fileName);
}

string[] fileNames = GetFilesByRelativeDirectoryAscSorted(optionsFolder, "*.json");
IEnumerable<string> optionsListIds = fileNames.Select(Path.GetFileNameWithoutExtension);
return optionsListIds.ToArray();
}

Expand Down Expand Up @@ -779,6 +774,18 @@ public void DeleteOptionsList(string optionsListId)
DeleteFileByRelativePath(optionsFilePath);
}

/// <summary>
/// Updates the ID of the option list by updating file name.
/// </summary>
/// <param name="oldOptionsListFileName">The file name of the option list to change filename of.</param>
/// <param name="newOptionsListFileName">The new file name of the option list file.</param>
public void UpdateOptionsListId(string oldOptionsListFileName, string newOptionsListFileName)
{
string currentFilePath = Path.Combine(OptionsFolderPath, oldOptionsListFileName);
string newFilePath = Path.Combine(OptionsFolderPath, newOptionsListFileName);
MoveFileByRelativePath(currentFilePath, newFilePath, newOptionsListFileName);
}

/// <summary>
/// Saves the process definition file on disk.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions backend/src/Designer/Infrastructure/GitRepository/GitRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ protected string[] GetFilesByRelativeDirectory(string relativeDirectory, string
return Directory.GetFiles(absoluteDirectory, searchPatternMatch, searchOption);
}

/// <summary>
/// Gets all the files within the specified directory in an alphabetically sorted order.
/// </summary>
/// <param name="relativeDirectory">Relative path to a directory within the repository.</param>
/// <param name="patternMatch">An optional pattern that the retrieved files must match</param>
/// <param name="searchInSubdirectories">An optional parameter to also get files in sub directories</param>
protected string[] GetFilesByRelativeDirectoryAscSorted(string relativeDirectory, string patternMatch = null, bool searchInSubdirectories = false)
{
string[] fileNames = GetFilesByRelativeDirectory(relativeDirectory, patternMatch, searchInSubdirectories);

return fileNames.OrderBy(path => path, StringComparer.OrdinalIgnoreCase).ToArray();
}

/// <summary>
/// Gets all the directories within the specified directory.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions backend/src/Designer/Services/Implementation/OptionsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using Altinn.Studio.Designer.Exceptions.Options;
using Altinn.Studio.Designer.Infrastructure.GitRepository;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Services.Interfaces;
using LibGit2Sharp;
Expand Down Expand Up @@ -126,4 +127,15 @@ public async Task<bool> OptionsListExists(string org, string repo, string develo
return false;
}
}

/// <inheritdoc />
public void UpdateOptionsListId(AltinnRepoEditingContext altinnRepoEditingContext, string optionsListId,
string newOptionsListName, CancellationToken cancellationToken = default)
{
AltinnAppGitRepository altinnAppGitRepository =
_altinnGitRepositoryFactory.GetAltinnAppGitRepository(altinnRepoEditingContext.Org,
altinnRepoEditingContext.Repo, altinnRepoEditingContext.Developer);
altinnAppGitRepository.UpdateOptionsListId($"{optionsListId}.json", $"{newOptionsListName}.json");

}
}
11 changes: 10 additions & 1 deletion backend/src/Designer/Services/Interfaces/IOptionsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public interface IOptionsService
/// <param name="org">Organisation</param>
/// <param name="repo">Repository</param>
/// <param name="developer">Username of developer</param>
/// <param name="optionsListId">Name of the new options list</param>
/// <param name="optionsListId">Name of the options list</param>
public void DeleteOptionsList(string org, string repo, string developer, string optionsListId);

/// <summary>
Expand All @@ -73,4 +73,13 @@ public interface IOptionsService
/// <param name="optionsListId">Name of the options list</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param>
public Task<bool> OptionsListExists(string org, string repo, string developer, string optionsListId, CancellationToken cancellationToken = default);

/// <summary>
/// Updates the name of the options list by changing the filename.
/// </summary>
/// <param name="altinnRepoEditingContext">An <see cref="AltinnRepoEditingContext"/>.</param>
/// <param name="optionsListId">Name of the options list</param>
/// <param name="newOptionsListId">The new name of the options list file.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is cancelled.</param>
public void UpdateOptionsListId(AltinnRepoEditingContext altinnRepoEditingContext, string optionsListId, string newOptionsListId, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Designer.Tests.Controllers.ApiTests;
using Designer.Tests.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;

namespace Designer.Tests.Controllers.OptionsController;

public class UpdateOptionListIdTests(WebApplicationFactory<Program> factory)
: DesignerEndpointsTestsBase<UpdateOptionListIdTests>(factory), IClassFixture<WebApplicationFactory<Program>>
{
private const string Org = "ttd";
private const string Developer = "testUser";

[Fact]
public async Task Put_Returns_200OK_With_Same_File_Content_When_Updating_Id()
{
// Arrange
const string repo = "app-with-options";
const string optionsListId = "test-options";
const string newOptionListId = "new-option-list-id";

string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(Org, repo, Developer, targetRepository);
var originalOptionList = TestDataHelper.GetFileFromRepo(Org, repo, Developer, $"App/options/{optionsListId}.json");

string apiUrl = $"/designer/api/{Org}/{targetRepository}/options/change-name/{optionsListId}";
using HttpRequestMessage httpRequestMessage = new(HttpMethod.Put, apiUrl);
httpRequestMessage.Content =
new StringContent($"\"{newOptionListId}\"", Encoding.UTF8, MediaTypeNames.Application.Json);

// Act
using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage);

// Assert
var optionListWithNewId = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/options/{newOptionListId}.json");
Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode);
Assert.Equal(originalOptionList, optionListWithNewId);
}

[Fact]
public async Task Put_Returns_400BadRequest_When_Updating_Id_To_Existing_Option_File_Name()
{
// Arrange
const string repo = "app-with-options";
const string optionsListId = "test-options";
const string newOptionListId = "other-options";

string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(Org, repo, Developer, targetRepository);
var originalOptionList = TestDataHelper.GetFileFromRepo(Org, repo, Developer, $"App/options/{optionsListId}.json");

string apiUrl = $"/designer/api/{Org}/{targetRepository}/options/change-name/{optionsListId}";
using HttpRequestMessage httpRequestMessage = new(HttpMethod.Put, apiUrl);
httpRequestMessage.Content =
new StringContent($"\"{newOptionListId}\"", Encoding.UTF8, MediaTypeNames.Application.Json);

// Act
using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage);
var responseContent = await response.Content.ReadAsStringAsync();
string responseString = JsonSerializer.Deserialize<string>(responseContent);

// Assert
var optionListWithSameId = TestDataHelper.GetFileFromRepo(Org, targetRepository, Developer, $"App/options/{optionsListId}.json");
Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode);
Assert.Equal($"Suggested file name {newOptionListId}.json already exists.", responseString);
Assert.Equal(originalOptionList, optionListWithSameId);
}

[Fact]
public async Task Put_Returns_400BadRequest_When_Updating_Id_Of_NonExistent_Option_File()
{
// Arrange
const string repo = "app-with-options";
const string optionsListId = "options-that-does-not-exist";
const string newOptionListId = "new-option-list-id";

string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(Org, repo, Developer, targetRepository);
string relativePath = $"App/options/{optionsListId}.json";

string apiUrl = $"/designer/api/{Org}/{targetRepository}/options/change-name/{optionsListId}";
using HttpRequestMessage httpRequestMessage = new(HttpMethod.Put, apiUrl);
httpRequestMessage.Content =
new StringContent($"\"{newOptionListId}\"", Encoding.UTF8, MediaTypeNames.Application.Json);

// Act
using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage);
var responseContent = await response.Content.ReadAsStringAsync();
string responseString = JsonSerializer.Deserialize<string>(responseContent);

// Assert
Assert.Equal(StatusCodes.Status400BadRequest, (int)response.StatusCode);
Assert.Equal($"File {relativePath} does not exist.", responseString);
}
}

0 comments on commit fc6bfbb

Please sign in to comment.