@ethlete/query 6.0.0-next.59 → 6.0.0-next.60
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/CHANGELOG.md +6 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs +1 -6
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +371 -353
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -1
- package/fesm2022/ethlete-query-testing.mjs +525 -612
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +13466 -18089
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/package.json +2 -2
- package/types/ethlete-query-devtools-contract.d.ts +2 -1
- package/types/ethlete-query-ngrx-toolkit.d.ts +78 -90
- package/types/ethlete-query-testing.d.ts +218 -227
- package/types/ethlete-query.d.ts +6935 -7083
- package/fesm2022/ethlete-query-devtools-contract.mjs.map +0 -1
|
@@ -1,644 +1,557 @@
|
|
|
1
|
-
import { DestroyRef, ErrorHandler, Injector } from
|
|
2
|
-
import { TestBed } from
|
|
3
|
-
import { createBearerAuthProvider,
|
|
4
|
-
import { HttpErrorResponse, provideHttpClient
|
|
5
|
-
import {
|
|
6
|
-
import { provideRouter } from
|
|
7
|
-
|
|
8
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
|
+
import { DestroyRef, ErrorHandler, Injector } from "@angular/core";
|
|
2
|
+
import { TestBed } from "@angular/core/testing";
|
|
3
|
+
import { createBearerAuthProvider, createDeleteQuery, createGetQuery, createPatchQuery, createPostQuery, createPutQuery, createQueryClient, isQueryErrorResponse, withAuthenticationQuery, withRefreshQuery } from "@ethlete/query";
|
|
4
|
+
import { HttpClient, HttpErrorResponse, provideHttpClient } from "@angular/common/http";
|
|
5
|
+
import { HttpTestingController, provideHttpClientTesting } from "@angular/common/http/testing";
|
|
6
|
+
import { provideRouter } from "@angular/router";
|
|
9
7
|
const expectAndFlush = (httpTesting, url, response, statusOpts) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
req.flush(response);
|
|
16
|
-
}
|
|
8
|
+
const req = httpTesting.expectOne(url);
|
|
9
|
+
if (statusOpts) req.flush(response, statusOpts);
|
|
10
|
+
else req.flush(response);
|
|
17
11
|
};
|
|
18
12
|
const expectFlushAndWait = (httpTesting, url, response, statusOpts) => {
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
expectAndFlush(httpTesting, url, response, statusOpts);
|
|
14
|
+
TestBed.tick();
|
|
21
15
|
};
|
|
22
|
-
|
|
23
16
|
const setupAuthTest = (config) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
consumerDestroyRef: TestBed.inject(DestroyRef),
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
return {
|
|
93
|
-
auth: auth,
|
|
94
|
-
login: loginHelper,
|
|
95
|
-
refresh: refreshHelper,
|
|
96
|
-
makeSecureRequest,
|
|
97
|
-
};
|
|
17
|
+
const { querySetup, loginPath = "/auth/login", refreshPath = "/auth/refresh", autoRetryOn401 = true, extractLoginTokens = (response) => ({
|
|
18
|
+
accessToken: response.accessToken,
|
|
19
|
+
refreshToken: response.refreshToken
|
|
20
|
+
}), extractRefreshTokens = (response) => ({
|
|
21
|
+
accessToken: response.accessToken,
|
|
22
|
+
refreshToken: response.refreshToken
|
|
23
|
+
}), buildRefreshArgs, features, bearerDecryptFn, refreshStrategy, minRefreshInterval, refreshIfExpired, expiresInPropertyName, onRefreshFailure } = config;
|
|
24
|
+
const login = querySetup.createPost(loginPath, {});
|
|
25
|
+
const refresh = querySetup.createPost(refreshPath, {});
|
|
26
|
+
const { inject: injectAuth } = createBearerAuthProvider({
|
|
27
|
+
name: "test-auth",
|
|
28
|
+
queryClientRef: querySetup.queryClientRef,
|
|
29
|
+
queries: [withAuthenticationQuery("login", {
|
|
30
|
+
queryCreator: login,
|
|
31
|
+
extractTokens: extractLoginTokens
|
|
32
|
+
}), withRefreshQuery("refresh", {
|
|
33
|
+
queryCreator: refresh,
|
|
34
|
+
extractTokens: extractRefreshTokens,
|
|
35
|
+
buildArgs: buildRefreshArgs,
|
|
36
|
+
autoRetryOn401,
|
|
37
|
+
refreshStrategy,
|
|
38
|
+
minRefreshInterval,
|
|
39
|
+
refreshIfExpired,
|
|
40
|
+
expiresInPropertyName,
|
|
41
|
+
onRefreshFailure
|
|
42
|
+
})],
|
|
43
|
+
features: features ?? [],
|
|
44
|
+
bearerDecryptFn
|
|
45
|
+
});
|
|
46
|
+
const auth = TestBed.runInInjectionContext(() => {
|
|
47
|
+
const provider = injectAuth();
|
|
48
|
+
if (!provider) throw new Error("Failed to create auth provider in test setup");
|
|
49
|
+
return provider;
|
|
50
|
+
});
|
|
51
|
+
const loginHelper = (credentials, response) => {
|
|
52
|
+
TestBed.runInInjectionContext(() => {
|
|
53
|
+
auth.queries.login.execute({ body: credentials });
|
|
54
|
+
});
|
|
55
|
+
const fullUrl = `${querySetup.baseUrl}${loginPath}`;
|
|
56
|
+
expectFlushAndWait(querySetup.httpTesting, fullUrl, response);
|
|
57
|
+
};
|
|
58
|
+
const refreshHelper = (token, response) => {
|
|
59
|
+
TestBed.runInInjectionContext(() => {
|
|
60
|
+
auth.queries.refresh.execute(buildRefreshArgs?.(token) ?? { body: { token } });
|
|
61
|
+
});
|
|
62
|
+
const fullUrl = `${querySetup.baseUrl}${refreshPath}`;
|
|
63
|
+
expectFlushAndWait(querySetup.httpTesting, fullUrl, response);
|
|
64
|
+
};
|
|
65
|
+
const makeSecureRequest = (route) => {
|
|
66
|
+
TestBed.runInInjectionContext(() => {
|
|
67
|
+
querySetup.queryClient.repository.request({
|
|
68
|
+
route,
|
|
69
|
+
method: "GET",
|
|
70
|
+
isSecure: true,
|
|
71
|
+
consumerDestroyRef: TestBed.inject(DestroyRef)
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
auth,
|
|
77
|
+
login: loginHelper,
|
|
78
|
+
refresh: refreshHelper,
|
|
79
|
+
makeSecureRequest
|
|
80
|
+
};
|
|
98
81
|
};
|
|
99
|
-
|
|
100
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
101
|
-
// Captured at import, before a spec can call `vi.useFakeTimers()`: delivery must not wait on a faked clock.
|
|
102
82
|
const nodeSetImmediate = globalThis.setImmediate;
|
|
103
|
-
const scheduleTask = nodeSetImmediate
|
|
104
|
-
? nodeSetImmediate.bind(globalThis)
|
|
105
|
-
: setTimeout.bind(globalThis);
|
|
106
|
-
/**
|
|
107
|
-
* Replaces `globalThis.BroadcastChannel` with an in-memory bus.
|
|
108
|
-
*
|
|
109
|
-
* Messages are structured-cloned at post time (so a non-cloneable payload throws exactly like the
|
|
110
|
-
* real API does), delivered as a task (never synchronously, and after every pending microtask), and
|
|
111
|
-
* never delivered back to the channel that posted them - all three matching browser behavior. Delivery
|
|
112
|
-
* does not depend on fake timers; `await flushMultiTabSync()` in a spec flushes it.
|
|
113
|
-
*/
|
|
83
|
+
const scheduleTask = nodeSetImmediate ? nodeSetImmediate.bind(globalThis) : setTimeout.bind(globalThis);
|
|
114
84
|
const installFakeBroadcastChannel = () => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
},
|
|
158
|
-
};
|
|
85
|
+
const original = globalThis.BroadcastChannel;
|
|
86
|
+
const instances = /* @__PURE__ */ new Set();
|
|
87
|
+
const posted = [];
|
|
88
|
+
class FakeBroadcastChannel {
|
|
89
|
+
constructor(name) {
|
|
90
|
+
this.name = name;
|
|
91
|
+
this.closed = false;
|
|
92
|
+
this.onmessage = null;
|
|
93
|
+
instances.add(this);
|
|
94
|
+
}
|
|
95
|
+
postMessage(data) {
|
|
96
|
+
if (this.closed) throw new DOMException("Channel is closed", "InvalidStateError");
|
|
97
|
+
const clone = structuredClone(data);
|
|
98
|
+
posted.push({
|
|
99
|
+
channel: this.name,
|
|
100
|
+
data: clone
|
|
101
|
+
});
|
|
102
|
+
for (const instance of instances) {
|
|
103
|
+
if (instance === this || instance.closed || instance.name !== this.name) continue;
|
|
104
|
+
scheduleTask(() => {
|
|
105
|
+
if (instance.closed) return;
|
|
106
|
+
instance.onmessage?.({ data: structuredClone(clone) });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
close() {
|
|
111
|
+
this.closed = true;
|
|
112
|
+
instances.delete(this);
|
|
113
|
+
}
|
|
114
|
+
addEventListener() {
|
|
115
|
+
throw new Error("The fake BroadcastChannel only supports `onmessage`.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
globalThis.BroadcastChannel = FakeBroadcastChannel;
|
|
119
|
+
return {
|
|
120
|
+
posted,
|
|
121
|
+
restore: () => {
|
|
122
|
+
for (const instance of [...instances]) instance.closed = true;
|
|
123
|
+
instances.clear();
|
|
124
|
+
globalThis.BroadcastChannel = original;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
159
127
|
};
|
|
160
|
-
/**
|
|
161
|
-
* Installs an in-memory `navigator.locks` implementing exclusive locks: one holder per name, the
|
|
162
|
-
* rest queued FIFO, granted on a microtask (never synchronously), released when the callback's
|
|
163
|
-
* promise settles. `signal` is honored for queued requests and - as in the real API - ignored once a
|
|
164
|
-
* request has been granted.
|
|
165
|
-
*
|
|
166
|
-
* `ifAvailable` is a try-lock: a request that cannot be granted straight away runs its callback with
|
|
167
|
-
* `null` instead of queueing, exactly as the real API does. `steal` releases the current holder -
|
|
168
|
-
* rejecting its request with an `AbortError` - and is granted ahead of everything queued.
|
|
169
|
-
*
|
|
170
|
-
* `mode: 'shared'` is not implemented; nothing in the library uses it.
|
|
171
|
-
*/
|
|
172
128
|
const installFakeWebLocks = () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
else {
|
|
286
|
-
delete navigator.locks;
|
|
287
|
-
}
|
|
288
|
-
},
|
|
289
|
-
};
|
|
129
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(navigator, "locks");
|
|
130
|
+
const holders = /* @__PURE__ */ new Map();
|
|
131
|
+
const queues = /* @__PURE__ */ new Map();
|
|
132
|
+
const abortError = () => new DOMException("The lock request was aborted", "AbortError");
|
|
133
|
+
const pump = (name) => {
|
|
134
|
+
if (holders.has(name)) return;
|
|
135
|
+
const waiter = queues.get(name)?.shift();
|
|
136
|
+
if (!waiter) return;
|
|
137
|
+
waiter.granted = true;
|
|
138
|
+
holders.set(name, waiter);
|
|
139
|
+
const settle = (settleWaiter) => {
|
|
140
|
+
if (holders.get(name) === waiter) holders.delete(name);
|
|
141
|
+
waiter.releaseSignal();
|
|
142
|
+
settleWaiter();
|
|
143
|
+
pump(name);
|
|
144
|
+
};
|
|
145
|
+
try {
|
|
146
|
+
Promise.resolve(waiter.callback({
|
|
147
|
+
name,
|
|
148
|
+
mode: "exclusive"
|
|
149
|
+
})).then((value) => settle(() => waiter.resolve(value)), (error) => settle(() => waiter.reject(error)));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
settle(() => waiter.reject(error));
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const request = (name, optionsOrCallback, maybeCallback) => {
|
|
155
|
+
const options = typeof optionsOrCallback === "function" ? {} : optionsOrCallback ?? {};
|
|
156
|
+
const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
if (options.signal?.aborted) {
|
|
159
|
+
reject(abortError());
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const waiter = {
|
|
163
|
+
callback,
|
|
164
|
+
resolve,
|
|
165
|
+
reject,
|
|
166
|
+
granted: false,
|
|
167
|
+
releaseSignal: () => void 0
|
|
168
|
+
};
|
|
169
|
+
const signal = options.signal;
|
|
170
|
+
if (signal) {
|
|
171
|
+
const onAbort = () => {
|
|
172
|
+
waiter.releaseSignal();
|
|
173
|
+
if (waiter.granted) return;
|
|
174
|
+
const queue = queues.get(name);
|
|
175
|
+
if (queue) queues.set(name, queue.filter((entry) => entry !== waiter));
|
|
176
|
+
reject(abortError());
|
|
177
|
+
};
|
|
178
|
+
signal.addEventListener("abort", onAbort);
|
|
179
|
+
waiter.releaseSignal = () => {
|
|
180
|
+
waiter.releaseSignal = () => void 0;
|
|
181
|
+
signal.removeEventListener("abort", onAbort);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (options.steal) {
|
|
185
|
+
queueMicrotask(() => {
|
|
186
|
+
const holder = holders.get(name);
|
|
187
|
+
if (holder) {
|
|
188
|
+
holders.delete(name);
|
|
189
|
+
holder.reject(abortError());
|
|
190
|
+
}
|
|
191
|
+
queues.set(name, [waiter, ...queues.get(name) ?? []]);
|
|
192
|
+
pump(name);
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (options.ifAvailable) {
|
|
197
|
+
queueMicrotask(() => {
|
|
198
|
+
if (holders.has(name) || (queues.get(name)?.length ?? 0) > 0) {
|
|
199
|
+
waiter.releaseSignal();
|
|
200
|
+
Promise.resolve(callback(null)).then(resolve, reject);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
queues.set(name, [waiter]);
|
|
204
|
+
pump(name);
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
queues.set(name, [...queues.get(name) ?? [], waiter]);
|
|
209
|
+
queueMicrotask(() => pump(name));
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
const query = () => Promise.resolve({
|
|
213
|
+
held: [...holders.keys()].map((name) => ({
|
|
214
|
+
name,
|
|
215
|
+
mode: "exclusive"
|
|
216
|
+
})),
|
|
217
|
+
pending: [...queues.entries()].flatMap(([name, waiters]) => waiters.map(() => ({
|
|
218
|
+
name,
|
|
219
|
+
mode: "exclusive"
|
|
220
|
+
})))
|
|
221
|
+
});
|
|
222
|
+
Object.defineProperty(navigator, "locks", {
|
|
223
|
+
value: {
|
|
224
|
+
request,
|
|
225
|
+
query
|
|
226
|
+
},
|
|
227
|
+
configurable: true,
|
|
228
|
+
writable: true
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
heldNames: () => [...holders.keys()],
|
|
232
|
+
pendingNames: () => [...queues.entries()].filter(([, queue]) => queue.length > 0).map(([name]) => name),
|
|
233
|
+
restore: () => {
|
|
234
|
+
for (const waiter of [...holders.values(), ...[...queues.values()].flat()]) waiter.releaseSignal();
|
|
235
|
+
holders.clear();
|
|
236
|
+
queues.clear();
|
|
237
|
+
if (originalDescriptor) Object.defineProperty(navigator, "locks", originalDescriptor);
|
|
238
|
+
else delete navigator.locks;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
290
241
|
};
|
|
291
|
-
/**
|
|
292
|
-
* Waits for the fakes' pending work - message delivery (a task) and lock grants (a microtask) - even
|
|
293
|
-
* under fake timers. Awaiting it once is enough for a single hop; a grant that has to wait for another
|
|
294
|
-
* tab's release, or a reply to a delivered message, needs one await per hop.
|
|
295
|
-
*/
|
|
296
242
|
const flushMultiTabSync = () => new Promise((resolve) => scheduleTask(resolve));
|
|
297
|
-
|
|
298
|
-
const quotaError = () => new DOMException('The quota has been exceeded.', 'QuotaExceededError');
|
|
243
|
+
const quotaError = () => new DOMException("The quota has been exceeded.", "QuotaExceededError");
|
|
299
244
|
const createFakeQueryPersistenceStore = () => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
},
|
|
408
|
-
};
|
|
245
|
+
const store = /* @__PURE__ */ new Map();
|
|
246
|
+
const calls = {
|
|
247
|
+
loadIndex: 0,
|
|
248
|
+
read: 0,
|
|
249
|
+
write: 0,
|
|
250
|
+
remove: 0,
|
|
251
|
+
clear: 0
|
|
252
|
+
};
|
|
253
|
+
const deferredReads = [];
|
|
254
|
+
const deferredLoadIndexes = [];
|
|
255
|
+
let failingWrites = 0;
|
|
256
|
+
let writeError = null;
|
|
257
|
+
let failingRemoves = 0;
|
|
258
|
+
let removeError = null;
|
|
259
|
+
let loadIndexError = null;
|
|
260
|
+
let isDeferringReads = false;
|
|
261
|
+
let isDeferringLoadIndex = false;
|
|
262
|
+
const clone = (value) => structuredClone(value);
|
|
263
|
+
const loadIndex = async () => {
|
|
264
|
+
calls.loadIndex++;
|
|
265
|
+
if (loadIndexError !== null) {
|
|
266
|
+
const error = loadIndexError;
|
|
267
|
+
loadIndexError = null;
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
const snapshot = Array.from(store.values()).map(({ body: _body, ...meta }) => clone(meta));
|
|
271
|
+
if (isDeferringLoadIndex) await new Promise((resolve) => deferredLoadIndexes.push(resolve));
|
|
272
|
+
return snapshot;
|
|
273
|
+
};
|
|
274
|
+
const read = async (key) => {
|
|
275
|
+
calls.read++;
|
|
276
|
+
const entry = store.get(key);
|
|
277
|
+
const snapshot = entry ? {
|
|
278
|
+
body: clone(entry.body),
|
|
279
|
+
version: entry.version
|
|
280
|
+
} : null;
|
|
281
|
+
if (isDeferringReads) await new Promise((resolve) => deferredReads.push(resolve));
|
|
282
|
+
return snapshot;
|
|
283
|
+
};
|
|
284
|
+
const write = async (entries) => {
|
|
285
|
+
calls.write++;
|
|
286
|
+
if (failingWrites > 0) {
|
|
287
|
+
failingWrites--;
|
|
288
|
+
throw writeError ?? quotaError();
|
|
289
|
+
}
|
|
290
|
+
for (const entry of entries) store.set(entry.key, clone(entry));
|
|
291
|
+
};
|
|
292
|
+
const remove = async (keys) => {
|
|
293
|
+
calls.remove++;
|
|
294
|
+
if (failingRemoves > 0) {
|
|
295
|
+
failingRemoves--;
|
|
296
|
+
throw removeError ?? /* @__PURE__ */ new Error("The removal failed.");
|
|
297
|
+
}
|
|
298
|
+
for (const key of keys) store.delete(key);
|
|
299
|
+
};
|
|
300
|
+
const clear = async () => {
|
|
301
|
+
calls.clear++;
|
|
302
|
+
store.clear();
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
adapter: {
|
|
306
|
+
loadIndex,
|
|
307
|
+
read,
|
|
308
|
+
write,
|
|
309
|
+
remove,
|
|
310
|
+
clear,
|
|
311
|
+
isSupported: true
|
|
312
|
+
},
|
|
313
|
+
seed: (entries) => {
|
|
314
|
+
for (const entry of entries) store.set(entry.key, clone(entry));
|
|
315
|
+
},
|
|
316
|
+
entries: () => Array.from(store.values()).map(clone),
|
|
317
|
+
entry: (key) => {
|
|
318
|
+
const entry = store.get(key);
|
|
319
|
+
return entry ? clone(entry) : void 0;
|
|
320
|
+
},
|
|
321
|
+
calls: () => ({ ...calls }),
|
|
322
|
+
failNextWrites: (count, error) => {
|
|
323
|
+
failingWrites = count;
|
|
324
|
+
writeError = error ?? null;
|
|
325
|
+
},
|
|
326
|
+
failNextRemoves: (count, error) => {
|
|
327
|
+
failingRemoves = count;
|
|
328
|
+
removeError = error ?? null;
|
|
329
|
+
},
|
|
330
|
+
failNextLoadIndex: (error) => {
|
|
331
|
+
loadIndexError = error ?? quotaError();
|
|
332
|
+
},
|
|
333
|
+
deferReads: () => {
|
|
334
|
+
isDeferringReads = true;
|
|
335
|
+
},
|
|
336
|
+
flushReads: async () => {
|
|
337
|
+
isDeferringReads = false;
|
|
338
|
+
const pending = deferredReads.splice(0, deferredReads.length);
|
|
339
|
+
for (const resolve of pending) resolve();
|
|
340
|
+
await Promise.resolve();
|
|
341
|
+
},
|
|
342
|
+
deferLoadIndex: () => {
|
|
343
|
+
isDeferringLoadIndex = true;
|
|
344
|
+
},
|
|
345
|
+
flushLoadIndex: async () => {
|
|
346
|
+
isDeferringLoadIndex = false;
|
|
347
|
+
const pending = deferredLoadIndexes.splice(0, deferredLoadIndexes.length);
|
|
348
|
+
for (const resolve of pending) resolve();
|
|
349
|
+
await Promise.resolve();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
409
352
|
};
|
|
410
|
-
|
|
411
353
|
let originalError = null;
|
|
412
354
|
const filteredError = (...args) => {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (typeof message === 'string' && message.includes('Failed to extract tokens from')) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
originalError?.(...args);
|
|
355
|
+
const message = args[0];
|
|
356
|
+
if (message && typeof message === "object" && "name" in message && message.name === "HttpErrorResponse") return;
|
|
357
|
+
if (typeof message === "string" && message.includes("Failed to decrypt bearer token")) return;
|
|
358
|
+
if (typeof message === "string" && message.includes("Failed to extract tokens from")) return;
|
|
359
|
+
originalError?.(...args);
|
|
424
360
|
};
|
|
425
|
-
// Capture only what is not already the wrapper, so repeated calls reinstall the one filter instead
|
|
426
|
-
// of nesting a new closure over the previous one and stranding the pristine handlers.
|
|
427
361
|
const installConsoleFilters = () => {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
console.error = filteredError;
|
|
362
|
+
if (console.error !== filteredError) originalError = console.error;
|
|
363
|
+
console.error = filteredError;
|
|
431
364
|
};
|
|
432
365
|
const restoreConsole = () => {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
originalError = null;
|
|
366
|
+
if (originalError && console.error === filteredError) console.error = originalError;
|
|
367
|
+
originalError = null;
|
|
436
368
|
};
|
|
437
369
|
const isFailedRequestError = (error) => error instanceof HttpErrorResponse || isQueryErrorResponse(error);
|
|
438
|
-
const requestErrorHandler = {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
throw error;
|
|
443
|
-
},
|
|
444
|
-
};
|
|
370
|
+
const requestErrorHandler = { handleError: (error) => {
|
|
371
|
+
if (isFailedRequestError(error)) return;
|
|
372
|
+
throw error;
|
|
373
|
+
} };
|
|
445
374
|
const setupQueryTest = (config) => {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
});
|
|
375
|
+
const baseUrl = config?.baseUrl ?? "https://api.test.com";
|
|
376
|
+
const name = config?.name ?? "test";
|
|
377
|
+
const mockErrorHandler = config?.mockErrorHandler ?? true;
|
|
378
|
+
installConsoleFilters();
|
|
379
|
+
const providers = [
|
|
380
|
+
provideHttpClient(),
|
|
381
|
+
provideHttpClientTesting(),
|
|
382
|
+
provideRouter([])
|
|
383
|
+
];
|
|
384
|
+
if (mockErrorHandler) providers.push({
|
|
385
|
+
provide: ErrorHandler,
|
|
386
|
+
useValue: mockErrorHandler === "all" ? { handleError: () => void 0 } : requestErrorHandler
|
|
387
|
+
});
|
|
388
|
+
TestBed.configureTestingModule({ providers });
|
|
389
|
+
const queryClientRef = createQueryClient({
|
|
390
|
+
baseUrl,
|
|
391
|
+
name
|
|
392
|
+
});
|
|
393
|
+
return TestBed.runInInjectionContext(() => {
|
|
394
|
+
const { inject } = queryClientRef;
|
|
395
|
+
const queryClient = inject();
|
|
396
|
+
TestBed.inject(DestroyRef).onDestroy(restoreConsole);
|
|
397
|
+
if (!queryClient) throw new Error("Failed to create query client in test setup");
|
|
398
|
+
return {
|
|
399
|
+
queryClient,
|
|
400
|
+
queryClientRef,
|
|
401
|
+
httpClient: TestBed.inject(HttpClient),
|
|
402
|
+
httpTesting: TestBed.inject(HttpTestingController),
|
|
403
|
+
injector: TestBed.inject(Injector),
|
|
404
|
+
baseUrl,
|
|
405
|
+
createGet: createGetQuery(queryClientRef),
|
|
406
|
+
createPost: createPostQuery(queryClientRef),
|
|
407
|
+
createPut: createPutQuery(queryClientRef),
|
|
408
|
+
createPatch: createPatchQuery(queryClientRef),
|
|
409
|
+
createDelete: createDeleteQuery(queryClientRef),
|
|
410
|
+
restoreConsole
|
|
411
|
+
};
|
|
412
|
+
});
|
|
485
413
|
};
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* A socket io test double, for specs around `createWebSocketClient` - which takes its `io` factory as
|
|
489
|
-
* an option precisely so a spec (or an app that never opens a socket) needs no `socket.io-client`.
|
|
490
|
-
*
|
|
491
|
-
* Nothing here connects on its own: the client's own `connect()` only flips
|
|
492
|
-
* {@link WebSocketTestDouble.state}, and it is {@link WebSocketTestDouble.serverConnect} that fires
|
|
493
|
-
* the `connect` listener. That split is what makes "joined a room while disconnected, and it was
|
|
494
|
-
* re-joined after a reconnect" testable.
|
|
495
|
-
*/
|
|
496
414
|
const createWebSocketTestDouble = () => {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
415
|
+
const sent = [];
|
|
416
|
+
const delivered = [];
|
|
417
|
+
const buffered = [];
|
|
418
|
+
const listeners = {
|
|
419
|
+
connect: [],
|
|
420
|
+
disconnect: [],
|
|
421
|
+
connect_error: []
|
|
422
|
+
};
|
|
423
|
+
const anyListeners = [];
|
|
424
|
+
const outgoingListeners = [];
|
|
425
|
+
let connection = null;
|
|
426
|
+
let withCredentials = null;
|
|
427
|
+
let auth;
|
|
428
|
+
const handshakes = [];
|
|
429
|
+
let connectRequested = false;
|
|
430
|
+
let disconnected = false;
|
|
431
|
+
let connected = false;
|
|
432
|
+
let active = false;
|
|
433
|
+
let connectCalls = 0;
|
|
434
|
+
let pingExpired = false;
|
|
435
|
+
let recovered = false;
|
|
436
|
+
const deliver = (message) => {
|
|
437
|
+
for (const listener of outgoingListeners) listener(message.event, message.data);
|
|
438
|
+
delivered.push(message);
|
|
439
|
+
};
|
|
440
|
+
const recordHandshake = () => {
|
|
441
|
+
if (auth) auth((data) => handshakes.push(data));
|
|
442
|
+
else handshakes.push(null);
|
|
443
|
+
};
|
|
444
|
+
const disconnect = (reason) => {
|
|
445
|
+
connected = false;
|
|
446
|
+
pingExpired = false;
|
|
447
|
+
for (const listener of listeners.disconnect) listener(reason);
|
|
448
|
+
};
|
|
449
|
+
const socket = {
|
|
450
|
+
connect: () => {
|
|
451
|
+
connectRequested = true;
|
|
452
|
+
active = true;
|
|
453
|
+
connectCalls++;
|
|
454
|
+
},
|
|
455
|
+
disconnect: () => {
|
|
456
|
+
disconnected = true;
|
|
457
|
+
active = false;
|
|
458
|
+
if (connected) disconnect("io client disconnect");
|
|
459
|
+
},
|
|
460
|
+
emit: (event, data) => {
|
|
461
|
+
sent.push({
|
|
462
|
+
event,
|
|
463
|
+
data
|
|
464
|
+
});
|
|
465
|
+
if (connected && !pingExpired) deliver({
|
|
466
|
+
event,
|
|
467
|
+
data
|
|
468
|
+
});
|
|
469
|
+
else buffered.push({
|
|
470
|
+
event,
|
|
471
|
+
data
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
on: (event, listener) => void listeners[event].push(listener),
|
|
475
|
+
onAny: (listener) => void anyListeners.push(listener),
|
|
476
|
+
onAnyOutgoing: (listener) => void outgoingListeners.push(listener),
|
|
477
|
+
get recovered() {
|
|
478
|
+
return recovered;
|
|
479
|
+
},
|
|
480
|
+
get active() {
|
|
481
|
+
return active;
|
|
482
|
+
},
|
|
483
|
+
get sendBuffer() {
|
|
484
|
+
return buffered;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
return {
|
|
488
|
+
io: (url, options) => {
|
|
489
|
+
connection = {
|
|
490
|
+
url,
|
|
491
|
+
transports: options.transports
|
|
492
|
+
};
|
|
493
|
+
withCredentials = options.withCredentials;
|
|
494
|
+
auth = options.auth;
|
|
495
|
+
return socket;
|
|
496
|
+
},
|
|
497
|
+
connection: () => connection,
|
|
498
|
+
withCredentials: () => withCredentials,
|
|
499
|
+
handshakes: () => [...handshakes],
|
|
500
|
+
sent: () => [...sent],
|
|
501
|
+
delivered: () => [...delivered],
|
|
502
|
+
state: () => ({
|
|
503
|
+
connectRequested,
|
|
504
|
+
disconnected,
|
|
505
|
+
active,
|
|
506
|
+
connectCalls
|
|
507
|
+
}),
|
|
508
|
+
serverConnect: (options) => {
|
|
509
|
+
recordHandshake();
|
|
510
|
+
active = true;
|
|
511
|
+
recovered = options?.recovered ?? false;
|
|
512
|
+
connected = true;
|
|
513
|
+
pingExpired = false;
|
|
514
|
+
for (const message of buffered.splice(0)) deliver(message);
|
|
515
|
+
for (const listener of listeners.connect) listener();
|
|
516
|
+
},
|
|
517
|
+
serverDisconnect: (options) => {
|
|
518
|
+
const reason = options?.reason ?? "transport close";
|
|
519
|
+
if (reason === "io server disconnect") active = false;
|
|
520
|
+
disconnect(reason);
|
|
521
|
+
},
|
|
522
|
+
serverRejectHandshake: (message) => {
|
|
523
|
+
recordHandshake();
|
|
524
|
+
active = false;
|
|
525
|
+
for (const listener of listeners.connect_error) listener(new Error(message ?? "unauthorized"));
|
|
526
|
+
},
|
|
527
|
+
serverPingExpire: () => void (pingExpired = true),
|
|
528
|
+
serverSend: (message) => {
|
|
529
|
+
const frame = JSON.stringify(message);
|
|
530
|
+
for (const listener of anyListeners) listener("message", frame);
|
|
531
|
+
},
|
|
532
|
+
serverSendRaw: (frame) => {
|
|
533
|
+
for (const listener of anyListeners) listener("message", frame);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
614
536
|
};
|
|
615
|
-
|
|
616
537
|
const toBase64Url = (json) => {
|
|
617
|
-
|
|
618
|
-
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
538
|
+
return btoa(encodeURIComponent(json).replace(/%([0-9A-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
619
539
|
};
|
|
620
540
|
let mintCount = 0;
|
|
621
|
-
/**
|
|
622
|
-
* Mints an unsigned JWT the bearer auth provider can decode, with `iat`, `exp` and a unique `jti`.
|
|
623
|
-
* Hand it to `setTokens()` to put the app's own provider into a session.
|
|
624
|
-
*
|
|
625
|
-
* @example
|
|
626
|
-
* ```ts
|
|
627
|
-
* TestBed.runInInjectionContext(() => authProviderRef.inject().setTokens(mintTestToken({ claims: { sub: '1' } }), 'refresh'));
|
|
628
|
-
* ```
|
|
629
|
-
*/
|
|
630
541
|
const mintTestToken = (options = {}) => {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
542
|
+
const { expiresInMs = 9e5, claims = {} } = options;
|
|
543
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
544
|
+
const exp = Math.floor((Date.now() + expiresInMs) / 1e3);
|
|
545
|
+
return `${toBase64Url(JSON.stringify({
|
|
546
|
+
alg: "none",
|
|
547
|
+
typ: "JWT"
|
|
548
|
+
}))}.${toBase64Url(JSON.stringify({
|
|
549
|
+
iat,
|
|
550
|
+
exp,
|
|
551
|
+
jti: ++mintCount,
|
|
552
|
+
...claims
|
|
553
|
+
}))}.`;
|
|
637
554
|
};
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Generated bundle index. Do not edit.
|
|
641
|
-
*/
|
|
642
|
-
|
|
643
555
|
export { createFakeQueryPersistenceStore, createWebSocketTestDouble, expectAndFlush, expectFlushAndWait, flushMultiTabSync, installFakeBroadcastChannel, installFakeWebLocks, mintTestToken, setupAuthTest, setupQueryTest };
|
|
644
|
-
|
|
556
|
+
|
|
557
|
+
//# sourceMappingURL=ethlete-query-testing.mjs.map
|