@sasjs/api 0.10.0 → 0.12.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 (42) hide show
  1. package/build/browser.js +557 -108
  2. package/build/cjs/corpus/code.js +44 -7
  3. package/build/cjs/corpus/deploy.js +50 -0
  4. package/build/cjs/corpus/runner.js +6 -1
  5. package/build/cjs/corpus/session.js +142 -0
  6. package/build/cjs/drive/registry.js +38 -0
  7. package/build/cjs/host/dispatch.js +44 -0
  8. package/build/cjs/index.js +5 -1
  9. package/build/cjs/routes/generated.js +48 -0
  10. package/build/cjs/routes/overlay.js +8 -22
  11. package/build/cjs/session/memory-host.js +74 -0
  12. package/build/cjs/session/service.js +65 -0
  13. package/build/esm/corpus/code.d.ts.map +1 -1
  14. package/build/esm/corpus/code.js +44 -7
  15. package/build/esm/corpus/deploy.d.ts +16 -0
  16. package/build/esm/corpus/deploy.d.ts.map +1 -0
  17. package/build/esm/corpus/deploy.js +47 -0
  18. package/build/esm/corpus/runner.d.ts.map +1 -1
  19. package/build/esm/corpus/runner.js +6 -1
  20. package/build/esm/corpus/session.d.ts +17 -0
  21. package/build/esm/corpus/session.d.ts.map +1 -0
  22. package/build/esm/corpus/session.js +139 -0
  23. package/build/esm/drive/registry.d.ts +52 -0
  24. package/build/esm/drive/registry.d.ts.map +1 -0
  25. package/build/esm/drive/registry.js +33 -0
  26. package/build/esm/host/dispatch.d.ts +12 -0
  27. package/build/esm/host/dispatch.d.ts.map +1 -1
  28. package/build/esm/host/dispatch.js +44 -0
  29. package/build/esm/index.d.ts +2 -0
  30. package/build/esm/index.d.ts.map +1 -1
  31. package/build/esm/index.js +2 -0
  32. package/build/esm/routes/generated.d.ts.map +1 -1
  33. package/build/esm/routes/generated.js +48 -0
  34. package/build/esm/routes/overlay.d.ts.map +1 -1
  35. package/build/esm/routes/overlay.js +8 -22
  36. package/build/esm/session/memory-host.d.ts +19 -0
  37. package/build/esm/session/memory-host.d.ts.map +1 -0
  38. package/build/esm/session/memory-host.js +70 -0
  39. package/build/esm/session/service.d.ts +62 -0
  40. package/build/esm/session/service.d.ts.map +1 -0
  41. package/build/esm/session/service.js +61 -0
  42. package/package.json +1 -1
package/build/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @sasjs/api 0.10.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.12.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,
@@ -41,6 +42,7 @@ var SASjsApi = (() => {
41
42
  PROVIDER_MANAGED: () => PROVIDER_MANAGED,
42
43
  RECORD_CORPUS: () => RECORD_CORPUS,
43
44
  ROUTES: () => ROUTES,
45
+ SESSION_CORPUS: () => SESSION_CORPUS,
44
46
  STATIC_AUTHORIZED_ROUTES: () => STATIC_AUTHORIZED_ROUTES,
45
47
  TAG_CAPABILITIES: () => TAG_CAPABILITIES,
46
48
  TOP_LEVEL_ROUTES: () => TOP_LEVEL_ROUTES,
@@ -892,6 +894,14 @@ var SASjsApi = (() => {
892
894
  {
893
895
  status: 200,
894
896
  description: "Ok"
897
+ },
898
+ {
899
+ status: 400,
900
+ description: '"code" is required / "code" must be a string / "code" is not allowed to be empty'
901
+ },
902
+ {
903
+ status: 401,
904
+ description: "Unauthorized"
895
905
  }
896
906
  ]
897
907
  },
@@ -920,6 +930,14 @@ var SASjsApi = (() => {
920
930
  {
921
931
  status: 200,
922
932
  description: "Ok"
933
+ },
934
+ {
935
+ status: 400,
936
+ description: '"code" is required / "code" must be a string / "code" is not allowed to be empty'
937
+ },
938
+ {
939
+ status: 401,
940
+ description: "Unauthorized"
923
941
  }
924
942
  ]
925
943
  },
@@ -1830,6 +1848,14 @@ var SASjsApi = (() => {
1830
1848
  {
1831
1849
  status: 200,
1832
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."
1833
1859
  }
1834
1860
  ]
1835
1861
  },
@@ -1852,6 +1878,14 @@ var SASjsApi = (() => {
1852
1878
  {
1853
1879
  status: 200,
1854
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."
1855
1889
  }
1856
1890
  ]
1857
1891
  },
@@ -1874,6 +1908,14 @@ var SASjsApi = (() => {
1874
1908
  {
1875
1909
  status: 200,
1876
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."
1877
1919
  }
1878
1920
  ]
1879
1921
  },
@@ -1896,6 +1938,14 @@ var SASjsApi = (() => {
1896
1938
  {
1897
1939
  status: 200,
1898
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."
1899
1949
  }
1900
1950
  ]
1901
1951
  },
@@ -2461,12 +2511,10 @@ var SASjsApi = (() => {
2461
2511
  };
2462
2512
  var DIRECTORY = "Synchronising with an external auth provider needs a directory port, which this package does not have: a host decides which directory it holds accounts in, and answers this itself.";
2463
2513
  var RUNTIME = "Starting a program needs a runtime port, which this package does not have: a host decides which languages it holds, and answers this itself until then.";
2464
- var SESSION_RECORD = "A triggered session's record, log, output and state belong to the runtime that ran the program, and this package has no runtime port.";
2465
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.";
2466
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.";
2467
2516
  var WEB_BUNDLE = "The host serves its own web bundle; there is no API decision here for this package to answer.";
2468
2517
  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.";
2469
- var UNPUBLISH = "Unpublishing inverts the deploy route, so the two land together.";
2470
2518
  var success = { status: "success" };
2471
2519
  var user = {
2472
2520
  uid: "",
@@ -2600,10 +2648,7 @@ var SASjsApi = (() => {
2600
2648
  },
2601
2649
  // `404` is a pair - the registry name and the appLoc it was published from -
2602
2650
  // that matches no entry, and the drive's content is deliberately left alone.
2603
- "DELETE /SASjsApi/drive/deploy": {
2604
- implemented: false,
2605
- reason: UNPUBLISH
2606
- },
2651
+ "DELETE /SASjsApi/drive/deploy": { implemented: true },
2607
2652
  // Group - members and the groups themselves
2608
2653
  "GET /SASjsApi/group": { implemented: true },
2609
2654
  "GET /SASjsApi/group/{uid}": { implemented: true },
@@ -2639,22 +2684,13 @@ var SASjsApi = (() => {
2639
2684
  implemented: true,
2640
2685
  responses: { 200: user }
2641
2686
  },
2642
- "GET /SASjsApi/session/{sessionId}": {
2643
- implemented: false,
2644
- reason: SESSION_RECORD
2645
- },
2646
- "GET /SASjsApi/session/{sessionId}/log": {
2647
- implemented: false,
2648
- reason: SESSION_RECORD
2649
- },
2650
- "GET /SASjsApi/session/{sessionId}/output": {
2651
- implemented: false,
2652
- reason: SESSION_RECORD
2653
- },
2654
- "GET /SASjsApi/session/{sessionId}/state": {
2655
- implemented: false,
2656
- reason: SESSION_RECORD
2657
- },
2687
+ // The port takes the owner as a uid and falls back to a username, which is
2688
+ // what sasjs/server records today - a host hands its sessions through as they
2689
+ // are, and the comparison tries the identifier first.
2690
+ "GET /SASjsApi/session/{sessionId}": { implemented: true },
2691
+ "GET /SASjsApi/session/{sessionId}/log": { implemented: true },
2692
+ "GET /SASjsApi/session/{sessionId}/output": { implemented: true },
2693
+ "GET /SASjsApi/session/{sessionId}/state": { implemented: true },
2658
2694
  // STP - a stored program, on the runtime port
2659
2695
  "GET /SASjsApi/stp/execute": { implemented: false, reason: RUNTIME },
2660
2696
  "POST /SASjsApi/stp/execute": { implemented: false, reason: RUNTIME },
@@ -2770,6 +2806,24 @@ var SASjsApi = (() => {
2770
2806
  return void 0;
2771
2807
  };
2772
2808
 
2809
+ // src/drive/registry.ts
2810
+ var unpublishAppStream = async (registry, body) => {
2811
+ if (!body?.streamServiceName)
2812
+ throw new ApiError(400, '"streamServiceName" is required');
2813
+ if (!body.appLoc) throw new ApiError(400, '"appLoc" is required');
2814
+ if (!await registry.unpublish(body.appLoc))
2815
+ throw new ApiError(404, "No such app stream entry.");
2816
+ return { status: "success" };
2817
+ };
2818
+ var MemoryAppStreamRegistry = class {
2819
+ constructor(folders = ["/Public/myApp"]) {
2820
+ this.folders = new Set(folders);
2821
+ }
2822
+ async unpublish(appLoc) {
2823
+ return this.folders.delete(appLoc);
2824
+ }
2825
+ };
2826
+
2773
2827
  // src/host/dispatch.ts
2774
2828
  var SECONDS_IN_A_DAY = 86400;
2775
2829
  var validate = (route, request) => {
@@ -2811,6 +2865,14 @@ var SASjsApi = (() => {
2811
2865
  const query = request.query || {};
2812
2866
  const body = request.body || {};
2813
2867
  const params = request.params || {};
2868
+ const sessionRequired = () => {
2869
+ if (!services.session)
2870
+ throw new ApiError(
2871
+ 501,
2872
+ `This host has no session service, so it cannot answer ${routeId}.`
2873
+ );
2874
+ return services.session;
2875
+ };
2814
2876
  const required = () => {
2815
2877
  if (!records)
2816
2878
  throw new ApiError(
@@ -2908,8 +2970,55 @@ var SASjsApi = (() => {
2908
2970
  await drive.deploy(String(body.appLoc ?? ""), body.fileTree);
2909
2971
  return { status: 200, body: DEPLOY_SUCCESS };
2910
2972
  }
2973
+ case "DELETE /SASjsApi/drive/deploy":
2974
+ if (!services.registry)
2975
+ throw new ApiError(
2976
+ 501,
2977
+ `This host has no app stream registry, so it cannot answer ${routeId}.`
2978
+ );
2979
+ return {
2980
+ status: 200,
2981
+ body: await unpublishAppStream(services.registry, body)
2982
+ };
2911
2983
  case "GET /SASjsApi/drive/filetree":
2912
2984
  return { status: 200, body: await drive.tree(query._folderPath) };
2985
+ // ------------------------------------------------------------- sessions
2986
+ case "GET /SASjsApi/session/{sessionId}": {
2987
+ return {
2988
+ status: 200,
2989
+ body: await sessionRequired().summary(
2990
+ await caller(),
2991
+ String(params.sessionId ?? "")
2992
+ )
2993
+ };
2994
+ }
2995
+ case "GET /SASjsApi/session/{sessionId}/log": {
2996
+ return {
2997
+ status: 200,
2998
+ contents: await sessionRequired().log(
2999
+ await caller(),
3000
+ String(params.sessionId ?? "")
3001
+ )
3002
+ };
3003
+ }
3004
+ case "GET /SASjsApi/session/{sessionId}/output": {
3005
+ return {
3006
+ status: 200,
3007
+ contents: await sessionRequired().output(
3008
+ await caller(),
3009
+ String(params.sessionId ?? "")
3010
+ )
3011
+ };
3012
+ }
3013
+ case "GET /SASjsApi/session/{sessionId}/state": {
3014
+ return {
3015
+ status: 200,
3016
+ body: await sessionRequired().state(
3017
+ await caller(),
3018
+ String(params.sessionId ?? "")
3019
+ )
3020
+ };
3021
+ }
2913
3022
  // ------------------------------------------------------------- records
2914
3023
  //
2915
3024
  // The administration routes: users, groups, permission rules, clients and the
@@ -3125,6 +3234,11 @@ var SASjsApi = (() => {
3125
3234
  }
3126
3235
  };
3127
3236
  var HANDLED_ROUTES = [
3237
+ "DELETE /SASjsApi/drive/deploy",
3238
+ "GET /SASjsApi/session/{sessionId}",
3239
+ "GET /SASjsApi/session/{sessionId}/log",
3240
+ "GET /SASjsApi/session/{sessionId}/output",
3241
+ "GET /SASjsApi/session/{sessionId}/state",
3128
3242
  "POST /SASjsApi/code/lint",
3129
3243
  "POST /SASjsApi/code/format",
3130
3244
  "GET /SASjsApi/info/authorizedRoutes",
@@ -3662,13 +3776,50 @@ var SASjsApi = (() => {
3662
3776
  ]
3663
3777
  }
3664
3778
  },
3665
- // Two refusals are deliberately not pinned here yet: a `code` that is not a
3666
- // string, and one that is empty. The package answers 400 `"code" must be a
3667
- // string` / `"code" is not allowed to be empty` - the messages the server's
3668
- // schema produces - but sasjs/server's code controller carries no
3669
- // `@Response(400)` for these routes, so the document states 200 alone and the
3670
- // parity gate refuses a case answering anything else. The cases land when the
3671
- // decorators do.
3779
+ {
3780
+ id: "code-lint-unauthenticated",
3781
+ route: "POST /SASjsApi/code/lint",
3782
+ description: "refuses a caller with no token",
3783
+ request: { body: { code: "data _null_; run;" } },
3784
+ expect: { status: 401, error: "Unauthorized" }
3785
+ },
3786
+ {
3787
+ id: "code-lint-non-string",
3788
+ route: "POST /SASjsApi/code/lint",
3789
+ description: "refuses a code value that is not a string, rather than coercing it",
3790
+ request: {
3791
+ subject: { userId: "user-admin", clientId: "web_app" },
3792
+ body: { code: 123 }
3793
+ },
3794
+ expect: { status: 400, error: '"code" must be a string' }
3795
+ },
3796
+ {
3797
+ id: "code-lint-empty",
3798
+ route: "POST /SASjsApi/code/lint",
3799
+ description: "refuses an empty buffer, which is not the same as a missing one",
3800
+ request: {
3801
+ subject: { userId: "user-admin", clientId: "web_app" },
3802
+ body: { code: "" }
3803
+ },
3804
+ expect: { status: 400, error: '"code" is not allowed to be empty' }
3805
+ },
3806
+ {
3807
+ id: "code-format-unauthenticated",
3808
+ route: "POST /SASjsApi/code/format",
3809
+ description: "refuses a caller with no token",
3810
+ request: { body: { code: "data _null_; run;" } },
3811
+ expect: { status: 401, error: "Unauthorized" }
3812
+ },
3813
+ {
3814
+ id: "code-format-non-string",
3815
+ route: "POST /SASjsApi/code/format",
3816
+ description: "refuses a code value that is not a string",
3817
+ request: {
3818
+ subject: { userId: "user-admin", clientId: "web_app" },
3819
+ body: { code: 123 }
3820
+ },
3821
+ expect: { status: 400, error: '"code" must be a string' }
3822
+ },
3672
3823
  {
3673
3824
  id: "code-format-adds-the-header",
3674
3825
  route: "POST /SASjsApi/code/format",
@@ -4805,6 +4956,196 @@ var SASjsApi = (() => {
4805
4956
  }
4806
4957
  ];
4807
4958
 
4959
+ // src/corpus/deploy.ts
4960
+ var SUBJECT = { userId: "user-admin", clientId: "web_app" };
4961
+ var DEPLOY_CORPUS = [
4962
+ {
4963
+ id: "deploy-unpublish",
4964
+ route: "DELETE /SASjsApi/drive/deploy",
4965
+ description: "removes a published app stream and answers the status",
4966
+ request: {
4967
+ subject: SUBJECT,
4968
+ body: { streamServiceName: "myApp", appLoc: "/Public/myApp" }
4969
+ },
4970
+ expect: { status: 200, body: { status: "success" } }
4971
+ },
4972
+ {
4973
+ id: "deploy-unpublish-other-name",
4974
+ route: "DELETE /SASjsApi/drive/deploy",
4975
+ description: "removes the app by its folder when the name has been renamed",
4976
+ request: {
4977
+ subject: SUBJECT,
4978
+ body: { streamServiceName: "renamed-app", appLoc: "/Public/myApp" }
4979
+ },
4980
+ expect: { status: 200, body: { status: "success" } }
4981
+ },
4982
+ {
4983
+ id: "deploy-unpublish-no-stream-name",
4984
+ route: "DELETE /SASjsApi/drive/deploy",
4985
+ description: "refuses a body that does not say which stream to remove",
4986
+ request: { subject: SUBJECT, body: { appLoc: "/Public/myApp" } },
4987
+ expect: { status: 400, error: '"streamServiceName" is required' }
4988
+ },
4989
+ {
4990
+ id: "deploy-unpublish-no-app-loc",
4991
+ route: "DELETE /SASjsApi/drive/deploy",
4992
+ description: "refuses a body that does not say which folder to remove",
4993
+ request: { subject: SUBJECT, body: { streamServiceName: "myApp" } },
4994
+ expect: { status: 400, error: '"appLoc" is required' }
4995
+ },
4996
+ {
4997
+ id: "deploy-unpublish-unknown-folder",
4998
+ route: "DELETE /SASjsApi/drive/deploy",
4999
+ description: "answers 404 for a folder nothing is published at",
5000
+ request: {
5001
+ subject: SUBJECT,
5002
+ body: { streamServiceName: "myApp", appLoc: "/Public/not-an-app" }
5003
+ },
5004
+ expect: { status: 404, error: "No such app stream entry." }
5005
+ }
5006
+ ];
5007
+
5008
+ // src/corpus/session.ts
5009
+ var ADMIN = { userId: "user-admin", clientId: "web_app" };
5010
+ var RESTRICTED = { userId: "user-restricted", clientId: "web_app" };
5011
+ var notFound = (id) => `Session with ID '${id}' was not found.`;
5012
+ var UNAUTHORIZED = "Unauthorized";
5013
+ var SESSION_CORPUS = [
5014
+ // ---------------------------------------------------------------- summary
5015
+ {
5016
+ id: "session-summary-own",
5017
+ route: "GET /SASjsApi/session/{sessionId}",
5018
+ description: "answers the owner the summary of their own session",
5019
+ request: { subject: ADMIN, params: { sessionId: "sess-admin" } },
5020
+ expect: {
5021
+ status: 200,
5022
+ body: {
5023
+ sessionId: "sess-admin",
5024
+ state: "completed",
5025
+ pid: 4242,
5026
+ creationTimeStamp: "2026-11-07T09:00:00.000Z",
5027
+ deathTimeStamp: "2026-11-07T09:00:04.000Z",
5028
+ expiresAfterMins: 60
5029
+ }
5030
+ }
5031
+ },
5032
+ {
5033
+ id: "session-summary-as-admin",
5034
+ route: "GET /SASjsApi/session/{sessionId}",
5035
+ description: "answers an administrator another account's session",
5036
+ request: { subject: ADMIN, params: { sessionId: "sess-restricted" } },
5037
+ expect: {
5038
+ status: 200,
5039
+ body: {
5040
+ sessionId: "sess-restricted",
5041
+ state: "running",
5042
+ pid: 4243,
5043
+ creationTimeStamp: "2026-11-07T09:05:00.000Z"
5044
+ }
5045
+ }
5046
+ },
5047
+ {
5048
+ id: "session-summary-unauthenticated",
5049
+ route: "GET /SASjsApi/session/{sessionId}",
5050
+ description: "refuses a caller with no token",
5051
+ request: { params: { sessionId: "sess-admin" } },
5052
+ expect: { status: 401, error: UNAUTHORIZED }
5053
+ },
5054
+ {
5055
+ id: "session-summary-foreign",
5056
+ route: "GET /SASjsApi/session/{sessionId}",
5057
+ description: "answers 404 for another account's session, not 403",
5058
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5059
+ expect: { status: 404, error: notFound("sess-admin") }
5060
+ },
5061
+ {
5062
+ id: "session-summary-unknown",
5063
+ route: "GET /SASjsApi/session/{sessionId}",
5064
+ description: "answers the same 404 for a session that does not exist",
5065
+ request: { subject: ADMIN, params: { sessionId: "sess-nope" } },
5066
+ expect: { status: 404, error: notFound("sess-nope") }
5067
+ },
5068
+ // -------------------------------------------------------------------- log
5069
+ {
5070
+ id: "session-log-own",
5071
+ route: "GET /SASjsApi/session/{sessionId}/log",
5072
+ description: "answers the log this session wrote",
5073
+ request: { subject: ADMIN, params: { sessionId: "sess-admin" } },
5074
+ expect: {
5075
+ status: 200,
5076
+ contents: "NOTE: session started\nNOTE: session completed\n"
5077
+ }
5078
+ },
5079
+ {
5080
+ id: "session-log-nothing-written",
5081
+ route: "GET /SASjsApi/session/{sessionId}/log",
5082
+ description: "answers an empty body for a session that has written nothing",
5083
+ request: { subject: ADMIN, params: { sessionId: "sess-silent" } },
5084
+ expect: { status: 200, contents: "" }
5085
+ },
5086
+ {
5087
+ id: "session-log-unauthenticated",
5088
+ route: "GET /SASjsApi/session/{sessionId}/log",
5089
+ description: "refuses a caller with no token",
5090
+ request: { params: { sessionId: "sess-admin" } },
5091
+ expect: { status: 401, error: UNAUTHORIZED }
5092
+ },
5093
+ {
5094
+ id: "session-log-foreign",
5095
+ route: "GET /SASjsApi/session/{sessionId}/log",
5096
+ description: "will not read another account's log",
5097
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5098
+ expect: { status: 404, error: notFound("sess-admin") }
5099
+ },
5100
+ // ----------------------------------------------------------------- output
5101
+ {
5102
+ id: "session-output-own",
5103
+ route: "GET /SASjsApi/session/{sessionId}/output",
5104
+ description: "answers the webout this session produced",
5105
+ request: { subject: ADMIN, params: { sessionId: "sess-admin" } },
5106
+ expect: { status: 200, contents: "<html><body>42</body></html>" }
5107
+ },
5108
+ {
5109
+ id: "session-output-unauthenticated",
5110
+ route: "GET /SASjsApi/session/{sessionId}/output",
5111
+ description: "refuses a caller with no token",
5112
+ request: { params: { sessionId: "sess-admin" } },
5113
+ expect: { status: 401, error: UNAUTHORIZED }
5114
+ },
5115
+ {
5116
+ id: "session-output-foreign",
5117
+ route: "GET /SASjsApi/session/{sessionId}/output",
5118
+ description: "will not read another account's webout",
5119
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5120
+ expect: { status: 404, error: notFound("sess-admin") }
5121
+ },
5122
+ // ------------------------------------------------------------------ state
5123
+ {
5124
+ id: "session-state-own",
5125
+ route: "GET /SASjsApi/session/{sessionId}/state",
5126
+ description: "answers the run time's state for this session",
5127
+ request: { subject: RESTRICTED, params: { sessionId: "sess-restricted" } },
5128
+ expect: {
5129
+ status: 200,
5130
+ body: { state: "running", pid: 4243, lineCount: 3 }
5131
+ }
5132
+ },
5133
+ {
5134
+ id: "session-state-unauthenticated",
5135
+ route: "GET /SASjsApi/session/{sessionId}/state",
5136
+ description: "refuses a caller with no token",
5137
+ request: { params: { sessionId: "sess-restricted" } },
5138
+ expect: { status: 401, error: UNAUTHORIZED }
5139
+ },
5140
+ {
5141
+ id: "session-state-foreign",
5142
+ route: "GET /SASjsApi/session/{sessionId}/state",
5143
+ description: "will not report another account's session state",
5144
+ request: { subject: RESTRICTED, params: { sessionId: "sess-admin" } },
5145
+ expect: { status: 404, error: notFound("sess-admin") }
5146
+ }
5147
+ ];
5148
+
4808
5149
  // src/corpus/records.ts
4809
5150
  var SIGNED_IN_TOKENS = [
4810
5151
  {
@@ -4867,8 +5208,8 @@ var SASjsApi = (() => {
4867
5208
  }
4868
5209
  ]
4869
5210
  };
4870
- var ADMIN = "admin-access";
4871
- var RESTRICTED = "user-access";
5211
+ var ADMIN2 = "admin-access";
5212
+ var RESTRICTED2 = "user-access";
4872
5213
  var RESTRICTED_UID = "user-restricted";
4873
5214
  var ADMIN_UID = "user-admin";
4874
5215
  var RECORD_CORPUS = [
@@ -4878,7 +5219,7 @@ var SASjsApi = (() => {
4878
5219
  route: "GET /SASjsApi/user",
4879
5220
  description: "lists the accounts any signed-in caller may see",
4880
5221
  identity: ADMIN_SIGNED_IN,
4881
- request: { token: ADMIN },
5222
+ request: { token: ADMIN2 },
4882
5223
  expect: {
4883
5224
  status: 200,
4884
5225
  body: [
@@ -4902,7 +5243,7 @@ var SASjsApi = (() => {
4902
5243
  route: "GET /SASjsApi/user/{uid}",
4903
5244
  description: "returns the caller their own details, with the auto-exec code",
4904
5245
  identity: ADMIN_SIGNED_IN,
4905
- request: { token: RESTRICTED, params: { uid: RESTRICTED_UID } },
5246
+ request: { token: RESTRICTED2, params: { uid: RESTRICTED_UID } },
4906
5247
  expect: {
4907
5248
  status: 200,
4908
5249
  partial: {
@@ -4927,7 +5268,7 @@ var SASjsApi = (() => {
4927
5268
  route: "GET /SASjsApi/user/{uid}",
4928
5269
  description: "answers 404 for an identifier no account has",
4929
5270
  identity: ADMIN_SIGNED_IN,
4930
- request: { token: ADMIN, params: { uid: "user-none" } },
5271
+ request: { token: ADMIN2, params: { uid: "user-none" } },
4931
5272
  expect: { status: 404, error: "User is not found." }
4932
5273
  },
4933
5274
  {
@@ -4935,7 +5276,7 @@ var SASjsApi = (() => {
4935
5276
  route: "GET /SASjsApi/user/by/username/{username}",
4936
5277
  description: "finds an account by username",
4937
5278
  identity: ADMIN_SIGNED_IN,
4938
- request: { token: ADMIN, params: { username: "user2" } },
5279
+ request: { token: ADMIN2, params: { username: "user2" } },
4939
5280
  expect: {
4940
5281
  status: 200,
4941
5282
  partial: { uid: RESTRICTED_UID, username: "user2", isAdmin: false }
@@ -4954,7 +5295,7 @@ var SASjsApi = (() => {
4954
5295
  route: "GET /SASjsApi/user/by/username/{username}",
4955
5296
  description: "answers 404 for a username no account has",
4956
5297
  identity: ADMIN_SIGNED_IN,
4957
- request: { token: ADMIN, params: { username: "nobody" } },
5298
+ request: { token: ADMIN2, params: { username: "nobody" } },
4958
5299
  expect: { status: 404, error: "User is not found." }
4959
5300
  },
4960
5301
  {
@@ -4963,7 +5304,7 @@ var SASjsApi = (() => {
4963
5304
  description: "creates an account, sanitised, and reports it",
4964
5305
  identity: ADMIN_SIGNED_IN,
4965
5306
  request: {
4966
- token: ADMIN,
5307
+ token: ADMIN2,
4967
5308
  body: {
4968
5309
  username: "newuser",
4969
5310
  displayName: " New User ",
@@ -4988,7 +5329,7 @@ var SASjsApi = (() => {
4988
5329
  description: "refuses a creation by a caller who is not an administrator",
4989
5330
  identity: ADMIN_SIGNED_IN,
4990
5331
  request: {
4991
- token: RESTRICTED,
5332
+ token: RESTRICTED2,
4992
5333
  body: {
4993
5334
  username: "newuser",
4994
5335
  displayName: "New User",
@@ -5011,7 +5352,7 @@ var SASjsApi = (() => {
5011
5352
  description: "refuses a username that is taken",
5012
5353
  identity: ADMIN_SIGNED_IN,
5013
5354
  request: {
5014
- token: ADMIN,
5355
+ token: ADMIN2,
5015
5356
  body: { username: "user2", displayName: "Another", password: "newpass" }
5016
5357
  },
5017
5358
  expect: { status: 409, error: "Username already exists." }
@@ -5022,7 +5363,7 @@ var SASjsApi = (() => {
5022
5363
  description: "changes an account's display name",
5023
5364
  identity: ADMIN_SIGNED_IN,
5024
5365
  request: {
5025
- token: ADMIN,
5366
+ token: ADMIN2,
5026
5367
  params: { uid: RESTRICTED_UID },
5027
5368
  body: {
5028
5369
  displayName: "Renamed User",
@@ -5041,7 +5382,7 @@ var SASjsApi = (() => {
5041
5382
  description: "refuses a change to somebody else's account",
5042
5383
  identity: ADMIN_SIGNED_IN,
5043
5384
  request: {
5044
- token: RESTRICTED,
5385
+ token: RESTRICTED2,
5045
5386
  params: { uid: ADMIN_UID },
5046
5387
  body: { displayName: "Hijacked", username: "admin", password: "admin123" }
5047
5388
  },
@@ -5053,7 +5394,7 @@ var SASjsApi = (() => {
5053
5394
  description: "answers 404 with the identifier it looked up",
5054
5395
  identity: ADMIN_SIGNED_IN,
5055
5396
  request: {
5056
- token: ADMIN,
5397
+ token: ADMIN2,
5057
5398
  params: { uid: "user-none" },
5058
5399
  body: { displayName: "Nobody", username: "nobody", password: "none123" }
5059
5400
  },
@@ -5065,7 +5406,7 @@ var SASjsApi = (() => {
5065
5406
  description: "refuses a username change on a provider-managed account",
5066
5407
  identity: PROVIDER_MANAGED,
5067
5408
  request: {
5068
- token: ADMIN,
5409
+ token: ADMIN2,
5069
5410
  params: { uid: "user-directory" },
5070
5411
  body: {
5071
5412
  username: "renamed",
@@ -5084,7 +5425,7 @@ var SASjsApi = (() => {
5084
5425
  description: "refuses a username another account already has",
5085
5426
  identity: ADMIN_SIGNED_IN,
5086
5427
  request: {
5087
- token: ADMIN,
5428
+ token: ADMIN2,
5088
5429
  params: { uid: RESTRICTED_UID },
5089
5430
  body: { username: "admin", displayName: "Demo User", password: "user123" }
5090
5431
  },
@@ -5096,7 +5437,7 @@ var SASjsApi = (() => {
5096
5437
  description: "changes an account looked up by username",
5097
5438
  identity: ADMIN_SIGNED_IN,
5098
5439
  request: {
5099
- token: ADMIN,
5440
+ token: ADMIN2,
5100
5441
  params: { username: "user2" },
5101
5442
  body: {
5102
5443
  isActive: false,
@@ -5113,7 +5454,7 @@ var SASjsApi = (() => {
5113
5454
  description: "refuses a change to another account by username",
5114
5455
  identity: ADMIN_SIGNED_IN,
5115
5456
  request: {
5116
- token: RESTRICTED,
5457
+ token: RESTRICTED2,
5117
5458
  params: { username: "admin" },
5118
5459
  body: { displayName: "Hijacked", username: "admin", password: "admin123" }
5119
5460
  },
@@ -5125,7 +5466,7 @@ var SASjsApi = (() => {
5125
5466
  description: "answers 404 with the username it looked up",
5126
5467
  identity: ADMIN_SIGNED_IN,
5127
5468
  request: {
5128
- token: ADMIN,
5469
+ token: ADMIN2,
5129
5470
  params: { username: "nobody" },
5130
5471
  body: { displayName: "Nobody", username: "nobody", password: "none123" }
5131
5472
  },
@@ -5137,7 +5478,7 @@ var SASjsApi = (() => {
5137
5478
  description: "refuses a display-name change on a provider-managed account",
5138
5479
  identity: PROVIDER_MANAGED,
5139
5480
  request: {
5140
- token: ADMIN,
5481
+ token: ADMIN2,
5141
5482
  params: { username: "diruser" },
5142
5483
  body: { displayName: "Renamed", username: "diruser", password: "dir123" }
5143
5484
  },
@@ -5152,7 +5493,7 @@ var SASjsApi = (() => {
5152
5493
  description: "refuses a username another account already has",
5153
5494
  identity: ADMIN_SIGNED_IN,
5154
5495
  request: {
5155
- token: ADMIN,
5496
+ token: ADMIN2,
5156
5497
  params: { username: "user2" },
5157
5498
  body: { username: "admin", displayName: "Demo User", password: "user123" }
5158
5499
  },
@@ -5163,7 +5504,7 @@ var SASjsApi = (() => {
5163
5504
  route: "DELETE /SASjsApi/user/{uid}",
5164
5505
  description: "deletes an account as an administrator, without a password",
5165
5506
  identity: ADMIN_SIGNED_IN,
5166
- request: { token: ADMIN, params: { uid: RESTRICTED_UID }, body: {} },
5507
+ request: { token: ADMIN2, params: { uid: RESTRICTED_UID }, body: {} },
5167
5508
  expect: { status: 200, body: "Account Deleted!" }
5168
5509
  },
5169
5510
  {
@@ -5171,7 +5512,7 @@ var SASjsApi = (() => {
5171
5512
  route: "DELETE /SASjsApi/user/{uid}",
5172
5513
  description: "refuses a deletion of somebody else's account",
5173
5514
  identity: ADMIN_SIGNED_IN,
5174
- request: { token: RESTRICTED, params: { uid: ADMIN_UID }, body: {} },
5515
+ request: { token: RESTRICTED2, params: { uid: ADMIN_UID }, body: {} },
5175
5516
  expect: { status: 401, error: "Admin account required" }
5176
5517
  },
5177
5518
  {
@@ -5179,7 +5520,7 @@ var SASjsApi = (() => {
5179
5520
  route: "DELETE /SASjsApi/user/{uid}",
5180
5521
  description: "answers 404 for an identifier no account has",
5181
5522
  identity: ADMIN_SIGNED_IN,
5182
- request: { token: ADMIN, params: { uid: "user-none" }, body: {} },
5523
+ request: { token: ADMIN2, params: { uid: "user-none" }, body: {} },
5183
5524
  expect: { status: 404, error: "User is not found." }
5184
5525
  },
5185
5526
  {
@@ -5188,7 +5529,7 @@ var SASjsApi = (() => {
5188
5529
  description: "deletes an account looked up by username",
5189
5530
  identity: ADMIN_SIGNED_IN,
5190
5531
  request: {
5191
- token: ADMIN,
5532
+ token: ADMIN2,
5192
5533
  params: { username: "user2" },
5193
5534
  body: {}
5194
5535
  },
@@ -5200,7 +5541,7 @@ var SASjsApi = (() => {
5200
5541
  description: "refuses a self-deletion whose password is wrong",
5201
5542
  identity: ADMIN_SIGNED_IN,
5202
5543
  request: {
5203
- token: RESTRICTED,
5544
+ token: RESTRICTED2,
5204
5545
  params: { username: "user2" },
5205
5546
  body: { password: "wrong123" }
5206
5547
  },
@@ -5211,7 +5552,7 @@ var SASjsApi = (() => {
5211
5552
  route: "DELETE /SASjsApi/user/by/username/{username}",
5212
5553
  description: "answers 404 for a username no account has",
5213
5554
  identity: ADMIN_SIGNED_IN,
5214
- request: { token: ADMIN, params: { username: "nobody" }, body: {} },
5555
+ request: { token: ADMIN2, params: { username: "nobody" }, body: {} },
5215
5556
  expect: { status: 404, error: "User is not found." }
5216
5557
  },
5217
5558
  // ----------------------------------------------------------------- groups
@@ -5220,7 +5561,7 @@ var SASjsApi = (() => {
5220
5561
  route: "GET /SASjsApi/group",
5221
5562
  description: "lists the groups",
5222
5563
  identity: ADMIN_SIGNED_IN,
5223
- request: { token: RESTRICTED },
5564
+ request: { token: RESTRICTED2 },
5224
5565
  expect: {
5225
5566
  status: 200,
5226
5567
  body: [
@@ -5242,7 +5583,7 @@ var SASjsApi = (() => {
5242
5583
  route: "GET /SASjsApi/group/{uid}",
5243
5584
  description: "returns a group and its members",
5244
5585
  identity: ADMIN_SIGNED_IN,
5245
- request: { token: RESTRICTED, params: { uid: "group-admins" } },
5586
+ request: { token: RESTRICTED2, params: { uid: "group-admins" } },
5246
5587
  expect: {
5247
5588
  status: 200,
5248
5589
  partial: {
@@ -5265,7 +5606,7 @@ var SASjsApi = (() => {
5265
5606
  route: "GET /SASjsApi/group/{uid}",
5266
5607
  description: "answers 404 for an identifier no group has",
5267
5608
  identity: ADMIN_SIGNED_IN,
5268
- request: { token: ADMIN, params: { uid: "group-none" } },
5609
+ request: { token: ADMIN2, params: { uid: "group-none" } },
5269
5610
  expect: { status: 404, error: "Group not found." }
5270
5611
  },
5271
5612
  {
@@ -5273,7 +5614,7 @@ var SASjsApi = (() => {
5273
5614
  route: "GET /SASjsApi/group/by/groupname/{name}",
5274
5615
  description: "finds a group by name",
5275
5616
  identity: ADMIN_SIGNED_IN,
5276
- request: { token: ADMIN, params: { name: "DCGroup" } },
5617
+ request: { token: ADMIN2, params: { name: "DCGroup" } },
5277
5618
  expect: {
5278
5619
  status: 200,
5279
5620
  partial: { uid: "group-data-controller", name: "DCGroup" }
@@ -5284,7 +5625,7 @@ var SASjsApi = (() => {
5284
5625
  route: "GET /SASjsApi/group/by/groupname/{name}",
5285
5626
  description: "answers 404 for a name no group has",
5286
5627
  identity: ADMIN_SIGNED_IN,
5287
- request: { token: ADMIN, params: { name: "nope" } },
5628
+ request: { token: ADMIN2, params: { name: "nope" } },
5288
5629
  expect: { status: 404, error: "Group not found." }
5289
5630
  },
5290
5631
  {
@@ -5293,7 +5634,7 @@ var SASjsApi = (() => {
5293
5634
  description: "creates a group with no members",
5294
5635
  identity: ADMIN_SIGNED_IN,
5295
5636
  request: {
5296
- token: ADMIN,
5637
+ token: ADMIN2,
5297
5638
  body: { name: "NewGroup", description: "A new group", isActive: true }
5298
5639
  },
5299
5640
  expect: {
@@ -5312,7 +5653,7 @@ var SASjsApi = (() => {
5312
5653
  description: "refuses a creation by a caller who is not an administrator",
5313
5654
  identity: ADMIN_SIGNED_IN,
5314
5655
  request: {
5315
- token: RESTRICTED,
5656
+ token: RESTRICTED2,
5316
5657
  body: { name: "NewGroup", description: "x" }
5317
5658
  },
5318
5659
  expect: { status: 401, error: "Admin account required" }
@@ -5322,7 +5663,7 @@ var SASjsApi = (() => {
5322
5663
  route: "POST /SASjsApi/group",
5323
5664
  description: "refuses a name that is taken",
5324
5665
  identity: ADMIN_SIGNED_IN,
5325
- request: { token: ADMIN, body: { name: "DCGroup", description: "again" } },
5666
+ request: { token: ADMIN2, body: { name: "DCGroup", description: "again" } },
5326
5667
  expect: { status: 409, error: "Group name already exists." }
5327
5668
  },
5328
5669
  {
@@ -5331,7 +5672,7 @@ var SASjsApi = (() => {
5331
5672
  description: "adds a member and returns the group",
5332
5673
  identity: ADMIN_SIGNED_IN,
5333
5674
  request: {
5334
- token: ADMIN,
5675
+ token: ADMIN2,
5335
5676
  params: { groupUid: "group-admins", userUid: RESTRICTED_UID }
5336
5677
  },
5337
5678
  expect: {
@@ -5345,7 +5686,7 @@ var SASjsApi = (() => {
5345
5686
  description: "refuses a change to the reserved public group",
5346
5687
  identity: PROVIDER_MANAGED,
5347
5688
  request: {
5348
- token: ADMIN,
5689
+ token: ADMIN2,
5349
5690
  params: { groupUid: "group-public", userUid: RESTRICTED_UID }
5350
5691
  },
5351
5692
  expect: { status: 400, error: "Can't add/remove user to 'public' group." }
@@ -5356,7 +5697,7 @@ var SASjsApi = (() => {
5356
5697
  description: "refuses a membership change by a non-administrator",
5357
5698
  identity: ADMIN_SIGNED_IN,
5358
5699
  request: {
5359
- token: RESTRICTED,
5700
+ token: RESTRICTED2,
5360
5701
  params: { groupUid: "group-admins", userUid: RESTRICTED_UID }
5361
5702
  },
5362
5703
  expect: { status: 401, error: "Admin account required" }
@@ -5367,7 +5708,7 @@ var SASjsApi = (() => {
5367
5708
  description: "answers 404 for a group that does not exist",
5368
5709
  identity: ADMIN_SIGNED_IN,
5369
5710
  request: {
5370
- token: ADMIN,
5711
+ token: ADMIN2,
5371
5712
  params: { groupUid: "group-none", userUid: RESTRICTED_UID }
5372
5713
  },
5373
5714
  expect: { status: 404, error: "Group not found." }
@@ -5378,7 +5719,7 @@ var SASjsApi = (() => {
5378
5719
  description: "refuses a membership change on a provider-managed group",
5379
5720
  identity: PROVIDER_MANAGED,
5380
5721
  request: {
5381
- token: ADMIN,
5722
+ token: ADMIN2,
5382
5723
  params: { groupUid: "group-directory", userUid: RESTRICTED_UID }
5383
5724
  },
5384
5725
  expect: {
@@ -5392,7 +5733,7 @@ var SASjsApi = (() => {
5392
5733
  description: "removes a member and returns the group",
5393
5734
  identity: ADMIN_SIGNED_IN,
5394
5735
  request: {
5395
- token: ADMIN,
5736
+ token: ADMIN2,
5396
5737
  params: { groupUid: "group-data-controller", userUid: RESTRICTED_UID }
5397
5738
  },
5398
5739
  expect: {
@@ -5406,7 +5747,7 @@ var SASjsApi = (() => {
5406
5747
  description: "refuses a membership change for a provider-managed user",
5407
5748
  identity: PROVIDER_MANAGED,
5408
5749
  request: {
5409
- token: ADMIN,
5750
+ token: ADMIN2,
5410
5751
  params: { groupUid: "group-admins", userUid: "user-directory" }
5411
5752
  },
5412
5753
  expect: {
@@ -5420,7 +5761,7 @@ var SASjsApi = (() => {
5420
5761
  description: "refuses a membership change for a provider-managed user",
5421
5762
  identity: PROVIDER_MANAGED,
5422
5763
  request: {
5423
- token: ADMIN,
5764
+ token: ADMIN2,
5424
5765
  params: { groupUid: "group-admins", userUid: "user-directory" }
5425
5766
  },
5426
5767
  expect: {
@@ -5434,7 +5775,7 @@ var SASjsApi = (() => {
5434
5775
  description: "refuses a change to the reserved public group",
5435
5776
  identity: PROVIDER_MANAGED,
5436
5777
  request: {
5437
- token: ADMIN,
5778
+ token: ADMIN2,
5438
5779
  params: { groupUid: "group-public", userUid: RESTRICTED_UID }
5439
5780
  },
5440
5781
  expect: { status: 400, error: "Can't add/remove user to 'public' group." }
@@ -5445,7 +5786,7 @@ var SASjsApi = (() => {
5445
5786
  description: "refuses a membership change by a non-administrator",
5446
5787
  identity: ADMIN_SIGNED_IN,
5447
5788
  request: {
5448
- token: RESTRICTED,
5789
+ token: RESTRICTED2,
5449
5790
  params: { groupUid: "group-data-controller", userUid: RESTRICTED_UID }
5450
5791
  },
5451
5792
  expect: { status: 401, error: "Admin account required" }
@@ -5456,7 +5797,7 @@ var SASjsApi = (() => {
5456
5797
  description: "answers 404 for a member that does not exist",
5457
5798
  identity: ADMIN_SIGNED_IN,
5458
5799
  request: {
5459
- token: ADMIN,
5800
+ token: ADMIN2,
5460
5801
  params: { groupUid: "group-admins", userUid: "user-none" }
5461
5802
  },
5462
5803
  expect: { status: 404, error: "User not found." }
@@ -5467,7 +5808,7 @@ var SASjsApi = (() => {
5467
5808
  description: "refuses a membership change on a provider-managed group",
5468
5809
  identity: PROVIDER_MANAGED,
5469
5810
  request: {
5470
- token: ADMIN,
5811
+ token: ADMIN2,
5471
5812
  params: { groupUid: "group-directory", userUid: RESTRICTED_UID }
5472
5813
  },
5473
5814
  expect: {
@@ -5480,7 +5821,7 @@ var SASjsApi = (() => {
5480
5821
  route: "DELETE /SASjsApi/group/{uid}",
5481
5822
  description: "deletes a group",
5482
5823
  identity: ADMIN_SIGNED_IN,
5483
- request: { token: ADMIN, params: { uid: "group-data-controller" } },
5824
+ request: { token: ADMIN2, params: { uid: "group-data-controller" } },
5484
5825
  expect: { status: 200 }
5485
5826
  },
5486
5827
  {
@@ -5488,7 +5829,7 @@ var SASjsApi = (() => {
5488
5829
  route: "DELETE /SASjsApi/group/{uid}",
5489
5830
  description: "refuses a deletion by a caller who is not an administrator",
5490
5831
  identity: ADMIN_SIGNED_IN,
5491
- request: { token: RESTRICTED, params: { uid: "group-admins" } },
5832
+ request: { token: RESTRICTED2, params: { uid: "group-admins" } },
5492
5833
  expect: { status: 401, error: "Admin account required" }
5493
5834
  },
5494
5835
  {
@@ -5496,7 +5837,7 @@ var SASjsApi = (() => {
5496
5837
  route: "DELETE /SASjsApi/group/{uid}",
5497
5838
  description: "answers 404 for an identifier no group has",
5498
5839
  identity: ADMIN_SIGNED_IN,
5499
- request: { token: ADMIN, params: { uid: "group-none" } },
5840
+ request: { token: ADMIN2, params: { uid: "group-none" } },
5500
5841
  expect: { status: 404, error: "Group not found." }
5501
5842
  },
5502
5843
  // ------------------------------------------------------------ permissions
@@ -5505,7 +5846,7 @@ var SASjsApi = (() => {
5505
5846
  route: "GET /SASjsApi/info/authorizedRoutes",
5506
5847
  description: "answers the routes that accept a rule, to any caller",
5507
5848
  identity: ADMIN_SIGNED_IN,
5508
- request: { token: RESTRICTED },
5849
+ request: { token: RESTRICTED2 },
5509
5850
  expect: {
5510
5851
  status: 200,
5511
5852
  body: {
@@ -5529,7 +5870,7 @@ var SASjsApi = (() => {
5529
5870
  route: "GET /SASjsApi/info/myAuthorizedRoutes",
5530
5871
  description: "answers every route to an administrator",
5531
5872
  identity: ADMIN_SIGNED_IN,
5532
- request: { token: ADMIN },
5873
+ request: { token: ADMIN2 },
5533
5874
  expect: {
5534
5875
  status: 200,
5535
5876
  body: {
@@ -5553,7 +5894,7 @@ var SASjsApi = (() => {
5553
5894
  route: "GET /SASjsApi/info/myAuthorizedRoutes",
5554
5895
  description: "answers a caller only the routes its rules grant",
5555
5896
  identity: ADMIN_SIGNED_IN,
5556
- request: { token: RESTRICTED },
5897
+ request: { token: RESTRICTED2 },
5557
5898
  expect: {
5558
5899
  status: 200,
5559
5900
  // /AppStream is visible whatever the rules say; the two grants come from
@@ -5569,7 +5910,7 @@ var SASjsApi = (() => {
5569
5910
  route: "GET /SASjsApi/permission",
5570
5911
  description: "returns every rule to an administrator",
5571
5912
  identity: ADMIN_SIGNED_IN,
5572
- request: { token: ADMIN },
5913
+ request: { token: ADMIN2 },
5573
5914
  expect: {
5574
5915
  status: 200,
5575
5916
  body: [
@@ -5602,7 +5943,7 @@ var SASjsApi = (() => {
5602
5943
  route: "GET /SASjsApi/permission",
5603
5944
  description: "returns a caller only their own rules and their groups'",
5604
5945
  identity: ADMIN_SIGNED_IN,
5605
- request: { token: RESTRICTED },
5946
+ request: { token: RESTRICTED2 },
5606
5947
  expect: {
5607
5948
  status: 200,
5608
5949
  body: [
@@ -5644,7 +5985,7 @@ var SASjsApi = (() => {
5644
5985
  description: "creates a rule for a user",
5645
5986
  identity: ADMIN_SIGNED_IN,
5646
5987
  request: {
5647
- token: ADMIN,
5988
+ token: ADMIN2,
5648
5989
  body: {
5649
5990
  path: "/SASjsApi/drive/folder",
5650
5991
  type: "Route",
@@ -5664,7 +6005,7 @@ var SASjsApi = (() => {
5664
6005
  description: "creates a rule for a group",
5665
6006
  identity: ADMIN_SIGNED_IN,
5666
6007
  request: {
5667
- token: ADMIN,
6008
+ token: ADMIN2,
5668
6009
  body: {
5669
6010
  path: "/SASjsApi/drive/rename",
5670
6011
  type: "Route",
@@ -5688,7 +6029,7 @@ var SASjsApi = (() => {
5688
6029
  description: "refuses a principal type that is neither a user nor a group",
5689
6030
  identity: ADMIN_SIGNED_IN,
5690
6031
  request: {
5691
- token: ADMIN,
6032
+ token: ADMIN2,
5692
6033
  body: {
5693
6034
  path: "/SASjsApi/drive/deploy",
5694
6035
  type: "Route",
@@ -5708,7 +6049,7 @@ var SASjsApi = (() => {
5708
6049
  description: "refuses a rule assigned to an administrator",
5709
6050
  identity: ADMIN_SIGNED_IN,
5710
6051
  request: {
5711
- token: ADMIN,
6052
+ token: ADMIN2,
5712
6053
  body: {
5713
6054
  path: "/SASjsApi/drive/folder",
5714
6055
  type: "Route",
@@ -5728,7 +6069,7 @@ var SASjsApi = (() => {
5728
6069
  description: "refuses a creation by a non-administrator",
5729
6070
  identity: ADMIN_SIGNED_IN,
5730
6071
  request: {
5731
- token: RESTRICTED,
6072
+ token: RESTRICTED2,
5732
6073
  body: {
5733
6074
  path: "/SASjsApi/drive/folder",
5734
6075
  type: "Route",
@@ -5745,7 +6086,7 @@ var SASjsApi = (() => {
5745
6086
  description: "answers 404 for a principal that does not exist",
5746
6087
  identity: ADMIN_SIGNED_IN,
5747
6088
  request: {
5748
- token: ADMIN,
6089
+ token: ADMIN2,
5749
6090
  body: {
5750
6091
  path: "/SASjsApi/drive/rename",
5751
6092
  type: "Route",
@@ -5762,7 +6103,7 @@ var SASjsApi = (() => {
5762
6103
  description: "refuses a group rule that already exists",
5763
6104
  identity: ADMIN_SIGNED_IN,
5764
6105
  request: {
5765
- token: ADMIN,
6106
+ token: ADMIN2,
5766
6107
  body: {
5767
6108
  path: "/SASjsApi/drive/file",
5768
6109
  type: "Route",
@@ -5782,7 +6123,7 @@ var SASjsApi = (() => {
5782
6123
  description: "refuses a rule that already exists",
5783
6124
  identity: ADMIN_SIGNED_IN,
5784
6125
  request: {
5785
- token: ADMIN,
6126
+ token: ADMIN2,
5786
6127
  body: {
5787
6128
  path: "/SASjsApi/code/execute",
5788
6129
  type: "Route",
@@ -5802,7 +6143,7 @@ var SASjsApi = (() => {
5802
6143
  description: "flips a rule from Deny to Grant",
5803
6144
  identity: ADMIN_SIGNED_IN,
5804
6145
  request: {
5805
- token: ADMIN,
6146
+ token: ADMIN2,
5806
6147
  params: { uid: "rule-code" },
5807
6148
  body: { setting: "Grant" }
5808
6149
  },
@@ -5814,7 +6155,7 @@ var SASjsApi = (() => {
5814
6155
  description: "refuses a change by a non-administrator",
5815
6156
  identity: ADMIN_SIGNED_IN,
5816
6157
  request: {
5817
- token: RESTRICTED,
6158
+ token: RESTRICTED2,
5818
6159
  params: { uid: "rule-code" },
5819
6160
  body: { setting: "Grant" }
5820
6161
  },
@@ -5826,7 +6167,7 @@ var SASjsApi = (() => {
5826
6167
  description: "answers 404 for a rule that does not exist",
5827
6168
  identity: ADMIN_SIGNED_IN,
5828
6169
  request: {
5829
- token: ADMIN,
6170
+ token: ADMIN2,
5830
6171
  params: { uid: "rule-none" },
5831
6172
  body: { setting: "Grant" }
5832
6173
  },
@@ -5837,7 +6178,7 @@ var SASjsApi = (() => {
5837
6178
  route: "DELETE /SASjsApi/permission/{uid}",
5838
6179
  description: "deletes a rule",
5839
6180
  identity: ADMIN_SIGNED_IN,
5840
- request: { token: ADMIN, params: { uid: "rule-code" } },
6181
+ request: { token: ADMIN2, params: { uid: "rule-code" } },
5841
6182
  expect: { status: 200, body: "Permission Deleted!" }
5842
6183
  },
5843
6184
  {
@@ -5845,7 +6186,7 @@ var SASjsApi = (() => {
5845
6186
  route: "DELETE /SASjsApi/permission/{uid}",
5846
6187
  description: "refuses a deletion by a non-administrator",
5847
6188
  identity: ADMIN_SIGNED_IN,
5848
- request: { token: RESTRICTED, params: { uid: "rule-code" } },
6189
+ request: { token: RESTRICTED2, params: { uid: "rule-code" } },
5849
6190
  expect: { status: 401, error: "Admin account required" }
5850
6191
  },
5851
6192
  {
@@ -5853,7 +6194,7 @@ var SASjsApi = (() => {
5853
6194
  route: "DELETE /SASjsApi/permission/{uid}",
5854
6195
  description: "answers 404 for a rule that does not exist",
5855
6196
  identity: ADMIN_SIGNED_IN,
5856
- request: { token: ADMIN, params: { uid: "rule-none" } },
6197
+ request: { token: ADMIN2, params: { uid: "rule-none" } },
5857
6198
  expect: { status: 404, error: "Permission not found." }
5858
6199
  },
5859
6200
  // --------------------------------------------------------------- clients
@@ -5862,7 +6203,7 @@ var SASjsApi = (() => {
5862
6203
  route: "GET /SASjsApi/client",
5863
6204
  description: "lists the registered clients",
5864
6205
  identity: ADMIN_SIGNED_IN,
5865
- request: { token: ADMIN },
6206
+ request: { token: ADMIN2 },
5866
6207
  expect: {
5867
6208
  status: 200,
5868
6209
  body: [
@@ -5886,7 +6227,7 @@ var SASjsApi = (() => {
5886
6227
  route: "GET /SASjsApi/client",
5887
6228
  description: "refuses the list to a caller who is not an administrator",
5888
6229
  identity: ADMIN_SIGNED_IN,
5889
- request: { token: RESTRICTED },
6230
+ request: { token: RESTRICTED2 },
5890
6231
  expect: { status: 401, error: "Admin account required" }
5891
6232
  },
5892
6233
  {
@@ -5895,7 +6236,7 @@ var SASjsApi = (() => {
5895
6236
  description: "registers a client",
5896
6237
  identity: ADMIN_SIGNED_IN,
5897
6238
  request: {
5898
- token: ADMIN,
6239
+ token: ADMIN2,
5899
6240
  body: {
5900
6241
  clientId: "new_app",
5901
6242
  clientSecret: "new_secret",
@@ -5919,7 +6260,7 @@ var SASjsApi = (() => {
5919
6260
  description: "applies the schema defaults when the lifetimes are omitted",
5920
6261
  identity: ADMIN_SIGNED_IN,
5921
6262
  request: {
5922
- token: ADMIN,
6263
+ token: ADMIN2,
5923
6264
  body: { clientId: "default_app", clientSecret: "default_secret" }
5924
6265
  },
5925
6266
  expect: {
@@ -5938,7 +6279,7 @@ var SASjsApi = (() => {
5938
6279
  description: "refuses a registration by a non-administrator",
5939
6280
  identity: ADMIN_SIGNED_IN,
5940
6281
  request: {
5941
- token: RESTRICTED,
6282
+ token: RESTRICTED2,
5942
6283
  body: {
5943
6284
  clientId: "new_app",
5944
6285
  clientSecret: "new_secret",
@@ -5954,7 +6295,7 @@ var SASjsApi = (() => {
5954
6295
  description: "refuses a client id that is registered, with the 403 the server sends",
5955
6296
  identity: ADMIN_SIGNED_IN,
5956
6297
  request: {
5957
- token: ADMIN,
6298
+ token: ADMIN2,
5958
6299
  body: {
5959
6300
  clientId: "web_app",
5960
6301
  clientSecret: "other",
@@ -5971,7 +6312,7 @@ var SASjsApi = (() => {
5971
6312
  description: "changes the caller’s own password",
5972
6313
  identity: ADMIN_SIGNED_IN,
5973
6314
  request: {
5974
- token: RESTRICTED,
6315
+ token: RESTRICTED2,
5975
6316
  body: { currentPassword: "user123", newPassword: "changed123" }
5976
6317
  },
5977
6318
  expect: { status: 204 }
@@ -5981,7 +6322,7 @@ var SASjsApi = (() => {
5981
6322
  route: "PATCH /SASjsApi/auth/updatePassword",
5982
6323
  description: "refuses a body without the current password",
5983
6324
  identity: ADMIN_SIGNED_IN,
5984
- request: { token: RESTRICTED, body: { newPassword: "changed123" } },
6325
+ request: { token: RESTRICTED2, body: { newPassword: "changed123" } },
5985
6326
  expect: { status: 400, error: '"currentPassword" is required' }
5986
6327
  },
5987
6328
  {
@@ -6000,7 +6341,7 @@ var SASjsApi = (() => {
6000
6341
  description: "refuses a change whose current password is wrong",
6001
6342
  identity: ADMIN_SIGNED_IN,
6002
6343
  request: {
6003
- token: RESTRICTED,
6344
+ token: RESTRICTED2,
6004
6345
  body: { currentPassword: "wrong", newPassword: "changed123" }
6005
6346
  },
6006
6347
  expect: { status: 403, error: "Invalid current password!" }
@@ -6021,6 +6362,112 @@ var SASjsApi = (() => {
6021
6362
  }
6022
6363
  ];
6023
6364
 
6365
+ // src/session/service.ts
6366
+ var createSessionService = (store, host) => {
6367
+ const notFound2 = (id) => `Session with ID '${id}' was not found.`;
6368
+ const visible = async (caller, id) => {
6369
+ if (!caller) throw new ApiError(401, "Unauthorized");
6370
+ const reader = await store.userById(
6371
+ caller.userId
6372
+ );
6373
+ if (!reader || !reader.isActive) throw new ApiError(401, "Unauthorized");
6374
+ const session = await host.session(id);
6375
+ if (!session) throw new ApiError(404, notFound2(id));
6376
+ const owner = session.owner;
6377
+ if (owner !== reader.uid && owner !== reader.username && !reader.isAdmin)
6378
+ throw new ApiError(404, notFound2(id));
6379
+ return session;
6380
+ };
6381
+ return {
6382
+ async summary(caller, id) {
6383
+ const session = await visible(caller, id);
6384
+ return session.summary;
6385
+ },
6386
+ async state(caller, id) {
6387
+ await visible(caller, id);
6388
+ const state = await host.state(id);
6389
+ if (!state) throw new ApiError(404, notFound2(id));
6390
+ return state;
6391
+ },
6392
+ async log(caller, id) {
6393
+ await visible(caller, id);
6394
+ const log = await host.log(id);
6395
+ if (log === void 0) throw new ApiError(404, notFound2(id));
6396
+ return log;
6397
+ },
6398
+ async output(caller, id) {
6399
+ await visible(caller, id);
6400
+ const output = await host.output(id);
6401
+ if (output === void 0) throw new ApiError(404, notFound2(id));
6402
+ return output;
6403
+ }
6404
+ };
6405
+ };
6406
+
6407
+ // src/session/memory-host.ts
6408
+ var DEFAULT_SESSIONS = {
6409
+ "sess-admin": {
6410
+ owner: "user-admin",
6411
+ summary: {
6412
+ sessionId: "sess-admin",
6413
+ state: "completed",
6414
+ pid: 4242,
6415
+ creationTimeStamp: "2026-11-07T09:00:00.000Z",
6416
+ deathTimeStamp: "2026-11-07T09:00:04.000Z",
6417
+ expiresAfterMins: 60
6418
+ }
6419
+ },
6420
+ "sess-restricted": {
6421
+ owner: "user-restricted",
6422
+ summary: {
6423
+ sessionId: "sess-restricted",
6424
+ state: "running",
6425
+ pid: 4243,
6426
+ creationTimeStamp: "2026-11-07T09:05:00.000Z"
6427
+ }
6428
+ },
6429
+ "sess-silent": {
6430
+ owner: "user-admin",
6431
+ summary: {
6432
+ sessionId: "sess-silent",
6433
+ state: "running",
6434
+ pid: 4244,
6435
+ creationTimeStamp: "2026-11-07T09:10:00.000Z"
6436
+ }
6437
+ }
6438
+ };
6439
+ var STATES = {
6440
+ "sess-admin": { state: "completed", pid: 4242, lineCount: 12 },
6441
+ "sess-restricted": { state: "running", pid: 4243, lineCount: 3 }
6442
+ };
6443
+ var LOGS = {
6444
+ "sess-admin": "NOTE: session started\nNOTE: session completed\n",
6445
+ "sess-restricted": "NOTE: session started\n",
6446
+ "sess-silent": ""
6447
+ };
6448
+ var OUTPUTS = {
6449
+ "sess-admin": "<html><body>42</body></html>",
6450
+ "sess-restricted": "",
6451
+ "sess-silent": ""
6452
+ };
6453
+ var MemorySessionHost = class {
6454
+ constructor(sessions = DEFAULT_SESSIONS) {
6455
+ this.sessions = sessions;
6456
+ }
6457
+ async session(id) {
6458
+ return this.sessions[id];
6459
+ }
6460
+ async state(id) {
6461
+ return STATES[id];
6462
+ }
6463
+ async log(id) {
6464
+ return LOGS[id];
6465
+ }
6466
+ async output(id) {
6467
+ return OUTPUTS[id];
6468
+ }
6469
+ };
6470
+
6024
6471
  // src/corpus/runner.ts
6025
6472
  var servicesFor = (testCase) => {
6026
6473
  const identity = new MemoryIdentityStore(
@@ -6031,7 +6478,9 @@ var SASjsApi = (() => {
6031
6478
  testCase.unwritable ? new RefusingDriveStore(testCase.seed || {}) : new MemoryDriveStore(testCase.seed || {})
6032
6479
  ),
6033
6480
  identity: createIdentityService(identity),
6034
- records: createRecordService(identity)
6481
+ records: createRecordService(identity),
6482
+ registry: new MemoryAppStreamRegistry(),
6483
+ session: createSessionService(identity, new MemorySessionHost())
6035
6484
  };
6036
6485
  };
6037
6486
  var RefusingDriveStore = class extends MemoryDriveStore {