@contrail/flexplm 1.7.7-alpha.99f62a7 → 1.7.7-alpha.c59ab5b

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.
@@ -28,23 +28,6 @@ const createConfig = (overrides) => ({
28
28
  useDistinctRestEndPointForImages: false,
29
29
  ...overrides,
30
30
  });
31
- // Added for Session reuse - ET-390 - Starts
32
- const csrfBody = { items: [{ attributes: { nonce_key: 'CSRF_NONCE', nonce: 'n1' } }] };
33
- // Response with Set-Cookie support. setCookie = list of raw Set-Cookie header lines.
34
- const resp = (status, setCookie = [], json = csrfBody) => ({
35
- status,
36
- json: jest.fn().mockResolvedValue(json),
37
- text: jest.fn().mockResolvedValue('error text'),
38
- headers: { getSetCookie: () => setCookie, get: () => null },
39
- });
40
- let userCounter = 0;
41
- const uniqueUser = () => 'sess-user-' + (++userCounter);
42
- // Session options are nested: { flexplmConnect: { sessionReuse: { enabled, ... } } }
43
- const sessionConfig = (user, sessionReuse = { enabled: true }) => createConfig({ userName: () => user, flexplmConnect: { sessionReuse } });
44
- const fetchMock = () => global.fetch;
45
- const callHeaders = (i) => fetchMock().mock.calls[i][1].headers;
46
- const callMethod = (i) => fetchMock().mock.calls[i][1].method;
47
- // Added for Session reuse - ET-390 - Ends
48
31
  describe('FlexPLMConnect.getRequest', () => {
49
32
  it('should call default URL with urlContext when no params provided', async () => {
50
33
  const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
@@ -138,603 +121,3 @@ describe('FlexPLMConnect.sendRequest', () => {
138
121
  consoleInfoSpy.mockRestore();
139
122
  });
140
123
  });
141
- // Added for Session reuse - ET-390 - Starts
142
- describe('FlexPLMConnect session reuse', () => {
143
- const payload = { id: '123' };
144
- it('should capture JSESSIONID from the CSRF GET and send it on the POST', async () => {
145
- const u = uniqueUser();
146
- fetchMock()
147
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/; HttpOnly']))
148
- .mockResolvedValueOnce(resp(200, [], {}));
149
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
150
- expect(fetchMock()).toHaveBeenCalledTimes(2);
151
- expect(callHeaders(0).Cookie).toBeUndefined(); // first ever call: no cached session
152
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
153
- expect(callHeaders(1).CSRF_NONCE).toBe('n1');
154
- });
155
- it('should reuse the cached session on the next event (new FlexPLMConnect instance)', async () => {
156
- const u = uniqueUser();
157
- fetchMock()
158
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/'])) // event 1 GET
159
- .mockResolvedValueOnce(resp(200, [], {})) // event 1 POST
160
- .mockResolvedValueOnce(resp(200, [])) // event 2 GET (valid session -> no Set-Cookie)
161
- .mockResolvedValueOnce(resp(200, [], {})); // event 2 POST
162
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
163
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
164
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1'); // GET reuses it
165
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=abc.tomcat1'); // POST reuses it
166
- });
167
- it('should replace the cached session when Windchill answers the GET with a new Set-Cookie', async () => {
168
- const u = uniqueUser();
169
- fetchMock()
170
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/']))
171
- .mockResolvedValueOnce(resp(200, [], {}))
172
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/'])) // old one was stale -> new issued
173
- .mockResolvedValueOnce(resp(200, [], {}))
174
- .mockResolvedValueOnce(resp(200, []))
175
- .mockResolvedValueOnce(resp(200, [], {}));
176
- const connect = () => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
177
- await connect().sendToFlexPLM(payload);
178
- await connect().sendToFlexPLM(payload);
179
- await connect().sendToFlexPLM(payload);
180
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=old.tomcat1'); // event 2 GET sent the old one
181
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1'); // POST uses the replacement
182
- expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1'); // event 3 GET sends the replacement
183
- });
184
- it('should retry once with a new session when the POST is rejected with 403', async () => {
185
- const u = uniqueUser();
186
- fetchMock()
187
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/'])) // GET
188
- .mockResolvedValueOnce(resp(403, [], {})) // POST rejected
189
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/'])) // GET again, fresh session
190
- .mockResolvedValueOnce(resp(200, [], { ok: true })); // POST succeeds
191
- const result = await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
192
- expect(fetchMock()).toHaveBeenCalledTimes(4);
193
- expect(callHeaders(2).Cookie).toBeUndefined(); // retry GET must NOT send the stale cookie
194
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=new.tomcat1');
195
- expect(result.status).toBe(200);
196
- expect(result.data).toEqual({ ok: true });
197
- });
198
- it('should also retry on 401', async () => {
199
- const u = uniqueUser();
200
- fetchMock()
201
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
202
- .mockResolvedValueOnce(resp(401, [], {}))
203
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
204
- .mockResolvedValueOnce(resp(200, [], {}));
205
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
206
- expect(fetchMock()).toHaveBeenCalledTimes(4);
207
- });
208
- it('should not loop: a second 403 after the retry is surfaced as an error', async () => {
209
- const u = uniqueUser();
210
- fetchMock()
211
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
212
- .mockResolvedValueOnce(resp(403, [], {}))
213
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
214
- .mockResolvedValueOnce(resp(403, [], {}));
215
- await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload))
216
- .rejects.toThrow('Error sending data to FlexPLM:status: 403');
217
- expect(fetchMock()).toHaveBeenCalledTimes(4);
218
- });
219
- it('should not retry on non-auth errors such as 500 (event may already be processed)', async () => {
220
- const u = uniqueUser();
221
- fetchMock()
222
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
223
- .mockResolvedValueOnce(resp(500, [], {}));
224
- await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload))
225
- .rejects.toThrow('Error sending data to FlexPLM:status: 500');
226
- expect(fetchMock()).toHaveBeenCalledTimes(2);
227
- });
228
- it('should retry the csrf GET without a cookie when the cached session is rejected there', async () => {
229
- const u = uniqueUser();
230
- fetchMock()
231
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1'])) // event 1 GET
232
- .mockResolvedValueOnce(resp(200, [], {})) // event 1 POST
233
- .mockResolvedValueOnce(resp(401, [])) // event 2 GET with cached cookie rejected
234
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1'])) // event 2 GET retry, no cookie
235
- .mockResolvedValueOnce(resp(200, [], {})); // event 2 POST
236
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
237
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
238
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=old.tomcat1');
239
- expect(callHeaders(3).Cookie).toBeUndefined();
240
- expect(callHeaders(4).Cookie).toBe('JSESSIONID=new.tomcat1');
241
- });
242
- it('should expire the cached session after ttlSeconds', async () => {
243
- const u = uniqueUser();
244
- const realNow = Date.now();
245
- const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
246
- fetchMock()
247
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
248
- .mockResolvedValueOnce(resp(200, [], {}))
249
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=def.tomcat1']))
250
- .mockResolvedValueOnce(resp(200, [], {}));
251
- const cfg = sessionConfig(u, { enabled: true, ttlSeconds: 1 });
252
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
253
- nowSpy.mockReturnValue(realNow + 2000); // 2s later > 1s ttl
254
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
255
- expect(callHeaders(2).Cookie).toBeUndefined(); // expired -> GET sends no cookie
256
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=def.tomcat1');
257
- nowSpy.mockRestore();
258
- });
259
- it('should use the last JSESSIONID when several Set-Cookie lines are returned', async () => {
260
- const u = uniqueUser();
261
- fetchMock()
262
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=first.tomcat1; Path=/', 'OTHER=x; Path=/', 'JSESSIONID=second.tomcat1; Path=/']))
263
- .mockResolvedValueOnce(resp(200, [], {}));
264
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
265
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=second.tomcat1');
266
- });
267
- it('should fall back to headers.get("set-cookie") when getSetCookie is not available', async () => {
268
- const u = uniqueUser();
269
- const legacy = {
270
- status: 200,
271
- json: jest.fn().mockResolvedValue(csrfBody),
272
- text: jest.fn(),
273
- headers: { get: (k) => (k.toLowerCase() === 'set-cookie' ? 'JSESSIONID=legacy.tomcat1; Path=/' : null) },
274
- };
275
- fetchMock().mockResolvedValueOnce(legacy).mockResolvedValueOnce(resp(200, [], {}));
276
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
277
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=legacy.tomcat1');
278
- });
279
- it('should honour a custom cookieName', async () => {
280
- const u = uniqueUser();
281
- fetchMock()
282
- .mockResolvedValueOnce(resp(200, ['MYSESSION=zzz; Path=/', 'JSESSIONID=ignored; Path=/']))
283
- .mockResolvedValueOnce(resp(200, [], {}));
284
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'MYSESSION' })).sendToFlexPLM(payload);
285
- expect(callHeaders(1).Cookie).toBe('MYSESSION=zzz');
286
- });
287
- it('should accept enabled as the string "true"', async () => {
288
- const u = uniqueUser();
289
- fetchMock()
290
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
291
- .mockResolvedValueOnce(resp(200, [], {}));
292
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: 'true' })).sendToFlexPLM(payload);
293
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
294
- });
295
- it('should evict expired sessions of other users when a new session is stored', async () => {
296
- const userA = uniqueUser();
297
- const userB = uniqueUser();
298
- const realNow = Date.now();
299
- const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
300
- fetchMock()
301
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=a.tomcat1'])) // A GET
302
- .mockResolvedValueOnce(resp(200, [], {})) // A POST
303
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=b.tomcat1'])) // B GET
304
- .mockResolvedValueOnce(resp(200, [], {})) // B POST
305
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=a2.tomcat1'])) // A GET again
306
- .mockResolvedValueOnce(resp(200, [], {}));
307
- const cfg = (u) => sessionConfig(u, { enabled: true, ttlSeconds: 1 });
308
- await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
309
- nowSpy.mockReturnValue(realNow + 2000); // A has expired
310
- await new flexplm_connect_1.FlexPLMConnect(cfg(userB)).sendToFlexPLM(payload); // storing B sweeps A
311
- nowSpy.mockReturnValue(realNow); // rewind: only a swept A has no entry left
312
- await new flexplm_connect_1.FlexPLMConnect(cfg(userA)).sendToFlexPLM(payload);
313
- expect(callHeaders(4).Cookie).toBeUndefined();
314
- nowSpy.mockRestore();
315
- });
316
- it('should not hold more than the maximum number of cached sessions', async () => {
317
- const first = uniqueUser();
318
- fetchMock().mockImplementation(async () => resp(200, ['JSESSIONID=s.tomcat1']));
319
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
320
- for (let i = 0; i < 100; i++) {
321
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(uniqueUser())).sendToFlexPLM(payload);
322
- }
323
- fetchMock().mockClear();
324
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(first)).sendToFlexPLM(payload);
325
- expect(callHeaders(0).Cookie).toBeUndefined(); // oldest entry was evicted
326
- });
327
- it('should keep separate sessions per user', async () => {
328
- const a = uniqueUser();
329
- const b = uniqueUser();
330
- fetchMock()
331
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=aaa.tomcat1']))
332
- .mockResolvedValueOnce(resp(200, [], {}))
333
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=bbb.tomcat1']))
334
- .mockResolvedValueOnce(resp(200, [], {}));
335
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(a)).sendToFlexPLM(payload);
336
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(b)).sendToFlexPLM(payload);
337
- expect(callHeaders(2).Cookie).toBeUndefined(); // user b must not see user a's cookie
338
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=bbb.tomcat1');
339
- });
340
- it('should never log the cookie value', async () => {
341
- const isInfoOn = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
342
- const info = jest.spyOn(console, 'info').mockImplementation();
343
- const u = uniqueUser();
344
- fetchMock()
345
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=supersecret.tomcat1']))
346
- .mockResolvedValueOnce(resp(200, [], {}))
347
- .mockResolvedValueOnce(resp(200, []))
348
- .mockResolvedValueOnce(resp(200, [], {}));
349
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload);
350
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload); // 2nd event sends Cookie on GET and POST
351
- const logged = info.mock.calls.map(c => String(c[0] ?? '')).join('\n');
352
- expect(logged).toContain('"Cookie":"***masked***"');
353
- expect(logged).not.toContain('supersecret');
354
- isInfoOn.mockRestore();
355
- info.mockRestore();
356
- });
357
- describe('static Cookie header', () => {
358
- const withStatic = (user, staticHeaders, sessionReuse = { enabled: true }) => createConfig({ userName: () => user, flexplmConnect: { sessionReuse, staticHeaders } });
359
- it('should keep other static cookies and replace only the managed one once a session is cached', async () => {
360
- const u = uniqueUser();
361
- fetchMock()
362
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1; Path=/']))
363
- .mockResolvedValueOnce(resp(200, [], {}));
364
- await new flexplm_connect_1.FlexPLMConnect(withStatic(u, { Cookie: 'lb=abc; JSESSIONID=stale; theme=dark' })).sendToFlexPLM(payload);
365
- expect(callHeaders(0).Cookie).toBe('lb=abc; theme=dark'); // cold: stale managed cookie dropped
366
- expect(callHeaders(1).Cookie).toBe('lb=abc; theme=dark; JSESSIONID=new.tomcat1');
367
- });
368
- it('should send a single Cookie header when the static header name is lowercase', async () => {
369
- const u = uniqueUser();
370
- fetchMock()
371
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/']))
372
- .mockResolvedValueOnce(resp(200, [], {}));
373
- await new flexplm_connect_1.FlexPLMConnect(withStatic(u, { cookie: 'lb=abc' })).sendToFlexPLM(payload);
374
- const postHeaders = callHeaders(1);
375
- expect(Object.keys(postHeaders).filter(k => k.toLowerCase() === 'cookie')).toEqual(['Cookie']);
376
- expect(postHeaders.Cookie).toBe('lb=abc; JSESSIONID=abc.tomcat1');
377
- });
378
- it('should send the static Cookie unchanged when session reuse is disabled', async () => {
379
- const u = uniqueUser();
380
- fetchMock()
381
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/']))
382
- .mockResolvedValueOnce(resp(200, [], {}));
383
- await new flexplm_connect_1.FlexPLMConnect(withStatic(u, { Cookie: 'lb=abc; JSESSIONID=static' }, { enabled: false })).sendToFlexPLM(payload);
384
- expect(callHeaders(0).Cookie).toBe('lb=abc; JSESSIONID=static');
385
- expect(callHeaders(1).Cookie).toBe('lb=abc; JSESSIONID=static');
386
- });
387
- it('should not resend a rejected static JSESSIONID on the fresh retry', async () => {
388
- const u = uniqueUser();
389
- fetchMock()
390
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1; Path=/']))
391
- .mockResolvedValueOnce(resp(403, [], {})) // POST rejected
392
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=fresh.tomcat1; Path=/'])) // fresh GET
393
- .mockResolvedValueOnce(resp(200, [], {}));
394
- await new flexplm_connect_1.FlexPLMConnect(withStatic(u, { Cookie: 'lb=abc; JSESSIONID=static' })).sendToFlexPLM(payload);
395
- expect(callHeaders(2).Cookie).toBe('lb=abc'); // fresh GET carries no JSESSIONID
396
- expect(callHeaders(3).Cookie).toBe('lb=abc; JSESSIONID=fresh.tomcat1');
397
- });
398
- it('should mask a lowercase static cookie header in logs', async () => {
399
- const isInfoOn = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
400
- const info = jest.spyOn(console, 'info').mockImplementation();
401
- fetchMock()
402
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
403
- .mockResolvedValueOnce(resp(200, [], {}));
404
- await new flexplm_connect_1.FlexPLMConnect(withStatic(uniqueUser(), { cookie: 'lb=topsecret' }, { enabled: false })).sendToFlexPLM(payload);
405
- const logged = info.mock.calls.map(c => String(c[0] ?? '')).join('\n');
406
- expect(logged).not.toContain('topsecret');
407
- isInfoOn.mockRestore();
408
- info.mockRestore();
409
- });
410
- });
411
- describe('option normalisation', () => {
412
- const rejectedOnceThenOk = (u, opts) => {
413
- fetchMock()
414
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
415
- .mockResolvedValueOnce(resp(403, [], {}))
416
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
417
- .mockResolvedValueOnce(resp(200, [], {}));
418
- return new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ...opts })).sendToFlexPLM(payload);
419
- };
420
- it.each([
421
- ['a scalar number', 403],
422
- ['a comma separated string', '401, 403'],
423
- ['garbage (falls back to 401/403)', 'abc'],
424
- ['an empty array (falls back to 401/403)', []],
425
- ])('should not throw and still retry on 403 when retryStatuses is %s', async (_label, retryStatuses) => {
426
- await rejectedOnceThenOk(uniqueUser(), { retryStatuses });
427
- expect(fetchMock()).toHaveBeenCalledTimes(4);
428
- });
429
- it.each([['abc'], [0], [-5]])('should fall back to the 900s default when ttlSeconds is invalid (%p)', async (ttlSeconds) => {
430
- const u = uniqueUser();
431
- const realNow = Date.now();
432
- const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
433
- fetchMock()
434
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
435
- .mockResolvedValueOnce(resp(200, [], {}))
436
- .mockResolvedValueOnce(resp(200, []))
437
- .mockResolvedValueOnce(resp(200, [], {}));
438
- const cfg = sessionConfig(u, { enabled: true, ttlSeconds });
439
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
440
- nowSpy.mockReturnValue(realNow + 2000);
441
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
442
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
443
- nowSpy.mockRestore();
444
- });
445
- it('should fall back to JSESSIONID when cookieName is blank', async () => {
446
- const u = uniqueUser();
447
- fetchMock()
448
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
449
- .mockResolvedValueOnce(resp(200, [], {}));
450
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: ' ' })).sendToFlexPLM(payload);
451
- expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
452
- });
453
- });
454
- describe('retryStatuses restricted to client-error statuses', () => {
455
- const postFailsWith = async (status, retryStatuses) => {
456
- const u = uniqueUser();
457
- fetchMock()
458
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
459
- .mockResolvedValueOnce(resp(status, [], {}));
460
- await expect(new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, retryStatuses })).sendToFlexPLM({ id: '1' }))
461
- .rejects.toThrow('Error sending data to FlexPLM:status: ' + status);
462
- expect(fetchMock()).toHaveBeenCalledTimes(2); // no retry
463
- };
464
- it.each([[500], ['500'], [[200, 500]]])('should ignore non-4xx retryStatuses (%p) and not retry a 500', async (retryStatuses) => {
465
- await postFailsWith(500, retryStatuses);
466
- });
467
- it('should keep 4xx entries and drop others from a mixed retryStatuses list', async () => {
468
- await postFailsWith(500, [401, 500]);
469
- });
470
- it('should not resend a successful event when retryStatuses is configured as 200', async () => {
471
- const u = uniqueUser();
472
- fetchMock()
473
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
474
- .mockResolvedValueOnce(resp(200, [], {}));
475
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, retryStatuses: 200 })).sendToFlexPLM({ id: '1' });
476
- expect(fetchMock()).toHaveBeenCalledTimes(2);
477
- });
478
- });
479
- describe('session cache key includes session options', () => {
480
- it('should not share a cached cookie between different cookieNames for the same user', async () => {
481
- const u = uniqueUser();
482
- fetchMock()
483
- .mockResolvedValueOnce(resp(200, ['AAA=a1; Path=/']))
484
- .mockResolvedValueOnce(resp(200, [], {}))
485
- .mockResolvedValueOnce(resp(200, ['BBB=b1; Path=/']))
486
- .mockResolvedValueOnce(resp(200, [], {}));
487
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'AAA' })).sendToFlexPLM({ id: '1' });
488
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'BBB' })).sendToFlexPLM({ id: '1' });
489
- expect(callHeaders(2).Cookie).toBeUndefined(); // BBB config must not pick up the AAA session
490
- expect(callHeaders(3).Cookie).toBe('BBB=b1');
491
- });
492
- it('should not share a cached session between different ttlSeconds for the same user', async () => {
493
- const u = uniqueUser();
494
- fetchMock()
495
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=short.tomcat1']))
496
- .mockResolvedValueOnce(resp(200, [], {}))
497
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=long.tomcat1']))
498
- .mockResolvedValueOnce(resp(200, [], {}));
499
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ttlSeconds: 1 })).sendToFlexPLM({ id: '1' });
500
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ttlSeconds: 900 })).sendToFlexPLM({ id: '1' });
501
- expect(callHeaders(2).Cookie).toBeUndefined();
502
- expect(callHeaders(3).Cookie).toBe('JSESSIONID=long.tomcat1');
503
- });
504
- });
505
- describe('rejected response disposal', () => {
506
- const withBody = (r) => Object.assign(r, { body: { cancel: jest.fn().mockResolvedValue(undefined) } });
507
- it('should cancel the body of a POST rejected with 403 before retrying', async () => {
508
- const u = uniqueUser();
509
- const rejected = withBody(resp(403, [], {}));
510
- fetchMock()
511
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
512
- .mockResolvedValueOnce(rejected)
513
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
514
- .mockResolvedValueOnce(resp(200, [], {}));
515
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
516
- expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
517
- });
518
- it('should cancel the body of a CSRF GET rejected with the cached cookie before retrying', async () => {
519
- const u = uniqueUser();
520
- const rejected = withBody(resp(401, []));
521
- fetchMock()
522
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
523
- .mockResolvedValueOnce(resp(200, [], {}))
524
- .mockResolvedValueOnce(rejected)
525
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
526
- .mockResolvedValueOnce(resp(200, [], {}));
527
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
528
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
529
- expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
530
- });
531
- it('should cancel the body of a getRequest rejected with the cached cookie before retrying', async () => {
532
- const u = uniqueUser();
533
- const rejected = withBody(resp(401, []));
534
- fetchMock()
535
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
536
- .mockResolvedValueOnce(resp(200, [], {}))
537
- .mockResolvedValueOnce(rejected)
538
- .mockResolvedValueOnce(resp(200, [], { items: [] }));
539
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
540
- await connect.sendToFlexPLM({ id: '1' });
541
- await connect.getRequest();
542
- expect(rejected.body.cancel).toHaveBeenCalledTimes(1);
543
- });
544
- it('should still retry when cancelling the body throws', async () => {
545
- const u = uniqueUser();
546
- const rejected = resp(403, [], {});
547
- rejected.body = { cancel: jest.fn().mockRejectedValue(new Error('locked')) };
548
- fetchMock()
549
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
550
- .mockResolvedValueOnce(rejected)
551
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
552
- .mockResolvedValueOnce(resp(200, [], {}));
553
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM({ id: '1' });
554
- expect(fetchMock()).toHaveBeenCalledTimes(4);
555
- });
556
- });
557
- describe('cookie names containing regex metacharacters', () => {
558
- it('should extract a cookie whose name contains "+"', async () => {
559
- const u = uniqueUser();
560
- fetchMock()
561
- .mockResolvedValueOnce(resp(200, ['SID+=abc; Path=/']))
562
- .mockResolvedValueOnce(resp(200, [], {}));
563
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'SID+' })).sendToFlexPLM(payload);
564
- expect(callHeaders(1).Cookie).toBe('SID+=abc');
565
- });
566
- it('should treat "." literally and not match a look-alike cookie', async () => {
567
- const u = uniqueUser();
568
- fetchMock()
569
- .mockResolvedValueOnce(resp(200, ['AxB=wrong; Path=/']))
570
- .mockResolvedValueOnce(resp(200, [], {}));
571
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'A.B' })).sendToFlexPLM(payload);
572
- expect(callHeaders(1).Cookie).toBeUndefined();
573
- });
574
- });
575
- describe('concurrent session acquisition', () => {
576
- const flush = () => new Promise(r => setImmediate(r));
577
- const isColdGet = (call) => call[1].method === 'GET' && !call[1].headers.Cookie;
578
- it('should not let a late response using an older cookie roll back a refreshed session', async () => {
579
- const u = uniqueUser();
580
- const cfg = () => sessionConfig(u);
581
- // seed the cache with 'old'
582
- fetchMock()
583
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=old']))
584
- .mockResolvedValueOnce(resp(200, [], {}));
585
- await new flexplm_connect_1.FlexPLMConnect(cfg()).sendToFlexPLM(payload);
586
- let lateGetsSeen = 0;
587
- let releaseLate;
588
- const lateGate = new Promise(r => { releaseLate = r; });
589
- fetchMock().mockReset();
590
- fetchMock().mockImplementation(async (_url, opts) => {
591
- const cookie = opts.headers.Cookie;
592
- if (opts.method === 'GET' && cookie === 'JSESSIONID=old') {
593
- if (++lateGetsSeen === 1)
594
- return resp(403, []); // event A: old rejected
595
- await lateGate; // event B: slow 200, no Set-Cookie
596
- return resp(200, []);
597
- }
598
- if (opts.method === 'GET' && !cookie)
599
- return resp(200, ['JSESSIONID=new']); // A's fresh acquisition
600
- if (opts.method === 'GET')
601
- return resp(200, []);
602
- return resp(200, [], {});
603
- });
604
- const a = new flexplm_connect_1.FlexPLMConnect(cfg()).sendToFlexPLM(payload);
605
- const b = new flexplm_connect_1.FlexPLMConnect(cfg()).sendToFlexPLM(payload);
606
- await a;
607
- releaseLate();
608
- await b;
609
- fetchMock().mockClear();
610
- await new flexplm_connect_1.FlexPLMConnect(cfg()).getCSRF();
611
- expect(callHeaders(0).Cookie).toBe('JSESSIONID=new'); // B must not have stored 'old' again
612
- });
613
- it('should share one session across concurrent events on a cold cache', async () => {
614
- const u = uniqueUser();
615
- let release;
616
- const gate = new Promise(r => { release = r; });
617
- fetchMock().mockImplementation(async (_url, opts) => {
618
- if (opts.method === 'GET' && !opts.headers.Cookie) {
619
- await gate;
620
- return resp(200, ['JSESSIONID=abc.tomcat1']);
621
- }
622
- return resp(200, []);
623
- });
624
- const all = Promise.all([1, 2, 3].map(() => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF()));
625
- await flush();
626
- expect(fetchMock()).toHaveBeenCalledTimes(1); // the other two are waiting for the first acquisition
627
- release();
628
- const results = await all;
629
- expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
630
- results.forEach(r => expect(r.sessionCookie).toBe('abc.tomcat1'));
631
- });
632
- it('should not make a waiter inherit the failure of the first acquisition', async () => {
633
- const u = uniqueUser();
634
- let release;
635
- const gate = new Promise(r => { release = r; });
636
- fetchMock()
637
- .mockImplementationOnce(async () => { await gate; return resp(500, []); })
638
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']));
639
- const first = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF();
640
- const firstResult = first.catch(e => e);
641
- const second = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF();
642
- await flush();
643
- release();
644
- expect(await firstResult).toBeInstanceOf(Error);
645
- expect((await second).sessionCookie).toBe('abc.tomcat1');
646
- });
647
- it('should create only one replacement session for parallel events rejected with 403', async () => {
648
- const u = uniqueUser();
649
- fetchMock().mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']));
650
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF(); // prime the cache with the old session
651
- fetchMock().mockReset();
652
- fetchMock().mockImplementation(async (_url, opts) => {
653
- const cookie = opts.headers.Cookie;
654
- if (opts.method === 'POST')
655
- return cookie === 'JSESSIONID=new.tomcat1' ? resp(200, [], {}) : resp(403, [], {});
656
- if (!cookie)
657
- return resp(200, ['JSESSIONID=new.tomcat1']);
658
- return resp(200, []);
659
- });
660
- await Promise.all([1, 2, 3].map(() => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload)));
661
- expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
662
- });
663
- });
664
- });
665
- describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
666
- const payload = { id: '123' };
667
- it('should send no Cookie header and make exactly one GET + one POST', async () => {
668
- const u = uniqueUser();
669
- fetchMock()
670
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // Set-Cookie present but must be ignored
671
- .mockResolvedValueOnce(resp(200, [], {}))
672
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=def.tomcat1']))
673
- .mockResolvedValueOnce(resp(200, [], {}));
674
- const cfg = createConfig({ userName: () => u }); // no flexplmConnect at all
675
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
676
- await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
677
- for (let i = 0; i < 4; i++) {
678
- expect(callHeaders(i).Cookie).toBeUndefined();
679
- }
680
- });
681
- it('should not retry on 403 (behaves exactly as before)', async () => {
682
- const u = uniqueUser();
683
- fetchMock()
684
- .mockResolvedValueOnce(resp(200))
685
- .mockResolvedValueOnce(resp(403, [], {}));
686
- await expect(new flexplm_connect_1.FlexPLMConnect(createConfig({ userName: () => u })).sendToFlexPLM(payload))
687
- .rejects.toThrow('Error sending data to FlexPLM:status: 403');
688
- expect(fetchMock()).toHaveBeenCalledTimes(2);
689
- });
690
- it('should behave the same as not configured when enabled is false', async () => {
691
- const u = uniqueUser();
692
- fetchMock()
693
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
694
- .mockResolvedValueOnce(resp(200, [], {}));
695
- await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: false })).sendToFlexPLM(payload);
696
- expect(callHeaders(1).Cookie).toBeUndefined();
697
- });
698
- });
699
- describe('FlexPLMConnect.getRequest session reuse', () => {
700
- it('should send the cached cookie on image/GET requests', async () => {
701
- const u = uniqueUser();
702
- fetchMock()
703
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // csrf GET primes the cache
704
- .mockResolvedValueOnce(resp(200, [], {})) // POST
705
- .mockResolvedValueOnce(resp(200, [], { items: [] })); // getRequest
706
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
707
- await connect.sendToFlexPLM({ id: '1' });
708
- await connect.getRequest();
709
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
710
- });
711
- it('should retry once without the cookie when the cached session is rejected on getRequest', async () => {
712
- const u = uniqueUser();
713
- fetchMock()
714
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
715
- .mockResolvedValueOnce(resp(200, [], {}))
716
- .mockResolvedValueOnce(resp(401, [])) // getRequest with cached cookie
717
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1'], { items: [] })); // retry, no cookie
718
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
719
- await connect.sendToFlexPLM({ id: '1' });
720
- await connect.getRequest();
721
- expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
722
- expect(callHeaders(3).Cookie).toBeUndefined();
723
- });
724
- it('should not re-store the rejected cookie when the retry issues no Set-Cookie', async () => {
725
- const u = uniqueUser();
726
- fetchMock()
727
- .mockResolvedValueOnce(resp(200, ['JSESSIONID=stale.tomcat1']))
728
- .mockResolvedValueOnce(resp(200, [], {}))
729
- .mockResolvedValueOnce(resp(401, [])) // getRequest with the cached cookie
730
- .mockResolvedValueOnce(resp(200, [], { items: [] })) // retry succeeds, no Set-Cookie
731
- .mockResolvedValueOnce(resp(200, [], { items: [] })); // next getRequest
732
- const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
733
- await connect.sendToFlexPLM({ id: '1' });
734
- await connect.getRequest();
735
- await connect.getRequest();
736
- expect(callHeaders(3).Cookie).toBeUndefined();
737
- expect(callHeaders(4).Cookie).toBeUndefined(); // stale cookie must not be sent again
738
- });
739
- });
740
- // Added for Session reuse - ET-390 - Ends
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.7.7-alpha.99f62a7",
3
+ "version": "1.7.7-alpha.c59ab5b",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",