@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.
Files changed (56) hide show
  1. package/README.md +1 -0
  2. package/dist/suites/authz-ordering-api.d.ts.map +1 -1
  3. package/dist/suites/authz-ordering-api.js +1 -26
  4. package/dist/suites/authz-ordering-api.js.map +1 -1
  5. package/dist/suites/blinded-index-api.js +1 -1
  6. package/dist/suites/blinded-index-api.js.map +1 -1
  7. package/dist/suites/changes-query-api.js +1 -1
  8. package/dist/suites/changes-query-api.js.map +1 -1
  9. package/dist/suites/chunks-api.js +1 -1
  10. package/dist/suites/chunks-api.js.map +1 -1
  11. package/dist/suites/client-spaces.d.ts.map +1 -1
  12. package/dist/suites/client-spaces.js +19 -6
  13. package/dist/suites/client-spaces.js.map +1 -1
  14. package/dist/suites/collection-api.d.ts.map +1 -1
  15. package/dist/suites/collection-api.js +317 -126
  16. package/dist/suites/collection-api.js.map +1 -1
  17. package/dist/suites/conditional-requests-api.d.ts.map +1 -1
  18. package/dist/suites/conditional-requests-api.js +122 -30
  19. package/dist/suites/conditional-requests-api.js.map +1 -1
  20. package/dist/suites/delegation-cryptosuites-api.js +1 -1
  21. package/dist/suites/delegation-cryptosuites-api.js.map +1 -1
  22. package/dist/suites/denial-reasons-api.d.ts.map +1 -1
  23. package/dist/suites/denial-reasons-api.js +5 -2
  24. package/dist/suites/denial-reasons-api.js.map +1 -1
  25. package/dist/suites/digest-api.js +1 -1
  26. package/dist/suites/digest-api.js.map +1 -1
  27. package/dist/suites/encryption-descriptor-api.d.ts.map +1 -1
  28. package/dist/suites/encryption-descriptor-api.js +48 -51
  29. package/dist/suites/encryption-descriptor-api.js.map +1 -1
  30. package/dist/suites/governed-log-api.d.ts.map +1 -1
  31. package/dist/suites/governed-log-api.js +18 -17
  32. package/dist/suites/governed-log-api.js.map +1 -1
  33. package/dist/suites/index.d.ts +2 -1
  34. package/dist/suites/index.d.ts.map +1 -1
  35. package/dist/suites/index.js +3 -1
  36. package/dist/suites/index.js.map +1 -1
  37. package/dist/suites/invocation-target-api.js +1 -1
  38. package/dist/suites/invocation-target-api.js.map +1 -1
  39. package/dist/suites/plaintext-declaration-api.d.ts.map +1 -1
  40. package/dist/suites/plaintext-declaration-api.js +24 -15
  41. package/dist/suites/plaintext-declaration-api.js.map +1 -1
  42. package/dist/suites/policy-api.js +1 -1
  43. package/dist/suites/policy-api.js.map +1 -1
  44. package/dist/suites/reserved-methods-api.d.ts +9 -0
  45. package/dist/suites/reserved-methods-api.d.ts.map +1 -0
  46. package/dist/suites/reserved-methods-api.js +549 -0
  47. package/dist/suites/reserved-methods-api.js.map +1 -0
  48. package/dist/suites/resource-api.js +1 -1
  49. package/dist/suites/resource-api.js.map +1 -1
  50. package/dist/suites/spaces-api.d.ts.map +1 -1
  51. package/dist/suites/spaces-api.js +193 -63
  52. package/dist/suites/spaces-api.js.map +1 -1
  53. package/dist/suites/write-validation-api.d.ts.map +1 -1
  54. package/dist/suites/write-validation-api.js +16 -36
  55. package/dist/suites/write-validation-api.js.map +1 -1
  56. 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: new URL(`/space/${spaceId}`, ctx.serverUrl).toString(),
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: new URL(`/space/${spaceId}`, ctx.serverUrl).toString(),
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
- assert.equal(aliceItem.url, `/space/${alice.space1.id}`);
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: new URL(`/space/${bobSpaceId}`, serverUrl).toString(),
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: new URL(`/space/${spaceId}`, serverUrl).toString(),
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: spaceUrl,
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({ url: spaceUrl, method: 'DELETE' });
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'), `${serverUrl}/spaces/${freshSpaceId}`);
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: new URL(`/space/${freshSpaceId}`, serverUrl).toString(),
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
- const spaceUrl = new URL(`/space/${alice.space2.id}`, serverUrl).toString();
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: spaceUrl,
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: spaceUrl,
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 spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
821
+ const metaUrl = spaceMetaUrl(serverUrl, spaceId);
791
822
  try {
792
823
  let expectedError;
793
824
  try {
794
825
  await bob.rootClient.request({
795
- url: spaceUrl,
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: spaceUrl,
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({ url: spaceUrl, method: 'DELETE' });
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 spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
894
+ const metaUrl = spaceMetaUrl(serverUrl, spaceId);
861
895
  try {
862
896
  let expectedError;
863
897
  try {
864
898
  await alice.rootClient.request({
865
- url: spaceUrl,
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: spaceUrl,
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({ url: spaceUrl, method: 'DELETE' });
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 a space with no auth headers falls through to policy and 404s (no public policy)',
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 spaceUrl = new URL(`/space/${alice.space1.id}`, serverUrl).toString();
913
- const response = await fetch(spaceUrl, { method: 'GET' });
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: spaceUrl,
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 space via GET with proper authorization',
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: spaceUrl,
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
- const spaceUrl = new URL(`/space/${alice.space1.id}`, serverUrl).toString();
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: spaceUrl,
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: '[root] Bob should not be able to GET Alice space',
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({ url: spaceUrl, action: 'GET' });
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({ url: spaceUrl, method: 'GET' });
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: spaceUrl,
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({ url: spaceUrl, method: 'DELETE' });
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: spaceUrl,
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({ url: spaceUrl, method: 'DELETE' });
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 spaceUrl = new URL(`/space/${spaceId}`, serverUrl).toString();
1297
+ const metaUrl = spaceMetaUrl(serverUrl, spaceId);
1193
1298
  let expectedError;
1194
1299
  try {
1195
1300
  await bob.rootClient.request({
1196
- url: spaceUrl,
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: spaceUrl,
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({ url: spaceUrl, method: 'DELETE' });
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/collections/ lists collections for a space',
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
- const collectionsUrl = new URL(`/space/${alice.space3.id}/collections/`, serverUrl).toString();
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: collectionsUrl,
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}/collections/`,
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
  };