Skip to content

Commit 4c93801

Browse files
committed
Extend ManifestV3Builder to add adjuncts
Tests for outputting external adjuncts
1 parent ed7c428 commit 4c93801

File tree

6 files changed

+154
-13
lines changed

6 files changed

+154
-13
lines changed

src/protagonist/Orchestrator.Tests/Integration/ManifestHandlingTests.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using IIIF.ImageApi.V3;
1010
using IIIF.Presentation.V3.Annotation;
1111
using IIIF.Presentation.V3.Content;
12+
using IIIF.Presentation.V3.Strings;
1213
using IIIF.Serialisation;
1314
using Microsoft.Extensions.DependencyInjection;
1415
using Newtonsoft.Json.Linq;
@@ -794,4 +795,66 @@ await dbFixture.DbContext.Images.AddTestAsset(id, roles: "clickthrough", maxUnau
794795
.ContainSingle(s => s is AuthProbeService2 && s.Id.Contains(id.ToString()));
795796
paintable.Service.OfType<AuthProbeService2>().Single().Id.Should().Contain(id.ToString());
796797
}
798+
799+
[Fact]
800+
public async Task Get_V3ManifestWithAdjuncts_OutputsAdjunctsInCorrectLocation()
801+
{
802+
// Arrange
803+
var id = AssetIdGenerator.GetAssetId();
804+
await dbFixture.DbContext.Images
805+
.AddTestAsset(id, imageDeliveryChannels: imageDeliveryChannels)
806+
.WithTestAdjunct("seeAlso1", type: "Dataset", mediaType: "text/xml", iiifLinkType: IIIFLinkType.SeeAlso,
807+
profile: "http://www.loc.gov/standards/alto/v3/alto.xsd", label: new LanguageMap("en", "METS-ALTO XML"),
808+
externalId: "https://mets.example/1", language: ["en-GB"])
809+
.WithTestAdjunct("anno1", type: "Ignored", mediaType: "text/ignored",
810+
iiifLinkType: IIIFLinkType.Annotations, label: new LanguageMap("en", "Line-level annos"),
811+
externalId: "https://mets.example/w3c", language: ["ignored"])
812+
.WithTestAdjunct("rendering1", type: "Text", mediaType: "application/pdf", iiifLinkType: IIIFLinkType.Rendering,
813+
label: new LanguageMap("none", "PDF of image"), externalId: "https://pdf.example/1", language: ["fr"])
814+
.WithTestAdjunct("seeAlso2", type: "Text", mediaType: "text/xml", iiifLinkType: IIIFLinkType.SeeAlso,
815+
externalId: "https://other.example/2");
816+
817+
await dbFixture.DbContext.SaveChangesAsync();
818+
819+
List<AnnotationPage> expectedAnnos =
820+
[
821+
new() { Id = "https://mets.example/w3c", Label = new LanguageMap("en", "Line-level annos") }
822+
];
823+
824+
List<ExternalResource> expectedSeeAlso =
825+
[
826+
new("Dataset")
827+
{
828+
Id = "https://mets.example/1", Label = new LanguageMap("en", "METS-ALTO XML"),
829+
Profile = "http://www.loc.gov/standards/alto/v3/alto.xsd", Format = "text/xml",
830+
Language = ["en-GB"]
831+
},
832+
new("Text")
833+
{
834+
Id = "https://other.example/2", Format = "text/xml"
835+
},
836+
];
837+
838+
List<ExternalResource> expectedRendering =
839+
[
840+
new("Text")
841+
{
842+
Id = "https://pdf.example/1", Label = new LanguageMap("none", "PDF of image"),
843+
Format = "application/pdf", Language = ["fr"]
844+
},
845+
];
846+
847+
var path = $"iiif-manifest/v3/{id}";
848+
849+
// Act
850+
var response = await httpClient.GetAsync(path);
851+
852+
// Assert
853+
var manifest = (await response.Content.ReadAsStreamAsync()).FromJsonStream<IIIF3.Manifest>();
854+
var canvas = manifest.Items!.Single();
855+
856+
canvas.SeeAlso.Should().BeEquivalentTo(expectedSeeAlso);
857+
canvas.Rendering.Should().BeEquivalentTo(expectedRendering);
858+
canvas.Annotations.Should().BeEquivalentTo(expectedAnnos);
859+
}
797860
}

src/protagonist/Orchestrator.Tests/Integration/NamedQueryTests.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public NamedQueryTests(ProtagonistAppFactory<Startup> factory, DlcsDatabaseFixtu
5757
dbFixture.DbContext.Images.AddTestAsset(AssetId.FromString("99/1/matching-nothumbs"), num1: 3, ref1: "my-ref",
5858
maxUnauthorised: 10, roles: "default")
5959
.WithTestThumbnailMetadata()
60-
.WithTestDeliveryChannel(AssetDeliveryChannels.Image);
60+
.WithTestDeliveryChannel(AssetDeliveryChannels.Image)
61+
.WithTestAdjunct("test-adjunct", externalId: "https://example.com/see-also-1");
6162
dbFixture.DbContext.Images.AddTestAsset(AssetId.FromString("99/1/not-for-delivery"), num1: 4, ref1: "my-ref",
6263
notForDelivery: true)
6364
.WithTestThumbnailMetadata()
@@ -373,6 +374,41 @@ await dbFixture.DbContext.Images.AddTestAsset(AssetId.FromString("99/1/second"),
373374
}
374375
}
375376

377+
[Fact]
378+
public async Task Get_ReturnsV3ManifestWithCorrectAdjuncts()
379+
{
380+
// Arrange
381+
const string path = "iiif-resource/v3/99/test-named-query/my-ref/1";
382+
const string iiif3 = "application/ld+json; profile=\"http://iiif.io/api/presentation/3/context.json\"";
383+
384+
// Act
385+
var response = await httpClient.GetAsync(path);
386+
387+
// Assert
388+
response.StatusCode.Should().Be(HttpStatusCode.OK);
389+
response.Headers.Vary.Should().Contain("Accept");
390+
response.Content.Headers.ContentType.ToString().Should().Be(iiif3);
391+
392+
var manifest = (await response.Content.ReadAsStreamAsync()).FromJsonStream<IIIF3.Manifest>();
393+
manifest.Items.Should().HaveCount(3);
394+
395+
bool checkedSeeAlso = false;
396+
foreach (var canvas in manifest.Items!)
397+
{
398+
if (canvas.Id.Contains("matching-nothumbs"))
399+
{
400+
checkedSeeAlso = true;
401+
canvas.SeeAlso.Should().ContainSingle(sa => sa.Id == "https://example.com/see-also-1");
402+
}
403+
else
404+
{
405+
canvas.SeeAlso.Should().BeNull();
406+
}
407+
}
408+
409+
checkedSeeAlso.Should().BeTrue("No seeAlso checked in test, verify test data");
410+
}
411+
376412
[Fact]
377413
public async Task Get_AssetsRequireAuth_ReturnsV2ManifestWithoutAuthServices()
378414
{

src/protagonist/Orchestrator/Features/Manifests/IIIFNamedQueryProjector.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using IIIF;
1111
using Microsoft.AspNetCore.Http;
1212
using Microsoft.EntityFrameworkCore;
13+
using Microsoft.Extensions.Logging;
1314
using Orchestrator.Infrastructure;
1415
using Orchestrator.Infrastructure.IIIF;
1516
using Orchestrator.Infrastructure.IIIF.Manifests;
@@ -21,15 +22,8 @@ namespace Orchestrator.Features.Manifests;
2122
/// <summary>
2223
/// Methods for generating IIIF results from NamedQueries
2324
/// </summary>
24-
public class IIIFNamedQueryProjector
25+
public class IIIFNamedQueryProjector(IIIFManifestBuilder manifestBuilder, ILogger<IIIFNamedQueryProjector> logger)
2526
{
26-
private readonly IIIFManifestBuilder manifestBuilder;
27-
28-
public IIIFNamedQueryProjector(IIIFManifestBuilder manifestBuilder)
29-
{
30-
this.manifestBuilder = manifestBuilder;
31-
}
32-
3327
/// <summary>
3428
/// Project NamedQueryResult to IIIF presentation object
3529
/// </summary>
@@ -43,7 +37,11 @@ public IIIFNamedQueryProjector(IIIFManifestBuilder manifestBuilder)
4337
.IncludeRelationsForProjections()
4438
.AsSplitQuery()
4539
.ToListAsync(cancellationToken);
46-
if (assets.Count == 0) return null;
40+
if (assets.Count == 0)
41+
{
42+
logger.LogDebug("No assets found that match NQ parameters");
43+
return null;
44+
}
4745

4846
var orderedImages = NamedQueryProjections.GetOrderedAssets(assets, parsedNamedQuery).ToList();
4947

src/protagonist/Orchestrator/Infrastructure/IIIF/Manifests/ManifestV3Builder.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ private async Task<AssetCanvas> GetCanvasForAsset(Asset asset, CustomerPathEleme
209209
{
210210
canvas.Thumbnail = thumbnail.AsListOf<ExternalResource>();
211211
}
212+
213+
AddAdjunctsToCanvas(canvas, asset);
212214

213215
return new AssetCanvas(canvas, additionalContexts);
214216
}
@@ -268,7 +270,7 @@ private async Task<AssetCanvas> GetCanvasForAsset(Asset asset, CustomerPathEleme
268270
{
269271
logger.LogTrace("{CanvasId} is timebased, processing", canvas.Id);
270272
var canvasId = canvas.Id;
271-
var transcodes = asset.AssetApplicationMetadata.GetTranscodeMetadata(false);
273+
var transcodes = asset.AssetApplicationMetadata.GetTranscodeMetadata();
272274

273275
if (transcodes.IsNullOrEmpty())
274276
{
@@ -481,4 +483,46 @@ private static List<IService> GetDistinctAccessServices(Dictionary<AssetId, Auth
481483
.ToList();
482484
return accessServices;
483485
}
486+
487+
private void AddAdjunctsToCanvas(Canvas canvas, Asset asset)
488+
{
489+
var adjuncts = asset.Adjuncts ?? Enumerable.Empty<Adjunct>();
490+
491+
foreach (var adjunct in adjuncts)
492+
{
493+
logger.LogTrace("Adding adjunct {AdjunctId} to {CanvasId}", adjunct.Id, canvas.Id);
494+
switch (adjunct.IIIFLink)
495+
{
496+
case IIIFLinkType.SeeAlso:
497+
canvas.SeeAlso ??= [];
498+
canvas.SeeAlso.Add(CreateExternalResource(adjunct));
499+
break;
500+
case IIIFLinkType.Annotations:
501+
canvas.Annotations ??= [];
502+
canvas.Annotations.Add(new AnnotationPage
503+
{
504+
Id = adjunct.ExternalId.ToString(),
505+
Label = adjunct.Label,
506+
});
507+
break;
508+
case IIIFLinkType.Rendering:
509+
canvas.Rendering ??= [];
510+
canvas.Rendering.Add(CreateExternalResource(adjunct));
511+
break;
512+
default:
513+
throw new ArgumentOutOfRangeException(nameof(adjunct.IIIFLink), adjunct.IIIFLink,
514+
"IIIFLink type not supported");
515+
}
516+
}
517+
518+
// "rendering" and "seeAlso" are ExternalResources
519+
ExternalResource CreateExternalResource(Adjunct adjunct) => new(adjunct.Type)
520+
{
521+
Id = adjunct.ExternalId.ToString(),
522+
Format = adjunct.MediaType,
523+
Profile = adjunct.Profile,
524+
Label = adjunct.Label,
525+
Language = adjunct.Language?.ToList(),
526+
};
527+
}
484528
}

src/protagonist/Orchestrator/Infrastructure/MetadataWithFallbackThumbSizeCalculator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public async Task<ThumbnailSizes> GetThumbSizesForImage(Asset asset, Cancellatio
5252

5353
if (thumbnailSizes != null)
5454
{
55-
logger.LogDebug("ThumbSizes metadata found for {AssetId}", asset.Id);
55+
logger.LogTrace("ThumbSizes metadata found for {AssetId}", asset.Id);
5656
return thumbnailSizes;
5757
}
5858

src/protagonist/Test.Helpers/Integration/DatabaseTestDataPopulation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static ValueTask<EntityEntry<Asset>> AddTestAsset(this DbSet<Asset> asset
6666

6767
public static ValueTask<EntityEntry<Asset>> WithTestAdjunct(
6868
this ValueTask<EntityEntry<Asset>> asset,
69-
string id, string type = "Image", string mediaType = "image/jpg",
69+
string id, string type = "Image", string mediaType = "image/jpeg",
7070
IIIFLinkType iiifLinkType = IIIFLinkType.SeeAlso,
7171
string profile = null, LanguageMap label = null,
7272
string[] language = null, string externalId = "https://someHost.com/someUri", DateTime? created = null,

0 commit comments

Comments
 (0)