Skip to content

Commit

Permalink
Merge pull request #216 from dlcs/feature/ignorePaintedResource
Browse files Browse the repository at this point in the history
Ignore painted resource when items are also present
  • Loading branch information
JackLewis-digirati authored Dec 20, 2024
2 parents cba835e + 440693b commit 3677676
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
using API.Features.Manifest.Validators;
using API.Settings;
using AWS.Settings;
using DLCS;
using FluentValidation.TestHelper;
using IIIF.Presentation.V3;
using Microsoft.Extensions.Options;
using Models.API.Manifest;

namespace API.Tests.Features.Manifest.Validators;

public class PresentationManifestValidatorTests
{
private readonly PresentationManifestValidator sut = new();
private readonly PresentationManifestValidator sut = new(Options.Create(new ApiSettings()
{
AWS = new AWSSettings(),
DLCS = new DlcsSettings
{
ApiUri = new Uri("https://localhost")
}
}));

[Theory]
[InlineData(null)]
Expand All @@ -29,4 +41,70 @@ public void Parent_Required(string? parent)
var result = sut.TestValidate(manifest);
result.ShouldHaveValidationErrorFor(m => m.Parent);
}
}

[Fact]
public void CanvasPaintingAndItems_Manifest_ErrorWhenDefaultSettings()
{
var manifest = new PresentationManifest
{
Items = new List<Canvas>()
{
new ()
{
Id = "someId",
}
},
PaintedResources = new List<PaintedResource>()
{
new ()
{
CanvasPainting = new CanvasPainting()
{
CanvasId = "someCanvasId"
}
}
},
};

var result = sut.TestValidate(manifest);
result.ShouldHaveValidationErrorFor(m => m.Items);
}

[Fact]
public void CanvasPaintingAndItems_Manifest_NoErrorWhenSettings()
{
var sutAllowedItemsAndPaintedResource = new PresentationManifestValidator(Options.Create(new ApiSettings()
{
AWS = new AWSSettings(),
IgnorePaintedResourcesWithItems = true,
DLCS = new DlcsSettings
{
ApiUri = new Uri("https://localhost")
}
}));

var manifest = new PresentationManifest
{
Items = new List<Canvas>()
{
new ()
{
Id = "someId",
}
},
PaintedResources = new List<PaintedResource>()
{
new ()
{
CanvasPainting = new CanvasPainting()
{
CanvasId = "someCanvasId"
}
}
},
};

var result = sutAllowedItemsAndPaintedResource.TestValidate(manifest);
result.ShouldNotHaveValidationErrorFor(m => m.Items);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
using DLCS.Exceptions;
using DLCS.Models;
using FakeItEasy;
using IIIF.Presentation.V3.Annotation;
using IIIF.Presentation.V3.Strings;
using IIIF.Serialisation;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Models.API.General;
using Models.API.Manifest;
using Models.Database.General;
using Models.Infrastucture;
using Repository;
using Test.Helpers;
using Test.Helpers.Helpers;
Expand Down Expand Up @@ -1235,21 +1237,42 @@ public async Task CreateManifest_BadRequest_WhenItemsAndPaintedResourceFilled()
var manifest = new PresentationManifest
{
Parent = $"http://localhost/1/collections/{RootCollection.Id}",
Behavior = [
Behavior.IsPublic
],
Slug = slug,
Items = new List<Canvas>()
{
new ()
Items =
[
new Canvas
{
Id = "https://iiif.example/manifest.json",
Id = "https://iiif.example/manifestFromItems.json",
Items =
[
new AnnotationPage
{
Id = "https://iiif.example/manifestFromItemsAnnotationPage.json",
Items =
[
new PaintingAnnotation
{
Id = "https://iiif.example/manifestFromItemsPaintingAnnotation.json",
Body = new Canvas
{
Id = "https://iiif.example/manifestFromItemsCanvasBody.json"
}
}
]
}
]
}
},
],
PaintedResources = new List<PaintedResource>()
{
new PaintedResource()
new ()
{
CanvasPainting = new CanvasPainting()
{
CanvasId = "https://iiif.example/manifest.json"
CanvasId = "https://iiif.example/manifestFromPainted.json"
}
}
}
Expand All @@ -1262,9 +1285,12 @@ public async Task CreateManifest_BadRequest_WhenItemsAndPaintedResourceFilled()
var response = await httpClient.AsCustomer().SendAsync(requestMessage);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
var error = await response.ReadAsPresentationResponseAsync<Error>();
error!.Detail.Should().Be("The properties \"items\" and \"paintedResource\" cannot be used at the same time");
error.ErrorTypeUri.Should().Be("http://localhost/errors/ModifyCollectionType/ValidationFailed");
response.StatusCode.Should().Be(HttpStatusCode.Created);

var presentationManifest = await response.ReadAsPresentationResponseAsync<PresentationManifest>();
presentationManifest.PaintedResources.Count.Should().Be(1);
presentationManifest.PaintedResources.First().CanvasPainting.CanvasOriginalId.Should()
.Be("https://iiif.example/manifestFromItems.json");
presentationManifest.Items.Count.Should().Be(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using API.Tests.Integration.Infrastructure;
using Core.Response;
using IIIF.Presentation.V3;
using IIIF.Presentation.V3.Annotation;
using IIIF.Presentation.V3.Strings;
using IIIF.Serialisation;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -440,35 +441,52 @@ private void SetCorrectEtag(HttpRequestMessage requestMessage, Manifest dbManife
}

[Fact]
public async Task PutFlatId_Update_BadRequest_WhenItemsAndPaintedResourceFilled()
public async Task PutFlatId_Update_IgnoresPaintedResources_WhenItemsAndPaintedResourceFilled()
{
// Arrange
var createdDate = DateTime.UtcNow.AddDays(-1);
var dbManifest = (await dbContext.Manifests.AddTestManifest(createdDate: createdDate)).Entity;
await dbContext.SaveChangesAsync();
var parent = $"http://localhost/{Customer}/collections/{RootCollection.Id}";
var slug = $"changed_{dbManifest.Hierarchy.Single().Slug}";
var manifest = new PresentationManifest
{
Parent = $"http://localhost/1/collections/{RootCollection.Id}",
Slug = slug,
Items = new List<Canvas>()
{
new ()
Items =
[
new Canvas
{
Id = "https://iiif.example/manifest.json",
Id = "https://iiif.example/manifestFromItems.json",
Items =
[
new AnnotationPage
{
Id = "https://iiif.example/manifestFromItemsAnnotationPage.json",
Items =
[
new PaintingAnnotation
{
Id = "https://iiif.example/manifestFromItemsPaintingAnnotation.json",
Body = new Canvas
{
Id = "https://iiif.example/manifestFromItemsCanvasBody.json"
}
}
]
}
]
}
},
PaintedResources = new List<PaintedResource>()
{
new PaintedResource()
],
PaintedResources =
[
new PaintedResource
{
CanvasPainting = new CanvasPainting()
CanvasPainting = new CanvasPainting
{
CanvasId = "https://iiif.example/manifest.json"
}
}
}
]
};

var requestMessage =
Expand All @@ -480,9 +498,12 @@ public async Task PutFlatId_Update_BadRequest_WhenItemsAndPaintedResourceFilled(
var response = await httpClient.AsCustomer().SendAsync(requestMessage);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
var error = await response.ReadAsPresentationResponseAsync<Error>();
error!.Detail.Should().Be("The properties \"items\" and \"paintedResource\" cannot be used at the same time");
error.ErrorTypeUri.Should().Be("http://localhost/errors/ModifyCollectionType/ValidationFailed");
response.StatusCode.Should().Be(HttpStatusCode.OK);

var presentationManifest = await response.ReadAsPresentationResponseAsync<PresentationManifest>();
presentationManifest.PaintedResources.Count.Should().Be(1);
presentationManifest.PaintedResources.First().CanvasPainting.CanvasOriginalId.Should()
.Be("https://iiif.example/manifestFromItems.json");
presentationManifest.Items.Count.Should().Be(1);
}
}
1 change: 1 addition & 0 deletions src/IIIFPresentation/API.Tests/appsettings.Testing.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"ResourceRoot": "https://localhost:7230",
"PageSize": 20,
"MaxPageSize": 100,
"IgnorePaintedResourcesWithItems": true,
"AWS": {
"S3": {
"StorageBucket": "presentation-storage"
Expand Down
15 changes: 11 additions & 4 deletions src/IIIFPresentation/API/Features/Manifest/ManifestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
using API.Infrastructure.Helpers;
using API.Infrastructure.IdGenerator;
using API.Infrastructure.Validation;
using API.Settings;
using Core;
using Core.Auth;
using Core.Helpers;
using DLCS;
using DLCS.API;
using DLCS.Exceptions;
using IIIF.Serialisation;
Expand All @@ -19,11 +19,8 @@
using Models.API.Manifest;
using Models.Database.Collections;
using Models.Database.General;
using Newtonsoft.Json.Linq;
using Repository;
using Repository.Helpers;
using Batch = DLCS.Models.Batch;
using CanvasPainting = Models.Database.CanvasPainting;
using Collection = Models.Database.Collections.Collection;
using DbManifest = Models.Database.Collections.Manifest;
using PresUpdateResult = API.Infrastructure.Requests.ModifyEntityResult<Models.API.Manifest.PresentationManifest, Models.API.General.ModifyCollectionType>;
Expand Down Expand Up @@ -59,10 +56,13 @@ public class ManifestService(
IIIFS3Service iiifS3,
IETagManager eTagManager,
CanvasPaintingResolver canvasPaintingResolver,
IOptions<ApiSettings> options,
IPathGenerator pathGenerator,
IDlcsApiClient dlcsApiClient,
ILogger<ManifestService> logger)
{
private readonly ApiSettings settings = options.Value;

/// <summary>
/// Create or update full manifest, using details provided in request object
/// </summary>
Expand Down Expand Up @@ -123,6 +123,13 @@ private async Task<PresUpdateResult> CreateInternal(WriteManifestRequest request
var (parentErrors, parentCollection) = await TryGetParent(request, cancellationToken);
if (parentErrors != null) return parentErrors;

// can't have both items and painted resources, so items takes precedence
if (settings.IgnorePaintedResourcesWithItems && !request.PresentationManifest.Items.IsNullOrEmpty() &&
!request.PresentationManifest.PaintedResources.IsNullOrEmpty())
{
request.PresentationManifest.PaintedResources = null;
}

using (logger.BeginScope("Creating Manifest for Customer {CustomerId}", request.CustomerId))
{
var (error, dbManifest) =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
using API.Infrastructure.Validation;
using API.Settings;
using Core.Helpers;
using DLCS;
using FluentValidation;
using Microsoft.Extensions.Options;
using Models.API.Manifest;

namespace API.Features.Manifest.Validators;

public class PresentationManifestValidator : AbstractValidator<PresentationManifest>
{
public PresentationManifestValidator()
public PresentationManifestValidator(IOptions<ApiSettings> options)
{
var settings = options.Value;

RuleFor(f => f.Parent).NotEmpty().WithMessage("Requires a 'parent' to be set");
RuleFor(f => f.Slug).NotEmpty().WithMessage("Requires a 'slug' to be set")
.Must(slug => !SpecConstants.ProhibitedSlugs.Contains(slug!))
.WithMessage("'slug' cannot be one of prohibited terms: '{PropertyValue}'");
RuleFor(f => f.Items).Empty()
.When(f => !f.PaintedResources.IsNullOrEmpty())
.WithMessage("The properties \"items\" and \"paintedResource\" cannot be used at the same time");

if (!settings.IgnorePaintedResourcesWithItems)
{
RuleFor(f => f.Items).Empty()
.When(f => !f.PaintedResources.IsNullOrEmpty())
.WithMessage("The properties \"items\" and \"paintedResource\" cannot be used at the same time");
}
}
}
7 changes: 6 additions & 1 deletion src/IIIFPresentation/API/Settings/ApiSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ public class ApiSettings

public string? PathBase { get; set; }

/// <summary>
/// Whether painted resources should be ignored when there are also items
/// </summary>
public bool IgnorePaintedResourcesWithItems { get; set; }

public required AWSSettings AWS { get; set; }

public required DlcsSettings DLCS { get; set; }
}
}

0 comments on commit 3677676

Please sign in to comment.