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

Initial deploy to production #52

Merged
merged 107 commits into from
Nov 29, 2022
Merged
Changes from 1 commit
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
60250a5
Update to aws-sdk v3 and create first mocked tests
mbklein Aug 4, 2022
0e7e542
Merge pull request #2 from nulib/3071-aws-sdk-v3-and-mocks
bmquinn Aug 8, 2022
78ff0a6
Adds opensearch tests and fixtures for search()
bmquinn Aug 8, 2022
1c864c6
Merge pull request #6 from nulib/3086-search-test
bmquinn Aug 9, 2022
3f067db
Add a test example for calling getWork() on a missing document return…
bmquinn Aug 9, 2022
9f56079
Merge pull request #7 from nulib/test-coverage
bmquinn Aug 9, 2022
4638c8e
Use an API Gateway v2 HTTP API instead of a v1 REST API
mbklein Aug 10, 2022
b522ae8
Merge pull request #8 from nulib/convert-to-http-api
mbklein Aug 10, 2022
33e87b8
Change RestApiId to ApiId in template
mbklein Aug 10, 2022
b6cd7e8
Enable CORS on API
kdid Aug 12, 2022
744f887
Merge pull request #9 from nulib/fix-cors
kdid Aug 15, 2022
c1b375b
Factor model checking out of handler and add tests
mbklein Aug 15, 2022
688af71
Merge pull request #10 from nulib/test-coverage
mbklein Aug 15, 2022
7b1e480
Add pagination to search
mbklein Aug 15, 2022
be52fed
Merge pull request #11 from nulib/3083-paginate-search
kdid Aug 15, 2022
9df7f99
Add integration tests and improve mocha config
mbklein Aug 16, 2022
cb8de00
Merge pull request #12 from nulib/integration-tests
mbklein Aug 16, 2022
d0de36c
Create API custom domain and mappings
mbklein Aug 19, 2022
fece771
Merge pull request #13 from nulib/3061-api-domain-config
mbklein Aug 22, 2022
af7654c
Normalize headers to lowercase in middleware
mbklein Aug 22, 2022
87aba31
Merge pull request #14 from nulib/improve-middleware
mbklein Aug 22, 2022
ad921c4
Add /works/{id}/thumbnail route
mbklein Aug 24, 2022
0a458de
Merge pull request #15 from nulib/3113-get-work-thumbnail
bmquinn Aug 25, 2022
c5bd14e
Add size and aspect parameters to thumbnail request
mbklein Aug 29, 2022
99fb66b
Merge pull request #16 from nulib/3113-get-work-thumbnail
mbklein Aug 29, 2022
22931df
Forgot to short-circuit the queryParameters reference
mbklein Aug 29, 2022
05ded52
Adds a GET '/collections' endpoint that paginates results
bmquinn Aug 29, 2022
4e0fba9
Merge pull request #17 from nulib/3116-collections
bmquinn Aug 30, 2022
4f739e9
Add documentation
mbklein Aug 29, 2022
8503177
Merge pull request #18 from nulib/3062-initial-documentation
mbklein Sep 2, 2022
8ea2a3d
Update README with instructions on running in dev mode
mbklein Sep 2, 2022
0a9c753
IIIF Collection search handler
kdid Aug 10, 2022
0645bd3
Merge pull request #19 from nulib/BAK-3066-collection-manifests
kdid Sep 6, 2022
7d16c1f
Add root redirect to /docs/v2
mbklein Sep 7, 2022
09ee2b2
Return correct content type header for JSON responses
mbklein Sep 7, 2022
b98536f
Merge pull request #20 from nulib/docs-with-redirect
mbklein Sep 7, 2022
076d59f
Merge pull request #21 from nulib/3164-correct-content-types
mbklein Sep 7, 2022
9388068
Default to full aspect thumbnail
mbklein Sep 26, 2022
42357bf
Merge pull request #22 from nulib/3210-full-thumbnail
mbklein Sep 26, 2022
907d969
Implement /collections/{id}/thumbnail route
mbklein Oct 5, 2022
1fcd63a
Merge pull request #23 from nulib/3217-collection-thumbnail
mbklein Oct 7, 2022
3942a73
Remove searchToken from /collections route
mbklein Oct 7, 2022
0f3a4e9
Merge pull request #24 from nulib/3137-remove-collections-token
mbklein Oct 10, 2022
83b080f
Allow paginator to take additional params for the base url
mbklein Oct 10, 2022
7bf3734
Implement API auth route handlers, tests, and configuration
kdid Oct 5, 2022
af9690b
Merge pull request #25 from nulib/3228-page-size-bug
bmquinn Oct 10, 2022
79f9f8b
Merge pull request #26 from nulib/2836-auth-routes
mbklein Oct 10, 2022
ad16b4c
Fix ApiSecret reference in deploy template
mbklein Oct 10, 2022
3acb5da
Use middleware to fix discrepancies between SAM and API Gateway
mbklein Oct 10, 2022
164f3fd
Shared links API implementation
kdid Oct 12, 2022
1a9ebdf
Merge pull request #27 from nulib/2883-shared-links
mbklein Oct 12, 2022
b44e1df
Add automatic deploy on push to deployable branch
mbklein Oct 13, 2022
d0214dc
Merge pull request #28 from nulib/3087-auto-deploy
mbklein Oct 13, 2022
7cad80d
Return a collection as IIIF collection
kdid Oct 14, 2022
4356af2
Merge pull request #29 from nulib/3103-rdc-collections-iiif
kdid Oct 14, 2022
0b0512c
Allow certain IPs to receive private results
mbklein Oct 14, 2022
0c70a0c
Use repo deploy key instead of personal access token
mbklein Oct 17, 2022
02a2cae
Merge pull request #30 from nulib/3182-reading-room
mbklein Oct 19, 2022
becf561
Adds getSimilar handler function
bmquinn Oct 14, 2022
cae0060
Refactor search handler to use more flexible doSearch
mbklein Oct 17, 2022
3e76809
Allow GET /search to include a models path param
mbklein Oct 19, 2022
f7a9b60
Merge pull request #31 from nulib/3184-more-like-this
kdid Oct 20, 2022
b61e18f
Fix template & add template validation to CI build
mbklein Oct 20, 2022
f838c37
Merge pull request #32 from nulib/fix-template
mbklein Oct 20, 2022
21d2786
Ensure event object has expected members
mbklein Oct 20, 2022
8bbb684
Merge pull request #33 from nulib/BUGFIX-missing-event-members
kdid Oct 20, 2022
9955cfd
Fix 500 error in GET /collections
mbklein Oct 20, 2022
7f6b377
Merge pull request #34 from nulib/BUGFIX-api-gateway-compatibility
bmquinn Oct 20, 2022
4556070
Fix errors in OpenAPI spec document
mbklein Oct 20, 2022
0cec72d
Merge pull request #35 from nulib/fix-docs-build
mbklein Oct 20, 2022
1247989
Add default charset if missing from content-type header
mbklein Oct 20, 2022
43dcf75
Fix missing parameters in swagger docs
mbklein Oct 20, 2022
0ae2d7f
Merge pull request #36 from nulib/3270-character-encoding
mbklein Oct 20, 2022
0c369a0
Replace accidentally deleted GET /search route
mbklein Oct 20, 2022
ac002c5
Calculate the effective path better
mbklein Oct 21, 2022
7791adf
Modifies the /shared-links/{id} handler to parse a shared links
bmquinn Oct 20, 2022
8aeed3e
Merge pull request #38 from nulib/3251-shared-links
bmquinn Oct 21, 2022
76d84f9
Added as-iiif to Work response giving manifest shape
adamjarling Oct 20, 2022
7661733
Merge pull request #37 from nulib/3261-as-iiif
kdid Oct 26, 2022
0066016
Adds thumbnail to IIIF collection if it's an RDC Collection
kdid Oct 27, 2022
df1dbf2
Address PR comments
kdid Oct 27, 2022
05bca03
Merge pull request #39 from nulib/3249-collection-thumbnail-in-iiif-c…
kdid Oct 28, 2022
c99d1c1
Update README
kdid Oct 28, 2022
03e6eae
Adds /auth/logout handler
bmquinn Oct 28, 2022
65526b8
Merge pull request #40 from nulib/3227-auth-logout
bmquinn Oct 28, 2022
6603eb8
Fix shared links allow published logic
kdid Oct 31, 2022
250c27e
Merge pull request #41 from nulib/shared-link-fix
bmquinn Oct 31, 2022
a662190
Workaround for NUSSO missing directory attribute issue
mbklein Oct 31, 2022
a41ab21
Merge pull request #42 from nulib/3305-auth-callback-fix
kdid Oct 31, 2022
4b9b906
Make stubbed user match shape of actual user
mbklein Oct 31, 2022
f03b099
Merge pull request #43 from nulib/3305-fix-stub-user
mbklein Oct 31, 2022
03e4b4f
Add support for the 'sort' query string parameter in GET searches
bmquinn Oct 31, 2022
86e8308
Merge pull request #44 from nulib/3161-search-sort
mbklein Nov 3, 2022
a6a9da3
Don't return unpublished file sets
kdid Nov 3, 2022
90053c4
Merge pull request #45 from nulib/3332-file-set-published-access
bmquinn Nov 3, 2022
1bb3d24
Add file set authorization routes
kdid Oct 28, 2022
a710ac5
Merge pull request #46 from nulib/3166-file-set-auth
mbklein Nov 7, 2022
f137027
Authorize fake “FileSet” requests when id matches 00000000-0000-0000-…
mbklein Nov 8, 2022
5c1471a
Make sure both types of token (directory / stub) include netid
mbklein Nov 9, 2022
4a7b71b
Merge pull request #47 from nulib/3348-add-missing-netid-to-token
mbklein Nov 9, 2022
874c626
Net ID file set authorization for reading room
kdid Nov 11, 2022
f520242
Merge pull request #48 from nulib/3352-reading-room-netid
kdid Nov 11, 2022
acb5516
Prefer the search context's size over the default
mbklein Nov 28, 2022
148cc3a
Merge pull request #50 from nulib/3374-pagination-bug
bmquinn Nov 28, 2022
50ea75d
Update HTTPS documentation
mbklein Nov 29, 2022
95e6db4
Merge pull request #51 from nulib/3279-https-docs
mbklein Nov 29, 2022
e0374ce
API mappings need to depend on API
mbklein Nov 29, 2022
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
Prev Previous commit
Next Next commit
Added as-iiif to Work response giving manifest shape
Co-authored-by: Karen Shaw <karendid@gmail.com>
Co-authored-by: Mat Jordan <mat@northwestern.edu>
3 people committed Oct 26, 2022
commit 76d84f96b59b132623bdfb4acf944e8ad51a8750
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -60,3 +60,18 @@ The API will be available at:
- `http://localhost:3000` (from your dev environment)
- `http://USER_PREFIX.dev.library.northwestern.edu:3000` (from elsewhere)
- Don't forget to [open port 3000](https://github.com/nulib/aws-developer-environment#convenience-scripts) if you want to access it remotely

## Running the API locally via our AWS dev domain

This will make the local environment live at: https://[NAME].dev.rdc.library.northwestern.edu:3002/search

```
docker run --rm -it -d \
-e "UPSTREAM_DOMAIN=172.17.0.1" \
-e "UPSTREAM_PORT=3000" \
-e "PROXY_DOMAIN=$DEV_PREFIX.dev.rdc.library.northwestern.edu" \
-v /home/ec2-user/.dev_cert/dev.rdc.cert.pem:/etc/nginx/certs/cert.pem \
-v /home/ec2-user/.dev_cert/dev.rdc.key.pem:/etc/nginx/certs/key.pem \
-p 3002:443 \
outrigger/https-proxy:1.0
```
316 changes: 316 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

261 changes: 261 additions & 0 deletions src/api/response/iiif/manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
const { IIIFBuilder } = require("iiif-builder");
const { dcApiEndpoint, dcUrl } = require("../../../aws/environment");
const {
buildAnnotationBody,
buildImageResourceId,
buildImageService,
isAudioVideo,
isImage,
} = require("./presentation-api/items");
const { metadataLabelFields } = require("./presentation-api/metadata");

function transform(response) {
if (response.statusCode === 200) {
const builder = new IIIFBuilder();
const openSearchResponse = JSON.parse(response.body);
const source = openSearchResponse._source;

const manifestId = `${dcApiEndpoint()}/works/${source.id}?as=iiif`;

const annotationsToTagOnAtEnd = [];

const normalizedFlatManifestObj = builder.createManifest(
manifestId,
(manifest) => {
function buildCanvasFromFileSet(fileSet, index, isAuxiliary) {
const canvasId = `${manifestId}/canvas/${fileSet.role.toLowerCase()}/${index}`;
manifest.createCanvas(canvasId, (canvas) => {
if (isAudioVideo(source.work_type))
canvas.duration = fileSet.duration || 1;

canvas.height = fileSet.height || 100;
canvas.width = fileSet.width || 100;

canvas.addLabel(fileSet.label, "none");

/** Add thumbnail for Canvas */
if (fileSet.representative_image_url) {
const canvasThumbnail = {
id: buildImageResourceId(fileSet.representative_image_url),
type: "Image",
width: 300,
height: 300,
format: "image/jpeg",
service: buildImageService(fileSet.representative_image_url),
};
canvas.addThumbnail(canvasThumbnail);
}

const annotationId = `${canvasId}/annotation/0`;
canvas.createAnnotation(annotationId, {
id: annotationId,
type: "Annotation",
motivation: "painting",
body: buildAnnotationBody(
fileSet,
isAuxiliary ? "Image" : source.work_type
),
});

if (!isAuxiliary && fileSet.webvtt) {
const annotations = {
id: `${canvasId}/annotations/page/0`,
type: "AnnotationPage",
items: [
{
id: `${canvasId}/annotations/page/0/a0`,
type: "Annotation",
motivation: "supplementing",
body: {
id: fileSet.webvtt,
type: "Text",
format: "text/vtt",
label: {
en: ["Chapters"],
},
language: "none",
},
target: canvasId,
},
],
};
annotationsToTagOnAtEnd.push(annotations);
}
});
}

/** Build out manifest descriptive properties */
manifest.addLabel(source.title, "none");
source.description.length > 0 &&
manifest.addSummary(source.description, "none");

/** Build metadata property */
metadataLabelFields(source).forEach((item) => {
if (item.value && item.value.length > 0) {
manifest.addMetadata({ none: [item.label] }, { none: item.value });
}
});

/** Add required statement */
let requiredStatement = [
"Courtesy of Northwestern University Libraries",
];
manifest.setRequiredStatement({
label: { none: ["Attribution"] },
value: {
none: source.terms_of_use
? requiredStatement.concat(source.terms_of_use)
: requiredStatement,
},
});

/** Add rights using rights statement */
source.rights_statement?.id &&
manifest.setRights(source.rights_statement.id);

/** Add thumbnail */
const thumbnail = {
id: source.thumbnail,
type: "Image",
width: 300,
height: 300,
format: "image/jpeg",
};
manifest.addThumbnail(thumbnail);

/** Add seeAlso reference for api_link */
const seeAlso = {
id: source.api_link,
type: "Dataset",
format: "application/json",
label: {
none: ["Northwestern University Libraries Digital Collections API"],
},
};
manifest.addSeeAlso(seeAlso);

/** Add homepage */
const homepage = {
id: `${dcUrl()}/items/${source.id}`,
type: "Text",
format: "text/html",
label: {
none: [
"Homepage at Northwestern University Libraries Digital Collections",
],
},
};
manifest.setHomepage(homepage);

/** Add partOf */
const collectionEndpoint = `${dcApiEndpoint()}/collections/${
source.collection.id
}`;
manifest.setPartOf([
{
id: `${collectionEndpoint}?as=iiif`,
type: "Collection",
label: { none: [source.collection.title] },
...(source.collection.description && {
summary: {
none: [source.collection.description],
},
}),

/**
* TODO: iiif-builder mangles this property, come back to it
*/
// thumbnail: [
// {
// id: `${collectionEndpoint}/thumbnail`,
// type: "Image",
// width: 300,
// height: 300,
// format: "image/jpeg",
// },
// ],

/**
* TODO: iiif-builder cleanses this property and doesn't include it.
* Come back to it.
*/
homepage: [
{
id: `${dcUrl()}/collections/${source.collection.id}`,
type: "Text",
format: "text/html",
label: {
none: [
"Homepage at Northwestern University Libraries Digital Collections",
],
},
},
],
},
]);

/** Add logo */

/** Add provider */

/** Add items (Canvases) from a Work's Filesets */
source.file_sets
.filter((fileSet) => fileSet.role === "Access")
.forEach((fileSet, index) => {
buildCanvasFromFileSet(fileSet, index);
});

source.file_sets
.filter((fileSet) => fileSet.role === "Auxiliary")
.forEach((fileSet, index) => {
buildCanvasFromFileSet(fileSet, index, true);
});
}
);

const jsonManifest = builder.toPresentation3({
id: normalizedFlatManifestObj.id,
type: "Manifest",
});

/**
* Workaround to add webVTT annotations
* (iiif-builder package currently doesn't support it)
*/

annotationsToTagOnAtEnd.forEach((a) => {
const matched = jsonManifest.items.find(
(canvas) => canvas.id === a.items[0].target
);

if (matched) {
matched.annotations = [a];
}
});

return {
statusCode: 200,
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
...jsonManifest,
}),
};
}
return transformError(response);
}

function transformError(response) {
const responseBody = {
status: response.statusCode,
error: "TODO",
};

return {
statusCode: response.statusCode,
body: JSON.stringify(responseBody),
};
}

module.exports = { transform };
58 changes: 58 additions & 0 deletions src/api/response/iiif/presentation-api/items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** */

function annotationType(workType) {
return workType === "Audio" ? "Sound" : workType;
}

function buildAnnotationBody(fileSet, workType) {
const body = {
id: buildAnnotationBodyId(fileSet, workType),
type: annotationType(workType),
format: fileSet.mime_type,
height: fileSet.height || 100,
width: fileSet.width || 100,
};

if (isImage(workType))
body.service = buildImageService(fileSet.representative_image_url);
if (isAudioVideo(workType)) body.duration = fileSet.duration;
return body;
}

function buildAnnotationBodyId(fileSet, workType) {
return isAudioVideo(workType)
? fileSet.streaming_url
: buildImageResourceId(fileSet.representative_image_url, "600,");
}

function buildImageResourceId(uri, size = "!300,300") {
return `${uri}/full/${size}/0/default.jpg`;
}

function buildImageService(representativeImageUrl) {
return [
{
id: representativeImageUrl,
profile: "http://iiif.io/api/image/2/level2.json",
type: "ImageService2",
},
];
}

function isAudioVideo(workType) {
return ["Audio", "Video"].includes(workType);
}

function isImage(workType) {
return workType === "Image";
}

module.exports = {
annotationType,
buildAnnotationBody,
buildAnnotationBodyId,
buildImageResourceId,
buildImageService,
isAudioVideo,
isImage,
};
125 changes: 125 additions & 0 deletions src/api/response/iiif/presentation-api/metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/** Build manifest metadata */
function formatSingleValuedField(value) {
return value ? [value] : [];
}

function metadataLabelFields(source) {
return [
{
label: "Alternate Title",
value: source.alternate_title,
},
{
label: "Abstract",
value: source.abstract,
},
{
label: "Caption",
value: source.caption,
},
{
label: "Contributor",
value: source.contributor.map((item) => item.label_with_role),
},
{
label: "Creator",
value: source.creator.map((item) => item.label),
},
{
label: "Cultural Context",
value: source.cultural_context,
},
{
label: "Date",
value: source.date_created,
},
{
label: "Department",
value: formatSingleValuedField(source.library_unit),
},
{
label: "Dimensions",
value: source.physical_description_size,
},
{
label: "Genre",
value: source.genre.map((item) => item.label),
},
{
label: "Keyword",
value: source.keywords,
},
{
label: "Last Modified",
value: formatSingleValuedField(source.modified_date),
},
{
label: "Language",
value: source.language.map((item) => item.label),
},
{
label: "Location",
value: source.location?.map((item) => item.label),
},
{
label: "Materials",
value: source.physical_description_material,
},
{
label: "Notes",
value: source.notes.map((item) => `${item.note} (${item.type})`),
},
{
label: "Provenance",
value: source.provenance,
},
{
label: "Publisher",
value: source.publisher,
},
{
label: "Related Material",
value: source.related_material,
},
{
label: "Related URL",
value: source.related_url.map(
(item) => `<span><a href=${item.url}>${item.label}</a></span>`
),
},
{
label: "Rights Holder",
value: source.rights_holder,
},
{
label: "Scope and Contents",
value: source.scope_and_contents,
},
{
label: "Series",
value: source.series,
},
{
label: "Source",
value: source.source,
},
{
label: "Style Period",
value: source.style_period.map((item) => item.label),
},
{
label: "Subject",
value: source.subject.map((item) => item.label_with_role),
},
{
label: "Table of Contents",
value: source.table_of_contents,
},
{
label: "Technique",
value: source.technique.map((item) => item.label),
},
];
}

module.exports = { formatSingleValuedField, metadataLabelFields };
5 changes: 5 additions & 0 deletions src/aws/environment.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,10 @@ function dcApiEndpoint() {
return process.env.DC_API_ENDPOINT;
}

function dcUrl() {
return process.env.DC_URL;
}

function elasticsearchEndpoint() {
return process.env.ELASTICSEARCH_ENDPOINT;
}
@@ -30,6 +34,7 @@ function region() {
module.exports = {
apiToken,
dcApiEndpoint,
dcUrl,
elasticsearchEndpoint,
prefix,
region,
14 changes: 13 additions & 1 deletion src/handlers/get-work-by-id.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { processRequest, processResponse } = require("./middleware");
const { getWork } = require("../api/opensearch");
const { isFromReadingRoom } = require("../helpers");

const manifestResponse = require("../api/response/iiif/manifest");
const opensearchResponse = require("../api/response/opensearch");

/**
@@ -11,6 +13,16 @@ exports.handler = async (event) => {
const id = event.pathParameters.id;
const allowPrivate = isFromReadingRoom(event);
const esResponse = await getWork(id, { allowPrivate });
const response = await opensearchResponse.transform(esResponse);

let response;
const as = event.queryStringParameters.as;

if (as && as === "iiif") {
// Make it IIIFy
response = manifestResponse.transform(esResponse);
} else {
response = await opensearchResponse.transform(esResponse);
}

return processResponse(event, response);
};
315 changes: 315 additions & 0 deletions src/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/package.json
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
"@aws-sdk/signature-v4": "^3.130.0",
"axios": ">=0.21.1",
"cookie": "^0.5.0",
"iiif-builder": "^1.0.6",
"jsonwebtoken": "^8.5.1",
"lz-string": "^1.4.4",
"parse-http-header": "^1.0.1"
366 changes: 363 additions & 3 deletions test/fixtures/mocks/work-1234.json
Original file line number Diff line number Diff line change
@@ -3,16 +3,376 @@
"_type": "_doc",
"_id": "1234",
"_version": 1,
"_seq_no": 719,
"_primary_term": 1,
"found": true,
"_source": {
"provenance": [
"Artist; sold to Mr. Blank in 1955; sold to Lancelot in 2017; gifted to Northwestern University in 2019"
],
"contributor": [
{
"facet": "http://id.loc.gov/authorities/names/n91114928|ctg|Metallica (Musical group) (Cartographer)",
"id": "http://id.loc.gov/authorities/names/n91114928",
"label": "Metallica (Musical group)",
"label_with_role": "Metallica (Musical group) (Cartographer)",
"role": "Cartographer",
"variants": []
},
{
"facet": "http://id.worldcat.org/fast/1204616|abr|South Africa (Abridger)",
"id": "http://id.worldcat.org/fast/1204616",
"label": "South Africa",
"label_with_role": "South Africa (Abridger)",
"role": "Abridger",
"variants": []
},
{
"facet": "http://id.worldcat.org/fast/1150166|app|Thistles (Applicant)",
"id": "http://id.worldcat.org/fast/1150166",
"label": "Thistles",
"label_with_role": "Thistles (Applicant)",
"role": "Applicant",
"variants": []
}
],
"batch_ids": [
"a846a5f2-da57-49e6-a138-f5462d113a55",
"97aac3e3-389a-47ac-a7b3-5dd6ffffd558",
"e2529123-6a8c-4f6d-89d5-0257a1b947d4",
"b3d3462d-c03e-4cae-9219-4e38335a25fc",
"1591cc1e-009d-4b39-97a2-7d8743fb957b",
"80a15dc2-92d5-48a4-9aa4-73ecdcb1d130"
],
"publisher": ["Northwestern University Press"],
"subject": [
{
"facet": "http://id.worldcat.org/fast/1902713|TOPICAL|Cats on postage stamps (Topical)",
"id": "http://id.worldcat.org/fast/1902713",
"label": "Cats on postage stamps",
"label_with_role": "Cats on postage stamps (Topical)",
"role": "Topical",
"variants": []
},
{
"facet": "info:nul/6cba23b5-a91a-4c13-8398-54967b329d48|TOPICAL|Test Record Canary (Topical)",
"id": "info:nul/6cba23b5-a91a-4c13-8398-54967b329d48",
"label": "Test Record Canary",
"label_with_role": "Test Record Canary (Topical)",
"role": "Topical",
"variants": []
},
{
"facet": "http://vocab.getty.edu/tgn/2000971|GEOGRAPHICAL|Leelanau (Geographical)",
"id": "http://vocab.getty.edu/tgn/2000971",
"label": "Leelanau",
"label_with_role": "Leelanau (Geographical)",
"role": "Geographical",
"variants": []
},
{
"facet": "http://id.worldcat.org/fast/1204587|GEOGRAPHICAL|Michigan--Ann Arbor (Geographical)",
"id": "http://id.worldcat.org/fast/1204587",
"label": "Michigan--Ann Arbor",
"label_with_role": "Michigan--Ann Arbor (Geographical)",
"role": "Geographical",
"variants": []
}
],
"scope_and_contents": ["I promise there is scope and content"],
"notes": [
{
"note": "Here are some notes",
"type": "General Note"
},
{
"note": "Awards type",
"type": "Awards"
},
{
"note": "Biographical note",
"type": "Biographical/Historical Note"
},
{
"note": "creation production credits",
"type": "Creation/Production Credits"
},
{
"note": "Language note",
"type": "Language Note"
},
{
"note": "Local Note",
"type": "Local Note"
},
{
"note": "Performers",
"type": "Performers"
},
{
"note": "Statement of Responsibility",
"type": "Statement of Responsibility"
},
{
"note": "Venue/event date",
"type": "Venue/Event Date"
},
{
"note": "massive add to all pages/check",
"type": "General Note"
}
],
"related_material": ["See Also: related material"],
"accession_number": "Canary_002",
"modified_date": "2022-10-13T20:56:31.249155Z",
"folder_names": ["Blue folder"],
"series": ["Canaries and How to Care for Them"],
"cultural_context": ["Test Context"],
"language": [
{
"facet": "http://id.loc.gov/vocabulary/languages/crh||Crimean Tatar",
"id": "http://id.loc.gov/vocabulary/languages/crh",
"label": "Crimean Tatar",
"variants": []
}
],
"location": [
{
"facet": "https://sws.geonames.org/4999069/||Leland Township",
"id": "https://sws.geonames.org/4999069/",
"label": "Leland Township",
"variants": []
}
],
"create_date": "2022-03-02T20:38:29.813494Z",
"thumbnail": "https://index.test.library.northwestern.edu/iiif/2/mbk-dev/5678/square/!300,300/0/default.jpg",
"id": "1234",
"collection": {
"id": "7c50096c-89eb-43e8-b357-5836a788ddeb",
"title": "TEST Canary Records",
"description": "This is the description of the collection"
},
"abstract": [],
"creator": [
{
"facet": "http://id.loc.gov/authorities/names/no2011059409||Dessa (Vocalist)",
"id": "http://id.loc.gov/authorities/names/no2011059409",
"label": "Dessa (Vocalist)",
"variants": [
"Dessa, 1981-",
"Wander, Dessa, 1981-",
"Dessa Darling",
"Wander, Margret"
]
},
{
"facet": "http://id.worldcat.org/fast/1152763||Tornadoes",
"id": "http://id.worldcat.org/fast/1152763",
"label": "Tornadoes",
"variants": []
},
{
"facet": "http://vocab.getty.edu/aat/300443944||photo editors",
"id": "http://vocab.getty.edu/aat/300443944",
"label": "photo editors",
"variants": []
},
{
"facet": "http://id.worldcat.org/fast/1717972||Schober, Franz von, 1796-1882",
"id": "http://id.worldcat.org/fast/1717972",
"label": "Schober, Franz von, 1796-1882",
"variants": []
}
],
"rights_holder": ["Artist"],
"box_number": ["88"],
"physical_description_size": ["16 x 24 inches"],
"description": [
"This is a private record for RepoDev testing on production"
],
"keywords": ["leaves"],
"indexed_at": "2022-10-14T14:19:42.844994",
"folder_numbers": ["88"],
"genre": [
{
"facet": "http://id.worldcat.org/fast/1919896||Biographies",
"id": "http://id.worldcat.org/fast/1919896",
"label": "Biographies",
"variants": []
},
{
"facet": "http://id.worldcat.org/fast/1019337||Mice",
"id": "http://id.worldcat.org/fast/1019337",
"label": "Mice",
"variants": []
}
],
"date_created": ["August 1906 to December 1910", "1958"],
"title": "Canary Record TEST 1",
"physical_description_material": ["Acrylic paint on cement block"],
"csv_metadata_update_jobs": [
"5753101a-42fa-4838-9b71-f1594a5b1d5f",
"6b46db60-6f6a-45e8-8b8d-ab0029a1e8fe",
"38988b3e-5778-41da-85a5-e16d13cb098a",
"21838181-8d12-4015-8b98-0874061adb98"
],
"ark": "ark:/99999/fk47h32p0m",
"caption": ["Beebo"],
"status": "Done",
"style_period": [
{
"facet": "http://vocab.getty.edu/aat/300018478||Qing (dynastic styles and periods)",
"id": "http://vocab.getty.edu/aat/300018478",
"label": "Qing (dynastic styles and periods)",
"variants": []
}
],
"api_model": "Work",
"published": true,
"catalog_key": ["MS-1984-1982-1989"],
"rights_statement": {
"id": "http://rightsstatements.org/vocab/InC-EDU/1.0/",
"label": "In Copyright - Educational Use Permitted"
},
"file_sets": [
{
"duration": null,
"height": 3024,
"id": "076dcbd8-8c57-40e8-bdf7-dc9153c87a36",
"label": "Access File - Tiff",
"mime_type": "image/tiff",
"original_filename": "Squirrel.tif",
"poster_offset": null,
"rank": 0,
"representative_image_url": "https://iiif.stack.rdc-staging.library.northwestern.edu/iiif/2/076dcbd8-8c57-40e8-bdf7-dc9153c87a36",
"role": "Access",
"streaming_url": null,
"webvtt": null,
"width": 4032
},
{
"duration": null,
"height": 3024,
"id": "d51cc0b6-562a-4a8f-8443-5e20221c308b",
"label": "Access File - Tiff",
"mime_type": "image/tiff",
"original_filename": "PXL_20211203_142315620.tif",
"poster_offset": null,
"rank": 1073741824,
"representative_image_url": "https://iiif.stack.rdc-staging.library.northwestern.edu/iiif/2/d51cc0b6-562a-4a8f-8443-5e20221c308b",
"role": "Supplemental",
"streaming_url": null,
"webvtt": null,
"width": 4032
},
{
"duration": null,
"height": 4032,
"id": "5a4ffcfb-e231-4a59-9e4d-92d1814604fb",
"label": "Preservation File - Tiff",
"mime_type": "image/tiff",
"original_filename": "distillery.tif",
"poster_offset": null,
"rank": 1073741824,
"representative_image_url": null,
"role": "Preservation",
"streaming_url": null,
"webvtt": null,
"width": 3024
},
{
"duration": null,
"height": null,
"id": "09617d98-9c67-414e-a0f7-4e69ca99546b",
"label": "Auxiliary File - PNG",
"mime_type": "image/jpeg",
"original_filename": "CoopersHawk.png",
"poster_offset": null,
"rank": 0,
"representative_image_url": "https://iiif.stack.rdc-staging.library.northwestern.edu/iiif/2/09617d98-9c67-414e-a0f7-4e69ca99546b",
"role": "Auxiliary",
"streaming_url": null,
"webvtt": null,
"width": null
},
{
"duration": null,
"height": null,
"id": "51862c1c-c024-45dc-ab26-694bd8ebc16c",
"label": "Access File - Jpeg",
"mime_type": "image/jpeg",
"original_filename": "Cassettetape.jpg",
"poster_offset": null,
"rank": 1610612736,
"representative_image_url": "https://iiif.stack.rdc-staging.library.northwestern.edu/iiif/2/51862c1c-c024-45dc-ab26-694bd8ebc16c",
"role": "Access",
"streaming_url": null,
"webvtt": null,
"width": null
},
{
"duration": null,
"height": null,
"id": "a10c1829-265c-44cf-996a-f8c59b16ba97",
"label": "Preservation File - Jpeg",
"mime_type": "image/jpeg",
"original_filename": "Cassettetape.jpg",
"poster_offset": null,
"rank": 0,
"representative_image_url": null,
"role": "Preservation",
"streaming_url": null,
"webvtt": null,
"width": null
}
],
"library_unit": "Charles Deering McCormick Library of Special Collections",
"technique": [
{
"facet": "http://vocab.getty.edu/aat/300053228||drypoint (printing process)",
"id": "http://vocab.getty.edu/aat/300053228",
"label": "drypoint (printing process)",
"variants": []
}
],
"table_of_contents": ["1. cats; 2. dogs"],
"representative_file_set": {
"fileSetId": "5678",
"url": "https://index.test.library.northwestern.edu/iiif/2/mbk-dev/5678"
},
"thumbnail": "https://index.test.library.northwestern.edu/iiif/2/mbk-dev/5678/square/!300,300/0/default.jpg",
"visibility": "Public"
"related_url": [
{
"label": "Finding Aid",
"url": "https://findingaids.library.northwestern.edu/"
},
{
"label": "Research Guide",
"url": "https://www.wbez.org/"
},
{
"label": "Related Information",
"url": "https://www.nationalgeographic.com/animals/mammals/facts/squirrels"
},
{
"label": "Hathi Trust Digital Library",
"url": "https://www.hathitrust.org/"
}
],
"terms_of_use": "Terms ",
"visibility": "Public",
"license": {
"id": "http://www.europeana.eu/portal/rights/rr-r.html",
"label": "All rights reserved",
"scheme": "license"
},
"api_link": "https://dcapi.rdc-staging.library.northwestern.edu/api/v2/works/1234",
"alternate_title": ["This is an alternative title"],
"preservation_level": "Level 1",
"published": true,
"source": ["Mars"],
"iiif_manifest": "https://iiif.stack.rdc-staging.library.northwestern.edu/public/15/6a/8f/8e/-5/49/b-/49/82/-8/6c/c-/37/5b/f0/41/04/ff-manifest.json",
"legacy_identifier": ["555"],
"work_type": "Image",
"identifier": ["555"],
"box_name": ["The name of a box"]
}
}
148 changes: 148 additions & 0 deletions test/fixtures/mocks/work-video-5678.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"_index": "kdid-dev-dc-v2-work-1666658122455",
"_type": "_doc",
"_id": "a3386c0e-e086-45ef-b3c5-9b45ea78211e",
"_version": 7,
"_seq_no": 31,
"_primary_term": 1,
"found": true,
"_source": {
"accession_number": "Video.Work.12342918",
"thumbnail": "https://dcapi.rdc-staging.library.northwestern.edu/works/a3386c0e-e086-45ef-b3c5-9b45ea78211e/thumbnail",
"indexed_at": "2022-10-25T18:22:37.800026",
"iiif_manifest": "https://kdid-dev-pyramids.s3.amazonaws.com/public/iiif3/a3/38/6c/0e/-e/08/6-/45/ef/-b/3c/5-/9b/45/ea/78/21/1e-manifest.json",
"folder_names": [],
"modified_date": "2022-10-25T18:22:13.531600Z",
"series": [],
"source": [],
"physical_description_size": [],
"batch_ids": [],
"publisher": [],
"date_created": ["circa 1990?", "August 1906 to December 1910"],
"csv_metadata_update_jobs": [],
"keywords": [],
"alternate_title": [],
"id": "a3386c0e-e086-45ef-b3c5-9b45ea78211e",
"box_number": [],
"box_name": [],
"create_date": "2022-10-25T00:36:58.251836Z",
"table_of_contents": [],
"project": {
"cycle": null,
"desc": null,
"manager": null,
"name": null,
"proposer": null,
"task_number": null
},
"style_period": [],
"technique": [],
"api_model": "Work",
"genre": [],
"folder_numbers": [],
"cultural_context": [],
"preservation_level": null,
"creator": [],
"related_url": [],
"visibility": "Public",
"api_link": "https://dcapi.rdc-staging.library.northwestern.edu/works/a3386c0e-e086-45ef-b3c5-9b45ea78211e",
"representative_file_set": {
"id": null,
"url": null
},
"subject": [],
"legacy_identifier": [],
"published": false,
"collection": {
"description": "This collection features digital copies of 113 antique maps of Africa and accompanying text dating from the mid-16th Century to the early 20th Century. All scanned maps are authentic and originally collected by the Melville J. Herskovits Library of African Studies. Melville J. Herskovits established Northwestern University's Program of African Studies in 1948 (the first of its kind at a major research university in the United States). The Herskovits Library, formally created as a separate library in 1954, has since its inception collected maps that describe Africa from their earliest appearance to the most current. Map area coverage includes the continent, regions (particularly North Africa and Algeria), islands (particularly Madagascar), and a few city plans. All of these maps are loose items, though many have been excised from published atlases. Some of the highlights of the digital collection are: a series of Ptolemic maps of North Africa by Ruscelli, ca. 1565; Forlani: Africa , 1562; Mercator: Africa, 1595; Blaeu: Æthiopia ca. 1650 (a Prester John map); Carey: Africa, 1795 (first map of Africa published in the United States), Arrowsmith: Africa, 1802 (notable for its large dimensions, 124 x 145 cm.), a series of Algerian maps published by the French government in the mid-1800's, and maps by other notable cartographers, such as Hondius, Jansson, de Jode, de L'Isle, Ortelius, Sanson, and de Wit. The original maps are kept and maintained in the map collection in the Government & Geographic Information Collection. We welcome questions, comments, and suggestions concerning any aspect of this digital collection, particularly with regards to provenance. Other antique maps from the Herskovits Library which were not included in this digital collection are either duplicate copies or other editions, such as French government sets covering Algeria.",
"id": "1c2e2200-c12d-4c7f-8b87-a935c349898a",
"title": "16th-Early 20th Century Maps of Africa"
},
"physical_description_material": [],
"notes": [],
"description": ["Yes!!!"],
"location": [],
"rights_statement": {},
"identifier": [],
"ingest_project": {},
"terms_of_use": null,
"abstract": [],
"file_sets": [
{
"duration": 5.599,
"height": 320,
"id": "5270faab-6575-4808-bad4-336523635a01",
"label": "This is the second file set",
"mime_type": "video/x-m4v",
"original_filename": "small.m4v",
"poster_offset": null,
"rank": 1073741824,
"representative_image_url": null,
"role": "Access",
"streaming_url": "https://kdid-dev-streaming.s3.amazonaws.com/52/70/fa/ab/-6/57/5-/48/08/-b/ad/4-/33/65/23/63/5a/01/5270faab-6575-4808-bad4-336523635a01.m4v",
"webvtt": null,
"width": 560
},
{
"duration": null,
"height": null,
"id": "92add7b5-95ff-410e-bac6-6de73dfefb56",
"label": "adasffasdfa",
"mime_type": "application/json",
"original_filename": "details.json",
"poster_offset": null,
"rank": 0,
"representative_image_url": null,
"role": "Supplemental",
"streaming_url": null,
"webvtt": null,
"width": null
},
{
"duration": null,
"height": 1024,
"id": "0a002b39-43c5-4807-9053-48be93fa67f6",
"label": "coffee!",
"mime_type": "image/tiff",
"original_filename": "coffee.tif",
"poster_offset": null,
"rank": 0,
"representative_image_url": "https://iiif.dev.rdc.library.northwestern.edu/iiif/2/kdid-dev/0a002b39-43c5-4807-9053-48be93fa67f6",
"role": "Auxiliary",
"streaming_url": null,
"webvtt": null,
"width": 1024
},
{
"duration": 5.599,
"height": 320,
"id": "7e10e4aa-2ecd-48ef-8424-124d893ff323",
"label": "Yes the is the label edited",
"mime_type": "video/x-m4v",
"original_filename": "small.m4v",
"poster_offset": 2868,
"rank": 0,
"representative_image_url": "https://iiif.dev.rdc.library.northwestern.edu/iiif/2/kdid-dev/posters/7e10e4aa-2ecd-48ef-8424-124d893ff323",
"role": "Access",
"streaming_url": "https://kdid-dev-streaming.s3.amazonaws.com/7e/10/e4/aa/-2/ec/d-/48/ef/-8/42/4-/12/4d/89/3f/f3/23/7e10e4aa-2ecd-48ef-8424-124d893ff323.m4v",
"webvtt": "https://kdid-dev-pyramids.s3.amazonaws.com/public/vtt/7e/10/e4/aa/-2/ec/d-/48/ef/-8/42/4-/12/4d/89/3f/f3/23/7e10e4aa-2ecd-48ef-8424-124d893ff323.vtt",
"width": 560
}
],
"language": [],
"title": "The title of the video work",
"ingest_sheet": {},
"ark": "ark:/99999/fk498553735",
"catalog_key": [],
"provenance": [],
"library_unit": null,
"work_type": "Video",
"contributor": [],
"license": null,
"scope_and_contents": [],
"caption": [],
"status": null,
"rights_holder": [],
"related_material": []
}
}
79 changes: 79 additions & 0 deletions test/integration/get-work-by-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";

const chai = require("chai");
const expect = chai.expect;
const RequestPipeline = require("../../src/api/request/pipeline");
chai.use(require("chai-http"));

describe("Retrieve work by id", () => {
helpers.saveEnvironment();
const mock = helpers.mockIndex();

describe("GET /works/{id}", () => {
const { handler } = require("../../src/handlers/get-work-by-id");

it("retrieves a single work document", async () => {
mock
.get("/dc-v2-work/_doc/1234")
.reply(200, helpers.testFixture("mocks/work-1234.json"));

const event = helpers
.mockEvent("GET", "/work/{id}")
.pathParams({ id: 1234 })
.render();
const result = await handler(event);
expect(result.statusCode).to.eq(200);
expect(result).to.have.header(
"content-type",
/application\/json;.*charset=UTF-8/
);

const resultBody = JSON.parse(result.body);
expect(resultBody.data.id).to.eq("1234");
});

it("404s a missing work", async () => {
mock
.get("/dc-v2-work/_doc/1234")
.reply(200, helpers.testFixture("mocks/missing-work-1234.json"));

const event = helpers
.mockEvent("GET", "/works/{id}")
.pathParams({ id: 1234 })
.render();
const result = await handler(event);
expect(result.statusCode).to.eq(404);
});

it("returns a single work as a IIIF Manifest", async () => {
// const originalQuery = {
// query: { query_string: { query: "collection.id:1234" } },
// };
// const authQuery = new RequestPipeline(originalQuery)
// .authFilter()
// .toJson();

mock
.get("/dc-v2-work/_doc/1234")
.reply(200, helpers.testFixture("mocks/work-1234.json"));

const event = helpers
.mockEvent("GET", "/works/{id}")
.pathParams({ id: 1234 })
.queryParams({ as: "iiif" })
.render();
const result = await handler(event);
expect(result.statusCode).to.eq(200);
expect(result).to.have.header(
"content-type",
/application\/json;.*charset=UTF-8/
);
const resultBody = JSON.parse(result.body);
expect(resultBody.type).to.eq("Manifest");
expect(resultBody["@context"]).to.eq(
"http://iiif.io/api/presentation/3/context.json"
);
expect(resultBody.label.none[0]).to.eq("Canary Record TEST 1");
});
});
});
185 changes: 185 additions & 0 deletions test/unit/api/response/iiif/manifest.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"use strict";

const transformer = require("../../../../../src/api/response/iiif/manifest");
const chai = require("chai");
const expect = chai.expect;
const { dcApiEndpoint, dcUrl } = require("../../../../../src/aws/environment");

describe("Image Work as IIIF Manifest response transformer", () => {
function getMetadataValueByLabel(metadataArray, targetLabel) {
const foundObj = metadataArray.find(
(item) => item.label.none[0] === targetLabel
);
return foundObj ? foundObj.value.none : undefined;
}

async function setup() {
const response = {
statusCode: 200,
body: helpers.testFixture("mocks/work-1234.json"),
};
const source = JSON.parse(response.body)._source;

const result = await transformer.transform(response);
expect(result.statusCode).to.eq(200);

return { source, manifest: JSON.parse(result.body) };
}

it("transforms an Image work response to minimal Manifest", async () => {
const { manifest } = await setup();
expect(manifest.type).to.eq("Manifest");
});

it("populates Manifest label", async () => {
const { source, manifest } = await setup();
expect(manifest.label.none[0]).to.eq(source.title);
});

it("populates Manifest summary", async () => {
const { source, manifest } = await setup();
expect(manifest.summary.none[0]).to.eq(source.description[0]);
});

it("populates Manifest metadata", async () => {
const { source, manifest } = await setup();
expect(
getMetadataValueByLabel(manifest.metadata, "Alternate Title")[0]
).to.eq(source.alternate_title[0]);
expect(getMetadataValueByLabel(manifest.metadata, "Abstract")).to.be
.undefined;
});

it("populates Manifest requiredStatement", async () => {
const { source, manifest } = await setup();
expect(manifest.requiredStatement.label.none[0]).to.eq("Attribution");
expect(manifest.requiredStatement.value.none[0]).to.eq(
"Courtesy of Northwestern University Libraries"
);
expect(manifest.requiredStatement.value.none.includes(source.terms_of_use))
.to.be.true;
});

it("populates Manifest rights", async () => {
const { source, manifest } = await setup();
expect(manifest.rights).to.eq(source.rights_statement.id);
});

it("populates Manifest thumbnail", async () => {
const { source, manifest } = await setup();
expect(manifest.thumbnail[0].id).to.eq(source.thumbnail);
});

it("populates Manifest seeAlso", async () => {
const { source, manifest } = await setup();
expect(manifest.seeAlso[0].id).to.eq(source.api_link);
expect(manifest.seeAlso[0].type).to.eq("Dataset");
expect(manifest.seeAlso[0].format).to.eq("application/json");
expect(manifest.seeAlso[0].label.none[0]).to.eq(
"Northwestern University Libraries Digital Collections API"
);
});

it("populates Manifest homepage", async () => {
const { source, manifest } = await setup();
expect(manifest.homepage[0].id).to.eq(`${dcUrl()}/items/${source.id}`);
expect(manifest.homepage[0].label.none[0]).to.eq(
"Homepage at Northwestern University Libraries Digital Collections"
);
});

it("populates Manifest partOf", async () => {
const { source, manifest } = await setup();
const partOf = manifest.partOf[0];
expect(partOf.id).to.eq(
`${dcApiEndpoint()}/collections/${source.collection.id}?as=iiif`
);
expect(partOf.type).to.eq("Collection");
expect(partOf.label.none).to.be.an("array").that.is.not.empty;
expect(partOf.summary.none).to.be.an("array").that.is.not.empty;
});

it("populates Manifest items (canvases)", async () => {
const { source, manifest } = await setup();
expect(manifest.items.length).to.eq(3);
manifest.items.forEach((canvas) => {
expect(canvas.type).to.eq("Canvas");
});
expect(manifest.items[0].width).to.eq(source.file_sets[0].width);
expect(manifest.items[0].height).to.eq(source.file_sets[0].height);
expect(manifest.items[0].label.none[0]).to.eq(source.file_sets[0].label);
expect(manifest.items[0].thumbnail[0].id).contains(
source.file_sets[0].representative_image_url
);
});

it("excludes Preservation and Supplemental filesets", async () => {
const { source, manifest } = await setup();
manifest.items.forEach((canvas) => {
expect(canvas.id).not.contains(["preservation", "supplemental"]);
});
});

it("populates Annotation (painting) for Image fileset", async () => {
const { source, manifest } = await setup();
const annotation = manifest.items[0].items[0].items[0];
expect(annotation.type).to.eq("Annotation");
expect(annotation.motivation).to.eq("painting");
expect(annotation.target).to.eq(manifest.items[0].id);
expect(annotation.body.id).contains(
source.file_sets[0].representative_image_url
);
expect(annotation.body.format).to.eq(source.file_sets[0].mime_type);
expect(annotation.body.type).to.eq("Image");
expect(annotation.body.width).to.eq(source.file_sets[0].width);
expect(annotation.body.height).to.eq(source.file_sets[0].height);
expect(annotation.body.service[0]["@id"]).to.eq(
source.file_sets[0].representative_image_url
);
});
});

describe("A/V Work as IIIF Manifest response transformer", () => {
async function setup() {
const response = {
statusCode: 200,
body: helpers.testFixture("mocks/work-video-5678.json"),
};
const source = JSON.parse(response.body)._source;

const result = await transformer.transform(response);
expect(result.statusCode).to.eq(200);

return { source, manifest: JSON.parse(result.body) };
}

it("transforms a Video work response to minimal Manifest", async () => {
const { manifest } = await setup();
expect(manifest.type).to.eq("Manifest");
});

it("renders duration on AV canvases", async () => {
const { manifest } = await setup();
expect(manifest.items[0].duration).to.eq(5.599);
});

it("renders annotation for type: Sound and Video ", async () => {
const { source, manifest } = await setup();
const annotation = manifest.items[0].items[0].items[0];

expect(annotation.body.duration).to.eq(5.599);
expect(annotation.body.type).to.eq("Video");
expect(annotation.body.id).to.eq(source.file_sets[0].streaming_url);
});
});

describe("404 network response", () => {
it("returns as expected", async () => {
const response = {
statusCode: 404,
body: helpers.testFixture("mocks/missing-work-1234.json"),
};
const result = await transformer.transform(response);
expect(result.statusCode).to.eq(404);
});
});
104 changes: 104 additions & 0 deletions test/unit/api/response/iiif/presentation-api/items.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use strict";

const items = require("../../../../../../src/api/response/iiif/presentation-api/items");
const chai = require("chai");
const expect = chai.expect;

describe("IIIF response presentation API items helpers", () => {
const accessImage = {
duration: null,
height: 3024,
id: "076dcbd8-8c57-40e8-bdf7-dc9153c87a36",
label: "Access File - Tiff",
mime_type: "image/tiff",
original_filename: "Squirrel.tif",
poster_offset: null,
rank: 0,
representative_image_url:
"https://iiif.stack.rdc-staging.library.northwestern.edu/iiif/2/076dcbd8-8c57-40e8-bdf7-dc9153c87a36",
role: "Access",
streaming_url: null,
webvtt: null,
width: 4032,
};

// const accessAudio = {
// duration: null,
// height: 3024,
// id: "076dcbd8-8c57-40e8-bdf7-dc9153c87a36",
// label: "Access File - Tiff",
// mime_type: "image/tiff",
// original_filename: "Squirrel.tif",
// poster_offset: null,
// rank: 0,
// representative_image_url:
// "https://iiif.stack.rdc-staging.library.northwestern.edu/iiif/2/076dcbd8-8c57-40e8-bdf7-dc9153c87a36",
// role: "Access",
// streaming_url: null,
// webvtt: null,
// width: 4032,
// };

it("annotationType(workType)", () => {
expect(items.annotationType("Audio")).to.eq("Sound");
expect(items.annotationType("Image")).to.eq("Image");
expect(items.annotationType("Video")).to.eq("Video");
});

it("buildAnnotationBody(fileSet, workType)", () => {
const body = items.buildAnnotationBody(accessImage, "Image");

expect(body.id).contains(accessImage.representative_image_url);
expect(body.width).to.eq(accessImage.width);
expect(body.format).to.eq(accessImage.mime_type);
expect(body.service[0].id).to.eq(accessImage.representative_image_url);
});

it("buildAnnotationBody(fileSet, workType)", () => {
const bodyId = items.buildAnnotationBodyId(accessImage, "Image");

expect(bodyId).eq(
`${accessImage.representative_image_url}/full/600,/0/default.jpg`
);
});

it('buildImageResourceId(representativeImageUrl,size = "!300,300")', () => {
expect(
items.buildImageResourceId(accessImage.representative_image_url)
).to.eq(
`${accessImage.representative_image_url}/full/!300,300/0/default.jpg`
);
expect(
items.buildImageResourceId(
accessImage.representative_image_url,
"1000,1000"
)
).to.eq(
`${accessImage.representative_image_url}/full/1000,1000/0/default.jpg`
);
});

it("buildImageService(representativeImageUrl)", () => {
const imageService = items.buildImageService(
accessImage.representative_image_url
)[0];

expect(imageService.id).to.eq(accessImage.representative_image_url);
expect(imageService.profile).to.eq(
"http://iiif.io/api/image/2/level2.json"
);
expect(imageService.type).to.eq("ImageService2");
});

it("isAudioVideo(workType)", () => {
expect(items.isAudioVideo("Audio")).to.be.true;
expect(items.isAudioVideo("Image")).to.be.false;
expect(items.isAudioVideo("Video")).to.be.true;
});

it("isImage(workType)", () => {
expect(items.isImage("Audio")).to.be.false;
expect(items.isImage("Image")).to.be.true;
expect(items.isImage("Video")).to.be.false;
});
});
33 changes: 33 additions & 0 deletions test/unit/api/response/iiif/presentation-api/metadata.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use strict";

const {
formatSingleValuedField,
metadataLabelFields,
} = require("../../../../../../src/api/response/iiif/presentation-api/metadata");
const chai = require("chai");
const expect = chai.expect;

describe("IIIF response presentation API metadata helpers", () => {
const response = {
statusCode: 200,
body: helpers.testFixture("mocks/work-1234.json"),
};
const source = JSON.parse(response.body)._source;

it("formatSingleValuedField(value)", () => {
expect(formatSingleValuedField("This value."))
.to.be.an("array")
.that.does.include("This value.");
expect(formatSingleValuedField(null)).to.be.an("array").that.is.empty;
});

it("metadataLabelFields(source)", () => {
const metadata = metadataLabelFields(source);
expect(Array.isArray(metadata)).to.be;
expect(metadata.length).to.eq(28);
metadata.forEach((item) => {
expect(item.label).to.be.a("string");
expect(item.value).to.be.an("array");
});
});
});