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

Allow Custom Headers to be managed via the API #569

Merged
merged 48 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
047f668
Add converter class for custom headers
griffri Jul 31, 2023
2415746
Add request class for retrieving the user's custom headers
griffri Jul 31, 2023
dd176ce
Add CustomHeadersController WIP
griffri Jul 31, 2023
9182af6
Fix typo
griffri Jul 31, 2023
9649d46
Add CustomerId and SpaceId to custom header hydra model
griffri Jul 31, 2023
6547108
Add parameters for ModelId and CustomerId in ToHydra()
griffri Jul 31, 2023
c0a53cf
Add validator for custom headers
griffri Jul 31, 2023
35336f0
Add ToDlcsModel() function
griffri Jul 31, 2023
a117c3b
Require key in validation
griffri Jul 31, 2023
4301036
Add CreateCustomHeader request
griffri Jul 31, 2023
e09885b
Add POST endpoint
griffri Jul 31, 2023
3d3a796
Add GetCustomHeader request
griffri Jul 31, 2023
56d9e02
Add GET endpoint for retrieving a single named query
griffri Jul 31, 2023
d20b13e
Use customerId in Init
griffri Jul 31, 2023
96df939
Rename spaceId to space
griffri Jul 31, 2023
fec80ed
Add a check that ensures that the user has specified a space that exi…
griffri Jul 31, 2023
b86b533
Rename ch to nq
griffri Jul 31, 2023
255c06b
Fix typo in variable names
griffri Jul 31, 2023
47d2b4d
Fix error message returning incorrect ID
griffri Jul 31, 2023
41df806
Fix typo
griffri Aug 1, 2023
1c46a16
Remove space check
griffri Aug 1, 2023
7bbb2ff
Add UpdateCustomHeader request class
griffri Aug 1, 2023
43b791a
Add PUT endpoint
griffri Aug 1, 2023
ebfd744
Remove unused response types
griffri Aug 1, 2023
a1bebd9
Add DeleteCustomHeader request
griffri Aug 1, 2023
ca60e40
Add DELETE endpoint
griffri Aug 1, 2023
fa86136
Add XML documentation
griffri Aug 1, 2023
b860eaf
Add test class for custom headers
griffri Aug 1, 2023
d673a9c
Add Get_CustomHeader_404_IfNotFound() test
griffri Aug 1, 2023
a3e1226
Add Delete_CustomHeader_204 test
griffri Aug 1, 2023
7ca72ca
Add Get_CustomHeaders_200 test
griffri Aug 1, 2023
9ec2fcc
Assert that the custom header has been deleted in Delete_CustomHeader…
griffri Aug 1, 2023
37c84ea
Add Post_CustomHeader_201() test
griffri Aug 1, 2023
09855e4
Add Put_CustomHeader_200 test
griffri Aug 1, 2023
c75cbf2
Add Put_CustomHeader_404_IfNotFound test
griffri Aug 1, 2023
d8f52b5
Fix typo
griffri Aug 1, 2023
a50c447
Clean up unused namespace
griffri Aug 1, 2023
251b8d8
Fix typo in validator
griffri Aug 1, 2023
0902541
Rename file to match class name
griffri Aug 1, 2023
9714bc0
Add unit tests for HydraCustomHeaderValidator
griffri Aug 1, 2023
17a80f5
Include Custom Header ID in paths instead of using Path.Combine() at …
griffri Aug 1, 2023
01ce2bf
Include failure message upon attempting to delete a nonexistent Custo…
griffri Aug 1, 2023
9577033
Set message and DeleteResult status in ResultMessage
griffri Aug 1, 2023
877f597
Add comments
griffri Aug 1, 2023
df51740
Ensure that values tested by NewCustomHeader_Requires_Key() and NewCu…
griffri Aug 1, 2023
ddc6b69
Add tests for validation
griffri Aug 1, 2023
a2bba53
Add test for ensuring that multiple custom headers using the same key…
griffri Aug 2, 2023
f23397a
Check only for custom headers assigned to user 95 in Post_CustomHeade…
griffri Aug 2, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using API.Features.CustomHeaders.Validation;
using API.Features.CustomHeaders.Converters;
using DLCS.HydraModel;
using FluentValidation.TestHelper;

namespace API.Tests.Features.CustomHeaders.Validation;

public class HydraCustomHeaderValidatorTests
{
private readonly HydraCustomHeaderValidator sut;

public HydraCustomHeaderValidatorTests()
{
sut = new HydraCustomHeaderValidator();
}

[Fact]
public void NewCustomHeader_CannotHave_AssetId()
{
var customHeader = new CustomHeader()
{
Id = Guid.NewGuid().ToString()
};
var result = sut.TestValidate(customHeader);
result.ShouldHaveValidationErrorFor(ch => ch.Id);
}

[Fact]
public void NewCustomHeader_CannotHave_CustomerId()
{
var customHeader = new CustomHeader()
{
CustomerId = 1
};
var result = sut.TestValidate(customHeader);
result.ShouldHaveValidationErrorFor(ch => ch.CustomerId);
}

[Fact]
public void NewCustomHeader_Requires_Key()
{
var customHeader = new CustomHeader()
{
Key = null
};
var result = sut.TestValidate(customHeader);
result.ShouldHaveValidationErrorFor(ch => ch.Key);
}

[Fact]
public void NewCustomHeader_Requires_Value()
{
var customHeader = new CustomHeader()
{
Value = null
};
var result = sut.TestValidate(customHeader);
result.ShouldHaveValidationErrorFor(ch => ch.Value);
griffri marked this conversation as resolved.
Show resolved Hide resolved
}
}
267 changes: 267 additions & 0 deletions src/protagonist/API.Tests/Integration/CustomHeaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using API.Client;
using API.Tests.Integration.Infrastructure;
using DLCS.Model.Assets.CustomHeaders;
using DLCS.Repository;
using Hydra.Collections;
using Microsoft.EntityFrameworkCore;
using Test.Helpers.Integration;
using Test.Helpers.Integration.Infrastructure;

namespace API.Tests.Integration;

[Trait("Category", "Integration")]
[Collection(CollectionDefinitions.DatabaseCollection.CollectionName)]
public class CustomHeaderTests : IClassFixture<ProtagonistAppFactory<Startup>>
{
private readonly HttpClient httpClient;
private readonly DlcsContext dlcsContext;

public CustomHeaderTests(DlcsDatabaseFixture dbFixture, ProtagonistAppFactory<Startup> factory)
{
dlcsContext = dbFixture.DbContext;
httpClient = factory.ConfigureBasicAuthedIntegrationTestHttpClient(dbFixture, "API-Test");
dbFixture.CleanUp();
}

[Fact]
public async Task Get_CustomHeader_200()
{
// Arrange
const int customerId = 90;
var customHeader = new CustomHeader()
{
Id = Guid.NewGuid().ToString(),
Customer = customerId,
Key = "test-key",
Value = "test-value"
};
var path = $"customers/{customerId}/customHeaders/{customHeader.Id}";

await dlcsContext.CustomHeaders.AddAsync(customHeader);
await dlcsContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(customerId).GetAsync(path);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task Get_CustomHeader_404_IfNotFound()
{
// Arrange
const int customerId = 91;
var path = $"customers/{customerId}/customHeaders/{Guid.Empty}";

// Act
var response = await httpClient.AsCustomer(customerId).GetAsync(path);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

[Fact]
public async Task Delete_CustomHeader_204()
{
// Arrange
const int customerId = 92;
var customHeader = new CustomHeader()
{
Id = Guid.NewGuid().ToString(),
Customer = customerId,
Key = "test-key",
Value = "test-value"
};
var path = $"customers/{customerId}/customHeaders/{customHeader.Id}";

await dlcsContext.CustomHeaders.AddAsync(customHeader);
await dlcsContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(customerId).DeleteAsync(path);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
var deletedCustomHeader = await dlcsContext.CustomHeaders.AnyAsync(ch => ch.Id == customHeader.Id);
deletedCustomHeader.Should().BeFalse();
}

[Fact]
public async Task Get_CustomHeaders_200()
{
// Arrange
const int customerId = 93;
var path = $"customers/{customerId}/customHeaders";

await dlcsContext.CustomHeaders.AddTestCustomHeader("test-key-1", "test-value-1", customerId);
await dlcsContext.CustomHeaders.AddTestCustomHeader("test-key-2", "test-value-2", customerId, space:1);
await dlcsContext.CustomHeaders.AddTestCustomHeader("test-key-3", "test-value-3", 1);
await dlcsContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(customerId).GetAsync(path);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);

var model = await response.ReadAsHydraResponseAsync<HydraCollection<CustomHeader>>();
model.Members.Should().HaveCount(2);
}

[Fact]
public async Task Post_CustomHeader_201()
griffri marked this conversation as resolved.
Show resolved Hide resolved
{
// Arrange
const int customerId = 94;
var path = $"customers/{customerId}/customHeaders";

const string newCustomHeaderJson = @"{
""key"": ""test-key"",
""value"": ""test-value"",
""space"": 1,
}";

// Act
var content = new StringContent(newCustomHeaderJson, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerId).PostAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);

var foundCustomHeader = dlcsContext.CustomHeaders.Single(ch => ch.Key == "test-key");
foundCustomHeader.Should().NotBeNull();
foundCustomHeader.Customer.Should().Be(customerId);
foundCustomHeader.Value.Should().Be("test-value");
foundCustomHeader.Space.Should().Be(1);
}

[Fact]
public async Task Post_CustomHeader_201_IfMultipleSameKey()
{
// Arrange
const int customerId = 95;
const int customHeaderCount = 4;
var responses = new List<HttpResponseMessage>();
var path = $"customers/{customerId}/customHeaders";

// Act
for (var i = 0; i < customHeaderCount; i++)
{
var newCustomHeaderJson = $@"{{
""key"": ""same-key"",
""value"": ""test-value-{i}""
}}";
var content = new StringContent(newCustomHeaderJson, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerId).PostAsync(path, content);
responses.Add(response);
}

// Assert
responses.Should().AllSatisfy(r => r.StatusCode = HttpStatusCode.Created);
dlcsContext.CustomHeaders.Where(ch => ch.Customer == customerId).Should().HaveCount(customHeaderCount);
}

[Fact]
public async Task Post_CustomHeader_400IfKeyNotSpecified()
{
// Arrange
const int customerId = 96;
var path = $"customers/{customerId}/customHeaders";

const string newCustomHeaderJson = @"{
""value"": ""test-value"",
""space"": 1,
}";

// Act
var content = new StringContent(newCustomHeaderJson, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerId).PostAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

[Fact]
public async Task Post_CustomHeader_400IfValueNotSpecified()
{
// Arrange
const int customerId = 97;
var path = $"customers/{customerId}/customHeaders";

const string newCustomHeaderJson = @"{
""key"": ""test-key"",
""space"": 1,
}";

// Act
var content = new StringContent(newCustomHeaderJson, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerId).PostAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

[Fact]
public async Task Put_CustomHeader_200()
{
// Arrange
const int customerId = 98;
var customHeader = new CustomHeader()
{
Id = Guid.NewGuid().ToString(),
Customer = customerId,
Key = "test-key",
Value = "test-value"
};
const string updatedCustomHeaderJson = @"{
""key"": ""test-key-2"",
""value"": ""test-value-2"",
""space"": 2,
}";
var path = $"customers/{customerId}/customHeaders/{customHeader.Id}";

await dlcsContext.CustomHeaders.AddAsync(customHeader);
await dlcsContext.SaveChangesAsync();

// Act
var content = new StringContent(updatedCustomHeaderJson, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerId).PutAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);

var updatedCustomHeader = dlcsContext.CustomHeaders.Single(ch => ch.Key == "test-key-2");
updatedCustomHeader.Should().NotBeNull();
updatedCustomHeader.Value.Should().Be("test-value-2");
updatedCustomHeader.Space.Should().Be(2);
}

[Fact]
public async Task Put_CustomHeader_404_IfNotFound()
{
// Arrange
const int customerId = 99;
var path = $"customers/{customerId}/customHeaders/{Guid.Empty}";
const string updatedCustomHeaderJson = @"{
""key"": ""test-key-2"",
""value"": ""test-value-2"",
""space"": 2,
griffri marked this conversation as resolved.
Show resolved Hide resolved
}";

// Act
var content = new StringContent(updatedCustomHeaderJson, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerId).PutAsync(path, content);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace API.Features.CustomHeaders.Converters;

/// <summary>
/// Conversion between API and EF forms of CustomHeader resource
/// </summary>
public static class CustomHeaderConverter
griffri marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Convert CustomHeader entity to API resource
/// </summary>
public static DLCS.HydraModel.CustomHeader ToHydra(this DLCS.Model.Assets.CustomHeaders.CustomHeader customHeader, string baseUrl)
{
return new DLCS.HydraModel.CustomHeader(baseUrl, customHeader.Customer, customHeader.Id, false)
{
SpaceId = customHeader.Space,
Role = customHeader.Role,
Key = customHeader.Key,
Value = customHeader.Value,
};
}

/// <summary>
/// Convert Hydra CustomHeader entity to EF resource
/// </summary>
public static DLCS.Model.Assets.CustomHeaders.CustomHeader ToDlcsModel(this DLCS.HydraModel.CustomHeader hydraNamedQuery)
{
return new DLCS.Model.Assets.CustomHeaders.CustomHeader()
{
Id = hydraNamedQuery.ModelId,
Customer = hydraNamedQuery.CustomerId,
Space = hydraNamedQuery.SpaceId,
Role = hydraNamedQuery.Role,
Key = hydraNamedQuery.Key,
Value = hydraNamedQuery.Value
};
}
}
Loading
Loading