@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 +284 -17
- package/build/cjs/corpus/deploy.js +139 -0
- package/build/cjs/corpus/runner.js +2 -0
- package/build/cjs/corpus/session.js +69 -0
- package/build/cjs/drive/registry.js +38 -0
- package/build/cjs/host/dispatch.js +15 -0
- package/build/cjs/index.js +3 -1
- package/build/cjs/routes/generated.js +32 -0
- package/build/cjs/routes/overlay.js +2 -10
- package/build/esm/corpus/deploy.d.ts +16 -0
- package/build/esm/corpus/deploy.d.ts.map +1 -0
- package/build/esm/corpus/deploy.js +136 -0
- package/build/esm/corpus/runner.d.ts.map +1 -1
- package/build/esm/corpus/runner.js +2 -0
- package/build/esm/corpus/session.d.ts +9 -8
- package/build/esm/corpus/session.d.ts.map +1 -1
- package/build/esm/corpus/session.js +69 -0
- package/build/esm/drive/registry.d.ts +52 -0
- package/build/esm/drive/registry.d.ts.map +1 -0
- package/build/esm/drive/registry.js +33 -0
- package/build/esm/host/dispatch.d.ts +6 -0
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +15 -0
- package/build/esm/index.d.ts +1 -0
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +1 -0
- package/build/esm/routes/generated.d.ts.map +1 -1
- package/build/esm/routes/generated.js +32 -0
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +2 -10
- package/package.json +1 -1
package/build/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @sasjs/api 0.
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
};
|