@sasjs/api 0.11.0 → 0.13.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/build/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @sasjs/api 0.11.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.13.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
2
2
  "use strict";
3
3
  var SASjsApi = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -28,6 +28,7 @@ var SASjsApi = (() => {
28
28
  CODE_CORPUS: () => CODE_CORPUS,
29
29
  DEFAULT_GRANTABLE_ROUTES: () => DEFAULT_GRANTABLE_ROUTES,
30
30
  DEFAULT_IDENTITY: () => DEFAULT_IDENTITY,
31
+ DEPLOY_CORPUS: () => DEPLOY_CORPUS,
31
32
  DEPLOY_LIMITS: () => DEPLOY_LIMITS,
32
33
  DEPLOY_PAYLOAD_BYTES: () => DEPLOY_PAYLOAD_BYTES,
33
34
  DEPLOY_SUCCESS: () => DEPLOY_SUCCESS,
@@ -1847,6 +1848,14 @@ var SASjsApi = (() => {
1847
1848
  {
1848
1849
  status: 200,
1849
1850
  description: "Ok"
1851
+ },
1852
+ {
1853
+ status: 401,
1854
+ description: "No valid access token or session was supplied"
1855
+ },
1856
+ {
1857
+ status: 404,
1858
+ description: "Session with the given ID was not found."
1850
1859
  }
1851
1860
  ]
1852
1861
  },
@@ -1869,6 +1878,14 @@ var SASjsApi = (() => {
1869
1878
  {
1870
1879
  status: 200,
1871
1880
  description: "Ok"
1881
+ },
1882
+ {
1883
+ status: 401,
1884
+ description: "No valid access token or session was supplied"
1885
+ },
1886
+ {
1887
+ status: 404,
1888
+ description: "Session with the given ID was not found."
1872
1889
  }
1873
1890
  ]
1874
1891
  },
@@ -1891,6 +1908,14 @@ var SASjsApi = (() => {
1891
1908
  {
1892
1909
  status: 200,
1893
1910
  description: "Ok"
1911
+ },
1912
+ {
1913
+ status: 401,
1914
+ description: "No valid access token or session was supplied"
1915
+ },
1916
+ {
1917
+ status: 404,
1918
+ description: "Session with the given ID was not found."
1894
1919
  }
1895
1920
  ]
1896
1921
  },
@@ -1913,6 +1938,14 @@ var SASjsApi = (() => {
1913
1938
  {
1914
1939
  status: 200,
1915
1940
  description: "Ok"
1941
+ },
1942
+ {
1943
+ status: 401,
1944
+ description: "No valid access token or session was supplied"
1945
+ },
1946
+ {
1947
+ status: 404,
1948
+ description: "Session with the given ID was not found."
1916
1949
  }
1917
1950
  ]
1918
1951
  },
@@ -2481,8 +2514,6 @@ var SASjsApi = (() => {
2481
2514
  var HOST_CONFIG = "The body is the host's own configuration - ports, run times and providers - which a mock states for itself and this package cannot know.";
2482
2515
  var HOST_OWNED = "The host owns this report: it describes the host's own process and files, which a browser build has no equivalent of.";
2483
2516
  var WEB_BUNDLE = "The host serves its own web bundle; there is no API decision here for this package to answer.";
2484
- var ARCHIVE = "Expanding the uploaded archive is the host's transport work - multer and a zip library on the server, `DecompressionStream` in a worker - and the host hands this package the tree it produces, which is the same tree `POST /SASjsApi/drive/deploy` takes.";
2485
- var UNPUBLISH = "Unpublishing inverts the deploy route, so the two land together.";
2486
2517
  var success = { status: "success" };
2487
2518
  var user = {
2488
2519
  uid: "",
@@ -2610,16 +2641,10 @@ var SASjsApi = (() => {
2610
2641
  500: { status: "failure", message: "Deployment failed!" }
2611
2642
  }
2612
2643
  },
2613
- "POST /SASjsApi/drive/deploy/upload": {
2614
- implemented: false,
2615
- reason: ARCHIVE
2616
- },
2644
+ "POST /SASjsApi/drive/deploy/upload": { implemented: true },
2617
2645
  // `404` is a pair - the registry name and the appLoc it was published from -
2618
2646
  // that matches no entry, and the drive's content is deliberately left alone.
2619
- "DELETE /SASjsApi/drive/deploy": {
2620
- implemented: false,
2621
- reason: UNPUBLISH
2622
- },
2647
+ "DELETE /SASjsApi/drive/deploy": { implemented: true },
2623
2648
  // Group - members and the groups themselves
2624
2649
  "GET /SASjsApi/group": { implemented: true },
2625
2650
  "GET /SASjsApi/group/{uid}": { implemented: true },
@@ -2777,6 +2802,24 @@ var SASjsApi = (() => {
2777
2802
  return void 0;
2778
2803
  };
2779
2804
 
2805
+ // src/drive/registry.ts
2806
+ var unpublishAppStream = async (registry, body) => {
2807
+ if (!body?.streamServiceName)
2808
+ throw new ApiError(400, '"streamServiceName" is required');
2809
+ if (!body.appLoc) throw new ApiError(400, '"appLoc" is required');
2810
+ if (!await registry.unpublish(body.appLoc))
2811
+ throw new ApiError(404, "No such app stream entry.");
2812
+ return { status: "success" };
2813
+ };
2814
+ var MemoryAppStreamRegistry = class {
2815
+ constructor(folders = ["/Public/myApp"]) {
2816
+ this.folders = new Set(folders);
2817
+ }
2818
+ async unpublish(appLoc) {
2819
+ return this.folders.delete(appLoc);
2820
+ }
2821
+ };
2822
+
2780
2823
  // src/host/dispatch.ts
2781
2824
  var SECONDS_IN_A_DAY = 86400;
2782
2825
  var validate = (route, request) => {
@@ -2919,10 +2962,25 @@ var SASjsApi = (() => {
2919
2962
  String(request.body?.newPath || "")
2920
2963
  )
2921
2964
  };
2965
+ // The archive is the host's transport - multer and a zip library on a
2966
+ // server, `DecompressionStream` in a worker - and it hands this the same tree
2967
+ // `POST /deploy` takes. The deploy cap is measured on that tree, after
2968
+ // expansion, which is why the two share this path rather than reading bytes.
2969
+ case "POST /SASjsApi/drive/deploy/upload":
2922
2970
  case "POST /SASjsApi/drive/deploy": {
2923
2971
  await drive.deploy(String(body.appLoc ?? ""), body.fileTree);
2924
2972
  return { status: 200, body: DEPLOY_SUCCESS };
2925
2973
  }
2974
+ case "DELETE /SASjsApi/drive/deploy":
2975
+ if (!services.registry)
2976
+ throw new ApiError(
2977
+ 501,
2978
+ `This host has no app stream registry, so it cannot answer ${routeId}.`
2979
+ );
2980
+ return {
2981
+ status: 200,
2982
+ body: await unpublishAppStream(services.registry, body)
2983
+ };
2926
2984
  case "GET /SASjsApi/drive/filetree":
2927
2985
  return { status: 200, body: await drive.tree(query._folderPath) };
2928
2986
  // ------------------------------------------------------------- sessions
@@ -3177,6 +3235,8 @@ var SASjsApi = (() => {
3177
3235
  }
3178
3236
  };
3179
3237
  var HANDLED_ROUTES = [
3238
+ "POST /SASjsApi/drive/deploy/upload",
3239
+ "DELETE /SASjsApi/drive/deploy",
3180
3240
  "GET /SASjsApi/session/{sessionId}",
3181
3241
  "GET /SASjsApi/session/{sessionId}/log",
3182
3242
  "GET /SASjsApi/session/{sessionId}/output",
@@ -4898,10 +4958,150 @@ var SASjsApi = (() => {
4898
4958
  }
4899
4959
  ];
4900
4960
 
4961
+ // src/corpus/deploy.ts
4962
+ var SUBJECT = { userId: "user-admin", clientId: "web_app" };
4963
+ var manyMembers2 = (count) => Array.from({ length: count }, (_, at) => ({
4964
+ name: `file-${at}.txt`,
4965
+ type: "file",
4966
+ code: ""
4967
+ }));
4968
+ var DEPLOY_CORPUS = [
4969
+ {
4970
+ id: "deploy-unpublish",
4971
+ route: "DELETE /SASjsApi/drive/deploy",
4972
+ description: "removes a published app stream and answers the status",
4973
+ request: {
4974
+ subject: SUBJECT,
4975
+ body: { streamServiceName: "myApp", appLoc: "/Public/myApp" }
4976
+ },
4977
+ expect: { status: 200, body: { status: "success" } }
4978
+ },
4979
+ {
4980
+ id: "deploy-unpublish-other-name",
4981
+ route: "DELETE /SASjsApi/drive/deploy",
4982
+ description: "removes the app by its folder when the name has been renamed",
4983
+ request: {
4984
+ subject: SUBJECT,
4985
+ body: { streamServiceName: "renamed-app", appLoc: "/Public/myApp" }
4986
+ },
4987
+ expect: { status: 200, body: { status: "success" } }
4988
+ },
4989
+ {
4990
+ id: "deploy-unpublish-no-stream-name",
4991
+ route: "DELETE /SASjsApi/drive/deploy",
4992
+ description: "refuses a body that does not say which stream to remove",
4993
+ request: { subject: SUBJECT, body: { appLoc: "/Public/myApp" } },
4994
+ expect: { status: 400, error: '"streamServiceName" is required' }
4995
+ },
4996
+ {
4997
+ id: "deploy-unpublish-no-app-loc",
4998
+ route: "DELETE /SASjsApi/drive/deploy",
4999
+ description: "refuses a body that does not say which folder to remove",
5000
+ request: { subject: SUBJECT, body: { streamServiceName: "myApp" } },
5001
+ expect: { status: 400, error: '"appLoc" is required' }
5002
+ },
5003
+ {
5004
+ id: "deploy-unpublish-unknown-folder",
5005
+ route: "DELETE /SASjsApi/drive/deploy",
5006
+ description: "answers 404 for a folder nothing is published at",
5007
+ request: {
5008
+ subject: SUBJECT,
5009
+ body: { streamServiceName: "myApp", appLoc: "/Public/not-an-app" }
5010
+ },
5011
+ expect: { status: 404, error: "No such app stream entry." }
5012
+ },
5013
+ // ------------------------------------------- the uploaded-archive route
5014
+ // The archive is expanded by the host, so what arrives here is the same tree
5015
+ // `POST /deploy` takes - which is why every answer below matches its twin.
5016
+ {
5017
+ id: "upload-tree",
5018
+ route: "POST /SASjsApi/drive/deploy/upload",
5019
+ description: "writes the tree an uploaded archive expanded to",
5020
+ request: {
5021
+ subject: SUBJECT,
5022
+ body: {
5023
+ appLoc: "/Public/apps/uploaded",
5024
+ fileTree: {
5025
+ members: [
5026
+ { name: "index.html", type: "file", code: "PGgxPkhlbGxvPC9oMT4=" }
5027
+ ]
5028
+ }
5029
+ }
5030
+ },
5031
+ expect: {
5032
+ status: 200,
5033
+ body: {
5034
+ status: "success",
5035
+ message: "Files deployed successfully to @sasjs/server."
5036
+ }
5037
+ }
5038
+ },
5039
+ {
5040
+ id: "upload-refused-by-shape",
5041
+ route: "POST /SASjsApi/drive/deploy/upload",
5042
+ description: "refuses an expansion that is not a tree",
5043
+ request: {
5044
+ subject: SUBJECT,
5045
+ body: { appLoc: "/Public/apps/bad", fileTree: { members: 1 } }
5046
+ },
5047
+ expect: {
5048
+ status: 400,
5049
+ body: {
5050
+ status: "failure",
5051
+ message: "Provided not supported data format."
5052
+ },
5053
+ error: "Provided not supported data format."
5054
+ }
5055
+ },
5056
+ {
5057
+ id: "upload-refused-by-entry-count",
5058
+ route: "POST /SASjsApi/drive/deploy/upload",
5059
+ description: "applies the deploy cap to the expanded tree, not the bytes",
5060
+ request: {
5061
+ subject: SUBJECT,
5062
+ body: {
5063
+ appLoc: "/Public/apps/many",
5064
+ fileTree: { members: manyMembers2(10001) }
5065
+ }
5066
+ },
5067
+ expect: {
5068
+ status: 413,
5069
+ body: {
5070
+ status: "failure",
5071
+ message: "The file tree exceeds the deploy limit: 10001 entries (maximum 10000)."
5072
+ },
5073
+ error: "The file tree exceeds the deploy limit: 10001 entries (maximum 10000)."
5074
+ }
5075
+ },
5076
+ {
5077
+ id: "upload-refused-by-store",
5078
+ route: "POST /SASjsApi/drive/deploy/upload",
5079
+ description: "answers the routes 500 when the drive refuses the write",
5080
+ unwritable: true,
5081
+ request: {
5082
+ subject: SUBJECT,
5083
+ body: {
5084
+ appLoc: "/Public/apps/demo",
5085
+ fileTree: {
5086
+ members: [{ name: "run", type: "service", code: "%put hi;" }]
5087
+ }
5088
+ }
5089
+ },
5090
+ expect: {
5091
+ status: 500,
5092
+ body: { status: "failure", message: "Deployment failed!" },
5093
+ error: "Deployment failed!"
5094
+ }
5095
+ }
5096
+ ];
5097
+
4901
5098
  // src/corpus/session.ts
4902
5099
  var ADMIN = { userId: "user-admin", clientId: "web_app" };
4903
5100
  var RESTRICTED = { userId: "user-restricted", clientId: "web_app" };
5101
+ var notFound = (id) => `Session with ID '${id}' was not found.`;
5102
+ var UNAUTHORIZED = "Unauthorized";
4904
5103
  var SESSION_CORPUS = [
5104
+ // ---------------------------------------------------------------- summary
4905
5105
  {
4906
5106
  id: "session-summary-own",
4907
5107
  route: "GET /SASjsApi/session/{sessionId}",
@@ -4934,6 +5134,28 @@ var SASjsApi = (() => {
4934
5134
  }
4935
5135
  }
4936
5136
  },
5137
+ {
5138
+ id: "session-summary-unauthenticated",
5139
+ route: "GET /SASjsApi/session/{sessionId}",
5140
+ description: "refuses a caller with no token",
5141
+ request: { params: { sessionId: "sess-admin" } },
5142
+ expect: { status: 401, error: UNAUTHORIZED }
5143
+ },
5144
+ {
5145
+ id: "session-summary-foreign",
5146
+ route: "GET /SASjsApi/session/{sessionId}",
5147
+ description: "answers 404 for another account's session, not 403",
5148
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5149
+ expect: { status: 404, error: notFound("sess-admin") }
5150
+ },
5151
+ {
5152
+ id: "session-summary-unknown",
5153
+ route: "GET /SASjsApi/session/{sessionId}",
5154
+ description: "answers the same 404 for a session that does not exist",
5155
+ request: { subject: ADMIN, params: { sessionId: "sess-nope" } },
5156
+ expect: { status: 404, error: notFound("sess-nope") }
5157
+ },
5158
+ // -------------------------------------------------------------------- log
4937
5159
  {
4938
5160
  id: "session-log-own",
4939
5161
  route: "GET /SASjsApi/session/{sessionId}/log",
@@ -4951,6 +5173,21 @@ var SASjsApi = (() => {
4951
5173
  request: { subject: ADMIN, params: { sessionId: "sess-silent" } },
4952
5174
  expect: { status: 200, contents: "" }
4953
5175
  },
5176
+ {
5177
+ id: "session-log-unauthenticated",
5178
+ route: "GET /SASjsApi/session/{sessionId}/log",
5179
+ description: "refuses a caller with no token",
5180
+ request: { params: { sessionId: "sess-admin" } },
5181
+ expect: { status: 401, error: UNAUTHORIZED }
5182
+ },
5183
+ {
5184
+ id: "session-log-foreign",
5185
+ route: "GET /SASjsApi/session/{sessionId}/log",
5186
+ description: "will not read another account's log",
5187
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5188
+ expect: { status: 404, error: notFound("sess-admin") }
5189
+ },
5190
+ // ----------------------------------------------------------------- output
4954
5191
  {
4955
5192
  id: "session-output-own",
4956
5193
  route: "GET /SASjsApi/session/{sessionId}/output",
@@ -4958,6 +5195,21 @@ var SASjsApi = (() => {
4958
5195
  request: { subject: ADMIN, params: { sessionId: "sess-admin" } },
4959
5196
  expect: { status: 200, contents: "<html><body>42</body></html>" }
4960
5197
  },
5198
+ {
5199
+ id: "session-output-unauthenticated",
5200
+ route: "GET /SASjsApi/session/{sessionId}/output",
5201
+ description: "refuses a caller with no token",
5202
+ request: { params: { sessionId: "sess-admin" } },
5203
+ expect: { status: 401, error: UNAUTHORIZED }
5204
+ },
5205
+ {
5206
+ id: "session-output-foreign",
5207
+ route: "GET /SASjsApi/session/{sessionId}/output",
5208
+ description: "will not read another account's webout",
5209
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5210
+ expect: { status: 404, error: notFound("sess-admin") }
5211
+ },
5212
+ // ------------------------------------------------------------------ state
4961
5213
  {
4962
5214
  id: "session-state-own",
4963
5215
  route: "GET /SASjsApi/session/{sessionId}/state",
@@ -4967,6 +5219,20 @@ var SASjsApi = (() => {
4967
5219
  status: 200,
4968
5220
  body: { state: "running", pid: 4243, lineCount: 3 }
4969
5221
  }
5222
+ },
5223
+ {
5224
+ id: "session-state-unauthenticated",
5225
+ route: "GET /SASjsApi/session/{sessionId}/state",
5226
+ description: "refuses a caller with no token",
5227
+ request: { params: { sessionId: "sess-restricted" } },
5228
+ expect: { status: 401, error: UNAUTHORIZED }
5229
+ },
5230
+ {
5231
+ id: "session-state-foreign",
5232
+ route: "GET /SASjsApi/session/{sessionId}/state",
5233
+ description: "will not report another account's session state",
5234
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5235
+ expect: { status: 404, error: notFound("sess-admin") }
4970
5236
  }
4971
5237
  ];
4972
5238
 
@@ -6188,7 +6454,7 @@ var SASjsApi = (() => {
6188
6454
 
6189
6455
  // src/session/service.ts
6190
6456
  var createSessionService = (store, host) => {
6191
- const notFound = (id) => `Session with ID '${id}' was not found.`;
6457
+ const notFound2 = (id) => `Session with ID '${id}' was not found.`;
6192
6458
  const visible = async (caller, id) => {
6193
6459
  if (!caller) throw new ApiError(401, "Unauthorized");
6194
6460
  const reader = await store.userById(
@@ -6196,10 +6462,10 @@ var SASjsApi = (() => {
6196
6462
  );
6197
6463
  if (!reader || !reader.isActive) throw new ApiError(401, "Unauthorized");
6198
6464
  const session = await host.session(id);
6199
- if (!session) throw new ApiError(404, notFound(id));
6465
+ if (!session) throw new ApiError(404, notFound2(id));
6200
6466
  const owner = session.owner;
6201
6467
  if (owner !== reader.uid && owner !== reader.username && !reader.isAdmin)
6202
- throw new ApiError(404, notFound(id));
6468
+ throw new ApiError(404, notFound2(id));
6203
6469
  return session;
6204
6470
  };
6205
6471
  return {
@@ -6210,19 +6476,19 @@ var SASjsApi = (() => {
6210
6476
  async state(caller, id) {
6211
6477
  await visible(caller, id);
6212
6478
  const state = await host.state(id);
6213
- if (!state) throw new ApiError(404, notFound(id));
6479
+ if (!state) throw new ApiError(404, notFound2(id));
6214
6480
  return state;
6215
6481
  },
6216
6482
  async log(caller, id) {
6217
6483
  await visible(caller, id);
6218
6484
  const log = await host.log(id);
6219
- if (log === void 0) throw new ApiError(404, notFound(id));
6485
+ if (log === void 0) throw new ApiError(404, notFound2(id));
6220
6486
  return log;
6221
6487
  },
6222
6488
  async output(caller, id) {
6223
6489
  await visible(caller, id);
6224
6490
  const output = await host.output(id);
6225
- if (output === void 0) throw new ApiError(404, notFound(id));
6491
+ if (output === void 0) throw new ApiError(404, notFound2(id));
6226
6492
  return output;
6227
6493
  }
6228
6494
  };
@@ -6303,6 +6569,7 @@ var SASjsApi = (() => {
6303
6569
  ),
6304
6570
  identity: createIdentityService(identity),
6305
6571
  records: createRecordService(identity),
6572
+ registry: new MemoryAppStreamRegistry(),
6306
6573
  session: createSessionService(identity, new MemorySessionHost())
6307
6574
  };
6308
6575
  };
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEPLOY_CORPUS = void 0;
4
+ const SUBJECT = { userId: 'user-admin', clientId: 'web_app' };
5
+ /** A tree of `count` file members, for the deploy cap. */
6
+ const manyMembers = (count) => Array.from({ length: count }, (_, at) => ({
7
+ name: `file-${at}.txt`,
8
+ type: 'file',
9
+ code: ''
10
+ }));
11
+ exports.DEPLOY_CORPUS = [
12
+ {
13
+ id: 'deploy-unpublish',
14
+ route: 'DELETE /SASjsApi/drive/deploy',
15
+ description: 'removes a published app stream and answers the status',
16
+ request: {
17
+ subject: SUBJECT,
18
+ body: { streamServiceName: 'myApp', appLoc: '/Public/myApp' }
19
+ },
20
+ expect: { status: 200, body: { status: 'success' } }
21
+ },
22
+ {
23
+ id: 'deploy-unpublish-other-name',
24
+ route: 'DELETE /SASjsApi/drive/deploy',
25
+ description: 'removes the app by its folder when the name has been renamed',
26
+ request: {
27
+ subject: SUBJECT,
28
+ body: { streamServiceName: 'renamed-app', appLoc: '/Public/myApp' }
29
+ },
30
+ expect: { status: 200, body: { status: 'success' } }
31
+ },
32
+ {
33
+ id: 'deploy-unpublish-no-stream-name',
34
+ route: 'DELETE /SASjsApi/drive/deploy',
35
+ description: 'refuses a body that does not say which stream to remove',
36
+ request: { subject: SUBJECT, body: { appLoc: '/Public/myApp' } },
37
+ expect: { status: 400, error: '"streamServiceName" is required' }
38
+ },
39
+ {
40
+ id: 'deploy-unpublish-no-app-loc',
41
+ route: 'DELETE /SASjsApi/drive/deploy',
42
+ description: 'refuses a body that does not say which folder to remove',
43
+ request: { subject: SUBJECT, body: { streamServiceName: 'myApp' } },
44
+ expect: { status: 400, error: '"appLoc" is required' }
45
+ },
46
+ {
47
+ id: 'deploy-unpublish-unknown-folder',
48
+ route: 'DELETE /SASjsApi/drive/deploy',
49
+ description: 'answers 404 for a folder nothing is published at',
50
+ request: {
51
+ subject: SUBJECT,
52
+ body: { streamServiceName: 'myApp', appLoc: '/Public/not-an-app' }
53
+ },
54
+ expect: { status: 404, error: 'No such app stream entry.' }
55
+ },
56
+ // ------------------------------------------- the uploaded-archive route
57
+ // The archive is expanded by the host, so what arrives here is the same tree
58
+ // `POST /deploy` takes - which is why every answer below matches its twin.
59
+ {
60
+ id: 'upload-tree',
61
+ route: 'POST /SASjsApi/drive/deploy/upload',
62
+ description: 'writes the tree an uploaded archive expanded to',
63
+ request: {
64
+ subject: SUBJECT,
65
+ body: {
66
+ appLoc: '/Public/apps/uploaded',
67
+ fileTree: {
68
+ members: [
69
+ { name: 'index.html', type: 'file', code: 'PGgxPkhlbGxvPC9oMT4=' }
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ expect: {
75
+ status: 200,
76
+ body: {
77
+ status: 'success',
78
+ message: 'Files deployed successfully to @sasjs/server.'
79
+ }
80
+ }
81
+ },
82
+ {
83
+ id: 'upload-refused-by-shape',
84
+ route: 'POST /SASjsApi/drive/deploy/upload',
85
+ description: 'refuses an expansion that is not a tree',
86
+ request: {
87
+ subject: SUBJECT,
88
+ body: { appLoc: '/Public/apps/bad', fileTree: { members: 1 } }
89
+ },
90
+ expect: {
91
+ status: 400,
92
+ body: {
93
+ status: 'failure',
94
+ message: 'Provided not supported data format.'
95
+ },
96
+ error: 'Provided not supported data format.'
97
+ }
98
+ },
99
+ {
100
+ id: 'upload-refused-by-entry-count',
101
+ route: 'POST /SASjsApi/drive/deploy/upload',
102
+ description: 'applies the deploy cap to the expanded tree, not the bytes',
103
+ request: {
104
+ subject: SUBJECT,
105
+ body: {
106
+ appLoc: '/Public/apps/many',
107
+ fileTree: { members: manyMembers(10001) }
108
+ }
109
+ },
110
+ expect: {
111
+ status: 413,
112
+ body: {
113
+ status: 'failure',
114
+ message: 'The file tree exceeds the deploy limit: 10001 entries (maximum 10000).'
115
+ },
116
+ error: 'The file tree exceeds the deploy limit: 10001 entries (maximum 10000).'
117
+ }
118
+ },
119
+ {
120
+ id: 'upload-refused-by-store',
121
+ route: 'POST /SASjsApi/drive/deploy/upload',
122
+ description: 'answers the routes 500 when the drive refuses the write',
123
+ unwritable: true,
124
+ request: {
125
+ subject: SUBJECT,
126
+ body: {
127
+ appLoc: '/Public/apps/demo',
128
+ fileTree: {
129
+ members: [{ name: 'run', type: 'service', code: '%put hi;' }]
130
+ }
131
+ }
132
+ },
133
+ expect: {
134
+ status: 500,
135
+ body: { status: 'failure', message: 'Deployment failed!' },
136
+ error: 'Deployment failed!'
137
+ }
138
+ }
139
+ ];
@@ -4,6 +4,7 @@ exports.runCase = exports.servicesFor = void 0;
4
4
  const dispatch_js_1 = require("../host/dispatch.js");
5
5
  const service_js_1 = require("../drive/service.js");
6
6
  const memory_store_js_1 = require("../drive/memory-store.js");
7
+ const registry_js_1 = require("../drive/registry.js");
7
8
  const service_js_2 = require("../identity/service.js");
8
9
  const records_js_1 = require("../identity/records.js");
9
10
  const service_js_3 = require("../session/service.js");
@@ -29,6 +30,7 @@ const servicesFor = (testCase) => {
29
30
  : new memory_store_js_1.MemoryDriveStore(testCase.seed || {})),
30
31
  identity: (0, service_js_2.createIdentityService)(identity),
31
32
  records: (0, records_js_1.createRecordService)(identity),
33
+ registry: new registry_js_1.MemoryAppStreamRegistry(),
32
34
  session: (0, service_js_3.createSessionService)(identity, new memory_host_js_1.MemorySessionHost())
33
35
  };
34
36
  };