@fedify/vocab 2.0.0-dev.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/LICENSE +20 -0
- package/deno.json +31 -0
- package/dist/accept.yaml +15 -0
- package/dist/activity.yaml +98 -0
- package/dist/actor.test.d.ts +2 -0
- package/dist/actor.test.js +6095 -0
- package/dist/add.yaml +16 -0
- package/dist/announce.yaml +30 -0
- package/dist/application.yaml +324 -0
- package/dist/arrive.yaml +15 -0
- package/dist/article.yaml +46 -0
- package/dist/audio.yaml +11 -0
- package/dist/block.yaml +16 -0
- package/dist/chatmessage.yaml +50 -0
- package/dist/collection.yaml +154 -0
- package/dist/collectionpage.yaml +55 -0
- package/dist/create.yaml +28 -0
- package/dist/dataintegrityproof.yaml +56 -0
- package/dist/delete.yaml +27 -0
- package/dist/deno-B-ypIMwF.js +1282 -0
- package/dist/didservice.yaml +22 -0
- package/dist/dislike.yaml +14 -0
- package/dist/document.yaml +31 -0
- package/dist/emoji.yaml +12 -0
- package/dist/emojireact.yaml +17 -0
- package/dist/endpoints.yaml +85 -0
- package/dist/event.yaml +11 -0
- package/dist/export.yaml +9 -0
- package/dist/flag.yaml +15 -0
- package/dist/follow.yaml +19 -0
- package/dist/group.yaml +324 -0
- package/dist/hashtag.yaml +14 -0
- package/dist/ignore.yaml +14 -0
- package/dist/image.yaml +9 -0
- package/dist/intransitiveactivity.yaml +15 -0
- package/dist/invite.yaml +14 -0
- package/dist/join.yaml +14 -0
- package/dist/key.yaml +28 -0
- package/dist/leave.yaml +14 -0
- package/dist/like.yaml +16 -0
- package/dist/link.yaml +101 -0
- package/dist/listen.yaml +12 -0
- package/dist/lookup.test.d.ts +2 -0
- package/dist/lookup.test.js +690 -0
- package/dist/mention.yaml +9 -0
- package/dist/mod.cjs +42036 -0
- package/dist/mod.d.cts +15329 -0
- package/dist/mod.d.ts +15330 -0
- package/dist/mod.js +41936 -0
- package/dist/move.yaml +15 -0
- package/dist/multikey.yaml +36 -0
- package/dist/note.yaml +48 -0
- package/dist/object.yaml +404 -0
- package/dist/offer.yaml +15 -0
- package/dist/orderedcollection.yaml +39 -0
- package/dist/orderedcollectionpage.yaml +50 -0
- package/dist/organization.yaml +324 -0
- package/dist/page.yaml +11 -0
- package/dist/person.yaml +324 -0
- package/dist/place.yaml +75 -0
- package/dist/profile.yaml +26 -0
- package/dist/propertyvalue.yaml +32 -0
- package/dist/question.yaml +103 -0
- package/dist/read.yaml +13 -0
- package/dist/reject.yaml +14 -0
- package/dist/relationship.yaml +52 -0
- package/dist/remove.yaml +14 -0
- package/dist/service.yaml +324 -0
- package/dist/source.yaml +26 -0
- package/dist/tentativeaccept.yaml +14 -0
- package/dist/tentativereject.yaml +14 -0
- package/dist/tombstone.yaml +24 -0
- package/dist/travel.yaml +16 -0
- package/dist/type-CNuABalk.js +13 -0
- package/dist/type.test.d.ts +2 -0
- package/dist/type.test.js +24 -0
- package/dist/undo.yaml +26 -0
- package/dist/update.yaml +58 -0
- package/dist/utils-BSWXlrig.js +13 -0
- package/dist/video.yaml +11 -0
- package/dist/view.yaml +13 -0
- package/dist/vocab-DBispxj5.js +41603 -0
- package/dist/vocab.test.d.ts +2 -0
- package/dist/vocab.test.js +1304 -0
- package/package.json +79 -0
- package/scripts/codegen.ts +20 -0
- package/src/__snapshots__/vocab.test.ts.snap +7903 -0
- package/src/accept.yaml +15 -0
- package/src/activity.yaml +98 -0
- package/src/actor.test.ts +263 -0
- package/src/actor.ts +293 -0
- package/src/add.yaml +16 -0
- package/src/announce.yaml +30 -0
- package/src/application.yaml +324 -0
- package/src/arrive.yaml +15 -0
- package/src/article.yaml +46 -0
- package/src/audio.yaml +11 -0
- package/src/block.yaml +16 -0
- package/src/chatmessage.yaml +50 -0
- package/src/collection.yaml +154 -0
- package/src/collectionpage.yaml +55 -0
- package/src/constants.ts +11 -0
- package/src/create.yaml +28 -0
- package/src/dataintegrityproof.yaml +56 -0
- package/src/delete.yaml +27 -0
- package/src/didservice.yaml +22 -0
- package/src/dislike.yaml +14 -0
- package/src/document.yaml +31 -0
- package/src/emoji.yaml +12 -0
- package/src/emojireact.yaml +17 -0
- package/src/endpoints.yaml +85 -0
- package/src/event.yaml +11 -0
- package/src/export.yaml +9 -0
- package/src/flag.yaml +15 -0
- package/src/follow.yaml +19 -0
- package/src/group.yaml +324 -0
- package/src/handle.ts +104 -0
- package/src/hashtag.yaml +14 -0
- package/src/ignore.yaml +14 -0
- package/src/image.yaml +9 -0
- package/src/intransitiveactivity.yaml +15 -0
- package/src/invite.yaml +14 -0
- package/src/join.yaml +14 -0
- package/src/key.yaml +28 -0
- package/src/keys.ts +50 -0
- package/src/leave.yaml +14 -0
- package/src/like.yaml +16 -0
- package/src/link.yaml +101 -0
- package/src/listen.yaml +12 -0
- package/src/lookup.test.ts +681 -0
- package/src/lookup.ts +318 -0
- package/src/mention.yaml +9 -0
- package/src/mod.ts +57 -0
- package/src/move.yaml +15 -0
- package/src/multikey.yaml +36 -0
- package/src/note.yaml +48 -0
- package/src/object.yaml +404 -0
- package/src/offer.yaml +15 -0
- package/src/orderedcollection.yaml +39 -0
- package/src/orderedcollectionpage.yaml +50 -0
- package/src/organization.yaml +324 -0
- package/src/page.yaml +11 -0
- package/src/person.yaml +324 -0
- package/src/place.yaml +75 -0
- package/src/profile.yaml +26 -0
- package/src/propertyvalue.yaml +32 -0
- package/src/question.yaml +103 -0
- package/src/read.yaml +13 -0
- package/src/reject.yaml +14 -0
- package/src/relationship.yaml +52 -0
- package/src/remove.yaml +14 -0
- package/src/service.yaml +324 -0
- package/src/source.yaml +26 -0
- package/src/tentativeaccept.yaml +14 -0
- package/src/tentativereject.yaml +14 -0
- package/src/tombstone.yaml +24 -0
- package/src/travel.yaml +16 -0
- package/src/type.test.ts +20 -0
- package/src/type.ts +102 -0
- package/src/undo.yaml +26 -0
- package/src/update.yaml +58 -0
- package/src/utils.ts +9 -0
- package/src/video.yaml +11 -0
- package/src/view.yaml +13 -0
- package/src/vocab.bench.ts +204 -0
- package/src/vocab.test.ts +2014 -0
- package/tsdown.config.ts +65 -0
|
@@ -0,0 +1,2014 @@
|
|
|
1
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
2
|
+
import { decodeMultibase, LanguageString } from "@fedify/vocab-runtime";
|
|
3
|
+
import {
|
|
4
|
+
areAllScalarTypes,
|
|
5
|
+
loadSchemaFiles,
|
|
6
|
+
type PropertySchema,
|
|
7
|
+
type TypeSchema,
|
|
8
|
+
} from "@fedify/vocab-tools";
|
|
9
|
+
import { pascalCase } from "es-toolkit";
|
|
10
|
+
import {
|
|
11
|
+
deepStrictEqual,
|
|
12
|
+
notDeepStrictEqual,
|
|
13
|
+
ok,
|
|
14
|
+
rejects,
|
|
15
|
+
throws,
|
|
16
|
+
} from "node:assert/strict";
|
|
17
|
+
import { assertInstanceOf } from "./utils.ts";
|
|
18
|
+
import * as vocab from "./vocab.ts";
|
|
19
|
+
import {
|
|
20
|
+
Activity,
|
|
21
|
+
Announce,
|
|
22
|
+
Collection,
|
|
23
|
+
Create,
|
|
24
|
+
CryptographicKey,
|
|
25
|
+
type DataIntegrityProof,
|
|
26
|
+
Follow,
|
|
27
|
+
Hashtag,
|
|
28
|
+
Link,
|
|
29
|
+
Note,
|
|
30
|
+
Object,
|
|
31
|
+
OrderedCollectionPage,
|
|
32
|
+
Person,
|
|
33
|
+
Place,
|
|
34
|
+
Question,
|
|
35
|
+
Source,
|
|
36
|
+
} from "./vocab.ts";
|
|
37
|
+
|
|
38
|
+
test("new Object()", () => {
|
|
39
|
+
const obj = new Object({
|
|
40
|
+
name: "Test",
|
|
41
|
+
contents: [
|
|
42
|
+
new LanguageString("Hello", "en"),
|
|
43
|
+
new LanguageString("你好", "zh"),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
deepStrictEqual(obj.name, "Test");
|
|
47
|
+
deepStrictEqual(obj.contents[0], new LanguageString("Hello", "en"));
|
|
48
|
+
deepStrictEqual(obj.contents[1], new LanguageString("你好", "zh"));
|
|
49
|
+
|
|
50
|
+
throws(
|
|
51
|
+
() => new Object({ id: 123 as unknown as URL }),
|
|
52
|
+
TypeError,
|
|
53
|
+
);
|
|
54
|
+
throws(
|
|
55
|
+
() => new Object({ name: "singular", names: ["plural"] }),
|
|
56
|
+
TypeError,
|
|
57
|
+
);
|
|
58
|
+
throws(
|
|
59
|
+
() => new Object({ name: 123 as unknown as string }),
|
|
60
|
+
TypeError,
|
|
61
|
+
);
|
|
62
|
+
throws(
|
|
63
|
+
() => new Object({ names: "foo" as unknown as string[] }),
|
|
64
|
+
TypeError,
|
|
65
|
+
);
|
|
66
|
+
throws(
|
|
67
|
+
() => new Object({ names: ["foo", 123 as unknown as string] }),
|
|
68
|
+
TypeError,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("Object.clone()", () => {
|
|
73
|
+
const obj = new Object({
|
|
74
|
+
id: new URL("https://example.com/"),
|
|
75
|
+
name: "Test",
|
|
76
|
+
contents: [
|
|
77
|
+
new LanguageString("Hello", "en"),
|
|
78
|
+
new LanguageString("你好", "zh"),
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const clone = obj.clone({ content: "Modified" });
|
|
83
|
+
assertInstanceOf(clone, Object);
|
|
84
|
+
deepStrictEqual(clone.id, new URL("https://example.com/"));
|
|
85
|
+
deepStrictEqual(clone.name, "Test");
|
|
86
|
+
deepStrictEqual(clone.content, "Modified");
|
|
87
|
+
|
|
88
|
+
const cloned2 = obj.clone({ id: new URL("https://example.com/modified") });
|
|
89
|
+
assertInstanceOf(cloned2, Object);
|
|
90
|
+
deepStrictEqual(cloned2.id, new URL("https://example.com/modified"));
|
|
91
|
+
deepStrictEqual(cloned2.name, "Test");
|
|
92
|
+
deepStrictEqual(cloned2.contents, [
|
|
93
|
+
new LanguageString("Hello", "en"),
|
|
94
|
+
new LanguageString("你好", "zh"),
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
throws(
|
|
98
|
+
() => obj.clone({ id: 123 as unknown as URL }),
|
|
99
|
+
TypeError,
|
|
100
|
+
);
|
|
101
|
+
throws(
|
|
102
|
+
() => obj.clone({ name: "singular", names: ["plural"] }),
|
|
103
|
+
TypeError,
|
|
104
|
+
);
|
|
105
|
+
throws(
|
|
106
|
+
() => obj.clone({ name: 123 as unknown as string }),
|
|
107
|
+
TypeError,
|
|
108
|
+
);
|
|
109
|
+
throws(
|
|
110
|
+
() => obj.clone({ names: "foo" as unknown as string[] }),
|
|
111
|
+
TypeError,
|
|
112
|
+
);
|
|
113
|
+
throws(
|
|
114
|
+
() => obj.clone({ names: ["foo", 123 as unknown as string] }),
|
|
115
|
+
TypeError,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("Object.fromJsonLd()", async () => {
|
|
120
|
+
const obj = await Object.fromJsonLd({
|
|
121
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
122
|
+
"type": "Object",
|
|
123
|
+
"name": "Test",
|
|
124
|
+
"contentMap": {
|
|
125
|
+
"en": "Hello",
|
|
126
|
+
"zh": "你好",
|
|
127
|
+
},
|
|
128
|
+
"source": {
|
|
129
|
+
"content": "Hello",
|
|
130
|
+
"mediaType": "text/plain",
|
|
131
|
+
},
|
|
132
|
+
"published": "2025-01-01 12:34:56",
|
|
133
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
134
|
+
assertInstanceOf(obj, Object);
|
|
135
|
+
deepStrictEqual(obj.name, "Test");
|
|
136
|
+
deepStrictEqual(obj.contents, [
|
|
137
|
+
new LanguageString("Hello", "en"),
|
|
138
|
+
new LanguageString("你好", "zh"),
|
|
139
|
+
]);
|
|
140
|
+
assertInstanceOf(obj.source, Source);
|
|
141
|
+
deepStrictEqual(obj.source.content, "Hello");
|
|
142
|
+
deepStrictEqual(obj.source.mediaType, "text/plain");
|
|
143
|
+
deepStrictEqual(obj.published, Temporal.Instant.from("2025-01-01T12:34:56Z"));
|
|
144
|
+
|
|
145
|
+
const createJsonLd = {
|
|
146
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
147
|
+
"type": "Create",
|
|
148
|
+
"name": "Test",
|
|
149
|
+
"contentMap": {
|
|
150
|
+
"en": "Hello",
|
|
151
|
+
"zh": "你好",
|
|
152
|
+
},
|
|
153
|
+
"object": {
|
|
154
|
+
"type": "Note",
|
|
155
|
+
"content": "Content",
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const create = await Object.fromJsonLd(
|
|
159
|
+
createJsonLd,
|
|
160
|
+
{ documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader },
|
|
161
|
+
);
|
|
162
|
+
assertInstanceOf(create, Create);
|
|
163
|
+
deepStrictEqual(create.name, "Test");
|
|
164
|
+
deepStrictEqual(create.contents, [
|
|
165
|
+
new LanguageString("Hello", "en"),
|
|
166
|
+
new LanguageString("你好", "zh"),
|
|
167
|
+
]);
|
|
168
|
+
deepStrictEqual(await create.toJsonLd(), createJsonLd);
|
|
169
|
+
const note = await create.getObject();
|
|
170
|
+
assertInstanceOf(note, Note);
|
|
171
|
+
deepStrictEqual(note.content, "Content");
|
|
172
|
+
|
|
173
|
+
const empty = await Object.fromJsonLd({});
|
|
174
|
+
assertInstanceOf(empty, Object);
|
|
175
|
+
|
|
176
|
+
await rejects(
|
|
177
|
+
() => Object.fromJsonLd(null),
|
|
178
|
+
TypeError,
|
|
179
|
+
);
|
|
180
|
+
await rejects(
|
|
181
|
+
() => Object.fromJsonLd(undefined),
|
|
182
|
+
TypeError,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("Object.toJsonLd()", async () => {
|
|
187
|
+
const obj = new Object({
|
|
188
|
+
name: "Test",
|
|
189
|
+
contents: [
|
|
190
|
+
new LanguageString("Hello", "en"),
|
|
191
|
+
new LanguageString("你好", "zh"),
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
deepStrictEqual(
|
|
195
|
+
await obj.toJsonLd({ format: "expand", contextLoader: mockDocumentLoader }),
|
|
196
|
+
[
|
|
197
|
+
{
|
|
198
|
+
"@type": [
|
|
199
|
+
"https://www.w3.org/ns/activitystreams#Object",
|
|
200
|
+
],
|
|
201
|
+
"https://www.w3.org/ns/activitystreams#name": [
|
|
202
|
+
{ "@value": "Test" },
|
|
203
|
+
],
|
|
204
|
+
"https://www.w3.org/ns/activitystreams#content": [
|
|
205
|
+
{ "@value": "Hello", "@language": "en" },
|
|
206
|
+
{ "@value": "你好", "@language": "zh" },
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
);
|
|
211
|
+
deepStrictEqual(await obj.toJsonLd({ contextLoader: mockDocumentLoader }), {
|
|
212
|
+
"@context": [
|
|
213
|
+
"https://www.w3.org/ns/activitystreams",
|
|
214
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
215
|
+
{
|
|
216
|
+
fedibird: "http://fedibird.com/ns#",
|
|
217
|
+
sensitive: "as:sensitive",
|
|
218
|
+
emojiReactions: {
|
|
219
|
+
"@id": "fedibird:emojiReactions",
|
|
220
|
+
"@type": "@id",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
type: "Object",
|
|
225
|
+
name: "Test",
|
|
226
|
+
contentMap: {
|
|
227
|
+
en: "Hello",
|
|
228
|
+
zh: "你好",
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("Note.toJsonLd()", async () => {
|
|
234
|
+
const note = new Note({
|
|
235
|
+
tags: [
|
|
236
|
+
new Hashtag({
|
|
237
|
+
name: "#Fedify",
|
|
238
|
+
href: new URL("https://fedify.dev/"),
|
|
239
|
+
}),
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
deepStrictEqual(await note.toJsonLd({ contextLoader: mockDocumentLoader }), {
|
|
243
|
+
"@context": [
|
|
244
|
+
"https://www.w3.org/ns/activitystreams",
|
|
245
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
246
|
+
{
|
|
247
|
+
Emoji: "toot:Emoji",
|
|
248
|
+
Hashtag: "as:Hashtag",
|
|
249
|
+
_misskey_quote: "misskey:_misskey_quote",
|
|
250
|
+
fedibird: "http://fedibird.com/ns#",
|
|
251
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
252
|
+
quoteUri: "fedibird:quoteUri",
|
|
253
|
+
quoteUrl: "as:quoteUrl",
|
|
254
|
+
sensitive: "as:sensitive",
|
|
255
|
+
toot: "http://joinmastodon.org/ns#",
|
|
256
|
+
emojiReactions: {
|
|
257
|
+
"@id": "fedibird:emojiReactions",
|
|
258
|
+
"@type": "@id",
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
tag: {
|
|
263
|
+
"@context": [
|
|
264
|
+
"https://www.w3.org/ns/activitystreams",
|
|
265
|
+
{
|
|
266
|
+
Hashtag: "as:Hashtag",
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
href: "https://fedify.dev/",
|
|
270
|
+
name: "#Fedify",
|
|
271
|
+
type: "Hashtag",
|
|
272
|
+
},
|
|
273
|
+
type: "Note",
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const noteWithName = note.clone({
|
|
277
|
+
name: "Test",
|
|
278
|
+
});
|
|
279
|
+
deepStrictEqual(
|
|
280
|
+
await noteWithName.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
281
|
+
await noteWithName.toJsonLd({
|
|
282
|
+
contextLoader: mockDocumentLoader,
|
|
283
|
+
format: "compact",
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("Activity.fromJsonLd()", async () => {
|
|
289
|
+
const follow = await Activity.fromJsonLd(
|
|
290
|
+
{
|
|
291
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
292
|
+
id: "https://activitypub.academy/80c50305-7405-4e38-809f-697647a1f679",
|
|
293
|
+
type: "Follow",
|
|
294
|
+
actor: "https://activitypub.academy/users/egulia_anbeiss",
|
|
295
|
+
object: "https://example.com/users/hongminhee",
|
|
296
|
+
},
|
|
297
|
+
{ documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader },
|
|
298
|
+
);
|
|
299
|
+
assertInstanceOf(follow, Follow);
|
|
300
|
+
deepStrictEqual(
|
|
301
|
+
follow.id,
|
|
302
|
+
new URL("https://activitypub.academy/80c50305-7405-4e38-809f-697647a1f679"),
|
|
303
|
+
);
|
|
304
|
+
deepStrictEqual(
|
|
305
|
+
follow.actorId,
|
|
306
|
+
new URL("https://activitypub.academy/users/egulia_anbeiss"),
|
|
307
|
+
);
|
|
308
|
+
deepStrictEqual(
|
|
309
|
+
follow.objectId,
|
|
310
|
+
new URL("https://example.com/users/hongminhee"),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const create = await Activity.fromJsonLd(
|
|
314
|
+
{
|
|
315
|
+
"@context": [
|
|
316
|
+
"https://www.w3.org/ns/activitystreams",
|
|
317
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
318
|
+
],
|
|
319
|
+
type: "Create",
|
|
320
|
+
actor: "https://server.example/users/alice",
|
|
321
|
+
object: {
|
|
322
|
+
type: "Note",
|
|
323
|
+
content: "Hello world",
|
|
324
|
+
},
|
|
325
|
+
proof: {
|
|
326
|
+
type: "DataIntegrityProof",
|
|
327
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
328
|
+
verificationMethod: "https://server.example/users/alice#ed25519-key",
|
|
329
|
+
proofPurpose: "assertionMethod",
|
|
330
|
+
proofValue:
|
|
331
|
+
// cSpell: disable
|
|
332
|
+
"z3sXaxjKs4M3BRicwWA9peyNPJvJqxtGsDmpt1jjoHCjgeUf71TRFz56osPSfDErszyLp5Ks1EhYSgpDaNM977Rg2",
|
|
333
|
+
// cSpell: enable
|
|
334
|
+
created: "2023-02-24T23:36:38Z",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{ documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader },
|
|
338
|
+
);
|
|
339
|
+
const proofs: DataIntegrityProof[] = [];
|
|
340
|
+
for await (const proof of create.getProofs()) proofs.push(proof);
|
|
341
|
+
deepStrictEqual(proofs.length, 1);
|
|
342
|
+
deepStrictEqual(proofs[0].cryptosuite, "eddsa-jcs-2022");
|
|
343
|
+
deepStrictEqual(
|
|
344
|
+
proofs[0].verificationMethodId,
|
|
345
|
+
new URL("https://server.example/users/alice#ed25519-key"),
|
|
346
|
+
);
|
|
347
|
+
deepStrictEqual(proofs[0].proofPurpose, "assertionMethod");
|
|
348
|
+
deepStrictEqual(
|
|
349
|
+
proofs[0].proofValue,
|
|
350
|
+
decodeMultibase(
|
|
351
|
+
// cSpell: disable
|
|
352
|
+
"z3sXaxjKs4M3BRicwWA9peyNPJvJqxtGsDmpt1jjoHCjgeUf71TRFz56osPSfDErszyLp5Ks1EhYSgpDaNM977Rg2",
|
|
353
|
+
// cSpell: enable
|
|
354
|
+
),
|
|
355
|
+
);
|
|
356
|
+
deepStrictEqual(
|
|
357
|
+
proofs[0].created,
|
|
358
|
+
Temporal.Instant.from("2023-02-24T23:36:38Z"),
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test({
|
|
363
|
+
name: "Activity.getObject()",
|
|
364
|
+
permissions: { env: true, read: true },
|
|
365
|
+
async fn() {
|
|
366
|
+
const activity = new Activity({
|
|
367
|
+
object: new URL("https://example.com/announce"),
|
|
368
|
+
});
|
|
369
|
+
const announce = await activity.getObject({
|
|
370
|
+
documentLoader: mockDocumentLoader,
|
|
371
|
+
contextLoader: mockDocumentLoader,
|
|
372
|
+
});
|
|
373
|
+
assertInstanceOf(announce, Announce);
|
|
374
|
+
deepStrictEqual(announce.id, new URL("https://example.com/announce"));
|
|
375
|
+
|
|
376
|
+
const object = await announce.getObject();
|
|
377
|
+
assertInstanceOf(object, Object);
|
|
378
|
+
deepStrictEqual(object.id, new URL("https://example.com/object"));
|
|
379
|
+
deepStrictEqual(object.name, "Fetched object");
|
|
380
|
+
|
|
381
|
+
// Is hydration applied to toJsonLd()?
|
|
382
|
+
const jsonLd = await activity.toJsonLd();
|
|
383
|
+
deepStrictEqual(jsonLd, {
|
|
384
|
+
"@context": [
|
|
385
|
+
"https://w3id.org/identity/v1",
|
|
386
|
+
"https://www.w3.org/ns/activitystreams",
|
|
387
|
+
"https://w3id.org/security/v1",
|
|
388
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
389
|
+
],
|
|
390
|
+
type: "Activity",
|
|
391
|
+
object: {
|
|
392
|
+
id: "https://example.com/announce",
|
|
393
|
+
type: "Announce",
|
|
394
|
+
object: {
|
|
395
|
+
type: "Object",
|
|
396
|
+
id: "https://example.com/object",
|
|
397
|
+
name: "Fetched object",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const activity2 = new Activity({
|
|
403
|
+
object: new URL("https://example.com/not-found"),
|
|
404
|
+
});
|
|
405
|
+
deepStrictEqual(await activity2.getObject({ suppressError: true }), null);
|
|
406
|
+
|
|
407
|
+
const activity3 = await Activity.fromJsonLd({
|
|
408
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
409
|
+
type: "Create",
|
|
410
|
+
object: {
|
|
411
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
412
|
+
type: "Note",
|
|
413
|
+
content: "Hello world",
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
const object3 = await activity3.getObject();
|
|
417
|
+
assertInstanceOf(object3, Note);
|
|
418
|
+
deepStrictEqual(await object3.toJsonLd(), {
|
|
419
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
420
|
+
type: "Note",
|
|
421
|
+
content: "Hello world",
|
|
422
|
+
});
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test({
|
|
427
|
+
name: "Activity.getObjects()",
|
|
428
|
+
permissions: { env: true, read: true },
|
|
429
|
+
async fn() {
|
|
430
|
+
const activity = new Activity({
|
|
431
|
+
objects: [
|
|
432
|
+
new URL("https://example.com/object"),
|
|
433
|
+
new Object({
|
|
434
|
+
name: "Second object",
|
|
435
|
+
}),
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
const objects = await Array.fromAsync(
|
|
439
|
+
activity.getObjects({
|
|
440
|
+
documentLoader: mockDocumentLoader,
|
|
441
|
+
contextLoader: mockDocumentLoader,
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
deepStrictEqual(objects.length, 2);
|
|
445
|
+
assertInstanceOf(objects[0], Object);
|
|
446
|
+
deepStrictEqual(objects[0].id, new URL("https://example.com/object"));
|
|
447
|
+
deepStrictEqual(objects[0].name, "Fetched object");
|
|
448
|
+
assertInstanceOf(objects[1], Object);
|
|
449
|
+
deepStrictEqual(objects[1].name, "Second object");
|
|
450
|
+
|
|
451
|
+
const activity2 = new Activity({
|
|
452
|
+
objects: [
|
|
453
|
+
new URL("https://example.com/not-found"),
|
|
454
|
+
new Object({
|
|
455
|
+
name: "Second object",
|
|
456
|
+
}),
|
|
457
|
+
],
|
|
458
|
+
});
|
|
459
|
+
const objects2 = await Array.fromAsync(
|
|
460
|
+
activity2.getObjects({ suppressError: true }),
|
|
461
|
+
);
|
|
462
|
+
deepStrictEqual(objects2.length, 1);
|
|
463
|
+
assertInstanceOf(objects2[0], Object);
|
|
464
|
+
deepStrictEqual(objects2[0].name, "Second object");
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("Activity.clone()", async () => {
|
|
469
|
+
const activity = new Activity({
|
|
470
|
+
actor: new Person({
|
|
471
|
+
name: "John Doe",
|
|
472
|
+
}),
|
|
473
|
+
object: new Object({
|
|
474
|
+
name: "Test",
|
|
475
|
+
}),
|
|
476
|
+
name: "Test",
|
|
477
|
+
summary: "Test",
|
|
478
|
+
});
|
|
479
|
+
const clone = activity.clone({
|
|
480
|
+
object: new Object({
|
|
481
|
+
name: "Modified",
|
|
482
|
+
}),
|
|
483
|
+
summary: "Modified",
|
|
484
|
+
});
|
|
485
|
+
deepStrictEqual((await activity.getActor())?.name, "John Doe");
|
|
486
|
+
deepStrictEqual((await clone.getActor())?.name, "John Doe");
|
|
487
|
+
deepStrictEqual((await activity.getObject())?.name, "Test");
|
|
488
|
+
deepStrictEqual((await clone.getObject())?.name, "Modified");
|
|
489
|
+
deepStrictEqual(activity.name, "Test");
|
|
490
|
+
deepStrictEqual(clone.name, "Test");
|
|
491
|
+
deepStrictEqual(activity.summary, "Test");
|
|
492
|
+
deepStrictEqual(clone.summary, "Modified");
|
|
493
|
+
|
|
494
|
+
throws(
|
|
495
|
+
() => activity.clone({ summary: "singular", summaries: ["plural"] }),
|
|
496
|
+
TypeError,
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("Question.voters", async () => {
|
|
501
|
+
const question = new Question({
|
|
502
|
+
voters: 123,
|
|
503
|
+
});
|
|
504
|
+
const json = await question.toJsonLd({ format: "compact" });
|
|
505
|
+
ok(typeof json === "object" && json != null);
|
|
506
|
+
ok("votersCount" in json);
|
|
507
|
+
deepStrictEqual((json as Record<string, unknown>)["votersCount"], 123);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test({
|
|
511
|
+
name: "Deno.inspect(Object)",
|
|
512
|
+
ignore: !("Deno" in globalThis),
|
|
513
|
+
fn() {
|
|
514
|
+
const obj = new Object({
|
|
515
|
+
id: new URL("https://example.com/"),
|
|
516
|
+
attribution: new URL("https://example.com/foo"),
|
|
517
|
+
name: "Test",
|
|
518
|
+
contents: [
|
|
519
|
+
new LanguageString("Hello", "en"),
|
|
520
|
+
new LanguageString("你好", "zh"),
|
|
521
|
+
],
|
|
522
|
+
});
|
|
523
|
+
deepStrictEqual(
|
|
524
|
+
Deno.inspect(obj, { colors: false, sorted: true, compact: false }),
|
|
525
|
+
"Deno" in globalThis
|
|
526
|
+
? "Object {\n" +
|
|
527
|
+
' attribution: URL "https://example.com/foo",\n' +
|
|
528
|
+
" contents: [\n" +
|
|
529
|
+
' <en> "Hello",\n' +
|
|
530
|
+
' <zh> "你好"\n' +
|
|
531
|
+
" ],\n" +
|
|
532
|
+
' id: URL "https://example.com/",\n' +
|
|
533
|
+
' name: "Test"\n' +
|
|
534
|
+
"}"
|
|
535
|
+
: "Object {\n" +
|
|
536
|
+
" attribution: URL 'https://example.com/foo',\n" +
|
|
537
|
+
" contents: [\n" +
|
|
538
|
+
" <en> 'Hello',\n" +
|
|
539
|
+
" <zh> '你好'\n" +
|
|
540
|
+
" ],\n" +
|
|
541
|
+
" id: URL 'https://example.com/',\n" +
|
|
542
|
+
" name: 'Test'\n" +
|
|
543
|
+
"}",
|
|
544
|
+
);
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("Person.fromJsonLd()", async () => {
|
|
549
|
+
const person = await Person.fromJsonLd({
|
|
550
|
+
"@context": [
|
|
551
|
+
"https://www.w3.org/ns/activitystreams",
|
|
552
|
+
"https://w3id.org/security/v1",
|
|
553
|
+
],
|
|
554
|
+
"id": "https://todon.eu/users/hongminhee",
|
|
555
|
+
"publicKey": {
|
|
556
|
+
"id": "https://todon.eu/users/hongminhee#main-key",
|
|
557
|
+
"owner": "https://todon.eu/users/hongminhee",
|
|
558
|
+
// cSpell: disable
|
|
559
|
+
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n" +
|
|
560
|
+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsRuvCkgJtflBTl4OVsm\n" +
|
|
561
|
+
"nt/J1mQfZasfJtN33dcZ3d1lJroxmgmMu69zjGEAwkNbMQaWNLqC4eogkJaeJ4RR\n" +
|
|
562
|
+
"5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7WQnKQgQMI+ezn24GHsZ/v1JIo77lerX5\n" +
|
|
563
|
+
"k4HNwTNVt+yaZVQWaOMR3+6FwziQR6kd0VuG9/a9dgAnz2cEoORRC1i4W7IZaB1s\n" +
|
|
564
|
+
"Znh1WbHbevlGd72HSXll5rocPIHn8gq6xpBgpHwRphlRsgn4KHaJ6brXDIJjrnQh\n" +
|
|
565
|
+
"Ie/YUBOGj/ImSEXhRwlFerKsoAVnZ0Hwbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2\n" +
|
|
566
|
+
"uwIDAQAB\n" +
|
|
567
|
+
"-----END PUBLIC KEY-----\n",
|
|
568
|
+
// cSpell: enable
|
|
569
|
+
},
|
|
570
|
+
}, {
|
|
571
|
+
documentLoader: mockDocumentLoader,
|
|
572
|
+
contextLoader: mockDocumentLoader,
|
|
573
|
+
baseUrl: new URL("https://todon.eu/"),
|
|
574
|
+
});
|
|
575
|
+
deepStrictEqual(
|
|
576
|
+
person.publicKeyId,
|
|
577
|
+
new URL("https://todon.eu/users/hongminhee#main-key"),
|
|
578
|
+
);
|
|
579
|
+
const publicKey = await person.getPublicKey({
|
|
580
|
+
documentLoader: mockDocumentLoader,
|
|
581
|
+
});
|
|
582
|
+
assertInstanceOf(publicKey, CryptographicKey);
|
|
583
|
+
deepStrictEqual(
|
|
584
|
+
publicKey?.ownerId,
|
|
585
|
+
new URL("https://todon.eu/users/hongminhee"),
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const person2 = await Person.fromJsonLd({
|
|
589
|
+
"@context": [
|
|
590
|
+
"https://www.w3.org/ns/activitystreams",
|
|
591
|
+
{
|
|
592
|
+
alsoKnownAs: {
|
|
593
|
+
"@id": "as:alsoKnownAs",
|
|
594
|
+
"@type": "@id",
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
"type": "Person",
|
|
599
|
+
// cSpell: disable
|
|
600
|
+
"alsoKnownAs": "at://did:plc:x7xdowahlhm5xulzqw4ehv6q",
|
|
601
|
+
// cSpell: enable
|
|
602
|
+
});
|
|
603
|
+
deepStrictEqual(
|
|
604
|
+
person2.aliasId,
|
|
605
|
+
// cSpell: disable
|
|
606
|
+
new URL("at://did%3Aplc%3Ax7xdowahlhm5xulzqw4ehv6q"),
|
|
607
|
+
// cSpell: enable
|
|
608
|
+
);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("Person.toJsonLd()", async () => {
|
|
612
|
+
const person = new Person({
|
|
613
|
+
aliases: [new URL("https://example.com/alias")],
|
|
614
|
+
});
|
|
615
|
+
deepStrictEqual(await person.toJsonLd(), {
|
|
616
|
+
"@context": [
|
|
617
|
+
"https://www.w3.org/ns/activitystreams",
|
|
618
|
+
"https://w3id.org/security/v1",
|
|
619
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
620
|
+
"https://www.w3.org/ns/did/v1",
|
|
621
|
+
"https://w3id.org/security/multikey/v1",
|
|
622
|
+
{
|
|
623
|
+
PropertyValue: "schema:PropertyValue",
|
|
624
|
+
alsoKnownAs: {
|
|
625
|
+
"@id": "as:alsoKnownAs",
|
|
626
|
+
"@type": "@id",
|
|
627
|
+
},
|
|
628
|
+
movedTo: {
|
|
629
|
+
"@id": "as:movedTo",
|
|
630
|
+
"@type": "@id",
|
|
631
|
+
},
|
|
632
|
+
discoverable: "toot:discoverable",
|
|
633
|
+
featured: {
|
|
634
|
+
"@id": "toot:featured",
|
|
635
|
+
"@type": "@id",
|
|
636
|
+
},
|
|
637
|
+
featuredTags: {
|
|
638
|
+
"@id": "toot:featuredTags",
|
|
639
|
+
"@type": "@id",
|
|
640
|
+
},
|
|
641
|
+
indexable: "toot:indexable",
|
|
642
|
+
_misskey_followedMessage: "misskey:_misskey_followedMessage",
|
|
643
|
+
isCat: "misskey:isCat",
|
|
644
|
+
manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
|
|
645
|
+
memorial: "toot:memorial",
|
|
646
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
647
|
+
schema: "http://schema.org#",
|
|
648
|
+
suspended: "toot:suspended",
|
|
649
|
+
toot: "http://joinmastodon.org/ns#",
|
|
650
|
+
value: "schema:value",
|
|
651
|
+
Emoji: "toot:Emoji",
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
alsoKnownAs: "https://example.com/alias",
|
|
655
|
+
type: "Person",
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test("Collection.fromJsonLd()", async () => {
|
|
660
|
+
const collection = await Collection.fromJsonLd({
|
|
661
|
+
"@context": [
|
|
662
|
+
"https://www.w3.org/ns/activitystreams",
|
|
663
|
+
"https://w3id.org/fep/5711",
|
|
664
|
+
],
|
|
665
|
+
"type": "Collection",
|
|
666
|
+
"id": "https://example.com/collection/jzc50wc28l",
|
|
667
|
+
"inboxOf": "https://example.com/person/bup9a8eqm",
|
|
668
|
+
});
|
|
669
|
+
deepStrictEqual(
|
|
670
|
+
collection.id,
|
|
671
|
+
new URL("https://example.com/collection/jzc50wc28l"),
|
|
672
|
+
);
|
|
673
|
+
deepStrictEqual(
|
|
674
|
+
collection.inboxOfId,
|
|
675
|
+
new URL("https://example.com/person/bup9a8eqm"),
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test("Note.quoteUrl", async () => {
|
|
680
|
+
const note = new Note({
|
|
681
|
+
quoteUrl: new URL("https://example.com/object"),
|
|
682
|
+
});
|
|
683
|
+
const expected = {
|
|
684
|
+
"@context": [
|
|
685
|
+
"https://www.w3.org/ns/activitystreams",
|
|
686
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
687
|
+
{
|
|
688
|
+
Emoji: "toot:Emoji",
|
|
689
|
+
Hashtag: "as:Hashtag",
|
|
690
|
+
_misskey_quote: "misskey:_misskey_quote",
|
|
691
|
+
fedibird: "http://fedibird.com/ns#",
|
|
692
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
693
|
+
quoteUri: "fedibird:quoteUri",
|
|
694
|
+
quoteUrl: "as:quoteUrl",
|
|
695
|
+
sensitive: "as:sensitive",
|
|
696
|
+
toot: "http://joinmastodon.org/ns#",
|
|
697
|
+
emojiReactions: {
|
|
698
|
+
"@id": "fedibird:emojiReactions",
|
|
699
|
+
"@type": "@id",
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
_misskey_quote: "https://example.com/object",
|
|
704
|
+
quoteUri: "https://example.com/object",
|
|
705
|
+
quoteUrl: "https://example.com/object",
|
|
706
|
+
type: "Note",
|
|
707
|
+
};
|
|
708
|
+
deepStrictEqual(await note.toJsonLd(), expected);
|
|
709
|
+
deepStrictEqual(await note.toJsonLd({ format: "compact" }), expected);
|
|
710
|
+
|
|
711
|
+
const jsonLd: Record<string, unknown> = {
|
|
712
|
+
"@context": [
|
|
713
|
+
"https://www.w3.org/ns/activitystreams",
|
|
714
|
+
{
|
|
715
|
+
_misskey_quote: "misskey:_misskey_quote",
|
|
716
|
+
fedibird: "http://fedibird.com/ns#",
|
|
717
|
+
misskey: "https://misskey-hub.net/ns#",
|
|
718
|
+
quoteUri: "fedibird:quoteUri",
|
|
719
|
+
quoteUrl: "as:quoteUrl",
|
|
720
|
+
},
|
|
721
|
+
],
|
|
722
|
+
type: "Note",
|
|
723
|
+
quoteUrl: "https://example.com/object",
|
|
724
|
+
_misskey_quote: "https://example.com/object2",
|
|
725
|
+
quoteUri: "https://example.com/object3",
|
|
726
|
+
};
|
|
727
|
+
const loaded = await Note.fromJsonLd(jsonLd);
|
|
728
|
+
deepStrictEqual(loaded.quoteUrl, new URL("https://example.com/object"));
|
|
729
|
+
|
|
730
|
+
delete jsonLd.quoteUrl;
|
|
731
|
+
const loaded2 = await Note.fromJsonLd(jsonLd);
|
|
732
|
+
deepStrictEqual(loaded2.quoteUrl, new URL("https://example.com/object2"));
|
|
733
|
+
|
|
734
|
+
delete jsonLd._misskey_quote;
|
|
735
|
+
const loaded3 = await Note.fromJsonLd(jsonLd);
|
|
736
|
+
deepStrictEqual(loaded3.quoteUrl, new URL("https://example.com/object3"));
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test("Key.publicKey", async () => {
|
|
740
|
+
const jwk = {
|
|
741
|
+
kty: "RSA",
|
|
742
|
+
alg: "RS256",
|
|
743
|
+
// cSpell: disable
|
|
744
|
+
n: "xsRuvCkgJtflBTl4OVsmnt_J1mQfZasfJtN33dcZ3d1lJroxmgmMu69zjGEAwkNbMQaWN" +
|
|
745
|
+
"LqC4eogkJaeJ4RR5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7WQnKQgQMI-ezn24GHsZ_v1J" +
|
|
746
|
+
"Io77lerX5k4HNwTNVt-yaZVQWaOMR3-6FwziQR6kd0VuG9_a9dgAnz2cEoORRC1i4W7IZa" +
|
|
747
|
+
"B1sZnh1WbHbevlGd72HSXll5rocPIHn8gq6xpBgpHwRphlRsgn4KHaJ6brXDIJjrnQhIe_" +
|
|
748
|
+
"YUBOGj_ImSEXhRwlFerKsoAVnZ0Hwbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2uw",
|
|
749
|
+
e: "AQAB",
|
|
750
|
+
// cSpell: enable
|
|
751
|
+
key_ops: ["verify"],
|
|
752
|
+
ext: true,
|
|
753
|
+
};
|
|
754
|
+
const key = new CryptographicKey({
|
|
755
|
+
publicKey: await crypto.subtle.importKey(
|
|
756
|
+
"jwk",
|
|
757
|
+
jwk,
|
|
758
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
759
|
+
true,
|
|
760
|
+
["verify"],
|
|
761
|
+
),
|
|
762
|
+
});
|
|
763
|
+
const jsonLd = await key.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
764
|
+
deepStrictEqual(jsonLd, {
|
|
765
|
+
"@context": "https://w3id.org/security/v1",
|
|
766
|
+
publicKeyPem: "-----BEGIN PUBLIC KEY-----\n" +
|
|
767
|
+
// cSpell: disable
|
|
768
|
+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsRuvCkgJtflBTl4OVsm\n" +
|
|
769
|
+
"nt/J1mQfZasfJtN33dcZ3d1lJroxmgmMu69zjGEAwkNbMQaWNLqC4eogkJaeJ4RR\n" +
|
|
770
|
+
"5MHYXkL9nNilVoTkjX5BVit3puzs7XJ7WQnKQgQMI+ezn24GHsZ/v1JIo77lerX5\n" +
|
|
771
|
+
"k4HNwTNVt+yaZVQWaOMR3+6FwziQR6kd0VuG9/a9dgAnz2cEoORRC1i4W7IZaB1s\n" +
|
|
772
|
+
"Znh1WbHbevlGd72HSXll5rocPIHn8gq6xpBgpHwRphlRsgn4KHaJ6brXDIJjrnQh\n" +
|
|
773
|
+
"Ie/YUBOGj/ImSEXhRwlFerKsoAVnZ0Hwbfa46qk44TAt8CyoPMWmpK6pt0ng4pQ2\n" +
|
|
774
|
+
"uwIDAQAB\n" +
|
|
775
|
+
// cSpell: enable
|
|
776
|
+
"-----END PUBLIC KEY-----\n",
|
|
777
|
+
type: "CryptographicKey",
|
|
778
|
+
});
|
|
779
|
+
const loadedKey = await CryptographicKey.fromJsonLd(jsonLd, {
|
|
780
|
+
documentLoader: mockDocumentLoader,
|
|
781
|
+
contextLoader: mockDocumentLoader,
|
|
782
|
+
});
|
|
783
|
+
notDeepStrictEqual(loadedKey.publicKey, null);
|
|
784
|
+
deepStrictEqual(
|
|
785
|
+
await crypto.subtle.exportKey("jwk", loadedKey.publicKey!),
|
|
786
|
+
jwk,
|
|
787
|
+
);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("Place.fromJsonLd()", async () => {
|
|
791
|
+
const place = await Place.fromJsonLd({
|
|
792
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
793
|
+
type: "Place",
|
|
794
|
+
name: "Fresno Area",
|
|
795
|
+
latitude: 36.75,
|
|
796
|
+
longitude: 119.7667,
|
|
797
|
+
radius: 15,
|
|
798
|
+
units: "miles",
|
|
799
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
800
|
+
assertInstanceOf(place, Place);
|
|
801
|
+
deepStrictEqual(place.name, "Fresno Area");
|
|
802
|
+
deepStrictEqual(place.latitude, 36.75);
|
|
803
|
+
deepStrictEqual(place.longitude, 119.7667);
|
|
804
|
+
deepStrictEqual(place.radius, 15);
|
|
805
|
+
deepStrictEqual(place.units, "miles");
|
|
806
|
+
|
|
807
|
+
let jsonLd = await place.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
808
|
+
deepStrictEqual(jsonLd, {
|
|
809
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
810
|
+
type: "Place",
|
|
811
|
+
name: "Fresno Area",
|
|
812
|
+
latitude: 36.75,
|
|
813
|
+
longitude: 119.7667,
|
|
814
|
+
radius: 15,
|
|
815
|
+
units: "miles",
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
jsonLd = await place.toJsonLd({
|
|
819
|
+
format: "compact",
|
|
820
|
+
contextLoader: mockDocumentLoader,
|
|
821
|
+
});
|
|
822
|
+
deepStrictEqual(jsonLd, {
|
|
823
|
+
"@context": [
|
|
824
|
+
"https://www.w3.org/ns/activitystreams",
|
|
825
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
826
|
+
],
|
|
827
|
+
type: "Place",
|
|
828
|
+
name: "Fresno Area",
|
|
829
|
+
latitude: 36.75,
|
|
830
|
+
longitude: 119.7667,
|
|
831
|
+
radius: 15,
|
|
832
|
+
units: "miles",
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
test("Actor.getOutbox()", async () => {
|
|
837
|
+
const person = new Person({
|
|
838
|
+
outbox: new URL("https://example.com/orderedcollectionpage"),
|
|
839
|
+
});
|
|
840
|
+
const outbox = await person.getOutbox({ documentLoader: mockDocumentLoader });
|
|
841
|
+
assertInstanceOf(outbox, OrderedCollectionPage);
|
|
842
|
+
deepStrictEqual(outbox.totalItems, 1);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test("Link.fromJsonLd()", async () => {
|
|
846
|
+
const link = await Link.fromJsonLd({
|
|
847
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
848
|
+
"type": "Link",
|
|
849
|
+
"rel": "canonical",
|
|
850
|
+
"href":
|
|
851
|
+
"at://did:plc:ia76kvnndjutgedggx2ibrem/app.bsky.feed.post/3lyxjjs27jkqg",
|
|
852
|
+
});
|
|
853
|
+
deepStrictEqual(link.rel, "canonical");
|
|
854
|
+
deepStrictEqual(
|
|
855
|
+
link.href,
|
|
856
|
+
new URL(
|
|
857
|
+
"at://did%3Aplc%3Aia76kvnndjutgedggx2ibrem/app.bsky.feed.post/3lyxjjs27jkqg",
|
|
858
|
+
),
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
const link2 = await Link.fromJsonLd({
|
|
862
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
863
|
+
"type": "Link",
|
|
864
|
+
"href": "at://bnewbold.bsky.team/app.bsky.feed.post/3jwdwj2ctlk26",
|
|
865
|
+
});
|
|
866
|
+
deepStrictEqual(
|
|
867
|
+
link2.href,
|
|
868
|
+
new URL("at://bnewbold.bsky.team/app.bsky.feed.post/3jwdwj2ctlk26"),
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
const link3 = await Link.fromJsonLd({
|
|
872
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
873
|
+
"type": "Link",
|
|
874
|
+
"href": "at://did:plc:ia76kvnndjutgedggx2ibrem",
|
|
875
|
+
});
|
|
876
|
+
deepStrictEqual(
|
|
877
|
+
link3.href,
|
|
878
|
+
new URL("at://did%3Aplc%3Aia76kvnndjutgedggx2ibrem"),
|
|
879
|
+
);
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
test("Person.fromJsonLd() with relative URLs", async () => {
|
|
883
|
+
const json = {
|
|
884
|
+
"@context": [
|
|
885
|
+
"https://www.w3.org/ns/activitystreams",
|
|
886
|
+
"https://w3id.org/security/v1",
|
|
887
|
+
],
|
|
888
|
+
id: "https://example.com/ap/actors/019382d3-63d7-7cf7-86e8-91e2551c306c",
|
|
889
|
+
type: "Person",
|
|
890
|
+
name: "Test User",
|
|
891
|
+
icon: { type: "Image", url: "/avatars/test-avatar.jpg" },
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
const person = await Person.fromJsonLd(json, {
|
|
895
|
+
documentLoader: mockDocumentLoader,
|
|
896
|
+
contextLoader: mockDocumentLoader,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const icon = await person.getIcon();
|
|
900
|
+
deepStrictEqual(
|
|
901
|
+
icon?.url,
|
|
902
|
+
new URL("https://example.com/avatars/test-avatar.jpg"),
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
const json2 = {
|
|
906
|
+
"@context": [
|
|
907
|
+
"https://www.w3.org/ns/activitystreams",
|
|
908
|
+
"https://w3id.org/security/v1",
|
|
909
|
+
],
|
|
910
|
+
id: "https://example.com/ap/actors/019382d3-63d7-7cf7-86e8-91e2551c306c",
|
|
911
|
+
type: "Person",
|
|
912
|
+
name: "Test User",
|
|
913
|
+
icon: {
|
|
914
|
+
id: "https://media.example.com/avatars/test-avatar.jpg",
|
|
915
|
+
type: "Image",
|
|
916
|
+
url: "/avatars/test-avatar.jpg",
|
|
917
|
+
},
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
const person2 = await Person.fromJsonLd(json2, {
|
|
921
|
+
documentLoader: mockDocumentLoader,
|
|
922
|
+
contextLoader: mockDocumentLoader,
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
const icon2 = await person2.getIcon();
|
|
926
|
+
deepStrictEqual(
|
|
927
|
+
icon2?.url,
|
|
928
|
+
new URL("https://media.example.com/avatars/test-avatar.jpg"),
|
|
929
|
+
);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
test("Person.fromJsonLd() with relative URLs and baseUrl", async () => {
|
|
933
|
+
const json = {
|
|
934
|
+
"@context": [
|
|
935
|
+
"https://www.w3.org/ns/activitystreams",
|
|
936
|
+
"https://w3id.org/security/v1",
|
|
937
|
+
],
|
|
938
|
+
"id": "https://example.com/ap/actors/019382d3-63d7-7cf7-86e8-91e2551c306c",
|
|
939
|
+
"type": "Person",
|
|
940
|
+
"name": "Test User",
|
|
941
|
+
"icon": {
|
|
942
|
+
"type": "Image",
|
|
943
|
+
"url": "/avatars/test-avatar.jpg",
|
|
944
|
+
},
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const personWithBase = await Person.fromJsonLd(json, {
|
|
948
|
+
documentLoader: mockDocumentLoader,
|
|
949
|
+
contextLoader: mockDocumentLoader,
|
|
950
|
+
baseUrl: new URL("https://example.com"),
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
const icon = await personWithBase.getIcon();
|
|
954
|
+
deepStrictEqual(
|
|
955
|
+
icon?.url,
|
|
956
|
+
new URL("https://example.com/avatars/test-avatar.jpg"),
|
|
957
|
+
);
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
test("FEP-fe34: Trust tracking in object construction", async () => {
|
|
961
|
+
// Test that objects created with embedded objects have trust set
|
|
962
|
+
const note = new Note({
|
|
963
|
+
id: new URL("https://example.com/note"),
|
|
964
|
+
content: "Hello World",
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const create = new Create({
|
|
968
|
+
id: new URL("https://example.com/create"),
|
|
969
|
+
actor: new URL("https://example.com/actor"),
|
|
970
|
+
object: note, // Embedded object should be trusted
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// Trust should be automatically set for embedded objects during construction
|
|
974
|
+
// We can verify this by checking that the object is returned immediately
|
|
975
|
+
// without requiring remote fetching
|
|
976
|
+
deepStrictEqual(create.objectId, new URL("https://example.com/note"));
|
|
977
|
+
|
|
978
|
+
// Should return the embedded object directly (no remote fetch needed)
|
|
979
|
+
const result = await create.getObject();
|
|
980
|
+
deepStrictEqual(result, note);
|
|
981
|
+
deepStrictEqual(result?.content, "Hello World");
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
test("FEP-fe34: Trust tracking in object cloning", () => {
|
|
985
|
+
const originalNote = new Note({
|
|
986
|
+
id: new URL("https://example.com/note"),
|
|
987
|
+
content: "Original content",
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
const create = new Create({
|
|
991
|
+
id: new URL("https://example.com/create"),
|
|
992
|
+
actor: new URL("https://example.com/actor"),
|
|
993
|
+
object: originalNote,
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
const newNote = new Note({
|
|
997
|
+
id: new URL("https://example.com/new-note"),
|
|
998
|
+
content: "New content",
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
// Clone with a new embedded object - should establish new trust
|
|
1002
|
+
const clonedCreate = create.clone({
|
|
1003
|
+
object: newNote,
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
deepStrictEqual(
|
|
1007
|
+
clonedCreate.objectId,
|
|
1008
|
+
new URL("https://example.com/new-note"),
|
|
1009
|
+
);
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
test("FEP-fe34: crossOrigin ignore behavior (default)", async () => {
|
|
1013
|
+
// Create a mock document loader that returns objects with different origins
|
|
1014
|
+
// deno-lint-ignore require-await
|
|
1015
|
+
const crossOriginDocumentLoader = async (url: string) => {
|
|
1016
|
+
if (url === "https://different-origin.com/note") {
|
|
1017
|
+
return {
|
|
1018
|
+
documentUrl: url,
|
|
1019
|
+
contextUrl: null,
|
|
1020
|
+
document: {
|
|
1021
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1022
|
+
"@type": "Note",
|
|
1023
|
+
"@id": "https://malicious.com/fake-note", // Different origin!
|
|
1024
|
+
"content": "This is a spoofed note",
|
|
1025
|
+
},
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
throw new Error("Document not found");
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
const create = new Create({
|
|
1032
|
+
id: new URL("https://example.com/create"),
|
|
1033
|
+
actor: new URL("https://example.com/actor"),
|
|
1034
|
+
object: new URL("https://different-origin.com/note"),
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// Default behavior should ignore cross-origin objects and return null
|
|
1038
|
+
const result = await create.getObject({
|
|
1039
|
+
documentLoader: crossOriginDocumentLoader,
|
|
1040
|
+
});
|
|
1041
|
+
deepStrictEqual(result, null);
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
test("FEP-fe34: crossOrigin throw behavior", async () => {
|
|
1045
|
+
// deno-lint-ignore require-await
|
|
1046
|
+
const crossOriginDocumentLoader = async (url: string) => {
|
|
1047
|
+
if (url === "https://different-origin.com/note") {
|
|
1048
|
+
return {
|
|
1049
|
+
documentUrl: url,
|
|
1050
|
+
contextUrl: null,
|
|
1051
|
+
document: {
|
|
1052
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1053
|
+
"@type": "Note",
|
|
1054
|
+
"@id": "https://malicious.com/fake-note", // Different origin!
|
|
1055
|
+
"content": "This is a spoofed note",
|
|
1056
|
+
},
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
throw new Error("Document not found");
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const create = new Create({
|
|
1063
|
+
id: new URL("https://example.com/create"),
|
|
1064
|
+
actor: new URL("https://example.com/actor"),
|
|
1065
|
+
object: new URL("https://different-origin.com/note"),
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
// Should throw an error when encountering cross-origin objects
|
|
1069
|
+
await rejects(
|
|
1070
|
+
() =>
|
|
1071
|
+
create.getObject({
|
|
1072
|
+
documentLoader: crossOriginDocumentLoader,
|
|
1073
|
+
crossOrigin: "throw",
|
|
1074
|
+
}),
|
|
1075
|
+
Error,
|
|
1076
|
+
);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
test("FEP-fe34: crossOrigin trust behavior", async () => {
|
|
1080
|
+
// deno-lint-ignore require-await
|
|
1081
|
+
const crossOriginDocumentLoader = async (url: string) => {
|
|
1082
|
+
if (url === "https://different-origin.com/note") {
|
|
1083
|
+
return {
|
|
1084
|
+
documentUrl: url,
|
|
1085
|
+
contextUrl: null,
|
|
1086
|
+
document: {
|
|
1087
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1088
|
+
"@type": "Note",
|
|
1089
|
+
"@id": "https://malicious.com/fake-note", // Different origin!
|
|
1090
|
+
"content": "This is a spoofed note",
|
|
1091
|
+
},
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
throw new Error("Document not found");
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
const create = new Create({
|
|
1098
|
+
id: new URL("https://example.com/create"),
|
|
1099
|
+
actor: new URL("https://example.com/actor"),
|
|
1100
|
+
object: new URL("https://different-origin.com/note"),
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
// Should bypass origin checks and return the object
|
|
1104
|
+
const result = await create.getObject({
|
|
1105
|
+
documentLoader: crossOriginDocumentLoader,
|
|
1106
|
+
crossOrigin: "trust",
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
assertInstanceOf(result, Note);
|
|
1110
|
+
deepStrictEqual(result?.id, new URL("https://malicious.com/fake-note"));
|
|
1111
|
+
deepStrictEqual(result?.content, "This is a spoofed note");
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test("FEP-fe34: Same origin objects are trusted", async () => {
|
|
1115
|
+
// deno-lint-ignore require-await
|
|
1116
|
+
const sameOriginDocumentLoader = async (url: string) => {
|
|
1117
|
+
if (url === "https://example.com/note") {
|
|
1118
|
+
return {
|
|
1119
|
+
documentUrl: url,
|
|
1120
|
+
contextUrl: null,
|
|
1121
|
+
document: {
|
|
1122
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1123
|
+
"@type": "Note",
|
|
1124
|
+
"@id": "https://example.com/note", // Same origin
|
|
1125
|
+
"content": "This is a legitimate note",
|
|
1126
|
+
},
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
throw new Error("Document not found");
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
const create = new Create({
|
|
1133
|
+
id: new URL("https://example.com/create"),
|
|
1134
|
+
actor: new URL("https://example.com/actor"),
|
|
1135
|
+
object: new URL("https://example.com/note"),
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
// Same origin objects should be returned normally
|
|
1139
|
+
const result = await create.getObject({
|
|
1140
|
+
documentLoader: sameOriginDocumentLoader,
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
assertInstanceOf(result, Note);
|
|
1144
|
+
deepStrictEqual(result?.id, new URL("https://example.com/note"));
|
|
1145
|
+
deepStrictEqual(result?.content, "This is a legitimate note");
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
test(
|
|
1149
|
+
"FEP-fe34: Embedded cross-origin objects from JSON-LD are ignored by default",
|
|
1150
|
+
async () => {
|
|
1151
|
+
// Mock document loader for creating the Create object from JSON-LD
|
|
1152
|
+
// deno-lint-ignore require-await
|
|
1153
|
+
const createDocumentLoader = async (url: string) => {
|
|
1154
|
+
if (url === "https://example.com/create") {
|
|
1155
|
+
return {
|
|
1156
|
+
documentUrl: url,
|
|
1157
|
+
contextUrl: null,
|
|
1158
|
+
document: {
|
|
1159
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1160
|
+
"@type": "Create",
|
|
1161
|
+
"@id": "https://example.com/create",
|
|
1162
|
+
"actor": "https://example.com/actor",
|
|
1163
|
+
"object": {
|
|
1164
|
+
"@type": "Note",
|
|
1165
|
+
// Different origin from parent!
|
|
1166
|
+
"@id": "https://different-origin.com/note",
|
|
1167
|
+
"content": "Embedded note from JSON-LD",
|
|
1168
|
+
},
|
|
1169
|
+
},
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
throw new Error("Document not found");
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
// Create object from JSON-LD (embedded objects won't be trusted)
|
|
1176
|
+
const create = await Create.fromJsonLd(
|
|
1177
|
+
await createDocumentLoader("https://example.com/create").then((r) =>
|
|
1178
|
+
r.document
|
|
1179
|
+
),
|
|
1180
|
+
{ documentLoader: createDocumentLoader },
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
// Mock document loader that would return the "legitimate" version
|
|
1184
|
+
// deno-lint-ignore require-await
|
|
1185
|
+
const objectDocumentLoader = async (url: string) => {
|
|
1186
|
+
if (url === "https://different-origin.com/note") {
|
|
1187
|
+
return {
|
|
1188
|
+
documentUrl: url,
|
|
1189
|
+
contextUrl: null,
|
|
1190
|
+
document: {
|
|
1191
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1192
|
+
"@type": "Note",
|
|
1193
|
+
"@id": "https://different-origin.com/note",
|
|
1194
|
+
"content": "Legitimate note from origin",
|
|
1195
|
+
},
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
throw new Error("Document not found");
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// Should fetch from origin instead of trusting embedded object
|
|
1202
|
+
const result = await create.getObject({
|
|
1203
|
+
documentLoader: objectDocumentLoader,
|
|
1204
|
+
});
|
|
1205
|
+
assertInstanceOf(result, Note);
|
|
1206
|
+
deepStrictEqual(result?.content, "Legitimate note from origin");
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
|
|
1210
|
+
test("FEP-fe34: Constructor vs JSON-LD parsing trust difference", async () => {
|
|
1211
|
+
// 1. Constructor-created objects: embedded objects are trusted
|
|
1212
|
+
const constructorCreate = new Create({
|
|
1213
|
+
id: new URL("https://example.com/create"),
|
|
1214
|
+
actor: new URL("https://example.com/actor"),
|
|
1215
|
+
object: new Note({
|
|
1216
|
+
id: new URL("https://different-origin.com/note"), // Different origin!
|
|
1217
|
+
content: "Constructor embedded note",
|
|
1218
|
+
}),
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
// Should return the embedded object directly (trusted)
|
|
1222
|
+
const constructorResult = await constructorCreate.getObject();
|
|
1223
|
+
deepStrictEqual(constructorResult?.content, "Constructor embedded note");
|
|
1224
|
+
|
|
1225
|
+
// 2. JSON-LD parsed objects: embedded objects are NOT trusted
|
|
1226
|
+
const jsonLdCreate = await Create.fromJsonLd({
|
|
1227
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1228
|
+
"@type": "Create",
|
|
1229
|
+
"@id": "https://example.com/create",
|
|
1230
|
+
"actor": "https://example.com/actor",
|
|
1231
|
+
"object": {
|
|
1232
|
+
"@type": "Note",
|
|
1233
|
+
"@id": "https://different-origin.com/note", // Same different origin!
|
|
1234
|
+
"content": "JSON-LD embedded note",
|
|
1235
|
+
},
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
// Mock document loader for the cross-origin fetch
|
|
1239
|
+
// deno-lint-ignore require-await
|
|
1240
|
+
const documentLoader = async (url: string) => {
|
|
1241
|
+
if (url === "https://different-origin.com/note") {
|
|
1242
|
+
return {
|
|
1243
|
+
documentUrl: url,
|
|
1244
|
+
contextUrl: null,
|
|
1245
|
+
document: {
|
|
1246
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1247
|
+
"@type": "Note",
|
|
1248
|
+
"@id": "https://different-origin.com/note",
|
|
1249
|
+
"content": "Fetched from origin",
|
|
1250
|
+
},
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
throw new Error("Document not found");
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
// Should fetch from origin instead of using embedded object (not trusted)
|
|
1257
|
+
const jsonLdResult = await jsonLdCreate.getObject({ documentLoader });
|
|
1258
|
+
deepStrictEqual(jsonLdResult?.content, "Fetched from origin");
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
test("FEP-fe34: Array properties respect cross-origin policy", async () => {
|
|
1262
|
+
// deno-lint-ignore require-await
|
|
1263
|
+
const crossOriginDocumentLoader = async (url: string) => {
|
|
1264
|
+
if (url === "https://different-origin.com/note1") {
|
|
1265
|
+
return {
|
|
1266
|
+
documentUrl: url,
|
|
1267
|
+
contextUrl: null,
|
|
1268
|
+
document: {
|
|
1269
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1270
|
+
"@type": "Note",
|
|
1271
|
+
"@id": "https://malicious.com/fake-note1", // Different origin!
|
|
1272
|
+
"content": "Fake note 1",
|
|
1273
|
+
},
|
|
1274
|
+
};
|
|
1275
|
+
} else if (url === "https://example.com/note2") {
|
|
1276
|
+
return {
|
|
1277
|
+
documentUrl: url,
|
|
1278
|
+
contextUrl: null,
|
|
1279
|
+
document: {
|
|
1280
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1281
|
+
"@type": "Note",
|
|
1282
|
+
"@id": "https://example.com/note2", // Same origin
|
|
1283
|
+
"content": "Legitimate note 2",
|
|
1284
|
+
},
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
throw new Error("Document not found");
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
const collection = new Collection({
|
|
1291
|
+
id: new URL("https://example.com/collection"),
|
|
1292
|
+
items: [
|
|
1293
|
+
new URL("https://different-origin.com/note1"), // Cross-origin
|
|
1294
|
+
new URL("https://example.com/note2"), // Same origin
|
|
1295
|
+
],
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
const items = [];
|
|
1299
|
+
for await (
|
|
1300
|
+
const item of collection.getItems({
|
|
1301
|
+
documentLoader: crossOriginDocumentLoader,
|
|
1302
|
+
})
|
|
1303
|
+
) {
|
|
1304
|
+
items.push(item);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// Should only get the same-origin item, cross-origin item should be filtered out
|
|
1308
|
+
deepStrictEqual(items.length, 1);
|
|
1309
|
+
assertInstanceOf(items[0], Note);
|
|
1310
|
+
deepStrictEqual((items[0] as Note).content, "Legitimate note 2");
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
test("FEP-fe34: Array properties with crossOrigin trust option", async () => {
|
|
1314
|
+
// deno-lint-ignore require-await
|
|
1315
|
+
const crossOriginDocumentLoader = async (url: string) => {
|
|
1316
|
+
if (url === "https://different-origin.com/note1") {
|
|
1317
|
+
return {
|
|
1318
|
+
documentUrl: url,
|
|
1319
|
+
contextUrl: null,
|
|
1320
|
+
document: {
|
|
1321
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1322
|
+
"@type": "Note",
|
|
1323
|
+
"@id": "https://malicious.com/fake-note1", // Different origin!
|
|
1324
|
+
"content": "Fake note 1",
|
|
1325
|
+
},
|
|
1326
|
+
};
|
|
1327
|
+
} else if (url === "https://example.com/note2") {
|
|
1328
|
+
return {
|
|
1329
|
+
documentUrl: url,
|
|
1330
|
+
contextUrl: null,
|
|
1331
|
+
document: {
|
|
1332
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1333
|
+
"@type": "Note",
|
|
1334
|
+
"@id": "https://example.com/note2", // Same origin
|
|
1335
|
+
"content": "Legitimate note 2",
|
|
1336
|
+
},
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
throw new Error("Document not found");
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
const collection = new Collection({
|
|
1343
|
+
id: new URL("https://example.com/collection"),
|
|
1344
|
+
items: [
|
|
1345
|
+
new URL("https://different-origin.com/note1"), // Cross-origin
|
|
1346
|
+
new URL("https://example.com/note2"), // Same origin
|
|
1347
|
+
],
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
const items = [];
|
|
1351
|
+
for await (
|
|
1352
|
+
const item of collection.getItems({
|
|
1353
|
+
documentLoader: crossOriginDocumentLoader,
|
|
1354
|
+
crossOrigin: "trust",
|
|
1355
|
+
})
|
|
1356
|
+
) {
|
|
1357
|
+
items.push(item);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// Should get both items when trust mode is enabled
|
|
1361
|
+
deepStrictEqual(items.length, 2);
|
|
1362
|
+
assertInstanceOf(items[0], Note);
|
|
1363
|
+
assertInstanceOf(items[1], Note);
|
|
1364
|
+
deepStrictEqual((items[0] as Note).content, "Fake note 1");
|
|
1365
|
+
deepStrictEqual((items[1] as Note).content, "Legitimate note 2");
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
test(
|
|
1369
|
+
"FEP-fe34: Embedded objects in arrays from JSON-LD respect cross-origin policy",
|
|
1370
|
+
async () => {
|
|
1371
|
+
// Mock document loader for creating the Collection object from JSON-LD
|
|
1372
|
+
// deno-lint-ignore require-await
|
|
1373
|
+
const collectionDocumentLoader = async (url: string) => {
|
|
1374
|
+
if (url === "https://example.com/collection") {
|
|
1375
|
+
return {
|
|
1376
|
+
documentUrl: url,
|
|
1377
|
+
contextUrl: null,
|
|
1378
|
+
document: {
|
|
1379
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1380
|
+
"@type": "Collection",
|
|
1381
|
+
"@id": "https://example.com/collection",
|
|
1382
|
+
"items": [
|
|
1383
|
+
{
|
|
1384
|
+
"@type": "Note",
|
|
1385
|
+
"@id": "https://example.com/trusted-note", // Same origin
|
|
1386
|
+
"content": "Trusted embedded note from JSON-LD",
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
"@type": "Note",
|
|
1390
|
+
"@id": "https://different-origin.com/untrusted-note", // Different origin!
|
|
1391
|
+
"content": "Untrusted embedded note from JSON-LD",
|
|
1392
|
+
},
|
|
1393
|
+
],
|
|
1394
|
+
},
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
throw new Error("Document not found");
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
// Create collection from JSON-LD (embedded objects won't be trusted)
|
|
1401
|
+
const collection = await Collection.fromJsonLd(
|
|
1402
|
+
await collectionDocumentLoader("https://example.com/collection").then((
|
|
1403
|
+
r,
|
|
1404
|
+
) => r.document),
|
|
1405
|
+
{ documentLoader: collectionDocumentLoader },
|
|
1406
|
+
);
|
|
1407
|
+
|
|
1408
|
+
// Mock document loader for fetching objects
|
|
1409
|
+
// deno-lint-ignore require-await
|
|
1410
|
+
const itemDocumentLoader = async (url: string) => {
|
|
1411
|
+
if (url === "https://example.com/trusted-note") {
|
|
1412
|
+
return {
|
|
1413
|
+
documentUrl: url,
|
|
1414
|
+
contextUrl: null,
|
|
1415
|
+
document: {
|
|
1416
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1417
|
+
"@type": "Note",
|
|
1418
|
+
"@id": "https://example.com/trusted-note",
|
|
1419
|
+
"content": "Trusted note from origin",
|
|
1420
|
+
},
|
|
1421
|
+
};
|
|
1422
|
+
} else if (url === "https://different-origin.com/untrusted-note") {
|
|
1423
|
+
return {
|
|
1424
|
+
documentUrl: url,
|
|
1425
|
+
contextUrl: null,
|
|
1426
|
+
document: {
|
|
1427
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
1428
|
+
"@type": "Note",
|
|
1429
|
+
"@id": "https://different-origin.com/untrusted-note",
|
|
1430
|
+
"content": "Legitimate note from actual origin",
|
|
1431
|
+
},
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
throw new Error("Document not found");
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
const items = [];
|
|
1438
|
+
for await (
|
|
1439
|
+
const item of collection.getItems({ documentLoader: itemDocumentLoader })
|
|
1440
|
+
) {
|
|
1441
|
+
items.push(item);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Should get both items
|
|
1445
|
+
deepStrictEqual(items.length, 2);
|
|
1446
|
+
|
|
1447
|
+
// First item (same origin) - should use embedded object since it's same-origin as parent
|
|
1448
|
+
assertInstanceOf(items[0], Note);
|
|
1449
|
+
deepStrictEqual(
|
|
1450
|
+
(items[0] as Note).content,
|
|
1451
|
+
"Trusted embedded note from JSON-LD",
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1454
|
+
// Second item (cross-origin) - should be fetched from origin, not embedded version
|
|
1455
|
+
assertInstanceOf(items[1], Note);
|
|
1456
|
+
deepStrictEqual(
|
|
1457
|
+
(items[1] as Note).content,
|
|
1458
|
+
"Legitimate note from actual origin",
|
|
1459
|
+
);
|
|
1460
|
+
},
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
function getAllProperties(
|
|
1464
|
+
type: TypeSchema,
|
|
1465
|
+
types: Record<string, TypeSchema>,
|
|
1466
|
+
): PropertySchema[] {
|
|
1467
|
+
const props: PropertySchema[] = type.properties;
|
|
1468
|
+
if (type.extends != null) {
|
|
1469
|
+
props.push(...getAllProperties(types[type.extends], types));
|
|
1470
|
+
}
|
|
1471
|
+
return props;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
const ed25519PublicKey = new CryptographicKey({
|
|
1475
|
+
id: new URL("https://example.com/person2#key4"),
|
|
1476
|
+
owner: new URL("https://example.com/person2"),
|
|
1477
|
+
publicKey: await crypto.subtle.importKey(
|
|
1478
|
+
"jwk",
|
|
1479
|
+
{
|
|
1480
|
+
crv: "Ed25519",
|
|
1481
|
+
ext: true,
|
|
1482
|
+
key_ops: ["verify"],
|
|
1483
|
+
kty: "OKP",
|
|
1484
|
+
// cSpell: disable
|
|
1485
|
+
x: "LR8epAGDe-cVq5p2Tx49CCfphpk1rNhkNoY9i-XEUfg",
|
|
1486
|
+
// cSpell: enable
|
|
1487
|
+
},
|
|
1488
|
+
"Ed25519",
|
|
1489
|
+
true,
|
|
1490
|
+
["verify"],
|
|
1491
|
+
),
|
|
1492
|
+
}) as CryptographicKey & { publicKey: CryptoKey };
|
|
1493
|
+
|
|
1494
|
+
const rsaPublicKey = new CryptographicKey({
|
|
1495
|
+
id: new URL("https://example.com/key"),
|
|
1496
|
+
owner: new URL("https://example.com/person"),
|
|
1497
|
+
publicKey: await crypto.subtle.importKey(
|
|
1498
|
+
"jwk",
|
|
1499
|
+
{
|
|
1500
|
+
kty: "RSA",
|
|
1501
|
+
alg: "RS256",
|
|
1502
|
+
// cSpell: disable
|
|
1503
|
+
n: "yIB9rotX8G6r6_6toT-x24BUiQ_HaPH1Em9dOt4c94s-OPFoEdH7DY7Iym9A8Ll" +
|
|
1504
|
+
"H4JaGF8KD38bLHWe1S4x0jV3gHJKhK7veJfGZCKUENcQecBZ-YWUs5HWvUIX1vVB" +
|
|
1505
|
+
"__0luHrg6BQKGOrSOE-WIAxyr0qsWCFfZzQrvSnUD2yvg1arJX2xhms14uxoRd5K" +
|
|
1506
|
+
"g9efKSCmmQaNEapicARUmFWrIEpGFa_nUUnqimssAGw1eZFqf3wA4TjhsuARBhGa" +
|
|
1507
|
+
"Jtv_3KEa016eMZxy3kDlOjZnXZTaTgWkXdodwUvy8563fes3Al6BlcS2iJ9qbtha" +
|
|
1508
|
+
"8rSm0FHqoUKH73JsLPKQIwQ",
|
|
1509
|
+
e: "AQAB",
|
|
1510
|
+
// cSpell: enable
|
|
1511
|
+
key_ops: ["verify"],
|
|
1512
|
+
ext: true,
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
1516
|
+
hash: "SHA-256",
|
|
1517
|
+
},
|
|
1518
|
+
true,
|
|
1519
|
+
["verify"],
|
|
1520
|
+
),
|
|
1521
|
+
}) as CryptographicKey & { publicKey: CryptoKey };
|
|
1522
|
+
|
|
1523
|
+
// deno-lint-ignore no-explicit-any
|
|
1524
|
+
const sampleValues: Record<string, any> = {
|
|
1525
|
+
"http://www.w3.org/2001/XMLSchema#boolean": true,
|
|
1526
|
+
"http://www.w3.org/2001/XMLSchema#integer": -123,
|
|
1527
|
+
"http://www.w3.org/2001/XMLSchema#nonNegativeInteger": 123,
|
|
1528
|
+
"http://www.w3.org/2001/XMLSchema#float": 12.34,
|
|
1529
|
+
"http://www.w3.org/2001/XMLSchema#string": "hello",
|
|
1530
|
+
"http://www.w3.org/2001/XMLSchema#anyURI": new URL("https://example.com/"),
|
|
1531
|
+
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString": new LanguageString(
|
|
1532
|
+
"hello",
|
|
1533
|
+
"en",
|
|
1534
|
+
),
|
|
1535
|
+
"http://www.w3.org/2001/XMLSchema#dateTime": Temporal.Instant.from(
|
|
1536
|
+
"2024-03-03T08:30:06.796196096Z",
|
|
1537
|
+
),
|
|
1538
|
+
"http://www.w3.org/2001/XMLSchema#duration": Temporal.Duration.from({
|
|
1539
|
+
hours: 1,
|
|
1540
|
+
}),
|
|
1541
|
+
"https://w3id.org/security#cryptosuiteString": "eddsa-jcs-2022",
|
|
1542
|
+
// deno-fmt-ignore
|
|
1543
|
+
"https://w3id.org/security#multibase": new Uint8Array([
|
|
1544
|
+
0x8f, 0x9b, 0x5a, 0xc9, 0x14, 0x17, 0xd0, 0xd1, 0x88, 0xbe, 0xfa, 0x85,
|
|
1545
|
+
0x8f, 0x74, 0x44, 0x98, 0x1d, 0xc8, 0x79, 0xda, 0xba, 0x50, 0x98, 0x3c,
|
|
1546
|
+
0x43, 0xeb, 0xcf, 0x72, 0x5f, 0x38, 0x58, 0x11, 0x9f, 0x23, 0xc5, 0xbf,
|
|
1547
|
+
0x84, 0x23, 0x76, 0xa2, 0x1d, 0x53, 0xc0, 0xbe, 0x1a, 0xaa, 0x96, 0x6e,
|
|
1548
|
+
0x30, 0x65, 0x59, 0x76, 0xf0, 0xb0, 0xdb, 0x78, 0x0d, 0xf5, 0xc1, 0xad,
|
|
1549
|
+
0x3f, 0xbd, 0xf3, 0x07,
|
|
1550
|
+
]),
|
|
1551
|
+
"fedify:langTag": new Intl.Locale("en-Latn-US"),
|
|
1552
|
+
"fedify:url": new URL("https://fedify.dev/"),
|
|
1553
|
+
"fedify:publicKey": rsaPublicKey.publicKey,
|
|
1554
|
+
"fedify:multibaseKey": ed25519PublicKey.publicKey,
|
|
1555
|
+
"fedify:proofPurpose": "assertionMethod",
|
|
1556
|
+
"fedify:units": "m",
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
const types: Record<string, TypeSchema> =
|
|
1560
|
+
navigator?.userAgent === "Cloudflare-Workers"
|
|
1561
|
+
? {} // FIXME: Cloudflare Workers does not support async I/O within global scope
|
|
1562
|
+
: await loadSchemaFiles(import.meta.dirname!);
|
|
1563
|
+
for (const typeUri in types) {
|
|
1564
|
+
const type = types[typeUri];
|
|
1565
|
+
// @ts-ignore: classes are all different
|
|
1566
|
+
const cls = vocab[type.name];
|
|
1567
|
+
sampleValues[typeUri] = new cls({
|
|
1568
|
+
"@id": "https://example.com/",
|
|
1569
|
+
"@type": typeUri,
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
const { assertSnapshot } = await import("@std/testing/snapshot").catch(
|
|
1574
|
+
() => ({ assertSnapshot: () => Promise.resolve() }),
|
|
1575
|
+
);
|
|
1576
|
+
|
|
1577
|
+
for (const typeUri in types) {
|
|
1578
|
+
const type = types[typeUri];
|
|
1579
|
+
// @ts-ignore: classes are all different
|
|
1580
|
+
const cls = vocab[type.name];
|
|
1581
|
+
const allProperties = getAllProperties(type, types);
|
|
1582
|
+
const initValues = globalThis.Object.fromEntries(
|
|
1583
|
+
allProperties.map((property) =>
|
|
1584
|
+
!property.functional
|
|
1585
|
+
? [property.pluralName, property.range.map((t) => sampleValues[t])]
|
|
1586
|
+
: [property.singularName, sampleValues[property.range[0]]]
|
|
1587
|
+
),
|
|
1588
|
+
);
|
|
1589
|
+
|
|
1590
|
+
test(`new ${type.name}() [auto]`, async () => {
|
|
1591
|
+
const instance = new cls(initValues);
|
|
1592
|
+
for (const property of allProperties) {
|
|
1593
|
+
if (areAllScalarTypes(property.range, types)) {
|
|
1594
|
+
if (property.functional || property.singularAccessor) {
|
|
1595
|
+
deepStrictEqual(
|
|
1596
|
+
instance[property.singularName],
|
|
1597
|
+
sampleValues[property.range[0]],
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
if (!property.functional) {
|
|
1601
|
+
deepStrictEqual(
|
|
1602
|
+
instance[property.pluralName],
|
|
1603
|
+
property.range.map((t) => sampleValues[t]),
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
} else {
|
|
1607
|
+
if (property.functional || property.singularAccessor) {
|
|
1608
|
+
deepStrictEqual(
|
|
1609
|
+
await instance[`get${pascalCase(property.singularName)}`].call(
|
|
1610
|
+
instance,
|
|
1611
|
+
{ documentLoader: mockDocumentLoader },
|
|
1612
|
+
),
|
|
1613
|
+
sampleValues[property.range[0]],
|
|
1614
|
+
);
|
|
1615
|
+
deepStrictEqual(
|
|
1616
|
+
instance[`${property.singularName}Id`],
|
|
1617
|
+
sampleValues[property.range[0]].id,
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
if (!property.functional) {
|
|
1621
|
+
deepStrictEqual(
|
|
1622
|
+
await Array.fromAsync(
|
|
1623
|
+
instance[`get${pascalCase(property.pluralName)}`].call(
|
|
1624
|
+
instance,
|
|
1625
|
+
{ documentLoader: mockDocumentLoader },
|
|
1626
|
+
),
|
|
1627
|
+
),
|
|
1628
|
+
property.range.map((t) => sampleValues[t]),
|
|
1629
|
+
);
|
|
1630
|
+
deepStrictEqual(
|
|
1631
|
+
instance[`${property.singularName}Ids`],
|
|
1632
|
+
property.range.map((t) => sampleValues[t].id).filter((i) =>
|
|
1633
|
+
i != null
|
|
1634
|
+
),
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
const empty = new cls({});
|
|
1640
|
+
for (const property of allProperties) {
|
|
1641
|
+
if (areAllScalarTypes(property.range, types)) {
|
|
1642
|
+
if (property.functional || property.singularAccessor) {
|
|
1643
|
+
deepStrictEqual(empty[property.singularName], null);
|
|
1644
|
+
}
|
|
1645
|
+
if (!property.functional) {
|
|
1646
|
+
deepStrictEqual(empty[property.pluralName], []);
|
|
1647
|
+
}
|
|
1648
|
+
} else {
|
|
1649
|
+
if (property.functional || property.singularAccessor) {
|
|
1650
|
+
deepStrictEqual(
|
|
1651
|
+
await empty[`get${pascalCase(property.singularName)}`].call(
|
|
1652
|
+
empty,
|
|
1653
|
+
{ documentLoader: mockDocumentLoader },
|
|
1654
|
+
),
|
|
1655
|
+
null,
|
|
1656
|
+
);
|
|
1657
|
+
deepStrictEqual(empty[`${property.singularName}Id`], null);
|
|
1658
|
+
}
|
|
1659
|
+
if (!property.functional) {
|
|
1660
|
+
deepStrictEqual(
|
|
1661
|
+
await Array.fromAsync(
|
|
1662
|
+
empty[`get${pascalCase(property.pluralName)}`].call(
|
|
1663
|
+
empty,
|
|
1664
|
+
{ documentLoader: mockDocumentLoader },
|
|
1665
|
+
),
|
|
1666
|
+
),
|
|
1667
|
+
[],
|
|
1668
|
+
);
|
|
1669
|
+
deepStrictEqual(empty[`${property.singularName}Ids`], []);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
for (const property of allProperties) {
|
|
1676
|
+
if (!property.functional && property.singularAccessor) {
|
|
1677
|
+
throws(
|
|
1678
|
+
() =>
|
|
1679
|
+
new cls({
|
|
1680
|
+
[property.singularName]: sampleValues[property.range[0]],
|
|
1681
|
+
[property.pluralName]: property.range.map((t) => sampleValues[t]),
|
|
1682
|
+
}),
|
|
1683
|
+
TypeError,
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
const instance2 = new cls({
|
|
1689
|
+
id: new URL("https://example.com/"),
|
|
1690
|
+
...globalThis.Object.fromEntries(
|
|
1691
|
+
allProperties.filter((p) => !areAllScalarTypes(p.range, types)).map(
|
|
1692
|
+
(p) =>
|
|
1693
|
+
p.functional
|
|
1694
|
+
? [p.singularName, new URL("https://example.com/test")]
|
|
1695
|
+
: [p.pluralName, [new URL("https://example.com/test")]],
|
|
1696
|
+
),
|
|
1697
|
+
),
|
|
1698
|
+
});
|
|
1699
|
+
for (const property of allProperties) {
|
|
1700
|
+
if (areAllScalarTypes(property.range, types)) continue;
|
|
1701
|
+
if (property.functional || property.singularAccessor) {
|
|
1702
|
+
deepStrictEqual(
|
|
1703
|
+
instance2[`${property.singularName}Id`],
|
|
1704
|
+
new URL("https://example.com/test"),
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
if (!property.functional) {
|
|
1708
|
+
deepStrictEqual(
|
|
1709
|
+
instance2[`${property.singularName}Ids`],
|
|
1710
|
+
[new URL("https://example.com/test")],
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
throws(
|
|
1716
|
+
() => new cls({ id: 123 as unknown as URL }),
|
|
1717
|
+
TypeError,
|
|
1718
|
+
"The id must be a URL.",
|
|
1719
|
+
);
|
|
1720
|
+
|
|
1721
|
+
for (const property of allProperties) {
|
|
1722
|
+
const wrongValues = globalThis.Object.fromEntries(
|
|
1723
|
+
globalThis.Object.entries(initValues),
|
|
1724
|
+
);
|
|
1725
|
+
if (property.functional) {
|
|
1726
|
+
wrongValues[property.singularName] = {};
|
|
1727
|
+
} else {
|
|
1728
|
+
wrongValues[property.pluralName] = [{}];
|
|
1729
|
+
}
|
|
1730
|
+
throws(() => new cls(wrongValues), TypeError);
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
test(`${type.name}.clone() [auto]`, () => {
|
|
1735
|
+
const instance = new cls({});
|
|
1736
|
+
for (const property of allProperties) {
|
|
1737
|
+
if (!property.functional && property.singularAccessor) {
|
|
1738
|
+
throws(
|
|
1739
|
+
() =>
|
|
1740
|
+
instance.clone({
|
|
1741
|
+
[property.singularName]: sampleValues[property.range[0]],
|
|
1742
|
+
[property.pluralName]: property.range.map((t) => sampleValues[t]),
|
|
1743
|
+
}),
|
|
1744
|
+
TypeError,
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
throws(
|
|
1750
|
+
() => instance.clone({ id: 123 as unknown as URL }),
|
|
1751
|
+
TypeError,
|
|
1752
|
+
"The id must be a URL.",
|
|
1753
|
+
);
|
|
1754
|
+
for (const property of allProperties) {
|
|
1755
|
+
const wrongValues = globalThis.Object.fromEntries(
|
|
1756
|
+
globalThis.Object.entries(initValues),
|
|
1757
|
+
);
|
|
1758
|
+
if (property.functional) {
|
|
1759
|
+
wrongValues[property.singularName] = {};
|
|
1760
|
+
} else {
|
|
1761
|
+
wrongValues[property.pluralName] = [{}];
|
|
1762
|
+
}
|
|
1763
|
+
throws(() => instance.clone(wrongValues), TypeError);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
for (const property of allProperties) {
|
|
1768
|
+
if (areAllScalarTypes(property.range, types)) continue;
|
|
1769
|
+
|
|
1770
|
+
const docLoader = async (url: string) => {
|
|
1771
|
+
if (url !== `https://example.com/test`) throw new Error("Not Found");
|
|
1772
|
+
return {
|
|
1773
|
+
documentUrl: url,
|
|
1774
|
+
contextUrl: null,
|
|
1775
|
+
document: await sampleValues[property.range[0]].toJsonLd({
|
|
1776
|
+
contextLoader: mockDocumentLoader,
|
|
1777
|
+
}),
|
|
1778
|
+
};
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
if (property.functional || property.singularAccessor) {
|
|
1782
|
+
test(
|
|
1783
|
+
`${type.name}.get${pascalCase(property.singularName)}() [auto]`,
|
|
1784
|
+
async () => {
|
|
1785
|
+
const instance = new cls({
|
|
1786
|
+
[property.singularName]: new URL("https://example.com/test"),
|
|
1787
|
+
});
|
|
1788
|
+
const value =
|
|
1789
|
+
await instance[`get${pascalCase(property.singularName)}`]
|
|
1790
|
+
.call(instance, { documentLoader: docLoader });
|
|
1791
|
+
deepStrictEqual(value, sampleValues[property.range[0]]);
|
|
1792
|
+
|
|
1793
|
+
if (property.untyped) return;
|
|
1794
|
+
const wrongRef = new cls({
|
|
1795
|
+
[property.singularName]: new URL("https://example.com/wrong-type"),
|
|
1796
|
+
});
|
|
1797
|
+
await rejects(
|
|
1798
|
+
() =>
|
|
1799
|
+
wrongRef[`get${pascalCase(property.singularName)}`].call(
|
|
1800
|
+
wrongRef,
|
|
1801
|
+
{
|
|
1802
|
+
documentLoader: mockDocumentLoader,
|
|
1803
|
+
},
|
|
1804
|
+
),
|
|
1805
|
+
TypeError,
|
|
1806
|
+
);
|
|
1807
|
+
},
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
if (!property.functional) {
|
|
1811
|
+
test(
|
|
1812
|
+
`${type.name}.get${pascalCase(property.pluralName)}() [auto]`,
|
|
1813
|
+
async () => {
|
|
1814
|
+
const instance = new cls({
|
|
1815
|
+
[property.pluralName]: [new URL("https://example.com/test")],
|
|
1816
|
+
});
|
|
1817
|
+
const value = instance[`get${pascalCase(property.pluralName)}`].call(
|
|
1818
|
+
instance,
|
|
1819
|
+
{ documentLoader: docLoader },
|
|
1820
|
+
);
|
|
1821
|
+
deepStrictEqual(await Array.fromAsync(value), [
|
|
1822
|
+
sampleValues[property.range[0]],
|
|
1823
|
+
]);
|
|
1824
|
+
|
|
1825
|
+
if (property.untyped) return;
|
|
1826
|
+
const wrongRef = new cls({
|
|
1827
|
+
[property.pluralName]: [new URL("https://example.com/wrong-type")],
|
|
1828
|
+
});
|
|
1829
|
+
await rejects(
|
|
1830
|
+
() =>
|
|
1831
|
+
Array.fromAsync(
|
|
1832
|
+
wrongRef[`get${pascalCase(property.pluralName)}`].call(
|
|
1833
|
+
wrongRef,
|
|
1834
|
+
{
|
|
1835
|
+
documentLoader: mockDocumentLoader,
|
|
1836
|
+
},
|
|
1837
|
+
),
|
|
1838
|
+
),
|
|
1839
|
+
TypeError,
|
|
1840
|
+
);
|
|
1841
|
+
},
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
test(`${type.name}.fromJsonLd() [auto]`, async () => {
|
|
1847
|
+
const instance = await cls.fromJsonLd(
|
|
1848
|
+
{
|
|
1849
|
+
"@id": "https://example.com/",
|
|
1850
|
+
"@type": typeUri,
|
|
1851
|
+
},
|
|
1852
|
+
{ documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader },
|
|
1853
|
+
) as vocab.Object;
|
|
1854
|
+
assertInstanceOf(instance, cls);
|
|
1855
|
+
deepStrictEqual(instance.id, new URL("https://example.com/"));
|
|
1856
|
+
deepStrictEqual(
|
|
1857
|
+
await instance.toJsonLd(),
|
|
1858
|
+
{
|
|
1859
|
+
"@id": "https://example.com/",
|
|
1860
|
+
"@type": typeUri,
|
|
1861
|
+
},
|
|
1862
|
+
);
|
|
1863
|
+
deepStrictEqual(
|
|
1864
|
+
await instance.toJsonLd({
|
|
1865
|
+
format: "compact",
|
|
1866
|
+
contextLoader: mockDocumentLoader,
|
|
1867
|
+
}),
|
|
1868
|
+
{
|
|
1869
|
+
"@context": type.defaultContext,
|
|
1870
|
+
"id": "https://example.com/",
|
|
1871
|
+
"type": type.compactName ??
|
|
1872
|
+
(type.name === "DataIntegrityProof" ? type.name : type.uri),
|
|
1873
|
+
},
|
|
1874
|
+
);
|
|
1875
|
+
|
|
1876
|
+
if (type.extends != null) {
|
|
1877
|
+
await rejects(() =>
|
|
1878
|
+
cls.fromJsonLd({
|
|
1879
|
+
"@id": "https://example.com/",
|
|
1880
|
+
"@type": "https://example.com/",
|
|
1881
|
+
}), TypeError);
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
await rejects(() => cls.fromJsonLd(null), TypeError);
|
|
1885
|
+
await rejects(() => cls.fromJsonLd(undefined), TypeError);
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
test(`${type.name}.toJsonLd() [auto]`, async () => {
|
|
1889
|
+
const instance = new cls({
|
|
1890
|
+
id: new URL("https://example.com/"),
|
|
1891
|
+
...initValues,
|
|
1892
|
+
});
|
|
1893
|
+
const jsonLd = await instance.toJsonLd({
|
|
1894
|
+
contextLoader: mockDocumentLoader,
|
|
1895
|
+
});
|
|
1896
|
+
deepStrictEqual(jsonLd["@context"], type.defaultContext);
|
|
1897
|
+
deepStrictEqual(jsonLd.id, "https://example.com/");
|
|
1898
|
+
const restored = await cls.fromJsonLd(jsonLd, {
|
|
1899
|
+
documentLoader: mockDocumentLoader,
|
|
1900
|
+
contextLoader: mockDocumentLoader,
|
|
1901
|
+
});
|
|
1902
|
+
deepStrictEqual(restored, instance);
|
|
1903
|
+
deepStrictEqual(
|
|
1904
|
+
await restored.toJsonLd({ contextLoader: mockDocumentLoader }),
|
|
1905
|
+
jsonLd,
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
const jsonLd2 = await instance.toJsonLd({
|
|
1909
|
+
contextLoader: mockDocumentLoader,
|
|
1910
|
+
format: "compact",
|
|
1911
|
+
context: "https://www.w3.org/ns/activitystreams",
|
|
1912
|
+
});
|
|
1913
|
+
deepStrictEqual(
|
|
1914
|
+
jsonLd2["@context"],
|
|
1915
|
+
"https://www.w3.org/ns/activitystreams",
|
|
1916
|
+
);
|
|
1917
|
+
deepStrictEqual(jsonLd2.id, "https://example.com/");
|
|
1918
|
+
const restored2 = await cls.fromJsonLd(jsonLd2, {
|
|
1919
|
+
documentLoader: mockDocumentLoader,
|
|
1920
|
+
contextLoader: mockDocumentLoader,
|
|
1921
|
+
});
|
|
1922
|
+
deepStrictEqual(restored2, instance);
|
|
1923
|
+
|
|
1924
|
+
const expanded = await instance.toJsonLd({
|
|
1925
|
+
contextLoader: mockDocumentLoader,
|
|
1926
|
+
format: "expand",
|
|
1927
|
+
});
|
|
1928
|
+
const restored3 = await cls.fromJsonLd(expanded, {
|
|
1929
|
+
documentLoader: mockDocumentLoader,
|
|
1930
|
+
contextLoader: mockDocumentLoader,
|
|
1931
|
+
});
|
|
1932
|
+
deepStrictEqual(restored3, instance);
|
|
1933
|
+
|
|
1934
|
+
const instance2 = new cls({
|
|
1935
|
+
id: new URL("https://example.com/"),
|
|
1936
|
+
...initValues,
|
|
1937
|
+
...globalThis.Object.fromEntries(
|
|
1938
|
+
allProperties.filter((p) => !areAllScalarTypes(p.range, types)).map(
|
|
1939
|
+
(p) =>
|
|
1940
|
+
p.functional
|
|
1941
|
+
? [p.singularName, new URL("https://example.com/test")]
|
|
1942
|
+
: [p.pluralName, [new URL("https://example.com/test")]],
|
|
1943
|
+
),
|
|
1944
|
+
),
|
|
1945
|
+
});
|
|
1946
|
+
const jsonLd3 = await instance2.toJsonLd({
|
|
1947
|
+
contextLoader: mockDocumentLoader,
|
|
1948
|
+
});
|
|
1949
|
+
const restored4 = await cls.fromJsonLd(jsonLd3, {
|
|
1950
|
+
documentLoader: mockDocumentLoader,
|
|
1951
|
+
contextLoader: mockDocumentLoader,
|
|
1952
|
+
});
|
|
1953
|
+
deepStrictEqual(restored4, instance2);
|
|
1954
|
+
|
|
1955
|
+
rejects(
|
|
1956
|
+
() =>
|
|
1957
|
+
instance.toJsonLd({ context: "https://www.w3.org/ns/activitystreams" }),
|
|
1958
|
+
TypeError,
|
|
1959
|
+
);
|
|
1960
|
+
rejects(
|
|
1961
|
+
() =>
|
|
1962
|
+
instance.toJsonLd({
|
|
1963
|
+
format: "expand",
|
|
1964
|
+
context: "https://www.w3.org/ns/activitystreams",
|
|
1965
|
+
}),
|
|
1966
|
+
TypeError,
|
|
1967
|
+
);
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
if ("Deno" in globalThis) {
|
|
1971
|
+
Deno.test(`Deno.inspect(${type.name}) [auto]`, async (t) => {
|
|
1972
|
+
const empty = new cls({});
|
|
1973
|
+
deepStrictEqual(Deno.inspect(empty), `${type.name} {}`);
|
|
1974
|
+
|
|
1975
|
+
const instance = new cls({
|
|
1976
|
+
id: new URL("https://example.com/"),
|
|
1977
|
+
...initValues,
|
|
1978
|
+
});
|
|
1979
|
+
await assertSnapshot(t, Deno.inspect(instance));
|
|
1980
|
+
|
|
1981
|
+
const instance2 = instance.clone(
|
|
1982
|
+
globalThis.Object.fromEntries(
|
|
1983
|
+
type.properties.filter((p) => !areAllScalarTypes(p.range, types)).map(
|
|
1984
|
+
(p) =>
|
|
1985
|
+
p.functional
|
|
1986
|
+
? [p.singularName, new URL("https://example.com/")]
|
|
1987
|
+
: [p.pluralName, [new URL("https://example.com/")]],
|
|
1988
|
+
),
|
|
1989
|
+
),
|
|
1990
|
+
);
|
|
1991
|
+
await assertSnapshot(t, Deno.inspect(instance2));
|
|
1992
|
+
|
|
1993
|
+
const instance3 = instance.clone(
|
|
1994
|
+
globalThis.Object.fromEntries(
|
|
1995
|
+
type.properties.filter((p) => !p.functional).map(
|
|
1996
|
+
(p) => {
|
|
1997
|
+
ok(!p.functional);
|
|
1998
|
+
return [
|
|
1999
|
+
p.pluralName,
|
|
2000
|
+
[sampleValues[p.range[0]], sampleValues[p.range[0]]],
|
|
2001
|
+
];
|
|
2002
|
+
},
|
|
2003
|
+
),
|
|
2004
|
+
),
|
|
2005
|
+
);
|
|
2006
|
+
// @ts-ignore: t is TestContext in node:test but Deno.TestContext in Deno
|
|
2007
|
+
await assertSnapshot(t, Deno.inspect(instance3));
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
test(`${type.name}.typeId`, () => {
|
|
2012
|
+
deepStrictEqual(cls.typeId, new URL(type.uri));
|
|
2013
|
+
});
|
|
2014
|
+
}
|