@interop/was-conformance-suite 0.15.0 → 0.16.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/README.md +1 -0
- package/dist/suites/authz-ordering-api.d.ts.map +1 -1
- package/dist/suites/authz-ordering-api.js +1 -26
- package/dist/suites/authz-ordering-api.js.map +1 -1
- package/dist/suites/blinded-index-api.js +1 -1
- package/dist/suites/blinded-index-api.js.map +1 -1
- package/dist/suites/changes-query-api.js +1 -1
- package/dist/suites/changes-query-api.js.map +1 -1
- package/dist/suites/chunks-api.js +1 -1
- package/dist/suites/chunks-api.js.map +1 -1
- package/dist/suites/client-spaces.d.ts.map +1 -1
- package/dist/suites/client-spaces.js +19 -6
- package/dist/suites/client-spaces.js.map +1 -1
- package/dist/suites/collection-api.d.ts.map +1 -1
- package/dist/suites/collection-api.js +317 -126
- package/dist/suites/collection-api.js.map +1 -1
- package/dist/suites/conditional-requests-api.d.ts.map +1 -1
- package/dist/suites/conditional-requests-api.js +122 -30
- package/dist/suites/conditional-requests-api.js.map +1 -1
- package/dist/suites/delegation-cryptosuites-api.js +1 -1
- package/dist/suites/delegation-cryptosuites-api.js.map +1 -1
- package/dist/suites/denial-reasons-api.d.ts.map +1 -1
- package/dist/suites/denial-reasons-api.js +5 -2
- package/dist/suites/denial-reasons-api.js.map +1 -1
- package/dist/suites/digest-api.js +1 -1
- package/dist/suites/digest-api.js.map +1 -1
- package/dist/suites/encryption-descriptor-api.d.ts.map +1 -1
- package/dist/suites/encryption-descriptor-api.js +48 -51
- package/dist/suites/encryption-descriptor-api.js.map +1 -1
- package/dist/suites/governed-log-api.d.ts.map +1 -1
- package/dist/suites/governed-log-api.js +18 -17
- package/dist/suites/governed-log-api.js.map +1 -1
- package/dist/suites/index.d.ts +2 -1
- package/dist/suites/index.d.ts.map +1 -1
- package/dist/suites/index.js +3 -1
- package/dist/suites/index.js.map +1 -1
- package/dist/suites/invocation-target-api.js +1 -1
- package/dist/suites/invocation-target-api.js.map +1 -1
- package/dist/suites/plaintext-declaration-api.d.ts.map +1 -1
- package/dist/suites/plaintext-declaration-api.js +24 -15
- package/dist/suites/plaintext-declaration-api.js.map +1 -1
- package/dist/suites/policy-api.js +1 -1
- package/dist/suites/policy-api.js.map +1 -1
- package/dist/suites/reserved-methods-api.d.ts +9 -0
- package/dist/suites/reserved-methods-api.d.ts.map +1 -0
- package/dist/suites/reserved-methods-api.js +549 -0
- package/dist/suites/reserved-methods-api.js.map +1 -0
- package/dist/suites/resource-api.js +1 -1
- package/dist/suites/resource-api.js.map +1 -1
- package/dist/suites/spaces-api.d.ts.map +1 -1
- package/dist/suites/spaces-api.js +193 -63
- package/dist/suites/spaces-api.js.map +1 -1
- package/dist/suites/write-validation-api.d.ts.map +1 -1
- package/dist/suites/write-validation-api.js +16 -36
- package/dist/suites/write-validation-api.js.map +1 -1
- package/package.json +3 -3
|
@@ -100,6 +100,29 @@ function failureDetail(problem) {
|
|
|
100
100
|
.filter(value => typeof value === 'string' && value.length > 0)
|
|
101
101
|
.join('\n');
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* The Space container URL, in its v0.5 canonical (trailing-slash) form: lists
|
|
105
|
+
* Collections (`GET`), adds one (`POST`), and removes the Space (`DELETE`).
|
|
106
|
+
*
|
|
107
|
+
* @param serverUrl {string}
|
|
108
|
+
* @param spaceId {string}
|
|
109
|
+
* @returns {string}
|
|
110
|
+
*/
|
|
111
|
+
function spaceUrl(serverUrl, spaceId) {
|
|
112
|
+
return new URL(`/space/${spaceId}/`, serverUrl).toString();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The Space Metadata object URL (spec "Space Metadata Data Model"): where
|
|
116
|
+
* Read Space and Update (or Create by Id) Space now live, since v0.5 moved
|
|
117
|
+
* them off the bare (no-slash) Space URL.
|
|
118
|
+
*
|
|
119
|
+
* @param serverUrl {string}
|
|
120
|
+
* @param spaceId {string}
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
function spaceMetaUrl(serverUrl, spaceId) {
|
|
124
|
+
return new URL(`/space/${spaceId}/meta`, serverUrl).toString();
|
|
125
|
+
}
|
|
103
126
|
/**
|
|
104
127
|
* Asserts the Space at `spaceId` was never created: its named controller
|
|
105
128
|
* (Alice) could read it if it had been, so her authorized read must 404.
|
|
@@ -114,7 +137,7 @@ async function assertSpaceNotCreated({ ctx, alice, spaceId }) {
|
|
|
114
137
|
let checkError;
|
|
115
138
|
try {
|
|
116
139
|
await alice.rootClient.request({
|
|
117
|
-
url:
|
|
140
|
+
url: spaceMetaUrl(ctx.serverUrl, spaceId),
|
|
118
141
|
method: 'GET'
|
|
119
142
|
});
|
|
120
143
|
}
|
|
@@ -153,7 +176,7 @@ export const spacesApi = {
|
|
|
153
176
|
for (const spaceId of [alice.space1.id, alice.space2.id, alice.space3.id]) {
|
|
154
177
|
try {
|
|
155
178
|
await alice.rootClient.request({
|
|
156
|
-
url:
|
|
179
|
+
url: spaceUrl(ctx.serverUrl, spaceId),
|
|
157
180
|
method: 'DELETE'
|
|
158
181
|
});
|
|
159
182
|
}
|
|
@@ -178,7 +201,7 @@ export const spacesApi = {
|
|
|
178
201
|
rootClient: alice.rootClient
|
|
179
202
|
});
|
|
180
203
|
await alice.rootClient.request({
|
|
181
|
-
url: new URL(`/space/${alice.space3.id}/${state.collectionId}`, ctx.serverUrl).toString(),
|
|
204
|
+
url: new URL(`/space/${alice.space3.id}/${state.collectionId}/meta`, ctx.serverUrl).toString(),
|
|
182
205
|
method: 'PUT',
|
|
183
206
|
json: { id: state.collectionId, name: 'Test Collection' }
|
|
184
207
|
});
|
|
@@ -239,12 +262,14 @@ export const spacesApi = {
|
|
|
239
262
|
assert.equal(listing.totalItems, listing.items.length);
|
|
240
263
|
const aliceItem = listing.items.find((item) => item.id === alice.space1.id);
|
|
241
264
|
assert.ok(aliceItem, "Alice's listing includes her pre-created space");
|
|
242
|
-
|
|
265
|
+
// A Space is a container, so its listed `url` carries the
|
|
266
|
+
// canonical trailing slash (spec "Space Metadata Data Model").
|
|
267
|
+
assert.equal(aliceItem.url, `/space/${alice.space1.id}/`);
|
|
243
268
|
assert.ok(!listing.items.some((item) => item.id === bobSpaceId), "Alice's listing must not reveal Bob's space");
|
|
244
269
|
}
|
|
245
270
|
finally {
|
|
246
271
|
await bob.rootClient.request({
|
|
247
|
-
url:
|
|
272
|
+
url: spaceUrl(serverUrl, bobSpaceId),
|
|
248
273
|
method: 'DELETE'
|
|
249
274
|
});
|
|
250
275
|
}
|
|
@@ -344,7 +369,7 @@ export const spacesApi = {
|
|
|
344
369
|
let checkError;
|
|
345
370
|
try {
|
|
346
371
|
await alice.rootClient.request({
|
|
347
|
-
url:
|
|
372
|
+
url: spaceMetaUrl(serverUrl, spaceId),
|
|
348
373
|
method: 'GET'
|
|
349
374
|
});
|
|
350
375
|
}
|
|
@@ -499,7 +524,6 @@ export const spacesApi = {
|
|
|
499
524
|
controller: aliceDelegatedApp.did
|
|
500
525
|
});
|
|
501
526
|
const spaceId = generateId();
|
|
502
|
-
const spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
|
|
503
527
|
try {
|
|
504
528
|
const response = await aliceAppClient.request({
|
|
505
529
|
url: spacesUrl,
|
|
@@ -516,7 +540,7 @@ export const spacesApi = {
|
|
|
516
540
|
assert.equal(response.data.controller, alice.did);
|
|
517
541
|
// The Space exists and is controlled by Alice: her root key reads it.
|
|
518
542
|
const checkResponse = await alice.rootClient.request({
|
|
519
|
-
url:
|
|
543
|
+
url: spaceMetaUrl(serverUrl, spaceId),
|
|
520
544
|
method: 'GET',
|
|
521
545
|
action: 'GET'
|
|
522
546
|
});
|
|
@@ -525,7 +549,10 @@ export const spacesApi = {
|
|
|
525
549
|
}
|
|
526
550
|
finally {
|
|
527
551
|
try {
|
|
528
|
-
await alice.rootClient.request({
|
|
552
|
+
await alice.rootClient.request({
|
|
553
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
554
|
+
method: 'DELETE'
|
|
555
|
+
});
|
|
529
556
|
}
|
|
530
557
|
catch {
|
|
531
558
|
/* best-effort cleanup */
|
|
@@ -686,17 +713,20 @@ export const spacesApi = {
|
|
|
686
713
|
rootClient: alice.rootClient
|
|
687
714
|
});
|
|
688
715
|
assert.equal(response.status, 201);
|
|
716
|
+
// The container `url` is the canonical trailing-slash form (spec
|
|
717
|
+
// "Space Metadata Data Model").
|
|
689
718
|
assert.deepStrictEqual(withoutCreatedBy(response.data), {
|
|
690
719
|
id: freshSpaceId,
|
|
691
720
|
name: 'Conformance Test Space',
|
|
692
721
|
type: ['Space'],
|
|
693
|
-
controller: alice.did
|
|
722
|
+
controller: alice.did,
|
|
723
|
+
url: `/space/${freshSpaceId}/`
|
|
694
724
|
});
|
|
695
725
|
assert.match(response.headers.get('content-type'), /application\/json/);
|
|
696
|
-
assert.equal(response.headers.get('location'),
|
|
726
|
+
assert.equal(response.headers.get('location'), spaceUrl(serverUrl, freshSpaceId));
|
|
697
727
|
// Clean up the space created by this test
|
|
698
728
|
await alice.rootClient.request({
|
|
699
|
-
url:
|
|
729
|
+
url: spaceUrl(serverUrl, freshSpaceId),
|
|
700
730
|
method: 'DELETE'
|
|
701
731
|
});
|
|
702
732
|
}
|
|
@@ -735,7 +765,7 @@ export const spacesApi = {
|
|
|
735
765
|
},
|
|
736
766
|
{
|
|
737
767
|
id: 'space.create-put',
|
|
738
|
-
name: '[root] create space by id via PUT',
|
|
768
|
+
name: '[root] create space by id via PUT of its Space Metadata object',
|
|
739
769
|
group: 'Space API',
|
|
740
770
|
specRefs: [
|
|
741
771
|
'https://wallet.storage/spec#update-or-create-by-id-space-operation'
|
|
@@ -748,15 +778,16 @@ export const spacesApi = {
|
|
|
748
778
|
name: "Alice's Space #2 (School)",
|
|
749
779
|
controller: alice.did
|
|
750
780
|
};
|
|
751
|
-
|
|
781
|
+
// The `Location` names the Space container, not the Metadata object
|
|
782
|
+
// that was written (spec "Update (or Create by Id) Space Operation").
|
|
752
783
|
const response = await alice.rootClient.request({
|
|
753
|
-
url:
|
|
784
|
+
url: spaceMetaUrl(serverUrl, alice.space2.id),
|
|
754
785
|
method: 'PUT',
|
|
755
786
|
json: spaceDescription
|
|
756
787
|
});
|
|
757
|
-
assert.equal(response.headers.get('location'), spaceUrl);
|
|
788
|
+
assert.equal(response.headers.get('location'), spaceUrl(serverUrl, alice.space2.id));
|
|
758
789
|
const checkResponse = await alice.rootClient.request({
|
|
759
|
-
url:
|
|
790
|
+
url: spaceMetaUrl(serverUrl, alice.space2.id),
|
|
760
791
|
method: 'GET'
|
|
761
792
|
});
|
|
762
793
|
assert.equal(checkResponse.status, 200);
|
|
@@ -787,12 +818,12 @@ export const spacesApi = {
|
|
|
787
818
|
},
|
|
788
819
|
rootClient: alice.rootClient
|
|
789
820
|
});
|
|
790
|
-
const
|
|
821
|
+
const metaUrl = spaceMetaUrl(serverUrl, spaceId);
|
|
791
822
|
try {
|
|
792
823
|
let expectedError;
|
|
793
824
|
try {
|
|
794
825
|
await bob.rootClient.request({
|
|
795
|
-
url:
|
|
826
|
+
url: metaUrl,
|
|
796
827
|
method: 'PUT',
|
|
797
828
|
action: 'PUT',
|
|
798
829
|
json: {
|
|
@@ -812,7 +843,7 @@ export const spacesApi = {
|
|
|
812
843
|
// The operation must not have been performed: Alice still controls
|
|
813
844
|
// the Space, name and controller unchanged.
|
|
814
845
|
const checkResponse = await alice.rootClient.request({
|
|
815
|
-
url:
|
|
846
|
+
url: metaUrl,
|
|
816
847
|
method: 'GET'
|
|
817
848
|
});
|
|
818
849
|
assert.equal(checkResponse.status, 200);
|
|
@@ -821,7 +852,10 @@ export const spacesApi = {
|
|
|
821
852
|
}
|
|
822
853
|
finally {
|
|
823
854
|
try {
|
|
824
|
-
await alice.rootClient.request({
|
|
855
|
+
await alice.rootClient.request({
|
|
856
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
857
|
+
method: 'DELETE'
|
|
858
|
+
});
|
|
825
859
|
}
|
|
826
860
|
catch {
|
|
827
861
|
/* best-effort cleanup */
|
|
@@ -857,12 +891,12 @@ export const spacesApi = {
|
|
|
857
891
|
},
|
|
858
892
|
rootClient: alice.rootClient
|
|
859
893
|
});
|
|
860
|
-
const
|
|
894
|
+
const metaUrl = spaceMetaUrl(serverUrl, spaceId);
|
|
861
895
|
try {
|
|
862
896
|
let expectedError;
|
|
863
897
|
try {
|
|
864
898
|
await alice.rootClient.request({
|
|
865
|
-
url:
|
|
899
|
+
url: metaUrl,
|
|
866
900
|
method: 'PUT',
|
|
867
901
|
action: 'PUT',
|
|
868
902
|
json: {
|
|
@@ -880,7 +914,7 @@ export const spacesApi = {
|
|
|
880
914
|
assert.match(expectedError.response.headers.get('content-type'), /application\/problem\+json/);
|
|
881
915
|
assert.equal(expectedError.data.type, 'https://wallet.storage/spec#invalid-request-body');
|
|
882
916
|
const checkResponse = await alice.rootClient.request({
|
|
883
|
-
url:
|
|
917
|
+
url: metaUrl,
|
|
884
918
|
method: 'GET'
|
|
885
919
|
});
|
|
886
920
|
assert.equal(checkResponse.status, 200);
|
|
@@ -888,7 +922,10 @@ export const spacesApi = {
|
|
|
888
922
|
}
|
|
889
923
|
finally {
|
|
890
924
|
try {
|
|
891
|
-
await alice.rootClient.request({
|
|
925
|
+
await alice.rootClient.request({
|
|
926
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
927
|
+
method: 'DELETE'
|
|
928
|
+
});
|
|
892
929
|
}
|
|
893
930
|
catch {
|
|
894
931
|
/* best-effort cleanup */
|
|
@@ -898,7 +935,7 @@ export const spacesApi = {
|
|
|
898
935
|
},
|
|
899
936
|
{
|
|
900
937
|
id: 'space.anonymous-read-404',
|
|
901
|
-
name: 'GET
|
|
938
|
+
name: 'GET /space/:spaceId/meta with no auth headers falls through to policy and 404s (no public policy)',
|
|
902
939
|
group: 'Space API',
|
|
903
940
|
specRefs: [
|
|
904
941
|
'https://wallet.storage/spec#read-space-operation',
|
|
@@ -909,15 +946,74 @@ export const spacesApi = {
|
|
|
909
946
|
const { alice } = state;
|
|
910
947
|
// Reads no longer 401 at the hook: an anonymous read is allowed to
|
|
911
948
|
// attempt, and is denied as 404 (no-leak) when no policy grants it.
|
|
912
|
-
const
|
|
913
|
-
|
|
949
|
+
const response = await fetch(spaceMetaUrl(serverUrl, alice.space1.id), {
|
|
950
|
+
method: 'GET'
|
|
951
|
+
});
|
|
914
952
|
assert.equal(response.status, 404);
|
|
915
953
|
assert.match(response.headers.get('content-type'), /application\/problem\+json/);
|
|
916
954
|
}
|
|
917
955
|
},
|
|
956
|
+
{
|
|
957
|
+
id: 'space.noncanonical-redirect-308',
|
|
958
|
+
name: '[root] the non-canonical Space URL 308s to the canonical trailing-slash form; the canonical form is not redirected',
|
|
959
|
+
group: 'Space API',
|
|
960
|
+
optional: true,
|
|
961
|
+
specRefs: ['https://wallet.storage/spec#reading-this-document'],
|
|
962
|
+
run: async (ctx, state) => {
|
|
963
|
+
const { serverUrl } = ctx;
|
|
964
|
+
const { alice } = state;
|
|
965
|
+
const bareUrl = new URL(`/space/${alice.space1.id}`, serverUrl).toString();
|
|
966
|
+
const canonicalUrl = spaceUrl(serverUrl, alice.space1.id);
|
|
967
|
+
const canonicalPath = `/space/${alice.space1.id}/`;
|
|
968
|
+
// Asserted for more than one method, so a server that serves both
|
|
969
|
+
// forms without redirecting does not pass by accident.
|
|
970
|
+
for (const method of ['GET', 'HEAD']) {
|
|
971
|
+
const response = await fetch(bareUrl, { method, redirect: 'manual' });
|
|
972
|
+
assert.equal(response.status, 308, `expected a 308 for ${method} on the non-canonical Space URL`);
|
|
973
|
+
assert.equal(response.headers.get('location'), canonicalPath);
|
|
974
|
+
}
|
|
975
|
+
// The canonical form itself is not redirected.
|
|
976
|
+
const canonicalResponse = await fetch(canonicalUrl, {
|
|
977
|
+
method: 'GET',
|
|
978
|
+
redirect: 'manual'
|
|
979
|
+
});
|
|
980
|
+
assert.notEqual(canonicalResponse.status, 308);
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
id: 'space.put-container-405',
|
|
985
|
+
name: '[root] PUT /space/:spaceId/ (the container URL) is refused with 405 and an Allow header that excludes PUT',
|
|
986
|
+
group: 'Space API',
|
|
987
|
+
specRefs: ['https://wallet.storage/spec#space-metadata-data-model'],
|
|
988
|
+
run: async (ctx, state) => {
|
|
989
|
+
const { serverUrl } = ctx;
|
|
990
|
+
const { alice } = state;
|
|
991
|
+
let expectedError;
|
|
992
|
+
try {
|
|
993
|
+
await alice.rootClient.request({
|
|
994
|
+
url: spaceUrl(serverUrl, alice.space1.id),
|
|
995
|
+
method: 'PUT',
|
|
996
|
+
action: 'PUT',
|
|
997
|
+
json: { name: 'Attempted Container Replacement' }
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
expectedError = err;
|
|
1002
|
+
}
|
|
1003
|
+
assert.ok(expectedError, 'expected a PUT at the Space container URL to be refused');
|
|
1004
|
+
assert.equal(expectedError.response.status, 405);
|
|
1005
|
+
const allow = (expectedError.response.headers.get('allow') ?? '')
|
|
1006
|
+
.split(',')
|
|
1007
|
+
.map((method) => method.trim())
|
|
1008
|
+
.filter(Boolean);
|
|
1009
|
+
assert.ok(allow.length > 0, 'expected a non-empty Allow header');
|
|
1010
|
+
assert.ok(!allow.includes('PUT'), 'the Allow header must not include PUT: a container is described ' +
|
|
1011
|
+
'at its "meta" sub-resource');
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
918
1014
|
{
|
|
919
1015
|
id: 'space.read-missing-404',
|
|
920
|
-
name: 'GET /space/:spaceId should 404 error on not found space id',
|
|
1016
|
+
name: 'GET /space/:spaceId/meta should 404 error on not found space id',
|
|
921
1017
|
group: 'Space API',
|
|
922
1018
|
specRefs: [
|
|
923
1019
|
'https://wallet.storage/spec#read-space-operation',
|
|
@@ -926,11 +1022,10 @@ export const spacesApi = {
|
|
|
926
1022
|
run: async (ctx, state) => {
|
|
927
1023
|
const { serverUrl } = ctx;
|
|
928
1024
|
const { alice } = state;
|
|
929
|
-
const spaceUrl = new URL('/space/space-id-that-does-not-exist', serverUrl).toString();
|
|
930
1025
|
let expectedError;
|
|
931
1026
|
try {
|
|
932
1027
|
await alice.rootClient.request({
|
|
933
|
-
url:
|
|
1028
|
+
url: spaceMetaUrl(serverUrl, 'space-id-that-does-not-exist'),
|
|
934
1029
|
method: 'GET',
|
|
935
1030
|
action: 'GET'
|
|
936
1031
|
});
|
|
@@ -944,15 +1039,14 @@ export const spacesApi = {
|
|
|
944
1039
|
},
|
|
945
1040
|
{
|
|
946
1041
|
id: 'space.read-authorized',
|
|
947
|
-
name: '[root] read
|
|
1042
|
+
name: '[root] read the Space Metadata object via GET with proper authorization',
|
|
948
1043
|
group: 'Space API',
|
|
949
1044
|
specRefs: ['https://wallet.storage/spec#read-space-operation'],
|
|
950
1045
|
run: async (ctx, state) => {
|
|
951
1046
|
const { serverUrl, withoutCreatedBy } = ctx;
|
|
952
1047
|
const { alice } = state;
|
|
953
|
-
const spaceUrl = new URL(`/space/${alice.space1.id}`, serverUrl).toString();
|
|
954
1048
|
const response = await alice.rootClient.request({
|
|
955
|
-
url:
|
|
1049
|
+
url: spaceMetaUrl(serverUrl, alice.space1.id),
|
|
956
1050
|
method: 'GET',
|
|
957
1051
|
action: 'GET'
|
|
958
1052
|
});
|
|
@@ -963,14 +1057,14 @@ export const spacesApi = {
|
|
|
963
1057
|
name: "Alice's Space #1 (Home)",
|
|
964
1058
|
type: ['Space'],
|
|
965
1059
|
controller: alice.did,
|
|
966
|
-
url: `/space/${alice.space1.id}
|
|
1060
|
+
url: `/space/${alice.space1.id}/`,
|
|
967
1061
|
linkset: `/space/${alice.space1.id}/linkset`
|
|
968
1062
|
});
|
|
969
1063
|
}
|
|
970
1064
|
},
|
|
971
1065
|
{
|
|
972
1066
|
id: 'space.read-delegated',
|
|
973
|
-
name: '[delegated] authorized app should GET /space/:spaceId',
|
|
1067
|
+
name: '[delegated] authorized app should GET /space/:spaceId/meta',
|
|
974
1068
|
group: 'Space API',
|
|
975
1069
|
specRefs: [
|
|
976
1070
|
'https://wallet.storage/spec#read-space-operation',
|
|
@@ -980,14 +1074,16 @@ export const spacesApi = {
|
|
|
980
1074
|
const { serverUrl, zcapClient, withoutCreatedBy } = ctx;
|
|
981
1075
|
const { alice, aliceDelegatedApp } = state;
|
|
982
1076
|
const aliceAppClient = zcapClient({ signer: aliceDelegatedApp.signer });
|
|
983
|
-
|
|
1077
|
+
// A capability delegated on the Space container covers its `meta`
|
|
1078
|
+
// sub-resource too (spec "Reading This Document"; ARCHITECTURE.md's
|
|
1079
|
+
// v0.5 subtree-attenuation note).
|
|
984
1080
|
const delegatedSpaceCapability = await alice.rootClient.delegate({
|
|
985
1081
|
allowedActions: ['GET'],
|
|
986
|
-
invocationTarget: spaceUrl,
|
|
1082
|
+
invocationTarget: spaceUrl(serverUrl, alice.space1.id),
|
|
987
1083
|
controller: aliceDelegatedApp.did
|
|
988
1084
|
});
|
|
989
1085
|
const appResponse = await aliceAppClient.request({
|
|
990
|
-
url:
|
|
1086
|
+
url: spaceMetaUrl(serverUrl, alice.space1.id),
|
|
991
1087
|
capability: delegatedSpaceCapability,
|
|
992
1088
|
method: 'GET',
|
|
993
1089
|
action: 'GET'
|
|
@@ -999,14 +1095,14 @@ export const spacesApi = {
|
|
|
999
1095
|
name: "Alice's Space #1 (Home)",
|
|
1000
1096
|
type: ['Space'],
|
|
1001
1097
|
controller: alice.did,
|
|
1002
|
-
url: `/space/${alice.space1.id}
|
|
1098
|
+
url: `/space/${alice.space1.id}/`,
|
|
1003
1099
|
linkset: `/space/${alice.space1.id}/linkset`
|
|
1004
1100
|
});
|
|
1005
1101
|
}
|
|
1006
1102
|
},
|
|
1007
1103
|
{
|
|
1008
1104
|
id: 'space.cross-user-read-404',
|
|
1009
|
-
name:
|
|
1105
|
+
name: "[root] Bob should not be able to GET Alice's Space Metadata object",
|
|
1010
1106
|
group: 'Space API',
|
|
1011
1107
|
specRefs: [
|
|
1012
1108
|
'https://wallet.storage/spec#read-space-operation',
|
|
@@ -1015,10 +1111,13 @@ export const spacesApi = {
|
|
|
1015
1111
|
run: async (ctx, state) => {
|
|
1016
1112
|
const { serverUrl } = ctx;
|
|
1017
1113
|
const { alice, bob } = state;
|
|
1018
|
-
const spaceUrl = new URL(`/space/${alice.space1.id}`, serverUrl).toString();
|
|
1019
1114
|
let expectedError;
|
|
1020
1115
|
try {
|
|
1021
|
-
await bob.rootClient.request({
|
|
1116
|
+
await bob.rootClient.request({
|
|
1117
|
+
url: spaceMetaUrl(serverUrl, alice.space1.id),
|
|
1118
|
+
method: 'GET',
|
|
1119
|
+
action: 'GET'
|
|
1120
|
+
});
|
|
1022
1121
|
}
|
|
1023
1122
|
catch (err) {
|
|
1024
1123
|
expectedError = err;
|
|
@@ -1045,15 +1144,17 @@ export const spacesApi = {
|
|
|
1045
1144
|
},
|
|
1046
1145
|
rootClient: alice.rootClient
|
|
1047
1146
|
});
|
|
1048
|
-
const spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
|
|
1049
1147
|
const deleteResponse = await alice.rootClient.request({
|
|
1050
|
-
url: spaceUrl,
|
|
1148
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
1051
1149
|
method: 'DELETE'
|
|
1052
1150
|
});
|
|
1053
1151
|
assert.equal(deleteResponse.status, 204);
|
|
1054
1152
|
let checkResponse;
|
|
1055
1153
|
try {
|
|
1056
|
-
await alice.rootClient.request({
|
|
1154
|
+
await alice.rootClient.request({
|
|
1155
|
+
url: spaceMetaUrl(serverUrl, spaceId),
|
|
1156
|
+
method: 'GET'
|
|
1157
|
+
});
|
|
1057
1158
|
}
|
|
1058
1159
|
catch (err) {
|
|
1059
1160
|
checkResponse = err.response;
|
|
@@ -1073,7 +1174,6 @@ export const spacesApi = {
|
|
|
1073
1174
|
const { serverUrl, createSpace, generateId } = ctx;
|
|
1074
1175
|
const { alice, bob } = state;
|
|
1075
1176
|
const spaceId = generateId();
|
|
1076
|
-
const spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
|
|
1077
1177
|
await createSpace({
|
|
1078
1178
|
spaceDescription: {
|
|
1079
1179
|
id: spaceId,
|
|
@@ -1110,7 +1210,7 @@ export const spacesApi = {
|
|
|
1110
1210
|
// The original Space is untouched: its name and controller are as
|
|
1111
1211
|
// first created, not the conflicting POST's proposed values.
|
|
1112
1212
|
const checkResponse = await alice.rootClient.request({
|
|
1113
|
-
url:
|
|
1213
|
+
url: spaceMetaUrl(serverUrl, spaceId),
|
|
1114
1214
|
method: 'GET',
|
|
1115
1215
|
action: 'GET'
|
|
1116
1216
|
});
|
|
@@ -1120,7 +1220,10 @@ export const spacesApi = {
|
|
|
1120
1220
|
}
|
|
1121
1221
|
finally {
|
|
1122
1222
|
try {
|
|
1123
|
-
await alice.rootClient.request({
|
|
1223
|
+
await alice.rootClient.request({
|
|
1224
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
1225
|
+
method: 'DELETE'
|
|
1226
|
+
});
|
|
1124
1227
|
}
|
|
1125
1228
|
catch {
|
|
1126
1229
|
/* best-effort cleanup */
|
|
@@ -1132,7 +1235,7 @@ export const spacesApi = {
|
|
|
1132
1235
|
id: 'space.create-ignores-body-createdby',
|
|
1133
1236
|
name: '[root] a body-supplied `createdBy` is ignored by the server',
|
|
1134
1237
|
group: 'Space API',
|
|
1135
|
-
specRefs: ['https://wallet.storage/spec#space-data-model'],
|
|
1238
|
+
specRefs: ['https://wallet.storage/spec#space-metadata-data-model'],
|
|
1136
1239
|
run: async (ctx, state) => {
|
|
1137
1240
|
const { serverUrl, createSpace, generateId } = ctx;
|
|
1138
1241
|
const { alice } = state;
|
|
@@ -1141,7 +1244,6 @@ export const spacesApi = {
|
|
|
1141
1244
|
// below is a well-formed but bogus DID that is not the creator.
|
|
1142
1245
|
const bogusCreatedBy = 'did:key:z6MkpBMbMaRSv5nsgifRAwEKvHHoiKDMhiAHShTFNmkJXXXX';
|
|
1143
1246
|
const spaceId = generateId();
|
|
1144
|
-
const spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
|
|
1145
1247
|
await createSpace({
|
|
1146
1248
|
spaceDescription: {
|
|
1147
1249
|
id: spaceId,
|
|
@@ -1153,7 +1255,7 @@ export const spacesApi = {
|
|
|
1153
1255
|
});
|
|
1154
1256
|
try {
|
|
1155
1257
|
const response = await alice.rootClient.request({
|
|
1156
|
-
url:
|
|
1258
|
+
url: spaceMetaUrl(serverUrl, spaceId),
|
|
1157
1259
|
method: 'GET',
|
|
1158
1260
|
action: 'GET'
|
|
1159
1261
|
});
|
|
@@ -1164,7 +1266,10 @@ export const spacesApi = {
|
|
|
1164
1266
|
}
|
|
1165
1267
|
finally {
|
|
1166
1268
|
try {
|
|
1167
|
-
await alice.rootClient.request({
|
|
1269
|
+
await alice.rootClient.request({
|
|
1270
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
1271
|
+
method: 'DELETE'
|
|
1272
|
+
});
|
|
1168
1273
|
}
|
|
1169
1274
|
catch {
|
|
1170
1275
|
/* best-effort cleanup */
|
|
@@ -1189,11 +1294,11 @@ export const spacesApi = {
|
|
|
1189
1294
|
// in her -- the direct signer-mismatch shape (this is the CREATE
|
|
1190
1295
|
// branch; the update branch is covered by space.update-controller-swap).
|
|
1191
1296
|
const spaceId = generateId();
|
|
1192
|
-
const
|
|
1297
|
+
const metaUrl = spaceMetaUrl(serverUrl, spaceId);
|
|
1193
1298
|
let expectedError;
|
|
1194
1299
|
try {
|
|
1195
1300
|
await bob.rootClient.request({
|
|
1196
|
-
url:
|
|
1301
|
+
url: metaUrl,
|
|
1197
1302
|
method: 'PUT',
|
|
1198
1303
|
action: 'PUT',
|
|
1199
1304
|
json: {
|
|
@@ -1214,7 +1319,7 @@ export const spacesApi = {
|
|
|
1214
1319
|
let checkError;
|
|
1215
1320
|
try {
|
|
1216
1321
|
await alice.rootClient.request({
|
|
1217
|
-
url:
|
|
1322
|
+
url: metaUrl,
|
|
1218
1323
|
method: 'GET',
|
|
1219
1324
|
action: 'GET'
|
|
1220
1325
|
});
|
|
@@ -1238,7 +1343,6 @@ export const spacesApi = {
|
|
|
1238
1343
|
const { serverUrl, createSpace, generateId } = ctx;
|
|
1239
1344
|
const { alice } = state;
|
|
1240
1345
|
const spaceId = generateId();
|
|
1241
|
-
const spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
|
|
1242
1346
|
const policyUrl = new URL(`/space/${spaceId}/policy`, serverUrl).toString();
|
|
1243
1347
|
await createSpace({
|
|
1244
1348
|
spaceDescription: {
|
|
@@ -1272,7 +1376,10 @@ export const spacesApi = {
|
|
|
1272
1376
|
}
|
|
1273
1377
|
finally {
|
|
1274
1378
|
try {
|
|
1275
|
-
await alice.rootClient.request({
|
|
1379
|
+
await alice.rootClient.request({
|
|
1380
|
+
url: spaceUrl(serverUrl, spaceId),
|
|
1381
|
+
method: 'DELETE'
|
|
1382
|
+
});
|
|
1276
1383
|
}
|
|
1277
1384
|
catch {
|
|
1278
1385
|
/* best-effort cleanup */
|
|
@@ -1282,15 +1389,17 @@ export const spacesApi = {
|
|
|
1282
1389
|
},
|
|
1283
1390
|
{
|
|
1284
1391
|
id: 'collections.list-for-space',
|
|
1285
|
-
name: '[root] GET /space/:spaceId/
|
|
1392
|
+
name: '[root] GET /space/:spaceId/ lists collections for a space',
|
|
1286
1393
|
group: 'Collections API',
|
|
1287
1394
|
specRefs: ['https://wallet.storage/spec#list-all-collections-operation'],
|
|
1288
1395
|
run: async (ctx, state) => {
|
|
1289
1396
|
const { serverUrl } = ctx;
|
|
1290
1397
|
const { alice, collectionId } = state;
|
|
1291
|
-
|
|
1398
|
+
// v0.5 moved listing (and creating) Collections onto the Space
|
|
1399
|
+
// container URL itself; the retired `collections` sub-resource is
|
|
1400
|
+
// covered separately (see `space.retired-collections-redirect`).
|
|
1292
1401
|
const response = await alice.rootClient.request({
|
|
1293
|
-
url:
|
|
1402
|
+
url: spaceUrl(serverUrl, alice.space3.id),
|
|
1294
1403
|
method: 'GET'
|
|
1295
1404
|
});
|
|
1296
1405
|
assert.equal(response.status, 200);
|
|
@@ -1308,17 +1417,38 @@ export const spacesApi = {
|
|
|
1308
1417
|
return core;
|
|
1309
1418
|
});
|
|
1310
1419
|
assert.deepStrictEqual({ ...response.data, items: coreItems }, {
|
|
1311
|
-
url: `/space/${alice.space3.id}
|
|
1420
|
+
url: `/space/${alice.space3.id}/`,
|
|
1312
1421
|
totalItems: 1,
|
|
1313
1422
|
items: [
|
|
1314
1423
|
{
|
|
1315
1424
|
id: collectionId,
|
|
1316
1425
|
name: 'Test Collection',
|
|
1317
|
-
url: `/space/${alice.space3.id}/${collectionId}
|
|
1426
|
+
url: `/space/${alice.space3.id}/${collectionId}/`
|
|
1318
1427
|
}
|
|
1319
1428
|
]
|
|
1320
1429
|
});
|
|
1321
1430
|
}
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
id: 'space.retired-collections-redirect',
|
|
1434
|
+
name: '[root] GET /space/:spaceId/collections/ (retired in v0.5) MAY 308 to the Space URL',
|
|
1435
|
+
group: 'Collections API',
|
|
1436
|
+
optional: true,
|
|
1437
|
+
specRefs: ['https://wallet.storage/spec#space-level-reserved-endpoints'],
|
|
1438
|
+
run: async (ctx, state) => {
|
|
1439
|
+
const { serverUrl } = ctx;
|
|
1440
|
+
const { alice } = state;
|
|
1441
|
+
const retiredUrl = new URL(`/space/${alice.space3.id}/collections/`, serverUrl).toString();
|
|
1442
|
+
const response = await fetch(retiredUrl, {
|
|
1443
|
+
method: 'GET',
|
|
1444
|
+
redirect: 'manual'
|
|
1445
|
+
});
|
|
1446
|
+
if (response.status !== 308) {
|
|
1447
|
+
ctx.skip('the retired `collections` endpoint is a MAY 308; this server ' +
|
|
1448
|
+
`answered ${response.status} instead`);
|
|
1449
|
+
}
|
|
1450
|
+
assert.equal(response.headers.get('location'), `/space/${alice.space3.id}/`);
|
|
1451
|
+
}
|
|
1322
1452
|
}
|
|
1323
1453
|
]
|
|
1324
1454
|
};
|