@noeldemartin/solid-utils 0.1.0 → 0.1.1-next.4e968af69bdec1e90b14bf8107fad4fafd43f952
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/.nvmrc +1 -0
- package/.semaphore/semaphore.yml +1 -1
- package/dist/noeldemartin-solid-utils.cjs.js +1 -1
- package/dist/noeldemartin-solid-utils.cjs.js.map +1 -1
- package/dist/noeldemartin-solid-utils.d.ts +32 -2
- package/dist/noeldemartin-solid-utils.esm.js +1 -1
- package/dist/noeldemartin-solid-utils.esm.js.map +1 -1
- package/noeldemartin.config.js +4 -2
- package/package.json +10 -8
- package/src/errors/NetworkRequestError.ts +3 -2
- package/src/helpers/identifiers.ts +56 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/interop.ts +6 -4
- package/src/helpers/io.ts +84 -28
- package/src/helpers/jsonld.ts +4 -0
- package/src/helpers/testing.ts +103 -28
- package/src/helpers/vocabs.ts +2 -1
- package/src/helpers/wac.ts +45 -0
- package/src/plugins/chai/assertions.ts +11 -2
- package/src/plugins/cypress/types.d.ts +1 -0
- package/src/plugins/jest/matchers.ts +45 -32
- package/src/plugins/jest/types.d.ts +1 -0
package/src/helpers/io.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { arr, arrayFilter, arrayReplace,objectWithoutEmpty } from '@noeldemartin/utils';
|
|
2
|
-
import { BlankNode as N3BlankNode, Quad as N3Quad, Parser as TurtleParser, Writer as TurtleWriter } from 'n3';
|
|
3
1
|
import md5 from 'md5';
|
|
2
|
+
import { arr, arrayFilter, arrayReplace, objectWithoutEmpty, stringMatchAll, tap } from '@noeldemartin/utils';
|
|
3
|
+
import { BlankNode as N3BlankNode, Quad as N3Quad, Parser as TurtleParser, Writer as TurtleWriter } from 'n3';
|
|
4
|
+
import { fromRDF, toRDF } from 'jsonld';
|
|
5
|
+
import type { JsonLdDocument } from 'jsonld';
|
|
4
6
|
import type { Quad } from 'rdf-js';
|
|
5
7
|
import type { Term as N3Term } from 'n3';
|
|
6
8
|
|
|
@@ -10,9 +12,11 @@ import MalformedSolidDocumentError, { SolidDocumentFormat } from '@/errors/Malfo
|
|
|
10
12
|
import NetworkRequestError from '@/errors/NetworkRequestError';
|
|
11
13
|
import NotFoundError from '@/errors/NotFoundError';
|
|
12
14
|
import UnauthorizedError from '@/errors/UnauthorizedError';
|
|
15
|
+
import { isJsonLDGraph } from '@/helpers/jsonld';
|
|
16
|
+
import type { JsonLD, JsonLDGraph, JsonLDResource } from '@/helpers/jsonld';
|
|
13
17
|
|
|
14
18
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
-
export declare type AnyFetch = (input: any, options?: any) => Promise<
|
|
19
|
+
export declare type AnyFetch = (input: any, options?: any) => Promise<Response>;
|
|
16
20
|
export declare type TypedFetch = (input: RequestInfo, options?: RequestInit) => Promise<Response>;
|
|
17
21
|
export declare type Fetch = TypedFetch | AnyFetch;
|
|
18
22
|
|
|
@@ -40,7 +44,10 @@ async function fetchRawSolidDocument(url: string, fetch: Fetch): Promise<{ body:
|
|
|
40
44
|
if (error instanceof UnauthorizedError)
|
|
41
45
|
throw error;
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
if (error instanceof NotFoundError)
|
|
48
|
+
throw error;
|
|
49
|
+
|
|
50
|
+
throw new NetworkRequestError(url, { cause: error });
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -48,26 +55,23 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
|
|
|
48
55
|
const normalizedQuads = quads.slice(0);
|
|
49
56
|
const quadsIndexes: Record<string, Set<number>> = {};
|
|
50
57
|
const blankNodeIds = arr(quads)
|
|
51
|
-
.flatMap(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return ids;
|
|
63
|
-
})
|
|
58
|
+
.flatMap(
|
|
59
|
+
(quad, index) => tap(
|
|
60
|
+
arrayFilter([
|
|
61
|
+
quad.object.termType === 'BlankNode' ? quad.object.value : null,
|
|
62
|
+
quad.subject.termType === 'BlankNode' ? quad.subject.value : null,
|
|
63
|
+
]),
|
|
64
|
+
ids => ids.forEach(id => (quadsIndexes[id] ??= new Set()).add(index)),
|
|
65
|
+
),
|
|
66
|
+
)
|
|
64
67
|
.filter()
|
|
65
68
|
.unique();
|
|
66
69
|
|
|
67
70
|
for (const originalId of blankNodeIds) {
|
|
71
|
+
const quadIndexes = quadsIndexes[originalId] as Set<number>;
|
|
68
72
|
const normalizedId = md5(
|
|
69
|
-
arr(
|
|
70
|
-
.map(index => quads[index])
|
|
73
|
+
arr(quadIndexes)
|
|
74
|
+
.map(index => quads[index] as Quad)
|
|
71
75
|
.filter(({ subject: { termType, value } }) => termType === 'BlankNode' && value === originalId)
|
|
72
76
|
.map(
|
|
73
77
|
({ predicate, object }) => object.termType === 'BlankNode'
|
|
@@ -78,9 +82,12 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
|
|
|
78
82
|
.join(),
|
|
79
83
|
);
|
|
80
84
|
|
|
81
|
-
for (const index of
|
|
82
|
-
const quad = normalizedQuads[index];
|
|
83
|
-
const terms: Record<string, N3Term> = {
|
|
85
|
+
for (const index of quadIndexes) {
|
|
86
|
+
const quad = normalizedQuads[index] as Quad;
|
|
87
|
+
const terms: Record<string, N3Term> = {
|
|
88
|
+
subject: quad.subject as N3Term,
|
|
89
|
+
object: quad.object as N3Term,
|
|
90
|
+
};
|
|
84
91
|
|
|
85
92
|
for (const [termName, termValue] of Object.entries(terms)) {
|
|
86
93
|
if (termValue.termType !== 'BlankNode' || termValue.value !== originalId)
|
|
@@ -89,7 +96,15 @@ function normalizeBlankNodes(quads: Quad[]): Quad[] {
|
|
|
89
96
|
terms[termName] = new N3BlankNode(normalizedId);
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
arrayReplace(
|
|
99
|
+
arrayReplace(
|
|
100
|
+
normalizedQuads,
|
|
101
|
+
quad,
|
|
102
|
+
new N3Quad(
|
|
103
|
+
terms.subject as N3Term,
|
|
104
|
+
quad.predicate as N3Term,
|
|
105
|
+
terms.object as N3Term,
|
|
106
|
+
),
|
|
107
|
+
);
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
110
|
|
|
@@ -102,7 +117,7 @@ export interface ParsingOptions {
|
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
export async function createSolidDocument(url: string, body: string, fetch?: Fetch): Promise<SolidDocument> {
|
|
105
|
-
fetch = fetch ?? window.fetch;
|
|
120
|
+
fetch = fetch ?? window.fetch.bind(window);
|
|
106
121
|
|
|
107
122
|
const statements = await turtleToQuads(body);
|
|
108
123
|
|
|
@@ -122,6 +137,29 @@ export async function fetchSolidDocument(url: string, fetch?: Fetch): Promise<So
|
|
|
122
137
|
return new SolidDocument(url, statements, headers);
|
|
123
138
|
}
|
|
124
139
|
|
|
140
|
+
export async function fetchSolidDocumentIfFound(url: string, fetch?: Fetch): Promise<SolidDocument | null> {
|
|
141
|
+
try {
|
|
142
|
+
const document = await fetchSolidDocument(url, fetch);
|
|
143
|
+
|
|
144
|
+
return document;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (!(error instanceof NotFoundError))
|
|
147
|
+
throw error;
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function jsonldToQuads(jsonld: JsonLD): Promise<Quad[]> {
|
|
154
|
+
if (isJsonLDGraph(jsonld)) {
|
|
155
|
+
const graphQuads = await Promise.all(jsonld['@graph'].map(jsonldToQuads));
|
|
156
|
+
|
|
157
|
+
return graphQuads.flat();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return toRDF(jsonld as JsonLdDocument) as Promise<Quad[]>;
|
|
161
|
+
}
|
|
162
|
+
|
|
125
163
|
export function normalizeSparql(sparql: string): string {
|
|
126
164
|
const quads = sparqlToQuadsSync(sparql);
|
|
127
165
|
|
|
@@ -135,6 +173,20 @@ export function normalizeSparql(sparql: string): string {
|
|
|
135
173
|
.join(' ;\n');
|
|
136
174
|
}
|
|
137
175
|
|
|
176
|
+
export async function quadsToJsonLD(quads: Quad[]): Promise<JsonLDGraph> {
|
|
177
|
+
const graph = await fromRDF(quads);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
'@graph': graph as JsonLDResource[],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function quadsToTurtle(quads: Quad[]): string {
|
|
185
|
+
const writer = new TurtleWriter;
|
|
186
|
+
|
|
187
|
+
return writer.quadsToString(quads);
|
|
188
|
+
}
|
|
189
|
+
|
|
138
190
|
export function quadToTurtle(quad: Quad): string {
|
|
139
191
|
const writer = new TurtleWriter;
|
|
140
192
|
|
|
@@ -155,7 +207,7 @@ export async function sparqlToQuads(
|
|
|
155
207
|
sparql: string,
|
|
156
208
|
options: Partial<ParsingOptions> = {},
|
|
157
209
|
): Promise<Record<string, Quad[]>> {
|
|
158
|
-
const operations = sparql
|
|
210
|
+
const operations = stringMatchAll<3>(sparql, /(\w+) DATA {([^}]+)}/g);
|
|
159
211
|
const quads: Record<string, Quad[]> = {};
|
|
160
212
|
|
|
161
213
|
await Promise.all([...operations].map(async operation => {
|
|
@@ -169,7 +221,7 @@ export async function sparqlToQuads(
|
|
|
169
221
|
}
|
|
170
222
|
|
|
171
223
|
export function sparqlToQuadsSync(sparql: string, options: Partial<ParsingOptions> = {}): Record<string, Quad[]> {
|
|
172
|
-
const operations = sparql
|
|
224
|
+
const operations = stringMatchAll<3>(sparql, /(\w+) DATA {([^}]+)}/g);
|
|
173
225
|
const quads: Record<string, Quad[]> = {};
|
|
174
226
|
|
|
175
227
|
for (const operation of operations) {
|
|
@@ -224,12 +276,16 @@ export function turtleToQuadsSync(turtle: string, options: Partial<ParsingOption
|
|
|
224
276
|
? normalizeBlankNodes(quads)
|
|
225
277
|
: quads;
|
|
226
278
|
} catch (error) {
|
|
227
|
-
throw new MalformedSolidDocumentError(
|
|
279
|
+
throw new MalformedSolidDocumentError(
|
|
280
|
+
options.documentUrl ?? null,
|
|
281
|
+
SolidDocumentFormat.Turtle,
|
|
282
|
+
(error as Error).message ?? '',
|
|
283
|
+
);
|
|
228
284
|
}
|
|
229
285
|
}
|
|
230
286
|
|
|
231
287
|
export async function updateSolidDocument(url: string, body: string, fetch?: Fetch): Promise<void> {
|
|
232
|
-
fetch = fetch ?? window.fetch;
|
|
288
|
+
fetch = fetch ?? window.fetch.bind(window);
|
|
233
289
|
|
|
234
290
|
await fetch(url, {
|
|
235
291
|
method: 'PATCH',
|
package/src/helpers/jsonld.ts
CHANGED
|
@@ -6,3 +6,7 @@ export type JsonLD = Partial<{
|
|
|
6
6
|
|
|
7
7
|
export type JsonLDResource = Omit<JsonLD, '@id'> & { '@id': string };
|
|
8
8
|
export type JsonLDGraph = { '@graph': JsonLDResource[] };
|
|
9
|
+
|
|
10
|
+
export function isJsonLDGraph(jsonld: JsonLD): jsonld is JsonLDGraph {
|
|
11
|
+
return '@graph' in jsonld;
|
|
12
|
+
}
|
package/src/helpers/testing.ts
CHANGED
|
@@ -1,39 +1,67 @@
|
|
|
1
|
-
import { pull } from '@noeldemartin/utils';
|
|
1
|
+
import { Error, arrayRemove, pull, stringMatchAll } from '@noeldemartin/utils';
|
|
2
|
+
import type { JsonLD } from '@/helpers/jsonld';
|
|
2
3
|
import type { Quad, Quad_Object } from 'rdf-js';
|
|
3
4
|
|
|
4
|
-
import { quadToTurtle, sparqlToQuadsSync, turtleToQuadsSync } from './io';
|
|
5
|
+
import { jsonldToQuads, quadToTurtle, quadsToTurtle, sparqlToQuadsSync, turtleToQuadsSync } from './io';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
let patternsRegExpsIndex: Record<string, RegExp> = {};
|
|
8
|
+
const builtInPatterns: Record<string, string> = {
|
|
9
|
+
'%uuid%': '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
class ExpectedQuadAssertionError extends Error {
|
|
13
|
+
|
|
14
|
+
constructor(public readonly expectedQuad: Quad) {
|
|
15
|
+
super(`Couldn't find the following triple: ${quadToTurtle(expectedQuad)}`);
|
|
16
|
+
}
|
|
7
17
|
|
|
8
|
-
function containsPatterns(value: string): boolean {
|
|
9
|
-
return /\[\[([^\]]+)\]\]/.test(value);
|
|
10
18
|
}
|
|
11
19
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
function assertExpectedQuadsExist(expectedQuads: Quad[], actualQuads: Quad[]): void {
|
|
21
|
+
for (const expectedQuad of expectedQuads) {
|
|
22
|
+
const matchingQuad = actualQuads.find(actualQuad => quadEquals(expectedQuad, actualQuad));
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const patterns: string[] = [];
|
|
19
|
-
let expectedRegExp = expected;
|
|
24
|
+
if (!matchingQuad)
|
|
25
|
+
throw new ExpectedQuadAssertionError(expectedQuad);
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
arrayRemove(actualQuads, matchingQuad);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
function containsPatterns(value: string): boolean {
|
|
32
|
+
return /\[\[(.*\]\[)?([^\]]+)\]\]/.test(value);
|
|
33
|
+
}
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
function createPatternRegexp(expected: string): RegExp {
|
|
36
|
+
const patternAliases = [];
|
|
37
|
+
const patternMatches = stringMatchAll<4, 1 | 2>(
|
|
38
|
+
expected,
|
|
39
|
+
/\[\[((.*?)\]\[)?([^\]]+)\]\]/g,
|
|
40
|
+
);
|
|
41
|
+
const patterns: string[] = [];
|
|
42
|
+
let expectedRegExp = expected;
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
for (const patternMatch of patternMatches) {
|
|
45
|
+
patternMatch[2] && patternAliases.push(patternMatch[2]);
|
|
46
|
+
|
|
47
|
+
patterns.push(patternMatch[3]);
|
|
32
48
|
|
|
33
|
-
|
|
49
|
+
expectedRegExp = expectedRegExp.replace(patternMatch[0], `%PATTERN${patterns.length - 1}%`);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
|
-
|
|
52
|
+
expectedRegExp = expectedRegExp.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
53
|
+
|
|
54
|
+
for (const [patternIndex, pattern] of Object.entries(patterns)) {
|
|
55
|
+
expectedRegExp = expectedRegExp.replace(`%PATTERN${patternIndex}%`, builtInPatterns[pattern] ?? pattern);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new RegExp(expectedRegExp);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function quadValueEquals(expected: string, actual: string): boolean {
|
|
62
|
+
return containsPatterns(expected)
|
|
63
|
+
? (patternsRegExpsIndex[expected] ??= createPatternRegexp(expected)).test(actual)
|
|
64
|
+
: expected === actual;
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
function quadObjectEquals(expected: Quad_Object, actual: Quad_Object): boolean {
|
|
@@ -59,6 +87,10 @@ function quadEquals(expected: Quad, actual: Quad): boolean {
|
|
|
59
87
|
&& quadValueEquals(expected.predicate.value, actual.predicate.value);
|
|
60
88
|
}
|
|
61
89
|
|
|
90
|
+
function resetPatterns(): void {
|
|
91
|
+
patternsRegExpsIndex = {};
|
|
92
|
+
}
|
|
93
|
+
|
|
62
94
|
export interface EqualityResult {
|
|
63
95
|
success: boolean;
|
|
64
96
|
message: string;
|
|
@@ -66,8 +98,39 @@ export interface EqualityResult {
|
|
|
66
98
|
actual: string;
|
|
67
99
|
}
|
|
68
100
|
|
|
101
|
+
export async function jsonldEquals(expected: JsonLD, actual: JsonLD): Promise<EqualityResult> {
|
|
102
|
+
// TODO catch parsing errors and improve message.
|
|
103
|
+
resetPatterns();
|
|
104
|
+
|
|
105
|
+
const expectedQuads = await jsonldToQuads(expected);
|
|
106
|
+
const actualQuads = await jsonldToQuads(actual);
|
|
107
|
+
const expectedTurtle = quadsToTurtle(expectedQuads);
|
|
108
|
+
const actualTurtle = quadsToTurtle(actualQuads);
|
|
109
|
+
const result = (success: boolean, message: string) => ({
|
|
110
|
+
success,
|
|
111
|
+
message,
|
|
112
|
+
expected: expectedTurtle,
|
|
113
|
+
actual: actualTurtle,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (expectedQuads.length !== actualQuads.length)
|
|
117
|
+
return result(false, `Expected ${expectedQuads.length} triples, found ${actualQuads.length}.`);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
assertExpectedQuadsExist(expectedQuads, actualQuads);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (!(error instanceof ExpectedQuadAssertionError))
|
|
123
|
+
throw error;
|
|
124
|
+
|
|
125
|
+
return result(false, error.message);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result(true, 'jsonld matches');
|
|
129
|
+
}
|
|
130
|
+
|
|
69
131
|
export function sparqlEquals(expected: string, actual: string): EqualityResult {
|
|
70
132
|
// TODO catch parsing errors and improve message.
|
|
133
|
+
resetPatterns();
|
|
71
134
|
|
|
72
135
|
const expectedOperations = sparqlToQuadsSync(expected, { normalizeBlankNodes: true });
|
|
73
136
|
const actualOperations = sparqlToQuadsSync(actual, { normalizeBlankNodes: true });
|
|
@@ -83,9 +146,16 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
|
|
|
83
146
|
if (expectedQuads.length !== actualQuads.length)
|
|
84
147
|
return result(false, `Expected ${expectedQuads.length} ${operation} triples, found ${actualQuads.length}.`);
|
|
85
148
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
149
|
+
try {
|
|
150
|
+
assertExpectedQuadsExist(expectedQuads, actualQuads);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (!(error instanceof ExpectedQuadAssertionError))
|
|
153
|
+
throw error;
|
|
154
|
+
|
|
155
|
+
return result(
|
|
156
|
+
false,
|
|
157
|
+
`Couldn't find the following ${operation} triple: ${quadToTurtle(error.expectedQuad)}`,
|
|
158
|
+
);
|
|
89
159
|
}
|
|
90
160
|
}
|
|
91
161
|
|
|
@@ -98,6 +168,7 @@ export function sparqlEquals(expected: string, actual: string): EqualityResult {
|
|
|
98
168
|
|
|
99
169
|
export function turtleEquals(expected: string, actual: string): EqualityResult {
|
|
100
170
|
// TODO catch parsing errors and improve message.
|
|
171
|
+
resetPatterns();
|
|
101
172
|
|
|
102
173
|
const expectedQuads = turtleToQuadsSync(expected, { normalizeBlankNodes: true });
|
|
103
174
|
const actualQuads = turtleToQuadsSync(actual, { normalizeBlankNodes: true });
|
|
@@ -106,9 +177,13 @@ export function turtleEquals(expected: string, actual: string): EqualityResult {
|
|
|
106
177
|
if (expectedQuads.length !== actualQuads.length)
|
|
107
178
|
return result(false, `Expected ${expectedQuads.length} triples, found ${actualQuads.length}.`);
|
|
108
179
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
180
|
+
try {
|
|
181
|
+
assertExpectedQuadsExist(expectedQuads, actualQuads);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (!(error instanceof ExpectedQuadAssertionError))
|
|
184
|
+
throw error;
|
|
185
|
+
|
|
186
|
+
return result(false, error.message);
|
|
112
187
|
}
|
|
113
188
|
|
|
114
189
|
return result(true, 'turtle matches');
|
package/src/helpers/vocabs.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface ExpandIRIOptions {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const knownPrefixes: RDFContext = {
|
|
9
|
+
acl: 'http://www.w3.org/ns/auth/acl#',
|
|
9
10
|
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
10
11
|
pim: 'http://www.w3.org/ns/pim/space#',
|
|
11
12
|
purl: 'http://purl.org/dc/terms/',
|
|
@@ -25,7 +26,7 @@ export function expandIRI(iri: string, options: Partial<ExpandIRIOptions> = {}):
|
|
|
25
26
|
|
|
26
27
|
const [prefix, name] = iri.split(':');
|
|
27
28
|
|
|
28
|
-
if (name) {
|
|
29
|
+
if (prefix && name) {
|
|
29
30
|
const expandedPrefix = knownPrefixes[prefix] ?? options.extraContext?.[prefix] ?? null;
|
|
30
31
|
|
|
31
32
|
if (!expandedPrefix)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { objectWithoutEmpty, requireUrlParentDirectory, urlResolve } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import { fetchSolidDocumentIfFound } from '@/helpers/io';
|
|
4
|
+
import type SolidDocument from '@/models/SolidDocument';
|
|
5
|
+
import type { Fetch } from '@/helpers/io';
|
|
6
|
+
|
|
7
|
+
async function fetchACLResourceUrl(resourceUrl: string, fetch: Fetch): Promise<string> {
|
|
8
|
+
fetch = fetch ?? window.fetch.bind(window);
|
|
9
|
+
|
|
10
|
+
const resourceHead = await fetch(resourceUrl, { method: 'HEAD' });
|
|
11
|
+
const linkHeader = resourceHead.headers.get('Link') ?? '';
|
|
12
|
+
const url = linkHeader.match(/<([^>]+)>;\s*rel="acl"/)?.[1] ?? null;
|
|
13
|
+
|
|
14
|
+
if (!url) {
|
|
15
|
+
throw new Error(`Could not find ACL Resource for '${resourceUrl}'`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return urlResolve(requireUrlParentDirectory(resourceUrl), url);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function fetchEffectiveACL(
|
|
22
|
+
resourceUrl: string,
|
|
23
|
+
fetch: Fetch,
|
|
24
|
+
aclResourceUrl?: string | null,
|
|
25
|
+
): Promise<SolidDocument> {
|
|
26
|
+
aclResourceUrl = aclResourceUrl ?? await fetchACLResourceUrl(resourceUrl, fetch);
|
|
27
|
+
|
|
28
|
+
return await fetchSolidDocumentIfFound(aclResourceUrl ?? '', fetch)
|
|
29
|
+
?? await fetchEffectiveACL(requireUrlParentDirectory(resourceUrl), fetch);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function fetchSolidDocumentACL(documentUrl: string, fetch: Fetch): Promise<{
|
|
33
|
+
url: string;
|
|
34
|
+
effectiveUrl: string;
|
|
35
|
+
document: SolidDocument;
|
|
36
|
+
}> {
|
|
37
|
+
const url = await fetchACLResourceUrl(documentUrl, fetch);
|
|
38
|
+
const document = await fetchEffectiveACL(documentUrl, fetch, url);
|
|
39
|
+
|
|
40
|
+
return objectWithoutEmpty({
|
|
41
|
+
url,
|
|
42
|
+
effectiveUrl: document.url,
|
|
43
|
+
document,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sparqlEquals } from '@/helpers/testing';
|
|
1
|
+
import { sparqlEquals, turtleEquals } from '@/helpers/testing';
|
|
2
2
|
|
|
3
3
|
type CustomAssertions = {
|
|
4
4
|
[assertion in keyof typeof assertions]: typeof assertions[assertion];
|
|
@@ -17,6 +17,15 @@ declare global {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) => void> = {
|
|
20
|
+
turtle(graph: string): void {
|
|
21
|
+
const self = this as unknown as Chai.AssertionStatic;
|
|
22
|
+
const actual = self._obj;
|
|
23
|
+
const assert = self.assert.bind(this);
|
|
24
|
+
const expected = graph;
|
|
25
|
+
const result = turtleEquals(expected, actual);
|
|
26
|
+
|
|
27
|
+
assert(result.success, result.message, '', result.expected, result.actual);
|
|
28
|
+
},
|
|
20
29
|
sparql(query: string): void {
|
|
21
30
|
const self = this as unknown as Chai.AssertionStatic;
|
|
22
31
|
const actual = self._obj;
|
|
@@ -24,7 +33,7 @@ const assertions: Record<string, (this: Chai.AssertionStatic, ...args: any[]) =>
|
|
|
24
33
|
const expected = query;
|
|
25
34
|
const result = sparqlEquals(expected, actual);
|
|
26
35
|
|
|
27
|
-
assert(result.success, result.message, expected, actual);
|
|
36
|
+
assert(result.success, result.message, '', result.expected, result.actual);
|
|
28
37
|
},
|
|
29
38
|
};
|
|
30
39
|
|
|
@@ -1,41 +1,54 @@
|
|
|
1
|
-
import diff from 'jest-diff';
|
|
2
|
-
|
|
3
1
|
import { normalizeSparql } from '@/helpers/io';
|
|
4
|
-
import { sparqlEquals } from '@/helpers/testing';
|
|
2
|
+
import { jsonldEquals, sparqlEquals } from '@/helpers/testing';
|
|
3
|
+
import type { EqualityResult } from '@/helpers/testing';
|
|
4
|
+
|
|
5
|
+
interface FormatResultOptions {
|
|
6
|
+
context: jest.MatcherContext;
|
|
7
|
+
hint: string;
|
|
8
|
+
expected: unknown;
|
|
9
|
+
received: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatResult(result: EqualityResult, options: FormatResultOptions) {
|
|
13
|
+
const pass = result.success;
|
|
14
|
+
const utils = options.context.utils;
|
|
15
|
+
const message = pass
|
|
16
|
+
? () => [
|
|
17
|
+
result.message,
|
|
18
|
+
utils.matcherHint(options.hint),
|
|
19
|
+
].join('\n\n')
|
|
20
|
+
: () => [
|
|
21
|
+
result.message,
|
|
22
|
+
utils.matcherHint(options.hint),
|
|
23
|
+
[
|
|
24
|
+
`Expected: not ${utils.printExpected(options.expected)}`,
|
|
25
|
+
`Received: ${utils.printReceived(options.received)}`,
|
|
26
|
+
].join('\n'),
|
|
27
|
+
].join('\n\n');
|
|
28
|
+
|
|
29
|
+
return { pass, message };
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
const matchers: jest.ExpectExtendMap = {
|
|
33
|
+
async toEqualJsonLD(received, expected) {
|
|
34
|
+
const result = await jsonldEquals(expected, received);
|
|
35
|
+
|
|
36
|
+
return formatResult(result, {
|
|
37
|
+
context: this,
|
|
38
|
+
hint: 'toEqualJsonLD',
|
|
39
|
+
expected,
|
|
40
|
+
received,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
7
43
|
toEqualSparql(received, expected) {
|
|
8
44
|
const result = sparqlEquals(expected, received);
|
|
9
|
-
const pass = result.success;
|
|
10
|
-
const normalizedReceived = normalizeSparql(received);
|
|
11
|
-
const normalizedExpected = normalizeSparql(expected);
|
|
12
|
-
const message = pass
|
|
13
|
-
? () => [
|
|
14
|
-
result.message,
|
|
15
|
-
this.utils.matcherHint('toEqualSparql'),
|
|
16
|
-
[
|
|
17
|
-
`Expected: not ${this.utils.printExpected(normalizedExpected)}`,
|
|
18
|
-
`Received: ${this.utils.printReceived(normalizedReceived)}`,
|
|
19
|
-
].join('\n'),
|
|
20
|
-
].join('\n\n')
|
|
21
|
-
: () => {
|
|
22
|
-
const diffString = diff(normalizedExpected, normalizedReceived, {
|
|
23
|
-
expand: this.expand,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return [
|
|
27
|
-
result.message,
|
|
28
|
-
this.utils.matcherHint('toEqualJsonLD'),
|
|
29
|
-
diffString && diffString.includes('- Expect')
|
|
30
|
-
? `Difference:\n\n${diffString}`
|
|
31
|
-
: [
|
|
32
|
-
`Expected: ${this.utils.printExpected(normalizedExpected)}`,
|
|
33
|
-
`Received: ${this.utils.printReceived(normalizedReceived)}`,
|
|
34
|
-
].join('\n'),
|
|
35
|
-
].join('\n\n');
|
|
36
|
-
};
|
|
37
45
|
|
|
38
|
-
return
|
|
46
|
+
return formatResult(result, {
|
|
47
|
+
context: this,
|
|
48
|
+
hint: 'toEqualSparql',
|
|
49
|
+
expected: normalizeSparql(expected),
|
|
50
|
+
received: normalizeSparql(received),
|
|
51
|
+
});
|
|
39
52
|
},
|
|
40
53
|
};
|
|
41
54
|
|