@bytescale/sdk 3.57.0 → 3.59.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 (29) hide show
  1. package/dist/browser/cjs/main.js +724 -313
  2. package/dist/browser/esm/main.mjs +724 -313
  3. package/dist/node/cjs/main.js +123 -29
  4. package/dist/node/esm/main.mjs +123 -29
  5. package/dist/types/private/AuthSessionState.d.ts +7 -0
  6. package/dist/types/private/UploadManagerBase.d.ts +0 -1
  7. package/dist/types/private/dtos/AuthSwSetConfigDto.d.ts +2 -0
  8. package/dist/types/private/model/AuthManagerInterface.d.ts +1 -93
  9. package/dist/types/private/model/AuthSession.d.ts +12 -5
  10. package/dist/types/private/model/AuthSessionConfig.d.ts +3 -0
  11. package/dist/types/private/model/AuthSessionConfigAuto.d.ts +6 -0
  12. package/dist/types/private/model/AuthSessionConfigBase.d.ts +12 -0
  13. package/dist/types/private/model/AuthSessionConfigManual.d.ts +7 -0
  14. package/dist/types/private/model/BeginAuthSessionParams.d.ts +3 -0
  15. package/dist/types/private/model/BeginAuthSessionParamsOptions.d.ts +2 -0
  16. package/dist/types/private/model/BeginAuthSessionParamsV1.d.ts +11 -0
  17. package/dist/types/private/model/BeginAuthSessionParamsV2.d.ts +16 -0
  18. package/dist/types/private/model/NonEmptyArray.d.ts +1 -0
  19. package/dist/types/private/model/UrlRewriteRule.d.ts +6 -0
  20. package/dist/types/public/browser/AuthManagerBrowser.d.ts +27 -11
  21. package/dist/types/public/node/AuthManagerNode.d.ts +12 -2
  22. package/dist/types/public/shared/generated/runtime.d.ts +13 -6
  23. package/dist/worker/cjs/main.js +123 -29
  24. package/dist/worker/esm/main.mjs +123 -29
  25. package/package.json +1 -1
  26. package/tests/ApiClientAuth.test.ts +222 -0
  27. package/tests/AuthManagerBrowser.test.ts +312 -192
  28. package/tests/AuthServiceWorkerRewrite.test.ts +337 -0
  29. package/tests/UploadManagerAuth.test.ts +156 -0
@@ -0,0 +1,337 @@
1
+ import { jest } from "@jest/globals";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { runInNewContext } from "node:vm";
5
+ import { Headers as NodeFetchHeaders, Request as NodeFetchRequest, Response as NodeFetchResponse } from "node-fetch";
6
+ import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
7
+ import type { AuthSwConfigEntryDto, UrlRewriteRule } from "../src/index.browser";
8
+
9
+ const workerSource = readFileSync(resolve(process.cwd(), "src/index.auth-sw.js"), "utf8");
10
+
11
+ describe("Auth service-worker URL rewriting", () => {
12
+ test("preserves the remaining path, query string, and fragment", async () => {
13
+ const harness = new AuthServiceWorkerHarness();
14
+ const rules = [rewriteRule("download/", "account-a/")];
15
+ await harness.setConfig([], rules);
16
+
17
+ const result = await harness.dispatchFetch(
18
+ "https://dashboard.example.com/download/path/to/file.pdf?download=true&version=2"
19
+ );
20
+
21
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/account-a/path/to/file.pdf?download=true&version=2");
22
+ expect(harness.getRewrittenUrl("https://dashboard.example.com/download/file.pdf?download=true#page=2", rules)).toBe(
23
+ "https://upcdn.io/account-a/file.pdf?download=true#page=2"
24
+ );
25
+ });
26
+
27
+ test("matches authentication against the rewritten URL and supports navigation requests", async () => {
28
+ const upstreamResponse = new NodeFetchResponse("streamed-body", {
29
+ headers: {
30
+ "Accept-Ranges": "bytes",
31
+ "Content-Disposition": 'attachment; filename="file.pdf"',
32
+ "Content-Length": "13",
33
+ "Content-Range": "bytes 0-12/13",
34
+ "Content-Type": "application/pdf"
35
+ },
36
+ status: 206
37
+ });
38
+ const blob = jest.spyOn(upstreamResponse, "blob");
39
+ const arrayBuffer = jest.spyOn(upstreamResponse, "arrayBuffer");
40
+ const harness = new AuthServiceWorkerHarness(upstreamResponse);
41
+ await harness.setConfig([authConfig("account-a/", "token-a")], [rewriteRule("download/", "account-a/")]);
42
+
43
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf", {
44
+ headers: {
45
+ "If-Modified-Since": "Wed, 21 Oct 2015 07:28:00 GMT",
46
+ "If-None-Match": '"etag"',
47
+ "If-Range": '"range-etag"',
48
+ "Range": "bytes=0-12"
49
+ },
50
+ navigation: true
51
+ });
52
+
53
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/account-a/file.pdf");
54
+ expect(result.outboundRequest?.mode).toBe("cors");
55
+ expect(result.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
56
+ expect(result.outboundRequest?.headers.get("Range")).toBe("bytes=0-12");
57
+ expect(result.outboundRequest?.headers.get("If-Range")).toBe('"range-etag"');
58
+ expect(result.outboundRequest?.headers.get("If-None-Match")).toBe('"etag"');
59
+ expect(result.outboundRequest?.headers.get("If-Modified-Since")).toBe("Wed, 21 Oct 2015 07:28:00 GMT");
60
+ expect(result.response).toBe(upstreamResponse);
61
+ expect(result.response?.status).toBe(206);
62
+ expect(result.response?.headers.get("Content-Disposition")).toBe('attachment; filename="file.pdf"');
63
+ expect(result.response?.headers.get("Accept-Ranges")).toBe("bytes");
64
+ expect(blob).not.toHaveBeenCalled();
65
+ expect(arrayBuffer).not.toHaveBeenCalled();
66
+ });
67
+
68
+ test("selects different authentication configurations for different rewrite destinations", async () => {
69
+ const harness = new AuthServiceWorkerHarness();
70
+ await harness.setConfig(
71
+ [authConfig("account-a/", "token-a"), authConfig("account-b/", "token-b")],
72
+ [rewriteRule("download-a/", "account-a/"), rewriteRule("download-b/", "account-b/")]
73
+ );
74
+
75
+ const first = await harness.dispatchFetch("https://dashboard.example.com/download-a/file.pdf");
76
+ const second = await harness.dispatchFetch("https://dashboard.example.com/download-b/file.pdf");
77
+
78
+ expect(first.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
79
+ expect(second.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-b");
80
+ });
81
+
82
+ test("never selects a token from the original URL", async () => {
83
+ const harness = new AuthServiceWorkerHarness();
84
+ await harness.setConfig(
85
+ [authConfigForUrl("https://dashboard.example.com/download/", "original-token")],
86
+ [rewriteRule("download/", "unconfigured-account/")]
87
+ );
88
+
89
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf");
90
+
91
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/unconfigured-account/file.pdf");
92
+ expect(result.outboundRequest?.headers.has("Authorization")).toBe(false);
93
+ });
94
+
95
+ test("fetches rewritten requests even when no authentication configuration matches", async () => {
96
+ const harness = new AuthServiceWorkerHarness();
97
+ await harness.setConfig([], [rewriteRule("download/", "public-account/")]);
98
+
99
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/public.pdf");
100
+
101
+ expect(result.responded).toBe(true);
102
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/public-account/public.pdf");
103
+ expect(result.outboundRequest?.headers.has("Authorization")).toBe(false);
104
+ });
105
+
106
+ test("uses only the first matching rewrite rule", async () => {
107
+ const harness = new AuthServiceWorkerHarness();
108
+ await harness.setConfig(
109
+ [authConfig("account-a/", "token-a"), authConfig("account-b/", "token-b")],
110
+ [rewriteRule("download/", "account-a/"), rewriteRule("download/", "account-b/")]
111
+ );
112
+
113
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf");
114
+
115
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/account-a/file.pdf");
116
+ expect(result.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
117
+ });
118
+
119
+ test("does not recursively rewrite the rewritten URL", async () => {
120
+ const harness = new AuthServiceWorkerHarness();
121
+ await harness.setConfig(
122
+ [authConfig("account-a/", "token-a")],
123
+ [
124
+ {
125
+ fromUrlPrefix: "https://dashboard.example.com/download/",
126
+ toUrlPrefix: "https://proxy.example.com/download/"
127
+ },
128
+ {
129
+ fromUrlPrefix: "https://proxy.example.com/download/",
130
+ toUrlPrefix: "https://upcdn.io/account-a/"
131
+ }
132
+ ]
133
+ );
134
+
135
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf");
136
+
137
+ expect(result.outboundRequest?.url).toBe("https://proxy.example.com/download/file.pdf");
138
+ expect(result.outboundRequest?.headers.has("Authorization")).toBe(false);
139
+ });
140
+
141
+ test("retains existing behavior when no rewrite rule matches", async () => {
142
+ const harness = new AuthServiceWorkerHarness();
143
+ await harness.setConfig([authConfig("account-a/", "token-a")], [rewriteRule("download/", "account-a/")]);
144
+
145
+ const authenticated = await harness.dispatchFetch("https://upcdn.io/account-a/file.pdf");
146
+ const untouched = await harness.dispatchFetch("https://dashboard.example.com/ordinary-page");
147
+
148
+ expect(authenticated.outboundRequest?.url).toBe("https://upcdn.io/account-a/file.pdf");
149
+ expect(authenticated.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
150
+ expect(untouched.responded).toBe(false);
151
+ });
152
+
153
+ test("keeps source-page authorization independent across multiple client IDs", async () => {
154
+ const harness = new AuthServiceWorkerHarness();
155
+ harness.setWindowClient("client-a", "https://app.example.com/a/");
156
+ harness.setWindowClient("client-b", "https://app.example.com/b/");
157
+ await harness.setConfig([
158
+ {
159
+ ...authConfig("account-a/", "token-a"),
160
+ sourceUrlPrefixes: ["https://app.example.com/a/"],
161
+ urlPrefix: "!bytescale-source-scoped!https://upcdn.io/account-a/"
162
+ },
163
+ {
164
+ ...authConfig("account-b/", "token-b"),
165
+ sourceUrlPrefixes: ["https://app.example.com/b/"],
166
+ urlPrefix: "!bytescale-source-scoped!https://upcdn.io/account-b/"
167
+ }
168
+ ]);
169
+
170
+ const a = await harness.dispatchFetch("https://upcdn.io/account-a/file.pdf", { clientId: "client-a" });
171
+ const wrongSource = await harness.dispatchFetch("https://upcdn.io/account-a/file.pdf", {
172
+ clientId: "client-b"
173
+ });
174
+ const b = await harness.dispatchFetch("https://upcdn.io/account-b/file.pdf", { clientId: "client-b" });
175
+
176
+ expect(a.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
177
+ expect(wrongSource.responded).toBe(true);
178
+ expect(wrongSource.outboundRequest?.headers.has("Authorization")).toBe(false);
179
+ expect(b.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-b");
180
+ });
181
+
182
+ test("source-scoped auth replaces both authentication headers and preserves unrelated headers", async () => {
183
+ const harness = new AuthServiceWorkerHarness();
184
+ harness.setWindowClient("client-a", "https://app.example.com/a/");
185
+ await harness.setConfig([
186
+ {
187
+ ...authConfig("account-a/", "token-a"),
188
+ sourceUrlPrefixes: ["https://app.example.com/a/"],
189
+ urlPrefix: "!bytescale-source-scoped!https://upcdn.io/account-a/"
190
+ }
191
+ ]);
192
+
193
+ const result = await harness.dispatchFetch("https://upcdn.io/account-a/file.pdf", {
194
+ clientId: "client-a",
195
+ headers: {
196
+ "Authorization": "Bearer stale",
197
+ "Authorization-Token": "stale-token",
198
+ "X-Trace-Id": "trace"
199
+ }
200
+ });
201
+
202
+ expect(result.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
203
+ expect(result.outboundRequest?.headers.has("Authorization-Token")).toBe(false);
204
+ expect(result.outboundRequest?.headers.get("X-Trace-Id")).toBe("trace");
205
+ });
206
+ });
207
+
208
+ interface FetchOptions {
209
+ clientId?: string;
210
+ headers?: HeadersInit;
211
+ navigation?: boolean;
212
+ }
213
+
214
+ interface FetchResult {
215
+ outboundRequest: TestRequest | undefined;
216
+ responded: boolean;
217
+ response: NodeFetchResponse | undefined;
218
+ }
219
+
220
+ type WorkerEventListener = (event: unknown) => void;
221
+
222
+ class AuthServiceWorkerHarness {
223
+ private readonly clientsById = new Map<string, { type: "window"; url: string }>();
224
+ private readonly context: {
225
+ getRewrittenUrl: (url: string, rules: UrlRewriteRule[]) => string | undefined;
226
+ setConfig: (config: AuthSwConfigEntryDto[], rules?: UrlRewriteRule[]) => Promise<void>;
227
+ };
228
+
229
+ private readonly fetchListener: WorkerEventListener;
230
+ private readonly fetchMock: jest.MockedFunction<(request: TestRequest) => Promise<NodeFetchResponse>>;
231
+
232
+ constructor(private readonly upstreamResponse: NodeFetchResponse = new NodeFetchResponse("ok")) {
233
+ const listeners = new Map<string, WorkerEventListener>();
234
+ const cacheEntries = new Map<string, NodeFetchResponse>();
235
+ this.fetchMock = jest.fn(async (_request: TestRequest): Promise<NodeFetchResponse> => this.upstreamResponse);
236
+
237
+ const self = {
238
+ addEventListener: (type: string, listener: WorkerEventListener): void => {
239
+ listeners.set(type, listener);
240
+ },
241
+ clients: {
242
+ claim: async (): Promise<void> => {},
243
+ get: async (clientId: string): Promise<{ type: "window"; url: string } | undefined> =>
244
+ this.clientsById.get(clientId)
245
+ },
246
+ skipWaiting: async (): Promise<void> => {}
247
+ };
248
+ const cache = {
249
+ match: async (key: string): Promise<NodeFetchResponse | undefined> => cacheEntries.get(key)?.clone(),
250
+ put: async (key: string, value: NodeFetchResponse): Promise<void> => {
251
+ cacheEntries.set(key, value.clone());
252
+ }
253
+ };
254
+ const sandbox = {
255
+ caches: { open: async (): Promise<typeof cache> => cache },
256
+ console: { error: jest.fn(), log: jest.fn() },
257
+ fetch: this.fetchMock,
258
+ Headers: NodeFetchHeaders,
259
+ Promise,
260
+ Request: TestRequest,
261
+ Response: NodeFetchResponse,
262
+ self,
263
+ setTimeout
264
+ };
265
+ runInNewContext(workerSource, sandbox);
266
+
267
+ this.context = sandbox as typeof sandbox & AuthServiceWorkerHarness["context"];
268
+ const fetchListener = listeners.get("fetch");
269
+ if (fetchListener === undefined) {
270
+ throw new Error("Auth service worker did not register a fetch listener.");
271
+ }
272
+ this.fetchListener = fetchListener;
273
+ }
274
+
275
+ async setConfig(config: AuthSwConfigEntryDto[], rules?: UrlRewriteRule[]): Promise<void> {
276
+ await this.context.setConfig(config, rules);
277
+ }
278
+
279
+ getRewrittenUrl(url: string, rules: UrlRewriteRule[]): string | undefined {
280
+ return this.context.getRewrittenUrl(url, rules);
281
+ }
282
+
283
+ setWindowClient(clientId: string, url: string): void {
284
+ this.clientsById.set(clientId, { type: "window", url });
285
+ }
286
+
287
+ async dispatchFetch(url: string, options: FetchOptions = {}): Promise<FetchResult> {
288
+ const request = new TestRequest(url, { headers: options.headers as NodeFetchRequestInit["headers"] });
289
+ if (options.navigation === true) {
290
+ Object.defineProperty(request, "mode", { configurable: true, value: "navigate" });
291
+ }
292
+
293
+ let responsePromise: Promise<NodeFetchResponse> | undefined;
294
+ this.fetchListener({
295
+ clientId: options.clientId ?? "",
296
+ request,
297
+ respondWith: (response: NodeFetchResponse | Promise<NodeFetchResponse>): void => {
298
+ responsePromise = Promise.resolve(response);
299
+ }
300
+ });
301
+
302
+ const response = await responsePromise;
303
+ return {
304
+ outboundRequest: this.fetchMock.mock.calls.at(-1)?.[0],
305
+ responded: responsePromise !== undefined,
306
+ response
307
+ };
308
+ }
309
+ }
310
+
311
+ class TestRequest extends NodeFetchRequest {
312
+ readonly mode: RequestMode;
313
+
314
+ constructor(input: string | NodeFetchRequest, init: NodeFetchRequestInit & { mode?: RequestMode } = {}) {
315
+ super(input, init);
316
+ this.mode = init.mode ?? (input instanceof TestRequest ? input.mode : "cors");
317
+ }
318
+ }
319
+
320
+ function authConfig(accountPath: string, token: string): AuthSwConfigEntryDto {
321
+ return authConfigForUrl(`https://upcdn.io/${accountPath}`, token);
322
+ }
323
+
324
+ function authConfigForUrl(urlPrefix: string, token: string): AuthSwConfigEntryDto {
325
+ return {
326
+ expires: undefined,
327
+ headers: [{ key: "Authorization", value: `Bearer ${token}` }],
328
+ urlPrefix
329
+ };
330
+ }
331
+
332
+ function rewriteRule(fromPath: string, toPath: string): UrlRewriteRule {
333
+ return {
334
+ fromUrlPrefix: `https://dashboard.example.com/${fromPath}`,
335
+ toUrlPrefix: `https://upcdn.io/${toPath}`
336
+ };
337
+ }
@@ -0,0 +1,156 @@
1
+ import { jest } from "@jest/globals";
2
+ import { AuthSessionState } from "../src/private/AuthSessionState";
3
+ import { UploadManagerBase } from "../src/private/UploadManagerBase";
4
+ import { AddCancellationHandler } from "../src/private/model/AddCancellationHandler";
5
+ import { AuthSessionConfigState } from "../src/private/model/AuthSession";
6
+ import { OnPartProgress } from "../src/private/model/OnPartProgress";
7
+ import { PreUploadInfo } from "../src/private/model/PreUploadInfo";
8
+ import { PutUploadPartResult } from "../src/private/model/PutUploadPartResult";
9
+ import { UploadManagerParams, UploadSource } from "../src/public/shared/CommonTypes";
10
+ import {
11
+ BeginMultipartUploadResponse,
12
+ CompleteMultipartUploadResponse,
13
+ UploadPart
14
+ } from "../src/public/shared/generated";
15
+
16
+ const accountA = "A123abc";
17
+
18
+ class TestUploadManager extends UploadManagerBase<string, undefined> {
19
+ protected processUploadSource(data: UploadSource): string {
20
+ return String(data);
21
+ }
22
+
23
+ protected getPreUploadInfoPartial(
24
+ _request: UploadManagerParams,
25
+ _source: string
26
+ ): Partial<PreUploadInfo> & { size: number } {
27
+ return { size: 1 };
28
+ }
29
+
30
+ protected preUpload(_source: string): undefined {
31
+ return undefined;
32
+ }
33
+
34
+ protected async postUpload(_init: undefined): Promise<void> {}
35
+
36
+ protected async doPutUploadPart(
37
+ _part: UploadPart,
38
+ _contentLength: number,
39
+ _source: string,
40
+ _onProgress: OnPartProgress,
41
+ _addCancellationHandler: AddCancellationHandler
42
+ ): Promise<PutUploadPartResult> {
43
+ return { etag: "etag", status: 200 };
44
+ }
45
+ }
46
+
47
+ describe("UploadManager AuthManager configuration", () => {
48
+ const originalWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
49
+
50
+ beforeAll(() => {
51
+ Object.defineProperty(globalThis, "window", { configurable: true, value: {} });
52
+ });
53
+
54
+ afterEach(() => AuthSessionState.setSession(undefined));
55
+
56
+ afterAll(() => {
57
+ if (originalWindow === undefined) {
58
+ Reflect.deleteProperty(globalThis, "window");
59
+ } else {
60
+ Object.defineProperty(globalThis, "window", originalWindow);
61
+ }
62
+ });
63
+
64
+ test("allows keyless construction and resolves the selected account once upload begins", async () => {
65
+ const manager = new TestUploadManager({ authConfigId: "customer" });
66
+ await expect(manager.upload({ data: "x" })).rejects.toThrow("No active AuthManager configuration");
67
+
68
+ setSession(state("customer", accountA));
69
+ const uploadApi = installUploadApiMock(manager);
70
+ await manager.upload({ data: "x" });
71
+
72
+ expect(uploadApi.beginMultipartUpload).toHaveBeenCalledWith(expect.objectContaining({ accountId: accountA }));
73
+ expect(uploadApi.completeUploadPart).toHaveBeenCalledWith(expect.objectContaining({ accountId: accountA }));
74
+ });
75
+
76
+ test("fails before upload network work when keyless AuthManager auth is explicitly disabled", async () => {
77
+ const manager = new TestUploadManager({ authConfigId: false });
78
+ const uploadApi = installUploadApiMock(manager);
79
+
80
+ await expect(manager.upload({ data: "x" })).rejects.toThrow("provide an API key");
81
+ expect(uploadApi.beginMultipartUpload).not.toHaveBeenCalled();
82
+ });
83
+ });
84
+
85
+ interface UploadApiMock {
86
+ beginMultipartUpload: jest.MockedFunction<(request: { accountId: string }) => Promise<BeginMultipartUploadResponse>>;
87
+ completeUploadPart: jest.MockedFunction<(request: { accountId: string }) => Promise<CompleteMultipartUploadResponse>>;
88
+ getUploadPart: jest.MockedFunction<(request: { accountId: string }) => Promise<UploadPart>>;
89
+ }
90
+
91
+ function installUploadApiMock(manager: TestUploadManager): UploadApiMock {
92
+ const firstPart: UploadPart = {
93
+ range: { inclusiveEnd: 0, inclusiveStart: 0 },
94
+ uploadId: "upload-id",
95
+ uploadPartIndex: 0,
96
+ uploadUrl: "https://upload.example.com/part"
97
+ };
98
+ const beginResponse: BeginMultipartUploadResponse = {
99
+ file: {
100
+ accountId: accountA,
101
+ etag: null,
102
+ filePath: "/file.txt",
103
+ fileUrl: `https://upcdn.io/${accountA}/raw/file.txt`,
104
+ lastModified: { tYPE: "EpochMillis" },
105
+ metadata: {},
106
+ mime: "text/plain",
107
+ originalFileName: null,
108
+ size: 1,
109
+ tags: []
110
+ },
111
+ uploadId: "upload-id",
112
+ uploadParts: { count: 1, first: firstPart }
113
+ };
114
+ const uploadApi: UploadApiMock = {
115
+ beginMultipartUpload: jest.fn<(request: { accountId: string }) => Promise<BeginMultipartUploadResponse>>(
116
+ async _request => beginResponse
117
+ ),
118
+ completeUploadPart: jest.fn<(request: { accountId: string }) => Promise<CompleteMultipartUploadResponse>>(
119
+ async (_request): Promise<CompleteMultipartUploadResponse> => ({ etag: "etag", status: "Completed" })
120
+ ),
121
+ getUploadPart: jest.fn<(request: { accountId: string }) => Promise<UploadPart>>(async _request => firstPart)
122
+ };
123
+ (manager as unknown as { uploadApi: UploadApiMock }).uploadApi = uploadApi;
124
+ return uploadApi;
125
+ }
126
+
127
+ function state(authConfigId: string, accountId: string): AuthSessionConfigState {
128
+ return {
129
+ accessToken: "access-token",
130
+ config: {
131
+ accountId,
132
+ authConfigId,
133
+ enableServiceWorkerAuth: false,
134
+ getAuthorizationToken: async () => "jwt"
135
+ },
136
+ expiresAt: Date.now() + 60_000,
137
+ jwt: "jwt",
138
+ refreshHandle: undefined
139
+ };
140
+ }
141
+
142
+ function setSession(configState: AuthSessionConfigState): void {
143
+ AuthSessionState.setSession({
144
+ accessToken: undefined,
145
+ accessTokenRefreshHandle: undefined,
146
+ authConfigs: [configState],
147
+ authServiceWorker: undefined,
148
+ isActive: true,
149
+ isReady: true,
150
+ params: {
151
+ authConfigs: async () => [configState.config],
152
+ serviceWorkerScript: undefined
153
+ },
154
+ serviceWorkerConfigured: false
155
+ });
156
+ }