@contrail/flexplm 1.7.7-alpha.85f6463 → 1.7.7-alpha.9292891
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.
|
@@ -6,10 +6,6 @@ export interface FCConfig {
|
|
|
6
6
|
eventObjectClassParam?: string;
|
|
7
7
|
eventFlexTypePathParam?: string;
|
|
8
8
|
flexplmConnect?: any;
|
|
9
|
-
'flexplmConnect.sessionReuse.enabled'?: boolean | string;
|
|
10
|
-
'flexplmConnect.sessionReuse.ttlSeconds'?: number | string;
|
|
11
|
-
'flexplmConnect.sessionReuse.cookieName'?: string;
|
|
12
|
-
'flexplmConnect.sessionReuse.retryStatuses'?: number | number[] | string;
|
|
13
9
|
taskId?: string;
|
|
14
10
|
apiHost: string;
|
|
15
11
|
userName(): string;
|
|
@@ -6,17 +6,7 @@ export declare class FlexPLMConnect {
|
|
|
6
6
|
payloadSendAsArray: boolean;
|
|
7
7
|
constructor(_config: FCConfig, endPoint?: any, payloadAsArray?: any);
|
|
8
8
|
private get sessionOptions();
|
|
9
|
-
/**
|
|
10
|
-
* Waits until no cold session acquisition is running for this key.
|
|
11
|
-
* Callers must `await` this only when it returns a promise: awaiting even a resolved promise yields,
|
|
12
|
-
* and the check-then-register in runColdAcquisition has to stay synchronous to be race free.
|
|
13
|
-
*/
|
|
14
|
-
private waitForInFlightAcquisition;
|
|
15
|
-
/** Runs a cold acquisition and registers it so concurrent callers wait for it instead of opening their own session. */
|
|
16
|
-
private runColdAcquisition;
|
|
17
9
|
private getSessionKey;
|
|
18
|
-
/** Releases the connection of a rejected response before it is replaced by a retry. */
|
|
19
|
-
private discardBody;
|
|
20
10
|
private getCachedSession;
|
|
21
11
|
private storeSession;
|
|
22
12
|
private clearSession;
|
|
@@ -24,8 +14,11 @@ export declare class FlexPLMConnect {
|
|
|
24
14
|
private extractSessionCookie;
|
|
25
15
|
private cookieHeader;
|
|
26
16
|
private getRequestOptions;
|
|
27
|
-
getCSRF(getNewSession?: boolean
|
|
28
|
-
|
|
17
|
+
getCSRF(getNewSession?: boolean): Promise<{
|
|
18
|
+
nonce_key: any;
|
|
19
|
+
nonce: any;
|
|
20
|
+
sessionCookie: string;
|
|
21
|
+
}>;
|
|
29
22
|
sendRequest(csrf: any, payload: any): Promise<Response>;
|
|
30
23
|
protected processRequest(payload: any): Promise<FlexPLMResponseData>;
|
|
31
24
|
sendToFlexPLM(payload: PayloadType): Promise<FlexPLMResponseData>;
|
|
@@ -4,53 +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
|
-
// In-flight cold session acquisitions per key, so concurrent events share one FlexPLM session instead of each creating their own.
|
|
21
|
-
const inFlightAcquisitions = new Map();
|
|
22
|
-
const DEFAULT_SESSION_COOKIE_NAME = 'JSESSIONID';
|
|
23
|
-
const DEFAULT_SESSION_TTL_SECONDS = 900;
|
|
24
|
-
const DEFAULT_RETRY_STATUSES = [401, 403];
|
|
25
|
-
const COOKIE_NAME_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
26
|
-
// Thrown internally when a cached cookie was rejected, so the caller can re-acquire under the lock.
|
|
27
|
-
class SessionRejectedError extends Error {
|
|
28
|
-
}
|
|
29
|
-
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
30
|
-
const parseRetryStatuses = (value) => {
|
|
31
|
-
let list = [];
|
|
32
|
-
if (Array.isArray(value))
|
|
33
|
-
list = value;
|
|
34
|
-
else if (typeof value === 'string')
|
|
35
|
-
list = value.split(',');
|
|
36
|
-
else if (value !== undefined && value !== null)
|
|
37
|
-
list = [value];
|
|
38
|
-
const statuses = list
|
|
39
|
-
.map(status => (typeof status === 'string' ? status.trim() : status))
|
|
40
|
-
.filter(status => status !== '' && status !== null && status !== undefined)
|
|
41
|
-
.map(Number)
|
|
42
|
-
// Only client-error statuses can mean "session rejected". 2xx/5xx would resend events the server already processed.
|
|
43
|
-
.filter(status => Number.isInteger(status) && status >= 400 && status <= 499);
|
|
44
|
-
return statuses.length ? statuses : DEFAULT_RETRY_STATUSES;
|
|
45
|
-
};
|
|
46
|
-
const parseTtlSeconds = (value) => {
|
|
47
|
-
const ttl = Number(value);
|
|
48
|
-
return Number.isFinite(ttl) && ttl > 0 ? ttl : DEFAULT_SESSION_TTL_SECONDS;
|
|
49
|
-
};
|
|
50
|
-
const parseCookieName = (value) => {
|
|
51
|
-
const name = typeof value === 'string' ? value.trim() : '';
|
|
52
|
-
return COOKIE_NAME_TOKEN.test(name) ? name : DEFAULT_SESSION_COOKIE_NAME;
|
|
53
|
-
};
|
|
54
7
|
// Added for Session reuse - ET-390 - Ends
|
|
55
8
|
class FlexPLMConnect {
|
|
56
9
|
constructor(_config, endPoint = undefined, payloadAsArray = undefined) {
|
|
@@ -90,57 +43,13 @@ class FlexPLMConnect {
|
|
|
90
43
|
const enabled = sessReuseEntries('enabled');
|
|
91
44
|
return {
|
|
92
45
|
enabled: enabled === true || String(enabled).toLowerCase() === 'true',
|
|
93
|
-
ttlMs:
|
|
94
|
-
cookieName:
|
|
95
|
-
retryStatuses:
|
|
46
|
+
ttlMs: (sessReuseEntries('ttlSeconds') ?? 900) * 1000,
|
|
47
|
+
cookieName: sessReuseEntries('cookieName') ?? 'JSESSIONID',
|
|
48
|
+
retryStatuses: (sessReuseEntries('retryStatuses') ?? [401, 403]),
|
|
96
49
|
};
|
|
97
50
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Waits until no cold session acquisition is running for this key.
|
|
100
|
-
* Callers must `await` this only when it returns a promise: awaiting even a resolved promise yields,
|
|
101
|
-
* and the check-then-register in runColdAcquisition has to stay synchronous to be race free.
|
|
102
|
-
*/
|
|
103
|
-
waitForInFlightAcquisition() {
|
|
104
|
-
const key = this.getSessionKey();
|
|
105
|
-
if (!inFlightAcquisitions.has(key))
|
|
106
|
-
return undefined;
|
|
107
|
-
return (async () => {
|
|
108
|
-
let pending = inFlightAcquisitions.get(key);
|
|
109
|
-
while (pending) {
|
|
110
|
-
await pending.catch(() => undefined);
|
|
111
|
-
pending = inFlightAcquisitions.get(key);
|
|
112
|
-
}
|
|
113
|
-
})();
|
|
114
|
-
}
|
|
115
|
-
/** Runs a cold acquisition and registers it so concurrent callers wait for it instead of opening their own session. */
|
|
116
|
-
async runColdAcquisition(acquire) {
|
|
117
|
-
const key = this.getSessionKey();
|
|
118
|
-
const acquisition = acquire();
|
|
119
|
-
inFlightAcquisitions.set(key, acquisition);
|
|
120
|
-
try {
|
|
121
|
-
return await acquisition;
|
|
122
|
-
}
|
|
123
|
-
finally {
|
|
124
|
-
if (inFlightAcquisitions.get(key) === acquisition)
|
|
125
|
-
inFlightAcquisitions.delete(key);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
51
|
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
|
-
}
|
|
52
|
+
return [this.config.apiHost, this.config.urlContext, this.config.userName(), this.config.plmEnviornment].join('|');
|
|
144
53
|
}
|
|
145
54
|
getCachedSession() {
|
|
146
55
|
if (!this.sessionOptions.enabled)
|
|
@@ -157,11 +66,7 @@ class FlexPLMConnect {
|
|
|
157
66
|
}
|
|
158
67
|
storeSession(cookie) {
|
|
159
68
|
// 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 });
|
|
69
|
+
sessionCache.set(this.getSessionKey(), { cookie, expiresAt: Date.now() + this.sessionOptions.ttlMs });
|
|
165
70
|
}
|
|
166
71
|
clearSession() {
|
|
167
72
|
sessionCache.delete(this.getSessionKey());
|
|
@@ -179,7 +84,7 @@ class FlexPLMConnect {
|
|
|
179
84
|
raw = [respHeader.get('set-cookie')];
|
|
180
85
|
}
|
|
181
86
|
const name = this.sessionOptions.cookieName;
|
|
182
|
-
const regexFilter = new RegExp('(?:^|[,\\s])' +
|
|
87
|
+
const regexFilter = new RegExp('(?:^|[,\\s])' + name + '=([^;,\\s]+)', 'g');
|
|
183
88
|
let jSession;
|
|
184
89
|
for (const line of raw) {
|
|
185
90
|
let cookieNameMatcher = regexFilter.exec(line);
|
|
@@ -218,7 +123,6 @@ class FlexPLMConnect {
|
|
|
218
123
|
csrfOptions.headers['Cookie'] = cookie;
|
|
219
124
|
}
|
|
220
125
|
// Added for Session reuse - ET-390 - Ends
|
|
221
|
-
console.info('cookie: ' + cookie);
|
|
222
126
|
if (app_framework_1.Logger.isInfoOn()) {
|
|
223
127
|
const logOptions = JSON.parse(JSON.stringify(csrfOptions));
|
|
224
128
|
logOptions['headers']['Authorization'] = logOptions?.headers?.Authorization.substring(0, 9);
|
|
@@ -235,48 +139,26 @@ class FlexPLMConnect {
|
|
|
235
139
|
/////////////////////////////////////////////////////////////////////////////
|
|
236
140
|
// ET-390 - adding a new parameter getNewSession to the getCSRF function
|
|
237
141
|
// Whenever a getCSRF is called without getNewSession, the default value of false will be used
|
|
238
|
-
|
|
239
|
-
async getCSRF(getNewSession = false, rejectedCookie) {
|
|
240
|
-
// Added for Session reuse - ET-390 - Starts
|
|
241
|
-
if (!this.sessionOptions.enabled)
|
|
242
|
-
return this.fetchCSRF(undefined);
|
|
243
|
-
for (let wait = this.waitForInFlightAcquisition(); wait; wait = this.waitForInFlightAcquisition())
|
|
244
|
-
await wait;
|
|
245
|
-
if (getNewSession) {
|
|
246
|
-
const current = this.getCachedSession();
|
|
247
|
-
if (rejectedCookie === undefined || !current || current === rejectedCookie)
|
|
248
|
-
this.clearSession();
|
|
249
|
-
}
|
|
250
|
-
const cached = this.getCachedSession();
|
|
251
|
-
if (cached) {
|
|
252
|
-
try {
|
|
253
|
-
return await this.fetchCSRF(cached);
|
|
254
|
-
}
|
|
255
|
-
catch (e) {
|
|
256
|
-
if (!(e instanceof SessionRejectedError))
|
|
257
|
-
throw e;
|
|
258
|
-
// if the cached cookie is rejected (401/403), drop it and acquire a new session (serialized)
|
|
259
|
-
if (this.getCachedSession() === cached)
|
|
260
|
-
this.clearSession();
|
|
261
|
-
return this.getCSRF();
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return this.runColdAcquisition(() => this.fetchCSRF(undefined));
|
|
265
|
-
// Added for Session reuse - ET-390 - Ends
|
|
266
|
-
}
|
|
267
|
-
async fetchCSRF(sentCookie) {
|
|
142
|
+
async getCSRF(getNewSession = false) {
|
|
268
143
|
const urlContext = this.config.urlContext;
|
|
269
144
|
const csrfEndpoint = this.config.csrfEndpoint;
|
|
270
145
|
const csrfURL = this.config.apiHost + urlContext + csrfEndpoint;
|
|
271
146
|
console.info('csrfURL: ' + csrfURL);
|
|
147
|
+
// Added for Session reuse - ET-390 - Starts
|
|
148
|
+
if (getNewSession)
|
|
149
|
+
this.clearSession();
|
|
150
|
+
let sentCookie = this.getCachedSession();
|
|
272
151
|
// getRequestOptions will be called with additional sentCookie parameter
|
|
273
152
|
const csrfOptions = this.getRequestOptions('GET', sentCookie);
|
|
274
|
-
|
|
153
|
+
// Added for Session reuse - ET-390 - Ends
|
|
154
|
+
let response = await fetch(csrfURL, csrfOptions);
|
|
275
155
|
// Added for Session reuse - ET-390 - Starts
|
|
156
|
+
// if the response status is 401 or 403, then get a new session id
|
|
276
157
|
if (sentCookie && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
277
158
|
console.warn('Cached FlexPLM session rejected on csrf call (status ' + response.status + '). Retrying with a new session.');
|
|
278
|
-
|
|
279
|
-
|
|
159
|
+
this.clearSession();
|
|
160
|
+
sentCookie = undefined;
|
|
161
|
+
response = await fetch(csrfURL, this.getRequestOptions('GET'));
|
|
280
162
|
}
|
|
281
163
|
// Added for Session reuse - ET-390 - Ends
|
|
282
164
|
if (response.status >= 300) {
|
|
@@ -296,7 +178,6 @@ class FlexPLMConnect {
|
|
|
296
178
|
this.storeSession(sessionCookie);
|
|
297
179
|
}
|
|
298
180
|
// Added for Session reuse - ET-390 - Ends
|
|
299
|
-
console.info('sessionCookie: ' + sessionCookie);
|
|
300
181
|
try {
|
|
301
182
|
const body = await response.json();
|
|
302
183
|
const nonce_key = body.items[0].attributes.nonce_key;
|
|
@@ -360,8 +241,7 @@ class FlexPLMConnect {
|
|
|
360
241
|
let response = await this.sendRequest(csrf, payload);
|
|
361
242
|
if (csrf.sessionCookie && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
362
243
|
console.warn('FlexPLM rejected session/nonce (status ' + response.status + '). Getting a new session and retrying once.');
|
|
363
|
-
await this.
|
|
364
|
-
csrf = await this.getCSRF(true, csrf.sessionCookie);
|
|
244
|
+
csrf = await this.getCSRF(true);
|
|
365
245
|
response = await this.sendRequest(csrf, payload);
|
|
366
246
|
}
|
|
367
247
|
// Added for Session reuse - ET-390 - Ends
|
|
@@ -418,32 +298,17 @@ class FlexPLMConnect {
|
|
|
418
298
|
const path = urlPath || ('/servlet/rest' + this.vibeEventEndpoint);
|
|
419
299
|
const vibeEventsURL = this.config.apiHost + urlContext + path;
|
|
420
300
|
// Added for Session reuse - ET-390 - Starts
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const cookie = this.extractSessionCookie(res) ?? sent;
|
|
433
|
-
if (cookie)
|
|
434
|
-
this.storeSession(cookie);
|
|
435
|
-
}
|
|
436
|
-
return { response: res, sent };
|
|
437
|
-
};
|
|
438
|
-
return sent ? call() : this.runColdAcquisition(call);
|
|
439
|
-
};
|
|
440
|
-
const first = await attempt();
|
|
441
|
-
let response = first.response;
|
|
442
|
-
if (first.sent && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
443
|
-
await this.discardBody(response);
|
|
444
|
-
if (this.getCachedSession() === first.sent)
|
|
445
|
-
this.clearSession();
|
|
446
|
-
response = (await attempt()).response;
|
|
301
|
+
const cached = this.getCachedSession();
|
|
302
|
+
const csrfOptions = this.getRequestOptions('GET', cached);
|
|
303
|
+
let response = await fetch(vibeEventsURL, csrfOptions);
|
|
304
|
+
if (cached && this.sessionOptions.retryStatuses.includes(response.status)) {
|
|
305
|
+
this.clearSession();
|
|
306
|
+
response = await fetch(vibeEventsURL, this.getRequestOptions('GET'));
|
|
307
|
+
}
|
|
308
|
+
if (this.sessionOptions.enabled) {
|
|
309
|
+
const cachedCookie = this.extractSessionCookie(response) ?? cached;
|
|
310
|
+
if (cachedCookie)
|
|
311
|
+
this.storeSession(cachedCookie);
|
|
447
312
|
}
|
|
448
313
|
// Added for Session reuse - ET-390 - Ends
|
|
449
314
|
if (response.status >= 300) {
|
|
@@ -145,7 +145,7 @@ describe('FlexPLMConnect.sendRequest', () => {
|
|
|
145
145
|
// Added for Session reuse - ET-390 - Starts
|
|
146
146
|
describe('FlexPLMConnect session reuse', () => {
|
|
147
147
|
const payload = { id: '123' };
|
|
148
|
-
it('should capture JSESSIONID from the
|
|
148
|
+
it('should capture JSESSIONID from the csrf GET and sends it on the POST', async () => {
|
|
149
149
|
const u = uniqueUser();
|
|
150
150
|
fetchMock()
|
|
151
151
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1; Path=/; HttpOnly']))
|
|
@@ -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();
|
|
@@ -358,228 +326,10 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
358
326
|
isInfoOn.mockRestore();
|
|
359
327
|
info.mockRestore();
|
|
360
328
|
});
|
|
361
|
-
describe('option normalisation', () => {
|
|
362
|
-
const rejectedOnceThenOk = (u, opts) => {
|
|
363
|
-
fetchMock()
|
|
364
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']))
|
|
365
|
-
.mockResolvedValueOnce(resp(403, [], {}))
|
|
366
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=new.tomcat1']))
|
|
367
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
368
|
-
return new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, ...opts })).sendToFlexPLM(payload);
|
|
369
|
-
};
|
|
370
|
-
it.each([
|
|
371
|
-
['a scalar number', 403],
|
|
372
|
-
['a comma separated string', '401, 403'],
|
|
373
|
-
['garbage (falls back to 401/403)', 'abc'],
|
|
374
|
-
['an empty array (falls back to 401/403)', []],
|
|
375
|
-
])('should not throw and still retry on 403 when retryStatuses is %s', async (_label, retryStatuses) => {
|
|
376
|
-
await rejectedOnceThenOk(uniqueUser(), { retryStatuses });
|
|
377
|
-
expect(fetchMock()).toHaveBeenCalledTimes(4);
|
|
378
|
-
});
|
|
379
|
-
it.each([['abc'], [0], [-5]])('should fall back to the 900s default when ttlSeconds is invalid (%p)', async (ttlSeconds) => {
|
|
380
|
-
const u = uniqueUser();
|
|
381
|
-
const realNow = Date.now();
|
|
382
|
-
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(realNow);
|
|
383
|
-
fetchMock()
|
|
384
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
385
|
-
.mockResolvedValueOnce(resp(200, [], {}))
|
|
386
|
-
.mockResolvedValueOnce(resp(200, []))
|
|
387
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
388
|
-
const cfg = sessionConfig(u, { enabled: true, ttlSeconds });
|
|
389
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
|
|
390
|
-
nowSpy.mockReturnValue(realNow + 2000);
|
|
391
|
-
await new flexplm_connect_1.FlexPLMConnect(cfg).sendToFlexPLM(payload);
|
|
392
|
-
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
393
|
-
nowSpy.mockRestore();
|
|
394
|
-
});
|
|
395
|
-
it('should fall back to JSESSIONID when cookieName is blank', async () => {
|
|
396
|
-
const u = uniqueUser();
|
|
397
|
-
fetchMock()
|
|
398
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
399
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
400
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: ' ' })).sendToFlexPLM(payload);
|
|
401
|
-
expect(callHeaders(1).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
402
|
-
});
|
|
403
|
-
});
|
|
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
|
-
describe('cookie names containing regex metacharacters', () => {
|
|
508
|
-
it('should extract a cookie whose name contains "+"', async () => {
|
|
509
|
-
const u = uniqueUser();
|
|
510
|
-
fetchMock()
|
|
511
|
-
.mockResolvedValueOnce(resp(200, ['SID+=abc; Path=/']))
|
|
512
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
513
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'SID+' })).sendToFlexPLM(payload);
|
|
514
|
-
expect(callHeaders(1).Cookie).toBe('SID+=abc');
|
|
515
|
-
});
|
|
516
|
-
it('should treat "." literally and not match a look-alike cookie', async () => {
|
|
517
|
-
const u = uniqueUser();
|
|
518
|
-
fetchMock()
|
|
519
|
-
.mockResolvedValueOnce(resp(200, ['AxB=wrong; Path=/']))
|
|
520
|
-
.mockResolvedValueOnce(resp(200, [], {}));
|
|
521
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u, { enabled: true, cookieName: 'A.B' })).sendToFlexPLM(payload);
|
|
522
|
-
expect(callHeaders(1).Cookie).toBeUndefined();
|
|
523
|
-
});
|
|
524
|
-
});
|
|
525
|
-
describe('concurrent session acquisition', () => {
|
|
526
|
-
const flush = () => new Promise(r => setImmediate(r));
|
|
527
|
-
const isColdGet = (call) => call[1].method === 'GET' && !call[1].headers.Cookie;
|
|
528
|
-
it('should share one session across concurrent events on a cold cache', async () => {
|
|
529
|
-
const u = uniqueUser();
|
|
530
|
-
let release;
|
|
531
|
-
const gate = new Promise(r => { release = r; });
|
|
532
|
-
fetchMock().mockImplementation(async (_url, opts) => {
|
|
533
|
-
if (opts.method === 'GET' && !opts.headers.Cookie) {
|
|
534
|
-
await gate;
|
|
535
|
-
return resp(200, ['JSESSIONID=abc.tomcat1']);
|
|
536
|
-
}
|
|
537
|
-
return resp(200, []);
|
|
538
|
-
});
|
|
539
|
-
const all = Promise.all([1, 2, 3].map(() => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF()));
|
|
540
|
-
await flush();
|
|
541
|
-
expect(fetchMock()).toHaveBeenCalledTimes(1); // the other two are waiting for the first acquisition
|
|
542
|
-
release();
|
|
543
|
-
const results = await all;
|
|
544
|
-
expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
|
|
545
|
-
results.forEach(r => expect(r.sessionCookie).toBe('abc.tomcat1'));
|
|
546
|
-
});
|
|
547
|
-
it('should not make a waiter inherit the failure of the first acquisition', async () => {
|
|
548
|
-
const u = uniqueUser();
|
|
549
|
-
let release;
|
|
550
|
-
const gate = new Promise(r => { release = r; });
|
|
551
|
-
fetchMock()
|
|
552
|
-
.mockImplementationOnce(async () => { await gate; return resp(500, []); })
|
|
553
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']));
|
|
554
|
-
const first = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF();
|
|
555
|
-
const firstResult = first.catch(e => e);
|
|
556
|
-
const second = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF();
|
|
557
|
-
await flush();
|
|
558
|
-
release();
|
|
559
|
-
expect(await firstResult).toBeInstanceOf(Error);
|
|
560
|
-
expect((await second).sessionCookie).toBe('abc.tomcat1');
|
|
561
|
-
});
|
|
562
|
-
it('should create only one replacement session for parallel events rejected with 403', async () => {
|
|
563
|
-
const u = uniqueUser();
|
|
564
|
-
fetchMock().mockResolvedValueOnce(resp(200, ['JSESSIONID=old.tomcat1']));
|
|
565
|
-
await new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).getCSRF(); // prime the cache with the old session
|
|
566
|
-
fetchMock().mockReset();
|
|
567
|
-
fetchMock().mockImplementation(async (_url, opts) => {
|
|
568
|
-
const cookie = opts.headers.Cookie;
|
|
569
|
-
if (opts.method === 'POST')
|
|
570
|
-
return cookie === 'JSESSIONID=new.tomcat1' ? resp(200, [], {}) : resp(403, [], {});
|
|
571
|
-
if (!cookie)
|
|
572
|
-
return resp(200, ['JSESSIONID=new.tomcat1']);
|
|
573
|
-
return resp(200, []);
|
|
574
|
-
});
|
|
575
|
-
await Promise.all([1, 2, 3].map(() => new flexplm_connect_1.FlexPLMConnect(sessionConfig(u)).sendToFlexPLM(payload)));
|
|
576
|
-
expect(fetchMock().mock.calls.filter(isColdGet)).toHaveLength(1);
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
329
|
});
|
|
580
330
|
describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
581
331
|
const payload = { id: '123' };
|
|
582
|
-
it('
|
|
332
|
+
it('sends no Cookie header and makes exactly one GET + one POST', async () => {
|
|
583
333
|
const u = uniqueUser();
|
|
584
334
|
fetchMock()
|
|
585
335
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // Set-Cookie present but must be ignored
|
|
@@ -593,7 +343,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
593
343
|
expect(callHeaders(i).Cookie).toBeUndefined();
|
|
594
344
|
}
|
|
595
345
|
});
|
|
596
|
-
it('
|
|
346
|
+
it('does not retry on 403 (behaves exactly as before)', async () => {
|
|
597
347
|
const u = uniqueUser();
|
|
598
348
|
fetchMock()
|
|
599
349
|
.mockResolvedValueOnce(resp(200))
|
|
@@ -602,7 +352,7 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
602
352
|
.rejects.toThrow('Error sending data to FlexPLM:status: 403');
|
|
603
353
|
expect(fetchMock()).toHaveBeenCalledTimes(2);
|
|
604
354
|
});
|
|
605
|
-
it('
|
|
355
|
+
it('explicit enabled:false behaves the same as not configured', async () => {
|
|
606
356
|
const u = uniqueUser();
|
|
607
357
|
fetchMock()
|
|
608
358
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -611,8 +361,8 @@ describe('FlexPLMConnect session reuse - feature disabled (default)', () => {
|
|
|
611
361
|
expect(callHeaders(1).Cookie).toBeUndefined();
|
|
612
362
|
});
|
|
613
363
|
});
|
|
614
|
-
describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
615
|
-
it('
|
|
364
|
+
describe('FlexPLMConnect.getRequest session reuse (only if you applied step 2g)', () => {
|
|
365
|
+
it('sends the cached cookie on image/GET requests', async () => {
|
|
616
366
|
const u = uniqueUser();
|
|
617
367
|
fetchMock()
|
|
618
368
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1'])) // csrf GET primes the cache
|
|
@@ -623,7 +373,7 @@ describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
|
623
373
|
await connect.getRequest();
|
|
624
374
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
625
375
|
});
|
|
626
|
-
it('
|
|
376
|
+
it('retries once without the cookie when the cached session is rejected on getRequest', async () => {
|
|
627
377
|
const u = uniqueUser();
|
|
628
378
|
fetchMock()
|
|
629
379
|
.mockResolvedValueOnce(resp(200, ['JSESSIONID=abc.tomcat1']))
|
|
@@ -636,20 +386,5 @@ describe('FlexPLMConnect.getRequest session reuse', () => {
|
|
|
636
386
|
expect(callHeaders(2).Cookie).toBe('JSESSIONID=abc.tomcat1');
|
|
637
387
|
expect(callHeaders(3).Cookie).toBeUndefined();
|
|
638
388
|
});
|
|
639
|
-
it('should not re-store the rejected cookie when the retry issues no Set-Cookie', async () => {
|
|
640
|
-
const u = uniqueUser();
|
|
641
|
-
fetchMock()
|
|
642
|
-
.mockResolvedValueOnce(resp(200, ['JSESSIONID=stale.tomcat1']))
|
|
643
|
-
.mockResolvedValueOnce(resp(200, [], {}))
|
|
644
|
-
.mockResolvedValueOnce(resp(401, [])) // getRequest with the cached cookie
|
|
645
|
-
.mockResolvedValueOnce(resp(200, [], { items: [] })) // retry succeeds, no Set-Cookie
|
|
646
|
-
.mockResolvedValueOnce(resp(200, [], { items: [] })); // next getRequest
|
|
647
|
-
const connect = new flexplm_connect_1.FlexPLMConnect(sessionConfig(u));
|
|
648
|
-
await connect.sendToFlexPLM({ id: '1' });
|
|
649
|
-
await connect.getRequest();
|
|
650
|
-
await connect.getRequest();
|
|
651
|
-
expect(callHeaders(3).Cookie).toBeUndefined();
|
|
652
|
-
expect(callHeaders(4).Cookie).toBeUndefined(); // stale cookie must not be sent again
|
|
653
|
-
});
|
|
654
389
|
});
|
|
655
390
|
// Added for Session reuse - ET-390 - Ends
|