@contrail/flexplm 1.7.7-alpha.6568df9 → 1.7.7-alpha.7ad4e7e
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.
|
@@ -15,8 +15,6 @@ export declare class FlexPLMConnect {
|
|
|
15
15
|
/** Runs a cold acquisition and registers it so concurrent callers wait for it instead of opening their own session. */
|
|
16
16
|
private runColdAcquisition;
|
|
17
17
|
private getSessionKey;
|
|
18
|
-
/** Releases the connection of a rejected response before it is replaced by a retry. */
|
|
19
|
-
private discardBody;
|
|
20
18
|
private getCachedSession;
|
|
21
19
|
private storeSession;
|
|
22
20
|
private clearSession;
|
|
@@ -4,19 +4,6 @@ exports.FlexPLMConnect = void 0;
|
|
|
4
4
|
const app_framework_1 = require("@contrail/app-framework");
|
|
5
5
|
// Module-level on purpose: a new FlexPLMConnect is constructed for every event. Declaring for Session reusing use case.
|
|
6
6
|
const sessionCache = new Map();
|
|
7
|
-
// Upper bound so users/option combinations that are never requested again cannot accumulate cookies forever.
|
|
8
|
-
const MAX_CACHED_SESSIONS = 100;
|
|
9
|
-
const pruneSessionCache = () => {
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
for (const [key, session] of sessionCache) {
|
|
12
|
-
if (session.expiresAt <= now)
|
|
13
|
-
sessionCache.delete(key);
|
|
14
|
-
}
|
|
15
|
-
// Still full: drop the oldest entries (Map keeps insertion order).
|
|
16
|
-
while (sessionCache.size >= MAX_CACHED_SESSIONS) {
|
|
17
|
-
sessionCache.delete(sessionCache.keys().next().value);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
7
|
// In-flight cold session acquisitions per key, so concurrent events share one FlexPLM session instead of each creating their own.
|
|
21
8
|
const inFlightAcquisitions = new Map();
|
|
22
9
|
const DEFAULT_SESSION_COOKIE_NAME = 'JSESSIONID';
|
|
@@ -36,11 +23,10 @@ const parseRetryStatuses = (value) => {
|
|
|
36
23
|
else if (value !== undefined && value !== null)
|
|
37
24
|
list = [value];
|
|
38
25
|
const statuses = list
|
|
39
|
-
.map(
|
|
40
|
-
.filter(
|
|
26
|
+
.map(v => (typeof v === 'string' ? v.trim() : v))
|
|
27
|
+
.filter(v => v !== '' && v !== null && v !== undefined)
|
|
41
28
|
.map(Number)
|
|
42
|
-
|
|
43
|
-
.filter(status => Number.isInteger(status) && status >= 400 && status <= 499);
|
|
29
|
+
.filter(n => Number.isInteger(n) && n >= 100 && n <= 599);
|
|
44
30
|
return statuses.length ? statuses : DEFAULT_RETRY_STATUSES;
|
|
45
31
|
};
|
|
46
32
|
const parseTtlSeconds = (value) => {
|
|
@@ -126,21 +112,7 @@ class FlexPLMConnect {
|
|
|
126
112
|
}
|
|
127
113
|
}
|
|
128
114
|
getSessionKey() {
|
|
129
|
-
|
|
130
|
-
const { cookieName, ttlMs } = this.sessionOptions;
|
|
131
|
-
return JSON.stringify([
|
|
132
|
-
this.config.apiHost, this.config.urlContext, this.config.userName(), this.config.plmEnviornment, cookieName, ttlMs,
|
|
133
|
-
]);
|
|
134
|
-
}
|
|
135
|
-
/** Releases the connection of a rejected response before it is replaced by a retry. */
|
|
136
|
-
async discardBody(response) {
|
|
137
|
-
try {
|
|
138
|
-
if (typeof response?.body?.cancel === 'function')
|
|
139
|
-
await response.body.cancel();
|
|
140
|
-
}
|
|
141
|
-
catch (e) {
|
|
142
|
-
// best effort: nothing to do if the body is already consumed or locked
|
|
143
|
-
}
|
|
115
|
+
return [this.config.apiHost, this.config.urlContext, this.config.userName(), this.config.plmEnviornment].join('|');
|
|
144
116
|
}
|
|
145
117
|
getCachedSession() {
|
|
146
118
|
if (!this.sessionOptions.enabled)
|
|
@@ -157,11 +129,7 @@ class FlexPLMConnect {
|
|
|
157
129
|
}
|
|
158
130
|
storeSession(cookie) {
|
|
159
131
|
// sliding expiry: Windchill's session timeout is also sliding
|
|
160
|
-
|
|
161
|
-
// delete first so a refreshed entry moves to the newest position (oldest-first eviction then acts as LRU)
|
|
162
|
-
sessionCache.delete(key);
|
|
163
|
-
pruneSessionCache();
|
|
164
|
-
sessionCache.set(key, { cookie, expiresAt: Date.now() + this.sessionOptions.ttlMs });
|
|
132
|
+
sessionCache.set(this.getSessionKey(), { cookie, expiresAt: Date.now() + this.sessionOptions.ttlMs });
|
|
165
133
|
}
|
|
166
134
|
clearSession() {
|
|
167
135
|
sessionCache.delete(this.getSessionKey());
|
|
@@ -243,7 +211,7 @@ class FlexPLMConnect {
|
|
|
243
211
|
await wait;
|
|
244
212
|
if (getNewSession) {
|
|
245
213
|
const current = this.getCachedSession();
|
|
246
|
-
if (
|
|
214
|
+
if (!current || current === rejectedCookie)
|
|
247
215
|
this.clearSession();
|
|
248
216
|
}
|
|
249
217
|
const cached = this.getCachedSession();
|
|
@@ -274,7 +242,6 @@ class FlexPLMConnect {
|
|
|
274
242
|
// Added for Session reuse - ET-390 - Starts
|
|
275
243
|
if (sentCookie && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
276
244
|
console.warn('Cached FlexPLM session rejected on csrf call (status ' + response.status + '). Retrying with a new session.');
|
|
277
|
-
await this.discardBody(response);
|
|
278
245
|
throw new SessionRejectedError();
|
|
279
246
|
}
|
|
280
247
|
// Added for Session reuse - ET-390 - Ends
|
|
@@ -358,7 +325,6 @@ class FlexPLMConnect {
|
|
|
358
325
|
let response = await this.sendRequest(csrf, payload);
|
|
359
326
|
if (csrf.sessionCookie && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
360
327
|
console.warn('FlexPLM rejected session/nonce (status ' + response.status + '). Getting a new session and retrying once.');
|
|
361
|
-
await this.discardBody(response);
|
|
362
328
|
csrf = await this.getCSRF(true, csrf.sessionCookie);
|
|
363
329
|
response = await this.sendRequest(csrf, payload);
|
|
364
330
|
}
|
|
@@ -438,7 +404,6 @@ class FlexPLMConnect {
|
|
|
438
404
|
const first = await attempt();
|
|
439
405
|
let response = first.response;
|
|
440
406
|
if (first.sent && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
441
|
-
await this.discardBody(response);
|
|
442
407
|
if (this.getCachedSession() === first.sent)
|
|
443
408
|
this.clearSession();
|
|
444
409
|
response = (await attempt()).response;
|
|
@@ -156,7 +156,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
156
156
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
157
157
|
expect(callHeaders(1).CSRF_NONCE).toBe('n1');
|
|
158
158
|
});
|
|
159
|
-
it('
|
|
159
|
+
it('reuses the cached session on the next event (new FlexPLMConnect instance)', async () => {
|
|
160
160
|
const u = uniqueUser();
|
|
161
161
|
fetchMock()
|
|
162
162
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/'])) // event 1 GET
|
|
@@ -168,7 +168,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
168
168
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1'); // GET reuses it
|
|
169
169
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=abc.tomcat1'); // POST reuses it
|
|
170
170
|
});
|
|
171
|
-
it('
|
|
171
|
+
it('replaces the cached session when Windchill answers the GET with a new Set-Cookie', async () => {
|
|
172
172
|
const u = uniqueUser();
|
|
173
173
|
fetchMock()
|
|
174
174
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/']))
|
|
@@ -185,7 +185,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
185
185
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1'); // POST uses the replacement
|
|
186
186
|
expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1'); // event 3 GET sends the replacement
|
|
187
187
|
});
|
|
188
|
-
it('
|
|
188
|
+
it('retries once with a new session when the POST is rejected with 403', async () => {
|
|
189
189
|
const u = uniqueUser();
|
|
190
190
|
fetchMock()
|
|
191
191
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/'])) // GET
|
|
@@ -199,7 +199,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
199
199
|
expect(result.status).toBe(200);
|
|
200
200
|
expect(result.data).toEqual({ ok: true });
|
|
201
201
|
});
|
|
202
|
-
it('
|
|
202
|
+
it('also retries on 401', async () => {
|
|
203
203
|
const u = uniqueUser();
|
|
204
204
|
fetchMock()
|
|
205
205
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
@@ -209,7 +209,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
209
209
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
|
|
210
210
|
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
211
211
|
});
|
|
212
|
-
it('
|
|
212
|
+
it('does not loop: a second 403 after the retry is surfaced as an error', async () => {
|
|
213
213
|
const u = uniqueUser();
|
|
214
214
|
fetchMock()
|
|
215
215
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
@@ -220,7 +220,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
220
220
|
.rejects.toThrow('Error sending data to FlexPLM:status: 403');
|
|
221
221
|
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
222
222
|
});
|
|
223
|
-
it('
|
|
223
|
+
it('does not retry on non-auth errors such as 500 (event may already be processed)', async () => {
|
|
224
224
|
const u = uniqueUser();
|
|
225
225
|
fetchMock()
|
|
226
226
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -229,7 +229,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
229
229
|
.rejects.toThrow('Error sending data to FlexPLM:status: 500');
|
|
230
230
|
expect(fetchMock()).toHaveBeenCalledTimes(2);
|
|
231
231
|
});
|
|
232
|
-
it('
|
|
232
|
+
it('retries the csrf GET without a cookie when the cached session is rejected there', async () => {
|
|
233
233
|
const u = uniqueUser();
|
|
234
234
|
fetchMock()
|
|
235
235
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1'])) // event 1 GET
|
|
@@ -243,7 +243,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
243
243
|
expect(callHeaders(3).Cookie).toBeUndefined();
|
|
244
244
|
expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1');
|
|
245
245
|
});
|
|
246
|
-
it('
|
|
246
|
+
it('expires the cached session after ttlSeconds', async () => {
|
|
247
247
|
const u = uniqueUser();
|
|
248
248
|
const realNow = Date.now();
|
|
249
249
|
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
@@ -260,7 +260,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
260
260
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=def.tomcat1');
|
|
261
261
|
nowSpy.mockRestore();
|
|
262
262
|
});
|
|
263
|
-
it('
|
|
263
|
+
it('uses the last JSESSIONID when several Set-Cookie lines are returned', async () => {
|
|
264
264
|
const u = uniqueUser();
|
|
265
265
|
fetchMock()
|
|
266
266
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=first.tomcat1; Path=/', 'OTHER=x; Path=/', 'JSESSIONID=second.tomcat1; Path=/']))
|
|
@@ -268,7 +268,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
268
268
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
|
|
269
269
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=second.tomcat1');
|
|
270
270
|
});
|
|
271
|
-
it('
|
|
271
|
+
it('falls back to headers.get("set-cookie") when getSetCookie is not available', async () => {
|
|
272
272
|
const u = uniqueUser();
|
|
273
273
|
const legacy = {
|
|
274
274
|
status: 200,
|
|
@@ -280,7 +280,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
280
280
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
|
|
281
281
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=legacy.tomcat1');
|
|
282
282
|
});
|
|
283
|
-
it('
|
|
283
|
+
it('honours a custom cookieName', async () => {
|
|
284
284
|
const u = uniqueUser();
|
|
285
285
|
fetchMock()
|
|
286
286
|
.mockResolvedValueOnce(resp(200, ['MYSESSION=zzz; Path=/', 'JSESSIONID=ignored; Path=/']))
|
|
@@ -288,7 +288,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
288
288
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'MYSESSION' })).sendToFlexPLM(payload);
|
|
289
289
|
expect(callHeaders(1).Cookie).toBe('MYSESSION=zzz');
|
|
290
290
|
});
|
|
291
|
-
it('
|
|
291
|
+
it('accepts enabled as the string "true"', async () => {
|
|
292
292
|
const u = uniqueUser();
|
|
293
293
|
fetchMock()
|
|
294
294
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -296,39 +296,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
296
296
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: 'true' })).sendToFlexPLM(payload);
|
|
297
297
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
298
298
|
});
|
|
299
|
-
it('
|
|
300
|
-
const userA = uniqueUser();
|
|
301
|
-
const userB = uniqueUser();
|
|
302
|
-
const realNow = Date.now();
|
|
303
|
-
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
304
|
-
fetchMock()
|
|
305
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=a.tomcat1'])) // A GET
|
|
306
|
-
.mockResolvedValueOnce(resp(200, [], {})) // A POST
|
|
307
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=b.tomcat1'])) // B GET
|
|
308
|
-
.mockResolvedValueOnce(resp(200, [], {})) // B POST
|
|
309
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=a2.tomcat1'])) // A GET again
|
|
310
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
311
|
-
const cfg = (u) => sessionConfig(u, { enabled: true, ttlSeconds: 1 });
|
|
312
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
|
|
313
|
-
nowSpy.mockReturnValue(realNow + 2000); // A has expired
|
|
314
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg(userB)).sendToFlexPLM(payload); // storing B sweeps A
|
|
315
|
-
nowSpy.mockReturnValue(realNow); // rewind: only a swept A has no entry left
|
|
316
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
|
|
317
|
-
expect(callHeaders(4).Cookie).toBeUndefined();
|
|
318
|
-
nowSpy.mockRestore();
|
|
319
|
-
});
|
|
320
|
-
it('should not hold more than the maximum number of cached sessions', async () => {
|
|
321
|
-
const first = uniqueUser();
|
|
322
|
-
fetchMock().mockImplementation(async () => resp(200, ['JSESSIONID=s.tomcat1']));
|
|
323
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
|
|
324
|
-
for (let i = 0; i < 100; i++) {
|
|
325
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(uniqueUser())).sendToFlexPLM(payload);
|
|
326
|
-
}
|
|
327
|
-
fetchMock().mockClear();
|
|
328
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
|
|
329
|
-
expect(callHeaders(0).Cookie).toBeUndefined(); // oldest entry was evicted
|
|
330
|
-
});
|
|
331
|
-
it('should keep separate sessions per user', async () => {
|
|
299
|
+
it('keeps separate sessions per user', async () => {
|
|
332
300
|
const a = uniqueUser();
|
|
333
301
|
const b = uniqueUser();
|
|
334
302
|
fetchMock()
|
|
@@ -341,7 +309,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
341
309
|
expect(callHeaders(2).Cookie).toBeUndefined(); // user b must not see user a's cookie
|
|
342
310
|
expect(callHeaders(3).Cookie).toBe('JSESSIONID=bbb.tomcat1');
|
|
343
311
|
});
|
|
344
|
-
it('
|
|
312
|
+
it('never logs the cookie value', async () => {
|
|
345
313
|
const isInfoOn = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
|
|
346
314
|
const info = jest.spyOn(console, 'info').mockImplementation();
|
|
347
315
|
const u = uniqueUser();
|
|
@@ -372,11 +340,11 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
372
340
|
['a comma separated string', '401, 403'],
|
|
373
341
|
['garbage (falls back to 401/403)', 'abc'],
|
|
374
342
|
['an empty array (falls back to 401/403)', []],
|
|
375
|
-
])('
|
|
343
|
+
])('retryStatuses given as %s does not throw and still retries on 403', async (_label, retryStatuses) => {
|
|
376
344
|
await rejectedOnceThenOk(uniqueUser(), { retryStatuses });
|
|
377
345
|
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
378
346
|
});
|
|
379
|
-
it.each([['abc'], [0], [-5]])('
|
|
347
|
+
it.each([['abc'], [0], [-5]])('invalid ttlSeconds (%p) falls back to the 900s default', async (ttlSeconds) => {
|
|
380
348
|
const u = uniqueUser();
|
|
381
349
|
const realNow = Date.now();
|
|
382
350
|
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
@@ -392,7 +360,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
392
360
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
393
361
|
nowSpy.mockRestore();
|
|
394
362
|
});
|
|
395
|
-
it('
|
|
363
|
+
it('a blank cookieName falls back to JSESSIONID', async () => {
|
|
396
364
|
const u = uniqueUser();
|
|
397
365
|
fetchMock()
|
|
398
366
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -401,111 +369,8 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
401
369
|
expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
402
370
|
});
|
|
403
371
|
});
|
|
404
|
-
describe('retryStatuses restricted to client-error statuses', () => {
|
|
405
|
-
const postFailsWith = async (status, retryStatuses) => {
|
|
406
|
-
const u = uniqueUser();
|
|
407
|
-
fetchMock()
|
|
408
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
409
|
-
.mockResolvedValueOnce(resp(status, [], {}));
|
|
410
|
-
await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, retryStatuses })).sendToFlexPLM({ id: '1' }))
|
|
411
|
-
.rejects.toThrow('Error sending data to FlexPLM:status: ' + status);
|
|
412
|
-
expect(fetchMock()).toHaveBeenCalledTimes(2); // no retry
|
|
413
|
-
};
|
|
414
|
-
it.each([[500], ['500'], [[200, 500]]])('should ignore non-4xx retryStatuses (%p) and not retry a 500', async (retryStatuses) => {
|
|
415
|
-
await postFailsWith(500, retryStatuses);
|
|
416
|
-
});
|
|
417
|
-
it('should keep 4xx entries and drop others from a mixed retryStatuses list', async () => {
|
|
418
|
-
await postFailsWith(500, [401, 500]);
|
|
419
|
-
});
|
|
420
|
-
it('should not resend a successful event when retryStatuses is configured as 200', async () => {
|
|
421
|
-
const u = uniqueUser();
|
|
422
|
-
fetchMock()
|
|
423
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
424
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
425
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, retryStatuses: 200 })).sendToFlexPLM({ id: '1' });
|
|
426
|
-
expect(fetchMock()).toHaveBeenCalledTimes(2);
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
describe('session cache key includes session options', () => {
|
|
430
|
-
it('should not share a cached cookie between different cookieNames for the same user', async () => {
|
|
431
|
-
const u = uniqueUser();
|
|
432
|
-
fetchMock()
|
|
433
|
-
.mockResolvedValueOnce(resp(200, ['AAA=a1; Path=/']))
|
|
434
|
-
.mockResolvedValueOnce(resp(200, [], {}))
|
|
435
|
-
.mockResolvedValueOnce(resp(200, ['BBB=b1; Path=/']))
|
|
436
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
437
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'AAA' })).sendToFlexPLM({ id: '1' });
|
|
438
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'BBB' })).sendToFlexPLM({ id: '1' });
|
|
439
|
-
expect(callHeaders(2).Cookie).toBeUndefined(); // BBB config must not pick up the AAA session
|
|
440
|
-
expect(callHeaders(3).Cookie).toBe('BBB=b1');
|
|
441
|
-
});
|
|
442
|
-
it('should not share a cached session between different ttlSeconds for the same user', async () => {
|
|
443
|
-
const u = uniqueUser();
|
|
444
|
-
fetchMock()
|
|
445
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=short.tomcat1']))
|
|
446
|
-
.mockResolvedValueOnce(resp(200, [], {}))
|
|
447
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=long.tomcat1']))
|
|
448
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
449
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ttlSeconds: 1 })).sendToFlexPLM({ id: '1' });
|
|
450
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ttlSeconds: 900 })).sendToFlexPLM({ id: '1' });
|
|
451
|
-
expect(callHeaders(2).Cookie).toBeUndefined();
|
|
452
|
-
expect(callHeaders(3).Cookie).toBe('JSESSIONID=long.tomcat1');
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
describe('rejected response disposal', () => {
|
|
456
|
-
const withBody = (r) => Object.assign(r, { body: { cancel: jest.fn().mockResolvedValue(undefined) } });
|
|
457
|
-
it('should cancel the body of a POST rejected with 403 before retrying', async () => {
|
|
458
|
-
const u = uniqueUser();
|
|
459
|
-
const rejected = withBody(resp(403, [], {}));
|
|
460
|
-
fetchMock()
|
|
461
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
462
|
-
.mockResolvedValueOnce(rejected)
|
|
463
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
|
|
464
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
465
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
|
|
466
|
-
expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
|
|
467
|
-
});
|
|
468
|
-
it('should cancel the body of a CSRF GET rejected with the cached cookie before retrying', async () => {
|
|
469
|
-
const u = uniqueUser();
|
|
470
|
-
const rejected = withBody(resp(401, []));
|
|
471
|
-
fetchMock()
|
|
472
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
473
|
-
.mockResolvedValueOnce(resp(200, [], {}))
|
|
474
|
-
.mockResolvedValueOnce(rejected)
|
|
475
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
|
|
476
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
477
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
|
|
478
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
|
|
479
|
-
expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
|
|
480
|
-
});
|
|
481
|
-
it('should cancel the body of a getRequest rejected with the cached cookie before retrying', async () => {
|
|
482
|
-
const u = uniqueUser();
|
|
483
|
-
const rejected = withBody(resp(401, []));
|
|
484
|
-
fetchMock()
|
|
485
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
486
|
-
.mockResolvedValueOnce(resp(200, [], {}))
|
|
487
|
-
.mockResolvedValueOnce(rejected)
|
|
488
|
-
.mockResolvedValueOnce(resp(200, [], { items: [] }));
|
|
489
|
-
const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
|
|
490
|
-
await connect.sendToFlexPLM({ id: '1' });
|
|
491
|
-
await connect.getRequest();
|
|
492
|
-
expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
|
|
493
|
-
});
|
|
494
|
-
it('should still retry when cancelling the body throws', async () => {
|
|
495
|
-
const u = uniqueUser();
|
|
496
|
-
const rejected = resp(403, [], {});
|
|
497
|
-
rejected.body = { cancel: jest.fn().mockRejectedValue(new Error('locked')) };
|
|
498
|
-
fetchMock()
|
|
499
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
500
|
-
.mockResolvedValueOnce(rejected)
|
|
501
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
|
|
502
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
503
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
|
|
504
|
-
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
372
|
describe('cookie names containing regex metacharacters', () => {
|
|
508
|
-
it('
|
|
373
|
+
it('extracts a cookie whose name contains "+"', async () => {
|
|
509
374
|
const u = uniqueUser();
|
|
510
375
|
fetchMock()
|
|
511
376
|
.mockResolvedValueOnce(resp(200, ['SID+=abc; Path=/']))
|
|
@@ -513,7 +378,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
513
378
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'SID+' })).sendToFlexPLM(payload);
|
|
514
379
|
expect(callHeaders(1).Cookie).toBe('SID+=abc');
|
|
515
380
|
});
|
|
516
|
-
it('
|
|
381
|
+
it('treats "." literally and does not match a look-alike cookie', async () => {
|
|
517
382
|
const u = uniqueUser();
|
|
518
383
|
fetchMock()
|
|
519
384
|
.mockResolvedValueOnce(resp(200, ['AxB=wrong; Path=/']))
|
|
@@ -525,7 +390,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
525
390
|
describe('concurrent session acquisition', () => {
|
|
526
391
|
const flush = () => new Promise(r => setImmediate(r));
|
|
527
392
|
const isColdGet = (call) => call[1].method === 'GET' && !call[1].headers.Cookie;
|
|
528
|
-
it('
|
|
393
|
+
it('shares one session across concurrent events on a cold cache', async () => {
|
|
529
394
|
const u = uniqueUser();
|
|
530
395
|
let release;
|
|
531
396
|
const gate = new Promise(r => { release = r; });
|
|
@@ -544,7 +409,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
544
409
|
expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
|
|
545
410
|
results.forEach(r => expect(r.sessionCookie).toBe('abc.tomcat1'));
|
|
546
411
|
});
|
|
547
|
-
it('
|
|
412
|
+
it('a waiter does not inherit the failure of the first acquisition', async () => {
|
|
548
413
|
const u = uniqueUser();
|
|
549
414
|
let release;
|
|
550
415
|
const gate = new Promise(r => { release = r; });
|
|
@@ -559,7 +424,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
559
424
|
expect(await firstResult).toBeInstanceOf(Error);
|
|
560
425
|
expect((await second).sessionCookie).toBe('abc.tomcat1');
|
|
561
426
|
});
|
|
562
|
-
it('
|
|
427
|
+
it('parallel events rejected with 403 create only one replacement session', async () => {
|
|
563
428
|
const u = uniqueUser();
|
|
564
429
|
fetchMock().mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']));
|
|
565
430
|
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF(); // prime the cache with the old session
|
|
@@ -579,7 +444,7 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
579
444
|
});
|
|
580
445
|
describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
581
446
|
const payload = { id: '123' };
|
|
582
|
-
it('
|
|
447
|
+
it('sends no Cookie header and makes exactly one GET + one POST', async () => {
|
|
583
448
|
const u = uniqueUser();
|
|
584
449
|
fetchMock()
|
|
585
450
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // Set-Cookie present but must be ignored
|
|
@@ -593,7 +458,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
593
458
|
expect(callHeaders(i).Cookie).toBeUndefined();
|
|
594
459
|
}
|
|
595
460
|
});
|
|
596
|
-
it('
|
|
461
|
+
it('does not retry on 403 (behaves exactly as before)', async () => {
|
|
597
462
|
const u = uniqueUser();
|
|
598
463
|
fetchMock()
|
|
599
464
|
.mockResolvedValueOnce(resp(200))
|
|
@@ -602,7 +467,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
602
467
|
.rejects.toThrow('Error sending data to FlexPLM:status: 403');
|
|
603
468
|
expect(fetchMock()).toHaveBeenCalledTimes(2);
|
|
604
469
|
});
|
|
605
|
-
it('
|
|
470
|
+
it('explicit enabled:false behaves the same as not configured', async () => {
|
|
606
471
|
const u = uniqueUser();
|
|
607
472
|
fetchMock()
|
|
608
473
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -612,7 +477,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
612
477
|
});
|
|
613
478
|
});
|
|
614
479
|
describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
615
|
-
it('
|
|
480
|
+
it('sends the cached cookie on image/GET requests', async () => {
|
|
616
481
|
const u = uniqueUser();
|
|
617
482
|
fetchMock()
|
|
618
483
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // csrf GET primes the cache
|
|
@@ -623,7 +488,7 @@ describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
|
623
488
|
await connect.getRequest();
|
|
624
489
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
625
490
|
});
|
|
626
|
-
it('
|
|
491
|
+
it('retries once without the cookie when the cached session is rejected on getRequest', async () => {
|
|
627
492
|
const u = uniqueUser();
|
|
628
493
|
fetchMock()
|
|
629
494
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -636,7 +501,7 @@ describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
|
636
501
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
637
502
|
expect(callHeaders(3).Cookie).toBeUndefined();
|
|
638
503
|
});
|
|
639
|
-
it('
|
|
504
|
+
it('does not re-store the rejected cookie when the retry issues no Set-Cookie', async () => {
|
|
640
505
|
const u = uniqueUser();
|
|
641
506
|
fetchMock()
|
|
642
507
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=stale.tomcat1']))
|