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

Modify collection table to expose root collection as root #38

Merged
merged 4 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 23 additions & 42 deletions src/IIIFPresentation/API.Tests/Integration/GetCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using IIIF.Presentation.V3;
using Microsoft.AspNetCore.Mvc.Testing;
using Models.API.Collection;
using Test.Helpers.Helpers;
using Test.Helpers.Integration;

#nullable disable
Expand Down Expand Up @@ -70,14 +71,14 @@ public async Task Get_Hierarchical_Redirects_WhenAuthAndShowExtrasHeaders()

// Assert
response.StatusCode.Should().Be(HttpStatusCode.SeeOther);
response.Headers.Location!.Should().Be("http://localhost/1/collections/RootStorage");
response.Headers.Location!.Should().Be($"http://localhost/1/collections/{RootCollection.Id}");
}

[Fact]
public async Task Get_RootFlat_Redirects_WhenNoAuthAndCsHeader()
{
// Act
var response = await httpClient.GetAsync("1/collections/root");
var response = await httpClient.GetAsync($"1/collections/{RootCollection.Id}");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.SeeOther);
Expand All @@ -88,7 +89,7 @@ public async Task Get_RootFlat_Redirects_WhenNoAuthAndCsHeader()
public async Task Get_RootFlat_Redirects_WhenNoCsHeader()
{
// Arrange
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "1/collections/root");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"1/collections/{RootCollection.Id}");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -102,7 +103,7 @@ public async Task Get_RootFlat_Redirects_WhenNoCsHeader()
public async Task Get_RootFlat_Redirects_WhenShowExtraHeaderNotAll()
{
// Arrange
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "1/collections/root");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"1/collections/{RootCollection.Id}");
requestMessage.Headers.Add("IIIF-CS-Show-Extra", "Incorrect");

// Act
Expand All @@ -117,7 +118,7 @@ public async Task Get_RootFlat_Redirects_WhenShowExtraHeaderNotAll()
public async Task Get_RootFlat_Redirects_WhenNoAuth()
{
// Arrange
var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root");
var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}");

// Act
var response = await httpClient.SendAsync(requestMessage);
Expand All @@ -131,7 +132,7 @@ public async Task Get_RootFlat_Redirects_WhenNoAuth()
public async Task Get_RootFlat_ReturnsEntryPointFlat_WhenAuthAndHeader()
{
// Arrange
var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root");
var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -140,29 +141,7 @@ public async Task Get_RootFlat_ReturnsEntryPointFlat_WhenAuthAndHeader()

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
collection!.Id.Should().Be("http://localhost/1/collections/RootStorage");
collection.PublicId.Should().Be("http://localhost/1");
collection.Items!.Count.Should().Be(2);
collection.Items[0].Id.Should().Be("http://localhost/1/collections/FirstChildCollection");
collection.TotalItems.Should().Be(2);
collection.CreatedBy.Should().Be("admin");
collection.Behavior.Should().Contain("public-iiif");
}

[Fact]
public async Task Get_RootFlat_ReturnsEntryPointFlat_WhenCalledById()
{
// Arrange
var requestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/RootStorage");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);

var collection = await response.ReadAsPresentationJsonAsync<FlatCollection>();

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
collection!.Id.Should().Be("http://localhost/1/collections/RootStorage");
collection!.Id.Should().Be($"http://localhost/1/collections/{RootCollection.Id}");
collection.PublicId.Should().Be("http://localhost/1");
collection.Items!.Count.Should().Be(2);
collection.Items[0].Id.Should().Be("http://localhost/1/collections/FirstChildCollection");
Expand Down Expand Up @@ -192,6 +171,7 @@ public async Task Get_ChildFlat_ReturnsEntryPointFlat_WhenCalledByChildId()
collection.TotalItems.Should().Be(1);
collection.CreatedBy.Should().Be("admin");
collection.Behavior.Should().Contain("public-iiif");
collection.Parent.Should().Be($"http://localhost/1/collections/{RootCollection.Id}");
}

[Fact]
Expand All @@ -214,6 +194,7 @@ public async Task Get_PrivateChild_ReturnsCorrectlyFlatAndHierarchical()
flatCollection.CreatedBy.Should().Be("admin");
flatCollection.Behavior.Should().Contain("storage-collection");
flatCollection.Behavior.Should().NotContain("public-iiif");
flatCollection.Parent.Should().Be($"http://localhost/1/collections/{RootCollection.Id}");
hierarchicalResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

Expand All @@ -222,7 +203,7 @@ public async Task Get_RootFlat_ReturnsItems_WhenCalledWithPageSize()
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root?page=1&pageSize=100");
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}?page=1&pageSize=100");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -238,11 +219,11 @@ public async Task Get_RootFlat_ReturnsItems_WhenCalledWithPageSize()
}

[Fact]
public async Task Get_RootFlat_ReturnsReducedItems_WhenCalledWithSmallPageSize()
public async Task Get_ChildFlat_ReturnsReducedItems_WhenCalledWithSmallPageSize()
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root?page=1&pageSize=1");
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}?page=1&pageSize=1");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -263,7 +244,7 @@ public async Task Get_RootFlat_ReturnsSecondPage_WhenCalledWithSmallPageSize()
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root?page=2&pageSize=1");
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}?page=2&pageSize=1");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -284,7 +265,7 @@ public async Task Get_RootFlat_ReturnsDefaults_WhenCalledWithZeroPage()
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root?page=0&pageSize=0");
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}?page=0&pageSize=0");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -304,7 +285,7 @@ public async Task Get_RootFlat_ReturnsMaxPageSize_WhenCalledPageSizeExceedsMax()
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, "1/collections/root?page=1&pageSize=1000");
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get, $"1/collections/{RootCollection.Id}?page=1&pageSize=1000");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -323,12 +304,12 @@ public async Task Get_RootFlat_ReturnsMaxPageSize_WhenCalledPageSizeExceedsMax()
[InlineData("id")]
[InlineData("slug")]
[InlineData("created")]
public async Task Get_RootFlat_ReturnsCorrectItem_WhenCalledWithSmallPageSizeAndOrderBy(string field)
public async Task Get_ChildFlat_ReturnsCorrectItem_WhenCalledWithSmallPageSizeAndOrderBy(string field)
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get,
$"1/collections/root?page=1&pageSize=1&orderBy={field}");
$"1/collections/{RootCollection.Id}?page=1&pageSize=1&orderBy={field}");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -353,7 +334,7 @@ public async Task Get_RootFlat_ReturnsFirstPageWithSecondItem_WhenCalledWithSmal
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get,
$"1/collections/root?page=1&pageSize=1&orderByDescending={field}");
$"1/collections/{RootCollection.Id}?page=1&pageSize=1&orderByDescending={field}");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -363,20 +344,20 @@ public async Task Get_RootFlat_ReturnsFirstPageWithSecondItem_WhenCalledWithSmal
// Assert
collection.TotalItems.Should().Be(2);
collection.View!.PageSize.Should().Be(1);
collection.View.Id.Should().Be($"http://localhost/1/collections/RootStorage?page=1&pageSize=1&orderByDescending={field}");
collection.View.Id.Should().Be($"http://localhost/1/collections/{RootCollection.Id}?page=1&pageSize=1&orderByDescending={field}");
collection.View.Page.Should().Be(1);
collection.View.TotalPages.Should().Be(2);
collection.Items!.Count.Should().Be(1);
collection.Items[0].Id.Should().Be("http://localhost/1/collections/NonPublic");
}

[Fact]
public async Task Get_RootFlat_IgnoresOrderBy_WhenCalledWithInvalidOrderBy()
public async Task Get_ChildFlat_IgnoresOrderBy_WhenCalledWithInvalidOrderBy()
{
// Arrange
var requestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get,
$"1/collections/root?page=1&pageSize=1&orderByDescending=notValid");
$"1/collections/{RootCollection.Id}?page=1&pageSize=1&orderByDescending=notValid");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(requestMessage);
Expand All @@ -386,7 +367,7 @@ public async Task Get_RootFlat_IgnoresOrderBy_WhenCalledWithInvalidOrderBy()
// Assert
collection.TotalItems.Should().Be(2);
collection.View!.PageSize.Should().Be(1);
collection.View.Id.Should().Be($"http://localhost/1/collections/RootStorage?page=1&pageSize=1");
collection.View.Id.Should().Be($"http://localhost/1/collections/{RootCollection.Id}?page=1&pageSize=1");
collection.View.Page.Should().Be(1);
collection.View.TotalPages.Should().Be(2);
collection.Items!.Count.Should().Be(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Models.Database.Collections;
using Models.Infrastucture;
using Repository;
using Test.Helpers.Helpers;
using Test.Helpers.Integration;
using JsonSerializer = System.Text.Json.JsonSerializer;

Expand Down Expand Up @@ -79,6 +80,9 @@ public async Task CreateCollection_CreatesCollection_WhenAllValuesProvided()
fromDatabase.Tags.Should().Be("some, tags");
fromDatabase.IsPublic.Should().BeTrue();
fromDatabase.IsStorageCollection.Should().BeTrue();
responseCollection!.View!.PageSize.Should().Be(20);
responseCollection.View.Page.Should().Be(1);
responseCollection.View.Id.Should().Contain("?page=1&pageSize=20");
}

[Fact]
Expand Down Expand Up @@ -234,7 +238,7 @@ public async Task UpdateCollection_UpdatesCollection_WhenAllValuesProvided()
IsStorageCollection = true,
IsPublic = false,
CustomerId = 1,
Parent = "RootStorage"
Parent = "root"
};

await dbContext.Collections.AddAsync(initialCollection);
Expand Down Expand Up @@ -283,6 +287,9 @@ public async Task UpdateCollection_UpdatesCollection_WhenAllValuesProvided()
fromDatabase.Tags.Should().Be("some, tags, 2");
fromDatabase.IsPublic.Should().BeTrue();
fromDatabase.IsStorageCollection.Should().BeTrue();
responseCollection!.View!.PageSize.Should().Be(20);
responseCollection.View.Page.Should().Be(1);
responseCollection.View.Id.Should().Contain("?page=1&pageSize=20");
}

[Fact]
Expand All @@ -306,7 +313,7 @@ public async Task UpdateCollection_UpdatesCollection_WhenAllValuesProvidedWithou
IsStorageCollection = true,
IsPublic = false,
CustomerId = 1,
Parent = "RootStorage"
Parent = "root"
};

await dbContext.Collections.AddAsync(initialCollection);
Expand Down Expand Up @@ -371,7 +378,7 @@ public async Task UpdateCollection_FailsToUpdateCollection_WhenNotStorageCollect
IsStorageCollection = true,
IsPublic = false,
CustomerId = 1,
Parent = "RootStorage"
Parent = "root"
};

await dbContext.Collections.AddAsync(initialCollection);
Expand Down Expand Up @@ -406,6 +413,65 @@ public async Task UpdateCollection_FailsToUpdateCollection_WhenNotStorageCollect
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

[Fact]
public async Task UpdateCollection_FailsToUpdateCollection_WhenParentDoesNotExist()
{
// Arrange
var initialCollection = new Collection()
{
Id = "UpdateTester-4",
Slug = "update-test-4",
UsePath = true,
Label = new LanguageMap
{
{ "en", new List<string> { "update testing" } }
},
Thumbnail = "some/location",
Created = DateTime.UtcNow,
Modified = DateTime.UtcNow,
CreatedBy = "admin",
Tags = "some, tags",
IsStorageCollection = true,
IsPublic = false,
CustomerId = 1,
Parent = "root"
donaldgray marked this conversation as resolved.
Show resolved Hide resolved
};

await dbContext.Collections.AddAsync(initialCollection);
await dbContext.SaveChangesAsync();

// TODO: remove this when better ETag support is implemented - this implementation requires GET to be called to retrieve the ETag
var getRequestMessage =
HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Get,
$"{Customer}/collections/{initialCollection.Id}");

var getResponse = await httpClient.AsCustomer(1).SendAsync(getRequestMessage);

var updatedCollection = new UpsertFlatCollection()
{
Behavior = new List<string>()
{
Behavior.IsPublic,
Behavior.IsStorageCollection
},
Label = new LanguageMap("en", ["test collection - updated"]),
Slug = "programmatic-child-3",
Parent = "doesNotExist"
};

var updateRequestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Put,
$"{Customer}/collections/{initialCollection.Id}", JsonSerializer.Serialize(updatedCollection));
updateRequestMessage.Headers.IfMatch.Add(new EntityTagHeaderValue(getResponse.Headers.ETag!.Tag));

// Act
var response = await httpClient.AsCustomer(1).SendAsync(updateRequestMessage);
var responseCollection = await response.ReadAsPresentationResponseAsync<Error>();

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
responseCollection!.Detail.Should().Be("The parent collection could not be found");
}

[Fact]
public async Task UpdateCollection_FailsToUpdateCollection_WhenETagIncorrect()
{
Expand Down Expand Up @@ -486,7 +552,7 @@ public async Task DeleteCollection_DeletesCollection_WhenAllValuesProvided()
IsStorageCollection = true,
IsPublic = false,
CustomerId = 1,
Parent = "RootStorage"
Parent = "root"
};

await dbContext.Collections.AddAsync(initialCollection);
Expand Down Expand Up @@ -525,7 +591,7 @@ public async Task DeleteCollection_FailsToDeleteCollection_WhenAttemptingToDelet
{
// Arrange
var deleteRequestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Delete,
$"{Customer}/collections/root");
$"{Customer}/collections/{RootCollection.Id}");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(deleteRequestMessage);
Expand All @@ -544,7 +610,7 @@ public async Task DeleteCollection_FailsToDeleteCollection_WhenAttemptingToDelet
{
// Arrange
var deleteRequestMessage = HttpRequestMessageBuilder.GetPrivateRequest(HttpMethod.Delete,
$"{Customer}/collections/RootStorage");
$"{Customer}/collections/{RootCollection.Id}");

// Act
var response = await httpClient.AsCustomer(1).SendAsync(deleteRequestMessage);
Expand Down
2 changes: 1 addition & 1 deletion src/IIIFPresentation/API/Converters/CollectionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@
{
var view = new View()
{
Id = dbAsset.GenerateFlatCollectionViewId(urlRoots, currentPage, pageSize, orderQueryParam),

Check warning on line 116 in src/IIIFPresentation/API/Converters/CollectionConverter.cs

View workflow job for this annotation

GitHub Actions / test-dotnet

Possible null reference argument for parameter 'orderQueryParam' in 'string CollectionHelperX.GenerateFlatCollectionViewId(Collection collection, UrlRoots urlRoots, int currentPage, int pageSize, string orderQueryParam)'.

Check warning on line 116 in src/IIIFPresentation/API/Converters/CollectionConverter.cs

View workflow job for this annotation

GitHub Actions / test-dotnet

Possible null reference argument for parameter 'orderQueryParam' in 'string CollectionHelperX.GenerateFlatCollectionViewId(Collection collection, UrlRoots urlRoots, int currentPage, int pageSize, string orderQueryParam)'.
Type = PresentationType.PartialCollectionView,
Page = currentPage,
PageSize = pageSize,
TotalPages = totalPages,
};

if (totalPages > 1)
if (currentPage > 1)
{
view.First = dbAsset.GenerateFlatCollectionViewFirst(urlRoots, pageSize, orderQueryParam);
view.Previous = dbAsset.GenerateFlatCollectionViewPrevious(urlRoots, currentPage, pageSize, orderQueryParam);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Core;
using Microsoft.EntityFrameworkCore;
using Models.API.Collection;
using Models.Database.Collections;
using Repository;
using Repository.Helpers;

Expand Down Expand Up @@ -35,4 +36,14 @@ public static class PresentationContextX

return null;
}

public static async Task<Collection?> RetrieveCollection(this PresentationContext dbContext, int customerId,
string collectionId, CancellationToken cancellationToken)
{
var collection = await dbContext.Collections.AsNoTracking().FirstOrDefaultAsync(
s => s.CustomerId == customerId && s.Id == collectionId,
cancellationToken);

return collection;
}
}
Loading
Loading