jskos-server 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +20 -0
- package/.editorconfig +9 -0
- package/.github/workflows/docker.yml +59 -0
- package/.github/workflows/gh-pages.yml +23 -0
- package/.github/workflows/gh-release.yml +19 -0
- package/.github/workflows/test.yml +39 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +2710 -0
- package/bin/extra.js +81 -0
- package/bin/import.js +438 -0
- package/bin/mongodb.js +21 -0
- package/bin/reset.js +257 -0
- package/bin/upgrade.js +34 -0
- package/config/config.default.json +88 -0
- package/config/config.schema.json +877 -0
- package/config/config.test.json +107 -0
- package/config/index.js +77 -0
- package/config/setup.js +212 -0
- package/depdendencies.png +0 -0
- package/docker/.env +1 -0
- package/docker/Dockerfile +20 -0
- package/docker/README.md +175 -0
- package/docker/docker-compose.yml +29 -0
- package/docker/docker-entrypoint.sh +8 -0
- package/docker/mongo-initdb.d/mongo_setup.sh +22 -0
- package/ecosystem.example.json +7 -0
- package/errors/index.js +94 -0
- package/eslint.config.js +17 -0
- package/index.js +10 -0
- package/models/annotations.js +13 -0
- package/models/concepts.js +12 -0
- package/models/concordances.js +12 -0
- package/models/index.js +33 -0
- package/models/mappings.js +20 -0
- package/models/meta.js +21 -0
- package/models/registries.js +12 -0
- package/models/schemes.js +12 -0
- package/package.json +91 -0
- package/routes/annotations.js +83 -0
- package/routes/common.js +86 -0
- package/routes/concepts.js +64 -0
- package/routes/concordances.js +86 -0
- package/routes/data.js +19 -0
- package/routes/mappings.js +108 -0
- package/routes/registries.js +24 -0
- package/routes/schemes.js +72 -0
- package/routes/validate.js +37 -0
- package/server.js +190 -0
- package/services/abstract.js +328 -0
- package/services/annotations.js +237 -0
- package/services/concepts.js +459 -0
- package/services/concordances.js +264 -0
- package/services/data.js +30 -0
- package/services/index.js +34 -0
- package/services/mappings.js +978 -0
- package/services/registries.js +319 -0
- package/services/schemes.js +318 -0
- package/services/validate.js +39 -0
- package/status.schema.json +145 -0
- package/test/abstract-service.js +36 -0
- package/test/annotations/annotation.json +13 -0
- package/test/api.js +2481 -0
- package/test/chai.js +14 -0
- package/test/changes.js +179 -0
- package/test/concepts/conceptNoFileEnding +4 -0
- package/test/concepts/concepts-ddc-6-60-61-62.json +123 -0
- package/test/concordances/concordances.ndjson +2 -0
- package/test/config.js +26 -0
- package/test/configs/complex-config.json +90 -0
- package/test/configs/empty-object.json +1 -0
- package/test/configs/fail-array.json +1 -0
- package/test/configs/fail-empty.json +0 -0
- package/test/configs/fail-mapping-only-props1.json +5 -0
- package/test/configs/fail-mapping-only-props2.json +5 -0
- package/test/configs/fail-mapping-only-props3.json +5 -0
- package/test/configs/fail-mapping-only-props4.json +5 -0
- package/test/configs/fail-nonexisting-prop.json +3 -0
- package/test/configs/fail-port-string.json +3 -0
- package/test/configs/fail-registry-types.json +16 -0
- package/test/configs/registry-types.json +16 -0
- package/test/data-write.js +784 -0
- package/test/eslint.js +22 -0
- package/test/import-reset.js +287 -0
- package/test/infer-mappings.js +340 -0
- package/test/ipcheck.js +287 -0
- package/test/mappings/README.md +1 -0
- package/test/mappings/ddc-gnd-1.mapping.json +33 -0
- package/test/mappings/ddc-gnd-2.mapping.json +67 -0
- package/test/mappings/mapping-ddc-gnd-noScheme.json +145 -0
- package/test/mappings/mapping-ddc-gnd.json +175 -0
- package/test/mappings/mappings-ddc.json +214 -0
- package/test/registries/registries.ndjson +2 -0
- package/test/services.js +557 -0
- package/test/terminologies/terminologies.json +94 -0
- package/test/test-utils.js +182 -0
- package/test/utils.js +425 -0
- package/test/validate.js +226 -0
- package/utils/adjust.js +206 -0
- package/utils/auth.js +154 -0
- package/utils/changes.js +88 -0
- package/utils/db.js +106 -0
- package/utils/ipcheck.js +76 -0
- package/utils/middleware.js +636 -0
- package/utils/searchHelper.js +153 -0
- package/utils/status.js +77 -0
- package/utils/users.js +7 -0
- package/utils/utils.js +114 -0
- package/utils/uuid.js +6 -0
- package/utils/version.js +324 -0
- package/views/base.ejs +172 -0
package/test/services.js
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import jskos from "jskos-tools"
|
|
2
|
+
import _ from "lodash"
|
|
3
|
+
import assert from "node:assert"
|
|
4
|
+
|
|
5
|
+
import { teardownInMemoryMongo, setupInMemoryMongo, createCollectionsAndIndexes, assertIndexes, assertMongoDB, dropDatabaseBeforeAndAfter, arrayToStream } from "./test-utils.js"
|
|
6
|
+
|
|
7
|
+
import { InvalidBodyError } from "../errors/index.js"
|
|
8
|
+
|
|
9
|
+
import { createServices } from "../index.js"
|
|
10
|
+
import config from "../config/config.test.json" with { type: "json" }
|
|
11
|
+
const services = createServices(config)
|
|
12
|
+
|
|
13
|
+
describe("Services Features", () => {
|
|
14
|
+
before(async () => {
|
|
15
|
+
const mongoUri = await setupInMemoryMongo({ replSet: false })
|
|
16
|
+
process.env.MONGO_URI = mongoUri
|
|
17
|
+
await createCollectionsAndIndexes()
|
|
18
|
+
await assertIndexes()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
after(async () => {
|
|
22
|
+
// close server if you started one
|
|
23
|
+
await teardownInMemoryMongo()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// 🗑 Drop DB before *and* after every single `it()` in this file
|
|
27
|
+
dropDatabaseBeforeAndAfter()
|
|
28
|
+
|
|
29
|
+
// 🔌 Sanity‐check that mongoose really is connected
|
|
30
|
+
assertMongoDB()
|
|
31
|
+
|
|
32
|
+
Object.keys(services).forEach(type => {
|
|
33
|
+
it(`should return an empty array for ${type} queryItems`, async () => {
|
|
34
|
+
const entities = await services[type].queryItems({ limit: 1, offset: 0 })
|
|
35
|
+
assert.strictEqual(entities.length, 0)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe("Concordance Service", () => {
|
|
40
|
+
|
|
41
|
+
const fromScheme = { uri: "test:fromScheme" }
|
|
42
|
+
const toScheme = { uri: "test:toScheme" }
|
|
43
|
+
|
|
44
|
+
it("should post schemes for testing concordances", async () => {
|
|
45
|
+
await services.scheme.createItem({ bodyStream: await arrayToStream([fromScheme, toScheme]) })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("should post a concordance with a named contributor, then remove the contributor name, then remove the contributor", async () => {
|
|
49
|
+
const concordance = {
|
|
50
|
+
fromScheme,
|
|
51
|
+
toScheme,
|
|
52
|
+
contributor: [{
|
|
53
|
+
uri: "test:user",
|
|
54
|
+
prefLabel: { de: "test" },
|
|
55
|
+
}],
|
|
56
|
+
}
|
|
57
|
+
const [postedConcordance] = await services.concordance.createItem({ bodyStream: await arrayToStream([concordance]) })
|
|
58
|
+
const patch = {
|
|
59
|
+
contributor: [{
|
|
60
|
+
uri: "test:user",
|
|
61
|
+
}],
|
|
62
|
+
}
|
|
63
|
+
const patchedConcordance = await services.concordance.patchConcordance({ body: patch, existing: postedConcordance })
|
|
64
|
+
assert.ok(!patchedConcordance.contributor[0].prefLabel, "PATCH requests should merge objects only on the top level.")
|
|
65
|
+
const patch2 = {
|
|
66
|
+
contributor: null,
|
|
67
|
+
}
|
|
68
|
+
const patchedConcordance2 = await services.concordance.patchConcordance({ body: patch2, existing: patchedConcordance })
|
|
69
|
+
assert.ok(patchedConcordance2.contributor === undefined, "A field should be removed when set to `null`.")
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
for (const scheme of [fromScheme, toScheme]) {
|
|
73
|
+
it("should delete scheme after testing concordances", async () => {
|
|
74
|
+
scheme._id = scheme.uri
|
|
75
|
+
scheme.concepts = []
|
|
76
|
+
await services.scheme.deleteItem({ uri: scheme.uri, existing: scheme })
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe("Mapping Service", () => {
|
|
83
|
+
|
|
84
|
+
describe("filter mappings by annotations", () => {
|
|
85
|
+
const mappings = [
|
|
86
|
+
{
|
|
87
|
+
uri: "mapping:1",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
uri: "mapping:2",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
uri: "mapping:3",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
to: { memberSet: [{ uri: "urn:test:concept" }] },
|
|
97
|
+
uri: "mapping:4",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
uri: "mapping:5",
|
|
101
|
+
},
|
|
102
|
+
].map(mapping => {
|
|
103
|
+
// Add fromScheme and toScheme
|
|
104
|
+
mapping.fromScheme = { uri: "urn:test:fromScheme" }
|
|
105
|
+
mapping.toScheme = { uri: "urn:test:toScheme" }
|
|
106
|
+
// Add from if necessary
|
|
107
|
+
if (!mapping.from) {
|
|
108
|
+
mapping.from = { memberSet: [{ uri: "urn:test:fromConcept" }] }
|
|
109
|
+
}
|
|
110
|
+
// Add empty to if necessary
|
|
111
|
+
if (!mapping.to) {
|
|
112
|
+
mapping.to = { memberSet: [] }
|
|
113
|
+
}
|
|
114
|
+
return mapping
|
|
115
|
+
})
|
|
116
|
+
const annotations = [
|
|
117
|
+
{
|
|
118
|
+
target: "mapping:1",
|
|
119
|
+
motivation: "assessing",
|
|
120
|
+
bodyValue: "+1",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
target: "mapping:1",
|
|
124
|
+
motivation: "assessing",
|
|
125
|
+
bodyValue: "-1",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
target: "mapping:2",
|
|
129
|
+
motivation: "moderating",
|
|
130
|
+
creator: {
|
|
131
|
+
id: "urn:test:creator",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
target: "mapping:3",
|
|
136
|
+
motivation: "moderating",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
target: "mapping:4",
|
|
140
|
+
motivation: "assessing",
|
|
141
|
+
bodyValue: "+1",
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
it("should post mappings and annotations used for tests", async () => {
|
|
146
|
+
let result
|
|
147
|
+
// Mappings
|
|
148
|
+
result = await services.mapping.createItem({ bodyStream: await arrayToStream(mappings) })
|
|
149
|
+
assert.strictEqual(result.length, mappings.length)
|
|
150
|
+
mappings.forEach(mapping => {
|
|
151
|
+
// Find corresponding mapping and set identifier/uri
|
|
152
|
+
const corresponding = result.find(m => jskos.compare(m, mapping))
|
|
153
|
+
assert.ok(!!corresponding)
|
|
154
|
+
mapping.identifier = corresponding.identifier
|
|
155
|
+
mapping.uri = corresponding.uri
|
|
156
|
+
})
|
|
157
|
+
// Annotations
|
|
158
|
+
// First adjust targets with actual URIs
|
|
159
|
+
annotations.forEach(annotation => {
|
|
160
|
+
const mapping = mappings.find(m => jskos.compare(m, { uri: annotation.target }))
|
|
161
|
+
assert.ok(!!mapping)
|
|
162
|
+
annotation.target = mapping.uri
|
|
163
|
+
})
|
|
164
|
+
result = await services.annotation.createItem({ bodyStream: await arrayToStream(annotations), admin: true })
|
|
165
|
+
assert.strictEqual(result.length, annotations.length)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it("should get correct number of mappings when using annotatedWith param", async () => {
|
|
169
|
+
const annotatedWith = "-1"
|
|
170
|
+
const result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedWith })
|
|
171
|
+
const expected = _.uniq(annotations.filter(a => a.bodyValue === annotatedWith).map(a => a.target)).sort()
|
|
172
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it("should get the correct mappings when using annotatedWith param with comparison operator", async () => {
|
|
176
|
+
for (let { from, to, annotatedWith } of [
|
|
177
|
+
{ to: "urn:test:concept", annotatedWith: ">=1" },
|
|
178
|
+
{ from: "urn:test:fromConcept", annotatedWith: "=0" },
|
|
179
|
+
]) {
|
|
180
|
+
const result = await services.mapping.queryItems({ limit: 100, offset: 0, from, to, annotatedWith })
|
|
181
|
+
// Replace = with == for later evaluation
|
|
182
|
+
if (annotatedWith.startsWith("=")) {
|
|
183
|
+
annotatedWith = `=${annotatedWith}`
|
|
184
|
+
}
|
|
185
|
+
const expected = mappings.filter(m => {
|
|
186
|
+
if (from && from !== _.get(m, "from.memberSet[0].uri") || to && to !== _.get(m, "to.memberSet[0].uri")) {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
const annotationSum = annotations.filter(a => a.target === m.uri).reduce((prev, cur) => {
|
|
190
|
+
if (cur.motivation !== "assessing") {
|
|
191
|
+
return prev
|
|
192
|
+
}
|
|
193
|
+
return prev + parseInt(cur.bodyValue)
|
|
194
|
+
}, 0)
|
|
195
|
+
return Function(`"use strict";return (${annotationSum}${annotatedWith})`)()
|
|
196
|
+
})
|
|
197
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected.map(e => e.uri).sort())
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it("should get correct number of mappings when using annotatedFor param", async () => {
|
|
202
|
+
const annotatedFor = "assessing"
|
|
203
|
+
const result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedFor })
|
|
204
|
+
const expected = _.uniq(annotations.filter(a => a.motivation === annotatedFor).map(a => a.target)).sort()
|
|
205
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it("should get correct number of mappings when using annotatedFor param with value `any`", async () => {
|
|
209
|
+
const annotatedFor = "any"
|
|
210
|
+
const result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedFor })
|
|
211
|
+
const expected = _.uniq(annotations.map(a => a.target)).sort()
|
|
212
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("should get correct number of mappings when using annotatedFor param with value `none`", async () => {
|
|
216
|
+
const annotatedFor = "none"
|
|
217
|
+
const result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedFor })
|
|
218
|
+
const expected = mappings.map(m => m.uri).filter(uri => !annotations.find(a => a.target === uri)).sort()
|
|
219
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it("should get correct number of mappings when using annotatedFor param with negative value", async () => {
|
|
223
|
+
const annotatedFor = "!assessing"
|
|
224
|
+
const result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedFor })
|
|
225
|
+
const expected = mappings.map(m => m.uri).filter(uri => !annotations.find(a => a.target === uri && a.motivation === annotatedFor.slice(1))).sort()
|
|
226
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it("should get correct number of mappings when using annotatedBy param", async () => {
|
|
230
|
+
const annotatedBy = "urn:test:creator|urn:other:uri"
|
|
231
|
+
const result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedBy })
|
|
232
|
+
const expected = _.uniq(annotations.filter(a => annotatedBy.split("|").includes(_.get(a, "creator.id"))).map(a => a.target)).sort()
|
|
233
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it("should get correct number of mappings when using annotatedFor together with annotatedBy", async () => {
|
|
237
|
+
let result, expected
|
|
238
|
+
const annotatedFor = "moderating"
|
|
239
|
+
// First only annotatedFor
|
|
240
|
+
result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedFor })
|
|
241
|
+
expected = _.uniq(annotations.filter(a => a.motivation === annotatedFor).map(a => a.target)).sort()
|
|
242
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
243
|
+
// Then with annotatedBy
|
|
244
|
+
const annotatedBy = "urn:test:creator|urn:other:uri"
|
|
245
|
+
result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedFor, annotatedBy })
|
|
246
|
+
expected = _.uniq(annotations.filter(a => annotatedBy.split("|").includes(_.get(a, "creator.id")) && a.motivation === annotatedFor).map(a => a.target)).sort()
|
|
247
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it("should get correct number of mappings when using annotatedWith together with to", async () => {
|
|
251
|
+
let result, expected
|
|
252
|
+
const annotatedWith = "+1"
|
|
253
|
+
// First only annotatedWith
|
|
254
|
+
result = await services.mapping.queryItems({ limit: 10, offset: 0, annotatedWith })
|
|
255
|
+
expected = _.uniq(annotations.filter(a => a.bodyValue === annotatedWith).map(a => a.target)).sort()
|
|
256
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected)
|
|
257
|
+
// Then with to
|
|
258
|
+
const to = "urn:test:concept"
|
|
259
|
+
result = await services.mapping.queryItems({ limit: 10, offset: 0, to, annotatedWith })
|
|
260
|
+
expected = mappings.filter(m => jskos.isContainedIn({ uri: to }, jskos.conceptsOfMapping(m, "to")) && annotations.find(a => a.target === m.uri && a.bodyValue === annotatedWith))
|
|
261
|
+
assert.deepStrictEqual(result.map(r => r.uri).sort(), expected.map(r => r.uri).sort())
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it("should reject mappings without fromScheme/toScheme", async () => {
|
|
265
|
+
for (const mapping of [
|
|
266
|
+
{
|
|
267
|
+
from: {
|
|
268
|
+
memberSet: [{
|
|
269
|
+
uri: "urn:test:concept",
|
|
270
|
+
}],
|
|
271
|
+
},
|
|
272
|
+
to: {
|
|
273
|
+
memberSet: [{
|
|
274
|
+
uri: "urn:test:concept",
|
|
275
|
+
}],
|
|
276
|
+
},
|
|
277
|
+
toScheme: { uri: "urn:test:toScheme" },
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
from: {
|
|
281
|
+
memberSet: [{
|
|
282
|
+
uri: "urn:test:concept",
|
|
283
|
+
}],
|
|
284
|
+
},
|
|
285
|
+
fromScheme: { uri: "urn:test:fromScheme" },
|
|
286
|
+
to: {
|
|
287
|
+
memberSet: [{
|
|
288
|
+
uri: "urn:test:concept",
|
|
289
|
+
}],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
]) {
|
|
293
|
+
try {
|
|
294
|
+
await services.mapping.createItem({ bodyStream: await arrayToStream([mapping]) })
|
|
295
|
+
assert.fail("Expected createItem to fail")
|
|
296
|
+
} catch (error) {
|
|
297
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it("should add fromScheme and toScheme fields from concepts' inScheme property", async () => {
|
|
303
|
+
const mapping = {
|
|
304
|
+
from: {
|
|
305
|
+
memberSet: [{
|
|
306
|
+
uri: "urn:test:concept",
|
|
307
|
+
inScheme: [{ uri: "urn:test:fromScheme" }],
|
|
308
|
+
}],
|
|
309
|
+
},
|
|
310
|
+
to: {
|
|
311
|
+
memberSet: [{
|
|
312
|
+
uri: "urn:test:concept",
|
|
313
|
+
inScheme: [{ uri: "urn:test:toScheme" }],
|
|
314
|
+
}],
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
const postedMapping = (await services.mapping.createItem({ bodyStream: await arrayToStream([mapping]) }))?.[0]
|
|
318
|
+
assert.deepStrictEqual(postedMapping.fromScheme?.uri, mapping.from.memberSet[0].inScheme[0].uri)
|
|
319
|
+
assert.deepStrictEqual(postedMapping.toScheme?.uri, mapping.to.memberSet[0].inScheme[0].uri)
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
describe("Annotation Service", () => {
|
|
326
|
+
|
|
327
|
+
const mismatchTagScheme = {
|
|
328
|
+
uri: "https://uri.gbv.de/terminology/mismatch/",
|
|
329
|
+
}
|
|
330
|
+
const mismatchTagConcept = {
|
|
331
|
+
uri: "https://uri.gbv.de/terminology/mismatch/test",
|
|
332
|
+
inScheme: [{uri: "https://uri.gbv.de/terminology/mismatch/"}],
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
it("should post tag mismatch scheme and concepts", async () => {
|
|
336
|
+
await services.scheme.createItem({ bodyStream: await arrayToStream([mismatchTagScheme]) })
|
|
337
|
+
await services.concept.createItem({ bodyStream: await arrayToStream([mismatchTagConcept]) })
|
|
338
|
+
const concept = await services.concept.getItem(mismatchTagConcept.uri)
|
|
339
|
+
assert.strictEqual(concept?.uri, mismatchTagConcept.uri)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it("should post negative assessment annotation that is correctly tagged", async () => {
|
|
343
|
+
const annotation = {
|
|
344
|
+
target: "abc:def",
|
|
345
|
+
bodyValue: "-1",
|
|
346
|
+
body: [
|
|
347
|
+
{
|
|
348
|
+
type: "SpecificResource",
|
|
349
|
+
value: mismatchTagConcept.uri,
|
|
350
|
+
purpose: "tagging",
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
}
|
|
354
|
+
const results = await services.annotation.createItem({ bodyStream: await arrayToStream([annotation]) })
|
|
355
|
+
assert.ok(results?.[0]?.id)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it("should not post negative assessment annotation that is correctly tagged with a URI that is not explicitly allowed", async () => {
|
|
359
|
+
const annotation = {
|
|
360
|
+
target: "abc:def",
|
|
361
|
+
bodyValue: "-1",
|
|
362
|
+
body: [
|
|
363
|
+
{
|
|
364
|
+
type: "SpecificResource",
|
|
365
|
+
value: mismatchTagConcept.uri + "2",
|
|
366
|
+
purpose: "tagging",
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
await services.annotation.createItem({ bodyStream: await arrayToStream([annotation]) })
|
|
372
|
+
assert.fail("No error was thrown even though it was expected.")
|
|
373
|
+
} catch (error) {
|
|
374
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
375
|
+
}
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it("should not post positive assessment annotation that is tagged", async () => {
|
|
379
|
+
const annotation = {
|
|
380
|
+
target: "abc:def",
|
|
381
|
+
bodyValue: "+1",
|
|
382
|
+
body: [
|
|
383
|
+
{
|
|
384
|
+
type: "SpecificResource",
|
|
385
|
+
value: mismatchTagConcept.uri,
|
|
386
|
+
purpose: "tagging",
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
await services.annotation.createItem({ bodyStream: await arrayToStream([annotation]) })
|
|
392
|
+
assert.fail("No error was thrown even though it was expected.")
|
|
393
|
+
} catch (error) {
|
|
394
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it("should require `body` to be an array", async () => {
|
|
399
|
+
const annotation = {
|
|
400
|
+
target: "abc:def",
|
|
401
|
+
bodyValue: "-1",
|
|
402
|
+
body: {
|
|
403
|
+
type: "SpecificResource",
|
|
404
|
+
value: mismatchTagConcept.uri,
|
|
405
|
+
purpose: "tagging",
|
|
406
|
+
},
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
await services.annotation.createItem({ bodyStream: await arrayToStream([annotation]) })
|
|
410
|
+
assert.fail("No error was thrown even though it was expected.")
|
|
411
|
+
} catch (error) {
|
|
412
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it("should not negative assessment annotation that is tagged incorrectly (1)", async () => {
|
|
417
|
+
const annotation = {
|
|
418
|
+
target: "abc:def",
|
|
419
|
+
bodyValue: "-1",
|
|
420
|
+
body: [
|
|
421
|
+
{
|
|
422
|
+
type: "SpecificResources",
|
|
423
|
+
value: mismatchTagConcept.uri,
|
|
424
|
+
purpose: "tagging",
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
await services.annotation.createItem({ bodyStream: await arrayToStream([annotation]) })
|
|
430
|
+
assert.fail("No error was thrown even though it was expected.")
|
|
431
|
+
} catch (error) {
|
|
432
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it("should not negative assessment annotation that is tagged incorrectly (2)", async () => {
|
|
437
|
+
const annotation = {
|
|
438
|
+
target: "abc:def",
|
|
439
|
+
bodyValue: "-1",
|
|
440
|
+
body: [
|
|
441
|
+
{
|
|
442
|
+
type: "SpecificResource",
|
|
443
|
+
value: mismatchTagConcept.uri,
|
|
444
|
+
purpose: "tag",
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
await services.annotation.createItem({ bodyStream: await arrayToStream([annotation]) })
|
|
450
|
+
assert.fail("No error was thrown even though it was expected.")
|
|
451
|
+
} catch (error) {
|
|
452
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
describe("Registry Service", () => {
|
|
459
|
+
|
|
460
|
+
const registryExample = {
|
|
461
|
+
uri: "urn:test:registry:1",
|
|
462
|
+
notation: ["ERMS"],
|
|
463
|
+
prefLabel: { en: "Example Registry" },
|
|
464
|
+
definition: { en: ["Example definition mentioning ERMS."] },
|
|
465
|
+
url: "https://example.org/registry/1",
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
it("should post a registry and return with uri", async () => {
|
|
469
|
+
const result = await services.registry.createItem({
|
|
470
|
+
bodyStream: await arrayToStream([registryExample]),
|
|
471
|
+
})
|
|
472
|
+
assert.ok(result.length === 1)
|
|
473
|
+
assert.ok(result[0].uri)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it("should get a registry by id/uri after posting", async () => {
|
|
477
|
+
const doc = await services.registry.getItem(registryExample.uri)
|
|
478
|
+
assert.strictEqual(doc?.uri, registryExample.uri)
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
it("should patch a registry and remove a field when set to null", async () => {
|
|
482
|
+
const existing = await services.registry.getItem(registryExample.uri)
|
|
483
|
+
|
|
484
|
+
// Add a field and verify it exists
|
|
485
|
+
const patched1 = await services.registry.patchRegistry({
|
|
486
|
+
existing,
|
|
487
|
+
body: { publisher: [{ uri: "urn:test:publisher", prefLabel: { en: "Pub" } }] },
|
|
488
|
+
})
|
|
489
|
+
assert.ok(patched1.publisher?.length)
|
|
490
|
+
|
|
491
|
+
// Now remove field and verify it is removed
|
|
492
|
+
const patched2 = await services.registry.patchRegistry({
|
|
493
|
+
existing: patched1,
|
|
494
|
+
body: { publisher: null },
|
|
495
|
+
})
|
|
496
|
+
assert.ok(patched2.publisher === undefined, "A field should be removed when set to `null`.")
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it("should put a registry and preserve immutable fields while updating", async () => {
|
|
500
|
+
const existing = await services.registry.getItem(registryExample.uri)
|
|
501
|
+
|
|
502
|
+
const body = {
|
|
503
|
+
// intentionally omit _id/id/created; service should preserve/override them
|
|
504
|
+
uri: registryExample.uri,
|
|
505
|
+
prefLabel: { en: "Example Registry Updated" },
|
|
506
|
+
notation: ["ERMS"],
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const updated = await services.registry.updateItem({ body, existing })
|
|
510
|
+
assert.strictEqual(updated.id, existing.id)
|
|
511
|
+
assert.strictEqual(updated.created, existing.created)
|
|
512
|
+
assert.ok(updated.modified, "modified should be set")
|
|
513
|
+
assert.strictEqual(updated.prefLabel?.en, "Example Registry Updated")
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it("should delete a registry", async () => {
|
|
517
|
+
const existing = await services.registry.getItem(registryExample.uri)
|
|
518
|
+
await services.registry.deleteItem({ existing })
|
|
519
|
+
try {
|
|
520
|
+
await services.registry.getItem(registryExample.uri)
|
|
521
|
+
assert.fail("Expected getItem to fail after delete")
|
|
522
|
+
} catch (error) {
|
|
523
|
+
assert.ok(error)
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it("should reject invalid registry bodies", async () => {
|
|
528
|
+
try {
|
|
529
|
+
await services.registry.createItem({
|
|
530
|
+
bodyStream: await arrayToStream([{uri:42}]),
|
|
531
|
+
bulk: false,
|
|
532
|
+
})
|
|
533
|
+
assert.fail("Expected createItem to fail")
|
|
534
|
+
} catch (error) {
|
|
535
|
+
assert.ok(error instanceof InvalidBodyError)
|
|
536
|
+
}
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it("should filter out invalid registry bodies in bulk mode", async () => {
|
|
540
|
+
const items = [
|
|
541
|
+
{ uri: "registry:1" },
|
|
542
|
+
{ uri: 42 },
|
|
543
|
+
]
|
|
544
|
+
try {
|
|
545
|
+
const res = await services.registry.createItem({
|
|
546
|
+
bodyStream: await arrayToStream(items),
|
|
547
|
+
bulk: true,
|
|
548
|
+
})
|
|
549
|
+
assert.deepStrictEqual(res, [ { uri: "registry:1" } ])
|
|
550
|
+
} catch (error) {
|
|
551
|
+
assert.fail("Should not fail")
|
|
552
|
+
}
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": [
|
|
4
|
+
"http://www.w3.org/2004/02/skos/core#ConceptScheme",
|
|
5
|
+
"http://w3id.org/nkos/nkostype#thesaurus"
|
|
6
|
+
],
|
|
7
|
+
"languages": [
|
|
8
|
+
"en",
|
|
9
|
+
"de"
|
|
10
|
+
],
|
|
11
|
+
"uri": "http://bartoc.org/en/node/313",
|
|
12
|
+
"license": [
|
|
13
|
+
{
|
|
14
|
+
"uri": "http://opendatacommons.org/licenses/odbl/1.0/"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"prefLabel": {
|
|
18
|
+
"de": "Standard Thesaurus Wirtschaft",
|
|
19
|
+
"en": "STW Thesaurus for Economics"
|
|
20
|
+
},
|
|
21
|
+
"subject": [
|
|
22
|
+
{
|
|
23
|
+
"uri": "http://dewey.info/class/3/e23/"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"uri": "http://dewey.info/scheme/edition/e23/",
|
|
29
|
+
"prefLabel": {
|
|
30
|
+
"de": "Dewey-Dezimalklassifikation",
|
|
31
|
+
"en": "Dewey Decimal Classification"
|
|
32
|
+
},
|
|
33
|
+
"notation": [
|
|
34
|
+
"DDC"
|
|
35
|
+
],
|
|
36
|
+
"identifier": [
|
|
37
|
+
"http://bartoc.org/en/node/241"
|
|
38
|
+
],
|
|
39
|
+
"license": [
|
|
40
|
+
{
|
|
41
|
+
"uri": "http://creativecommons.org/licenses/by-nc-nd/3.0/"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"publisher": [
|
|
45
|
+
{
|
|
46
|
+
"uri": "http://d-nb.info/gnd/1086052218",
|
|
47
|
+
"prefLabel": {
|
|
48
|
+
"de": "OCLC"
|
|
49
|
+
},
|
|
50
|
+
"altLabel": {
|
|
51
|
+
"de": [
|
|
52
|
+
"OCLC Online Computer Library Center"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"url": "https://www.oclc.org/"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"type": [
|
|
59
|
+
"http://www.w3.org/2004/02/skos/core#ConceptScheme",
|
|
60
|
+
"http://w3id.org/nkos/nkostype#classification_schema"
|
|
61
|
+
],
|
|
62
|
+
"subject": [
|
|
63
|
+
{
|
|
64
|
+
"uri": "http://dewey.info/class/0/e23/"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"languages": [
|
|
68
|
+
"en",
|
|
69
|
+
"fr",
|
|
70
|
+
"de"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
,
|
|
74
|
+
{
|
|
75
|
+
"uri" : "http://bartoc.org/en/node/732",
|
|
76
|
+
"identifier": [
|
|
77
|
+
"http://www.wikidata.org/entity/Q1571087"
|
|
78
|
+
],
|
|
79
|
+
"languages": [
|
|
80
|
+
"de"
|
|
81
|
+
],
|
|
82
|
+
"notation": [
|
|
83
|
+
"ASB"
|
|
84
|
+
],
|
|
85
|
+
"prefLabel":{
|
|
86
|
+
"de": "Allgemeine Systematik für Öffentliche Bibliotheken",
|
|
87
|
+
"en": "General Systematic for Public Libraries"
|
|
88
|
+
},
|
|
89
|
+
"type": [
|
|
90
|
+
"http://www.w3.org/2004/02/skos/core#ConceptScheme",
|
|
91
|
+
"http://w3id.org/nkos/nkostype#classification_schema"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
]
|