@interop/was-conformance-suite 0.1.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.md +20 -0
- package/README.md +264 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +69 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +64 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +294 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/reporters/json.d.ts +32 -0
- package/dist/cli/reporters/json.d.ts.map +1 -0
- package/dist/cli/reporters/json.js +30 -0
- package/dist/cli/reporters/json.js.map +1 -0
- package/dist/cli/reporters/pretty.d.ts +21 -0
- package/dist/cli/reporters/pretty.d.ts.map +1 -0
- package/dist/cli/reporters/pretty.js +152 -0
- package/dist/cli/reporters/pretty.js.map +1 -0
- package/dist/harness/assert.d.ts +13 -0
- package/dist/harness/assert.d.ts.map +1 -0
- package/dist/harness/assert.js +13 -0
- package/dist/harness/assert.js.map +1 -0
- package/dist/harness/runner.d.ts +25 -0
- package/dist/harness/runner.d.ts.map +1 -0
- package/dist/harness/runner.js +240 -0
- package/dist/harness/runner.js.map +1 -0
- package/dist/harness/types.d.ts +199 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/helpers.d.ts +103 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +195 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/suites/changes-query-api.d.ts +9 -0
- package/dist/suites/changes-query-api.d.ts.map +1 -0
- package/dist/suites/changes-query-api.js +115 -0
- package/dist/suites/changes-query-api.js.map +1 -0
- package/dist/suites/client-backends.d.ts +11 -0
- package/dist/suites/client-backends.d.ts.map +1 -0
- package/dist/suites/client-backends.js +198 -0
- package/dist/suites/client-backends.js.map +1 -0
- package/dist/suites/client-delegation.d.ts +21 -0
- package/dist/suites/client-delegation.d.ts.map +1 -0
- package/dist/suites/client-delegation.js +94 -0
- package/dist/suites/client-delegation.js.map +1 -0
- package/dist/suites/client-export-import.d.ts +19 -0
- package/dist/suites/client-export-import.d.ts.map +1 -0
- package/dist/suites/client-export-import.js +74 -0
- package/dist/suites/client-export-import.js.map +1 -0
- package/dist/suites/client-resources.d.ts +11 -0
- package/dist/suites/client-resources.d.ts.map +1 -0
- package/dist/suites/client-resources.js +193 -0
- package/dist/suites/client-resources.js.map +1 -0
- package/dist/suites/client-spaces.d.ts +11 -0
- package/dist/suites/client-spaces.d.ts.map +1 -0
- package/dist/suites/client-spaces.js +347 -0
- package/dist/suites/client-spaces.js.map +1 -0
- package/dist/suites/collection-api.d.ts +7 -0
- package/dist/suites/collection-api.d.ts.map +1 -0
- package/dist/suites/collection-api.js +266 -0
- package/dist/suites/collection-api.js.map +1 -0
- package/dist/suites/encryption-marker-api.d.ts +9 -0
- package/dist/suites/encryption-marker-api.d.ts.map +1 -0
- package/dist/suites/encryption-marker-api.js +266 -0
- package/dist/suites/encryption-marker-api.js.map +1 -0
- package/dist/suites/index.d.ts +23 -0
- package/dist/suites/index.d.ts.map +1 -0
- package/dist/suites/index.js +32 -0
- package/dist/suites/index.js.map +1 -0
- package/dist/suites/policy-api.d.ts +12 -0
- package/dist/suites/policy-api.d.ts.map +1 -0
- package/dist/suites/policy-api.js +187 -0
- package/dist/suites/policy-api.js.map +1 -0
- package/dist/suites/resource-api.d.ts +8 -0
- package/dist/suites/resource-api.d.ts.map +1 -0
- package/dist/suites/resource-api.js +393 -0
- package/dist/suites/resource-api.js.map +1 -0
- package/dist/suites/server.d.ts +3 -0
- package/dist/suites/server.d.ts.map +1 -0
- package/dist/suites/server.js +25 -0
- package/dist/suites/server.js.map +1 -0
- package/dist/suites/spaces-api.d.ts +11 -0
- package/dist/suites/spaces-api.d.ts.map +1 -0
- package/dist/suites/spaces-api.js +411 -0
- package/dist/suites/spaces-api.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* WAS conformance tests -- high-level WasClient: Spaces & Collections.
|
|
6
|
+
*
|
|
7
|
+
* Drives the published `@interop/was-client` against a live server over the
|
|
8
|
+
* HTTP contract, rather than the low-level `ZcapClient` used by the `*-api`
|
|
9
|
+
* suites. This is where the client's own integration coverage lives, so the
|
|
10
|
+
* client repo can stay free of any dependency on this server.
|
|
11
|
+
*/
|
|
12
|
+
import assert from '../harness/assert.js';
|
|
13
|
+
import { NotFoundError } from '@interop/was-client';
|
|
14
|
+
export const clientSpaces = {
|
|
15
|
+
id: 'client-spaces',
|
|
16
|
+
name: 'WasClient — Spaces & Collections',
|
|
17
|
+
setup: async (ctx) => {
|
|
18
|
+
// Shallow-clone the shared actor so per-suite scratch fields do not leak
|
|
19
|
+
// into other suites.
|
|
20
|
+
const alice = { ...ctx.actors.alice };
|
|
21
|
+
const createdSpaces = [];
|
|
22
|
+
// Creates a space via the high-level client and registers it for teardown.
|
|
23
|
+
const newSpace = async (name) => {
|
|
24
|
+
const space = await ctx.provisionSpace({ was: alice.was, name });
|
|
25
|
+
createdSpaces.push(space);
|
|
26
|
+
return space;
|
|
27
|
+
};
|
|
28
|
+
return { alice, createdSpaces, newSpace, space: undefined };
|
|
29
|
+
},
|
|
30
|
+
teardown: async (ctx, state) => {
|
|
31
|
+
for (const space of state.createdSpaces) {
|
|
32
|
+
try {
|
|
33
|
+
await space.delete();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* best-effort cleanup */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
groups: [
|
|
41
|
+
{
|
|
42
|
+
name: 'collections',
|
|
43
|
+
setup: async (ctx, state) => {
|
|
44
|
+
state.space = await state.newSpace('Collections Space');
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'backend & quota',
|
|
49
|
+
setup: async (ctx, state) => {
|
|
50
|
+
state.space = await state.newSpace('Backend & Quota Space');
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'space backends & quotas',
|
|
55
|
+
setup: async (ctx, state) => {
|
|
56
|
+
state.space = await state.newSpace('Backends & Quotas Space');
|
|
57
|
+
const collection = await state.space.createCollection({ id: 'docs' });
|
|
58
|
+
await collection.add({ hello: 'world' });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
tests: [
|
|
63
|
+
{
|
|
64
|
+
id: 'spaces.create-and-read',
|
|
65
|
+
name: 'creates a space and reads it back',
|
|
66
|
+
group: 'spaces',
|
|
67
|
+
run: async (ctx, state) => {
|
|
68
|
+
const { withoutCreatedBy } = ctx;
|
|
69
|
+
const { alice, newSpace } = state;
|
|
70
|
+
const space = await newSpace('Home');
|
|
71
|
+
const description = await space.describe();
|
|
72
|
+
assert.deepStrictEqual(withoutCreatedBy(description), {
|
|
73
|
+
id: space.id,
|
|
74
|
+
type: ['Space'],
|
|
75
|
+
name: 'Home',
|
|
76
|
+
controller: alice.did,
|
|
77
|
+
url: `/space/${space.id}`,
|
|
78
|
+
linkset: `/space/${space.id}/linkset`
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'spaces.describe-missing-null',
|
|
84
|
+
name: 'returns null when describing a missing space (404 conflation)',
|
|
85
|
+
group: 'spaces',
|
|
86
|
+
run: async (ctx, state) => {
|
|
87
|
+
const { alice } = state;
|
|
88
|
+
const missing = await alice.was.space('no-such-space').describe();
|
|
89
|
+
assert.equal(missing, null);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'spaces.delete-idempotent',
|
|
94
|
+
name: 'deletes a space and is idempotent',
|
|
95
|
+
group: 'spaces',
|
|
96
|
+
run: async (ctx, state) => {
|
|
97
|
+
const { newSpace } = state;
|
|
98
|
+
const space = await newSpace('Disposable');
|
|
99
|
+
await space.delete();
|
|
100
|
+
assert.equal(await space.describe(), null);
|
|
101
|
+
// Deleting again must not throw.
|
|
102
|
+
await space.delete();
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'spaces.configure-update',
|
|
107
|
+
name: 'configures (updates) an existing space',
|
|
108
|
+
group: 'spaces',
|
|
109
|
+
run: async (ctx, state) => {
|
|
110
|
+
const { newSpace } = state;
|
|
111
|
+
const space = await newSpace('Original');
|
|
112
|
+
const updated = await space.configure({ name: 'Renamed' });
|
|
113
|
+
assert.equal(updated.name, 'Renamed');
|
|
114
|
+
const reread = await space.describe();
|
|
115
|
+
assert.equal(reread?.name, 'Renamed');
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'spaces.list-includes-created',
|
|
120
|
+
name: 'listSpaces includes a created space',
|
|
121
|
+
group: 'spaces',
|
|
122
|
+
run: async (ctx, state) => {
|
|
123
|
+
const { alice, newSpace } = state;
|
|
124
|
+
// A persistent external server may hold other spaces for Alice from
|
|
125
|
+
// earlier runs, so assert containment rather than exact contents.
|
|
126
|
+
const space = await newSpace('Listed Space');
|
|
127
|
+
const listing = await alice.was.listSpaces();
|
|
128
|
+
assert.equal(listing.url, '/spaces/');
|
|
129
|
+
assert.equal(listing.totalItems, listing.items.length);
|
|
130
|
+
assert.deepStrictEqual(listing.items.find((item) => item.id === space.id), { id: space.id, name: 'Listed Space', url: `/space/${space.id}` });
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'collections.create-and-describe',
|
|
135
|
+
name: 'creates a collection by id and reads its description',
|
|
136
|
+
group: 'collections',
|
|
137
|
+
run: async (ctx, state) => {
|
|
138
|
+
const { withoutCreatedBy } = ctx;
|
|
139
|
+
const { space } = state;
|
|
140
|
+
const collection = await space.createCollection({
|
|
141
|
+
id: 'credentials',
|
|
142
|
+
name: 'Verifiable Credentials'
|
|
143
|
+
});
|
|
144
|
+
assert.equal(collection.id, 'credentials');
|
|
145
|
+
assert.deepStrictEqual(withoutCreatedBy(await collection.describe()), {
|
|
146
|
+
id: 'credentials',
|
|
147
|
+
type: ['Collection'],
|
|
148
|
+
name: 'Verifiable Credentials',
|
|
149
|
+
backend: { id: 'default' },
|
|
150
|
+
url: `/space/${space.id}/credentials`,
|
|
151
|
+
linkset: `/space/${space.id}/credentials/linkset`
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'collections.list-in-space',
|
|
157
|
+
name: 'lists collections in a space',
|
|
158
|
+
group: 'collections',
|
|
159
|
+
run: async (ctx, state) => {
|
|
160
|
+
const { space } = state;
|
|
161
|
+
const listing = await space.collections();
|
|
162
|
+
assert.ok(listing);
|
|
163
|
+
assert.ok(listing.totalItems >= 1);
|
|
164
|
+
assert.ok(listing.items.some(item => item.id === 'credentials'));
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'collections.add-missing-space-not-found',
|
|
169
|
+
name: 'throws NotFoundError adding to a collection in a missing space',
|
|
170
|
+
group: 'collections',
|
|
171
|
+
run: async (ctx, state) => {
|
|
172
|
+
const { alice } = state;
|
|
173
|
+
const orphan = alice.was.space('missing-space').collection('c');
|
|
174
|
+
await assert.rejects(orphan.add({ hello: 'world' }), (err) => err instanceof NotFoundError);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'backend-quota.read-collection-backend',
|
|
179
|
+
name: 'reads the backend a collection is stored on',
|
|
180
|
+
group: 'backend & quota',
|
|
181
|
+
optional: true,
|
|
182
|
+
run: async (ctx, state) => {
|
|
183
|
+
const { space } = state;
|
|
184
|
+
const collection = await space.createCollection({ id: 'backend-probe' });
|
|
185
|
+
const backend = await collection.backend();
|
|
186
|
+
assert.ok(backend);
|
|
187
|
+
// The display name is server-specific (e.g. 'Server Filesystem' or
|
|
188
|
+
// 'Server PostgreSQL'); the suite runs against any conforming server.
|
|
189
|
+
const { name, ...rest } = backend;
|
|
190
|
+
assert.ok(typeof name === 'string' && name.length > 0);
|
|
191
|
+
assert.deepStrictEqual(rest, {
|
|
192
|
+
id: 'default',
|
|
193
|
+
managedBy: 'server',
|
|
194
|
+
storageMode: ['document', 'blob'],
|
|
195
|
+
persistence: 'durable',
|
|
196
|
+
features: [
|
|
197
|
+
'conditional-writes',
|
|
198
|
+
'changes-query',
|
|
199
|
+
'blinded-index-query',
|
|
200
|
+
'key-epochs',
|
|
201
|
+
'chunked-streams'
|
|
202
|
+
]
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'backend-quota.missing-collection-backend-null',
|
|
208
|
+
name: 'returns null reading the backend of a missing collection (404 conflation)',
|
|
209
|
+
group: 'backend & quota',
|
|
210
|
+
optional: true,
|
|
211
|
+
run: async (ctx, state) => {
|
|
212
|
+
const { space } = state;
|
|
213
|
+
const missing = space.collection('no-such-collection');
|
|
214
|
+
assert.equal(await missing.backend(), null);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'backend-quota.read-collection-quota',
|
|
219
|
+
name: "reads a collection's storage quota, scoped to its backend",
|
|
220
|
+
group: 'backend & quota',
|
|
221
|
+
optional: true,
|
|
222
|
+
run: async (ctx, state) => {
|
|
223
|
+
const { space } = state;
|
|
224
|
+
const collection = await space.createCollection({ id: 'quota-probe' });
|
|
225
|
+
await collection.add({ hello: 'world' });
|
|
226
|
+
const usage = await collection.quota();
|
|
227
|
+
assert.ok(usage);
|
|
228
|
+
assert.equal(usage.id, 'default');
|
|
229
|
+
assert.equal(usage.managedBy, 'server');
|
|
230
|
+
assert.equal(usage.state, 'ok');
|
|
231
|
+
assert.ok(usage.usageBytes > 0, 'expected non-zero collection usage');
|
|
232
|
+
// The default filesystem backend has no configured capacity (unlimited).
|
|
233
|
+
assert.deepStrictEqual(usage.limit, { isUnlimited: true });
|
|
234
|
+
assert.deepStrictEqual(usage.restrictedActions, []);
|
|
235
|
+
assert.match(usage.measuredAt, /^\d{4}-\d{2}-\d{2}T/);
|
|
236
|
+
// The per-collection report is the whole report -- no nested breakdown.
|
|
237
|
+
assert.equal(usage.usageByCollection, undefined);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'backend-quota.missing-collection-quota-null',
|
|
242
|
+
name: 'returns null reading the quota of a missing collection (404 conflation)',
|
|
243
|
+
group: 'backend & quota',
|
|
244
|
+
optional: true,
|
|
245
|
+
run: async (ctx, state) => {
|
|
246
|
+
const { space } = state;
|
|
247
|
+
const missing = space.collection('no-such-collection');
|
|
248
|
+
assert.equal(await missing.quota(), null);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'space-backends.list-backends',
|
|
253
|
+
name: 'lists the storage backends available in the space',
|
|
254
|
+
group: 'space backends & quotas',
|
|
255
|
+
optional: true,
|
|
256
|
+
run: async (ctx, state) => {
|
|
257
|
+
const { space } = state;
|
|
258
|
+
const backends = await space.backends();
|
|
259
|
+
assert.ok(backends);
|
|
260
|
+
assert.equal(backends.length, 1);
|
|
261
|
+
// The display name is server-specific (e.g. 'Server Filesystem' or
|
|
262
|
+
// 'Server PostgreSQL'); the suite runs against any conforming server.
|
|
263
|
+
const { name, ...rest } = backends[0];
|
|
264
|
+
assert.ok(typeof name === 'string' && name.length > 0);
|
|
265
|
+
assert.deepStrictEqual(rest, {
|
|
266
|
+
id: 'default',
|
|
267
|
+
managedBy: 'server',
|
|
268
|
+
storageMode: ['document', 'blob'],
|
|
269
|
+
persistence: 'durable',
|
|
270
|
+
features: [
|
|
271
|
+
'conditional-writes',
|
|
272
|
+
'changes-query',
|
|
273
|
+
'blinded-index-query',
|
|
274
|
+
'key-epochs',
|
|
275
|
+
'chunked-streams'
|
|
276
|
+
]
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'space-backends.missing-space-backends-null',
|
|
282
|
+
name: 'returns null listing backends of a missing space (404 conflation)',
|
|
283
|
+
group: 'space backends & quotas',
|
|
284
|
+
optional: true,
|
|
285
|
+
run: async (ctx, state) => {
|
|
286
|
+
const { alice } = state;
|
|
287
|
+
assert.equal(await alice.was.space('no-such-space').backends(), null);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: 'space-backends.read-space-quotas',
|
|
292
|
+
name: 'reads the space storage quota report, grouped by backend',
|
|
293
|
+
group: 'space backends & quotas',
|
|
294
|
+
optional: true,
|
|
295
|
+
run: async (ctx, state) => {
|
|
296
|
+
const { space } = state;
|
|
297
|
+
const report = await space.quotas();
|
|
298
|
+
assert.ok(report);
|
|
299
|
+
assert.match(report.respondedAt, /^\d{4}-\d{2}-\d{2}T/);
|
|
300
|
+
assert.equal(report.backends.length, 1);
|
|
301
|
+
const entry = report.backends[0];
|
|
302
|
+
assert.ok(entry);
|
|
303
|
+
assert.equal(entry.id, 'default');
|
|
304
|
+
// Server-specific display name; just require one.
|
|
305
|
+
assert.ok(typeof entry.name === 'string' && entry.name.length > 0);
|
|
306
|
+
assert.equal(entry.managedBy, 'server');
|
|
307
|
+
assert.equal(entry.state, 'ok');
|
|
308
|
+
assert.ok(entry.usageBytes > 0, 'expected non-zero usage');
|
|
309
|
+
// The default filesystem backend has no configured capacity (unlimited).
|
|
310
|
+
assert.deepStrictEqual(entry.limit, { isUnlimited: true });
|
|
311
|
+
assert.deepStrictEqual(entry.restrictedActions, []);
|
|
312
|
+
assert.match(entry.measuredAt, /^\d{4}-\d{2}-\d{2}T/);
|
|
313
|
+
// The per-Collection breakdown is opt-in (spec `?include=collections`), so
|
|
314
|
+
// a bare report omits it.
|
|
315
|
+
assert.equal(entry.usageByCollection, undefined);
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'space-backends.include-collections-breakdown',
|
|
320
|
+
name: 'reads the per-collection breakdown with includeCollections',
|
|
321
|
+
group: 'space backends & quotas',
|
|
322
|
+
optional: true,
|
|
323
|
+
run: async (ctx, state) => {
|
|
324
|
+
const { space } = state;
|
|
325
|
+
const report = await space.quotas({ includeCollections: true });
|
|
326
|
+
assert.ok(report);
|
|
327
|
+
const entry = report.backends[0];
|
|
328
|
+
assert.ok(entry);
|
|
329
|
+
// With the opt-in, the space-level report carries a per-collection breakdown.
|
|
330
|
+
const breakdown = entry.usageByCollection;
|
|
331
|
+
assert.ok(breakdown, 'expected a usageByCollection breakdown');
|
|
332
|
+
assert.ok(breakdown.some(item => item.id === 'docs'), 'expected the docs collection in the breakdown');
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: 'space-backends.missing-space-quotas-null',
|
|
337
|
+
name: 'returns null reading quotas of a missing space (404 conflation)',
|
|
338
|
+
group: 'space backends & quotas',
|
|
339
|
+
optional: true,
|
|
340
|
+
run: async (ctx, state) => {
|
|
341
|
+
const { alice } = state;
|
|
342
|
+
assert.equal(await alice.was.space('no-such-space').quotas(), null);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
};
|
|
347
|
+
//# sourceMappingURL=client-spaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-spaces.js","sourceRoot":"","sources":["../../src/suites/client-spaces.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection-api.d.ts","sourceRoot":"","sources":["../../src/suites/collection-api.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEhD,UAAU,KAAK;IACb,KAAK,EAAE,GAAG,CAAA;CACX;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,KAAK,CAkTtC,CAAA"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* WAS conformance tests -- Collections API.
|
|
6
|
+
*/
|
|
7
|
+
import assert from '../harness/assert.js';
|
|
8
|
+
export const collectionApi = {
|
|
9
|
+
id: 'collection-api',
|
|
10
|
+
name: 'Collections API',
|
|
11
|
+
setup: async (ctx) => {
|
|
12
|
+
const alice = { ...ctx.actors.alice };
|
|
13
|
+
alice.space1 = { id: ctx.generateId() };
|
|
14
|
+
await ctx.createSpace({
|
|
15
|
+
spaceDescription: {
|
|
16
|
+
id: alice.space1.id,
|
|
17
|
+
name: "Alice's Space #1",
|
|
18
|
+
controller: alice.did
|
|
19
|
+
},
|
|
20
|
+
rootClient: alice.rootClient
|
|
21
|
+
});
|
|
22
|
+
return { alice };
|
|
23
|
+
},
|
|
24
|
+
teardown: async (ctx, state) => {
|
|
25
|
+
const { alice } = state;
|
|
26
|
+
try {
|
|
27
|
+
await alice.rootClient.request({
|
|
28
|
+
url: new URL(`/space/${alice.space1.id}`, ctx.serverUrl).toString(),
|
|
29
|
+
method: 'DELETE'
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* best-effort cleanup */
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
tests: [
|
|
37
|
+
{
|
|
38
|
+
id: 'collection.create-unauthorized-401',
|
|
39
|
+
name: 'POST /space/:spaceId/ should 401 error when no authorization headers',
|
|
40
|
+
run: async (ctx) => {
|
|
41
|
+
const { serverUrl } = ctx;
|
|
42
|
+
const response = await fetch(new URL('/space/any-space-id/', serverUrl), {
|
|
43
|
+
method: 'POST'
|
|
44
|
+
});
|
|
45
|
+
assert.equal(response.status, 401);
|
|
46
|
+
assert.match(response.headers.get('content-type'), /application\/problem\+json/);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'collection.create-missing-space-404',
|
|
51
|
+
name: 'POST /space/:spaceId/ should 404 error on not found space id',
|
|
52
|
+
run: async (ctx, state) => {
|
|
53
|
+
const { serverUrl } = ctx;
|
|
54
|
+
const { alice } = state;
|
|
55
|
+
const spaceUrl = new URL('/space/space-id-that-does-not-exist/', serverUrl).toString();
|
|
56
|
+
let expectedError;
|
|
57
|
+
try {
|
|
58
|
+
await alice.rootClient.request({
|
|
59
|
+
url: spaceUrl,
|
|
60
|
+
method: 'POST',
|
|
61
|
+
action: 'POST'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
expectedError = err;
|
|
66
|
+
}
|
|
67
|
+
assert.equal(expectedError.response.status, 404);
|
|
68
|
+
assert.match(expectedError.response.headers.get('content-type'), /application\/problem\+json/);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'collection.create-post',
|
|
73
|
+
name: '[root] create collection via POST',
|
|
74
|
+
run: async (ctx, state) => {
|
|
75
|
+
const { serverUrl, withoutCreatedBy } = ctx;
|
|
76
|
+
const { alice } = state;
|
|
77
|
+
const body = { id: 'credentials', name: 'Verifiable Credentials' };
|
|
78
|
+
const response = await alice.rootClient.request({
|
|
79
|
+
url: new URL(`/space/${alice.space1.id}/`, serverUrl).toString(),
|
|
80
|
+
method: 'POST',
|
|
81
|
+
action: 'POST',
|
|
82
|
+
json: body
|
|
83
|
+
});
|
|
84
|
+
assert.equal(response.status, 201);
|
|
85
|
+
assert.deepStrictEqual(withoutCreatedBy(response.data), {
|
|
86
|
+
id: 'credentials',
|
|
87
|
+
name: 'Verifiable Credentials',
|
|
88
|
+
type: ['Collection'],
|
|
89
|
+
backend: { id: 'default' }
|
|
90
|
+
});
|
|
91
|
+
assert.match(response.headers.get('content-type'), /application\/json/);
|
|
92
|
+
assert.equal(response.headers.get('location'), `${serverUrl}/space/${alice.space1.id}/${body.id}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'collection.create-post-id-conflict-409',
|
|
97
|
+
name: '[root] POST with an existing collection id yields id-conflict (409)',
|
|
98
|
+
specRefs: ['https://wallet.storage/spec#id-conflict'],
|
|
99
|
+
run: async (ctx, state) => {
|
|
100
|
+
const { serverUrl } = ctx;
|
|
101
|
+
const { alice } = state;
|
|
102
|
+
// 'credentials' was created by the POST test above.
|
|
103
|
+
let expectedError;
|
|
104
|
+
try {
|
|
105
|
+
await alice.rootClient.request({
|
|
106
|
+
url: new URL(`/space/${alice.space1.id}/`, serverUrl).toString(),
|
|
107
|
+
method: 'POST',
|
|
108
|
+
action: 'POST',
|
|
109
|
+
json: { id: 'credentials', name: 'Replacement' }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
expectedError = err;
|
|
114
|
+
}
|
|
115
|
+
assert.ok(expectedError, 'expected the duplicate-id POST to be rejected');
|
|
116
|
+
assert.equal(expectedError.response.status, 409);
|
|
117
|
+
assert.equal(expectedError.data.type, 'https://wallet.storage/spec#id-conflict');
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'collection.list-items',
|
|
122
|
+
name: '[root] list collection items via GET :collectionId/',
|
|
123
|
+
run: async (ctx, state) => {
|
|
124
|
+
const { serverUrl } = ctx;
|
|
125
|
+
const { alice } = state;
|
|
126
|
+
const response = await alice.rootClient.request({
|
|
127
|
+
url: new URL(`/space/${alice.space1.id}/credentials/`, serverUrl).toString(),
|
|
128
|
+
method: 'GET'
|
|
129
|
+
});
|
|
130
|
+
assert.equal(response.status, 200);
|
|
131
|
+
const listResponse = response.data;
|
|
132
|
+
assert.equal(listResponse.id, 'credentials');
|
|
133
|
+
assert.equal(listResponse.url, `/space/${alice.space1.id}/credentials`);
|
|
134
|
+
assert.equal(listResponse.name, 'Verifiable Credentials');
|
|
135
|
+
assert.deepStrictEqual(listResponse.type, ['Collection']);
|
|
136
|
+
assert.equal(typeof listResponse.totalItems, 'number');
|
|
137
|
+
assert.ok(Array.isArray(listResponse.items));
|
|
138
|
+
assert.equal(listResponse.totalItems, listResponse.items.length);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'collection.read-description',
|
|
143
|
+
name: '[root] get collection description via GET :collectionId',
|
|
144
|
+
run: async (ctx, state) => {
|
|
145
|
+
const { serverUrl, withoutCreatedBy } = ctx;
|
|
146
|
+
const { alice } = state;
|
|
147
|
+
const response = await alice.rootClient.request({
|
|
148
|
+
url: new URL(`/space/${alice.space1.id}/credentials`, serverUrl).toString(),
|
|
149
|
+
method: 'GET',
|
|
150
|
+
action: 'GET'
|
|
151
|
+
});
|
|
152
|
+
assert.equal(response.status, 200);
|
|
153
|
+
assert.deepStrictEqual(withoutCreatedBy(response.data), {
|
|
154
|
+
id: 'credentials',
|
|
155
|
+
name: 'Verifiable Credentials',
|
|
156
|
+
type: ['Collection'],
|
|
157
|
+
backend: { id: 'default' },
|
|
158
|
+
url: `/space/${alice.space1.id}/credentials`,
|
|
159
|
+
linkset: `/space/${alice.space1.id}/credentials/linkset`
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'collection.paginate-limit-next',
|
|
165
|
+
name: '[root] paginates List Collection via ?limit and follows next (spec Pagination)',
|
|
166
|
+
run: async (ctx, state) => {
|
|
167
|
+
const { serverUrl, generateId } = ctx;
|
|
168
|
+
const { alice } = state;
|
|
169
|
+
// Fresh Collection seeded with > one page of Resources, inserted out of order
|
|
170
|
+
// to prove the listing order is by id, not insertion.
|
|
171
|
+
const collectionId = generateId();
|
|
172
|
+
// WAS does not auto-create parent Collections, so provision it first.
|
|
173
|
+
await alice.rootClient.request({
|
|
174
|
+
url: new URL(`/space/${alice.space1.id}/${collectionId}`, serverUrl).toString(),
|
|
175
|
+
method: 'PUT',
|
|
176
|
+
json: { id: collectionId, name: 'Paginated Collection' }
|
|
177
|
+
});
|
|
178
|
+
const ids = ['g05', 'g01', 'g04', 'g02', 'g00', 'g03'];
|
|
179
|
+
for (const id of ids) {
|
|
180
|
+
await alice.rootClient.request({
|
|
181
|
+
url: new URL(`/space/${alice.space1.id}/${collectionId}/${id}`, serverUrl).toString(),
|
|
182
|
+
method: 'PUT',
|
|
183
|
+
json: { value: id }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const seen = [];
|
|
187
|
+
let nextUrl = new URL(`/space/${alice.space1.id}/${collectionId}/?limit=2`, serverUrl).toString();
|
|
188
|
+
let pages = 0;
|
|
189
|
+
while (nextUrl) {
|
|
190
|
+
const response = await alice.rootClient.request({
|
|
191
|
+
url: nextUrl,
|
|
192
|
+
method: 'GET'
|
|
193
|
+
});
|
|
194
|
+
assert.equal(response.status, 200);
|
|
195
|
+
pages++;
|
|
196
|
+
assert.ok(response.data.items.length <= 2, 'page respects the limit');
|
|
197
|
+
seen.push(...response.data.items.map((item) => item.id));
|
|
198
|
+
// `next` is server-relative; follow it verbatim, resolved against serverUrl.
|
|
199
|
+
nextUrl = response.data.next
|
|
200
|
+
? new URL(response.data.next, serverUrl).toString()
|
|
201
|
+
: undefined;
|
|
202
|
+
}
|
|
203
|
+
// 6 items at limit 2 -> 3 pages; the last omits `next` (end-of-list signal).
|
|
204
|
+
assert.equal(pages, 3);
|
|
205
|
+
assert.deepStrictEqual(seen, ['g00', 'g01', 'g02', 'g03', 'g04', 'g05']);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'collection.malformed-cursor-400',
|
|
210
|
+
name: '[root] a malformed cursor yields invalid-cursor (400)',
|
|
211
|
+
specRefs: ['https://wallet.storage/spec#invalid-cursor'],
|
|
212
|
+
run: async (ctx, state) => {
|
|
213
|
+
const { serverUrl } = ctx;
|
|
214
|
+
const { alice } = state;
|
|
215
|
+
let expectedError;
|
|
216
|
+
try {
|
|
217
|
+
await alice.rootClient.request({
|
|
218
|
+
url: new URL(`/space/${alice.space1.id}/credentials/?cursor=not-valid-%%%`, serverUrl).toString(),
|
|
219
|
+
method: 'GET'
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
expectedError = err;
|
|
224
|
+
}
|
|
225
|
+
assert.ok(expectedError, 'expected the malformed cursor to be rejected');
|
|
226
|
+
assert.equal(expectedError.response.status, 400);
|
|
227
|
+
assert.equal(expectedError.data.type, 'https://wallet.storage/spec#invalid-cursor');
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'collection.create-delete-by-id',
|
|
232
|
+
name: '[root] create and delete a collection by id',
|
|
233
|
+
run: async (ctx, state) => {
|
|
234
|
+
const { serverUrl } = ctx;
|
|
235
|
+
const { alice } = state;
|
|
236
|
+
const collectionId = 'new-collection';
|
|
237
|
+
const collectionUrl = new URL(`/space/${alice.space1.id}/${collectionId}`, serverUrl).toString();
|
|
238
|
+
const body = { id: collectionId, name: 'New Collection' };
|
|
239
|
+
await alice.rootClient.request({
|
|
240
|
+
url: collectionUrl,
|
|
241
|
+
method: 'PUT',
|
|
242
|
+
json: body
|
|
243
|
+
});
|
|
244
|
+
const existResponse = await alice.rootClient.request({
|
|
245
|
+
url: collectionUrl,
|
|
246
|
+
method: 'GET'
|
|
247
|
+
});
|
|
248
|
+
assert.equal(existResponse.status, 200);
|
|
249
|
+
const deleteResponse = await alice.rootClient.request({
|
|
250
|
+
url: collectionUrl,
|
|
251
|
+
method: 'DELETE'
|
|
252
|
+
});
|
|
253
|
+
assert.equal(deleteResponse.status, 204);
|
|
254
|
+
let checkResponse;
|
|
255
|
+
try {
|
|
256
|
+
await alice.rootClient.request({ url: collectionUrl, method: 'GET' });
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
checkResponse = err.response;
|
|
260
|
+
}
|
|
261
|
+
assert.equal(checkResponse.status, 404);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
};
|
|
266
|
+
//# sourceMappingURL=collection-api.js.map
|