@contrail/flexplm 1.7.7-alpha.279b642 → 1.7.7-alpha.3e12cec
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/util/flexplm-connect.js +20 -8
- package/lib/util/flexplm-connect.spec.js +89 -0
- package/lib/util/flexplm-session-manager.d.ts +13 -2
- package/lib/util/flexplm-session-manager.js +40 -5
- package/lib/util/flexplm-session-manager.spec.d.ts +1 -0
- package/lib/util/flexplm-session-manager.spec.js +70 -0
- package/package.json +1 -1
|
@@ -3,6 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FlexPLMConnect = void 0;
|
|
4
4
|
const app_framework_1 = require("@contrail/app-framework");
|
|
5
5
|
const flexplm_session_manager_1 = require("./flexplm-session-manager");
|
|
6
|
+
// Cookie values carry the session id: mask them in logs whatever the header-name casing.
|
|
7
|
+
const maskCookieHeaders = (headers) => {
|
|
8
|
+
if (!headers)
|
|
9
|
+
return;
|
|
10
|
+
for (const key of Object.keys(headers)) {
|
|
11
|
+
if (key.toLowerCase() === 'cookie' && headers[key])
|
|
12
|
+
headers[key] = '***masked***';
|
|
13
|
+
}
|
|
14
|
+
};
|
|
6
15
|
class FlexPLMConnect {
|
|
7
16
|
constructor(_config, endPoint = undefined, payloadAsArray = undefined) {
|
|
8
17
|
this.vibeEventEndpoint = '';
|
|
@@ -48,17 +57,21 @@ class FlexPLMConnect {
|
|
|
48
57
|
Object.assign(csrfOptions.headers, this.staticHeaders);
|
|
49
58
|
}
|
|
50
59
|
// Added for Session reuse - ET-390 - Starts
|
|
51
|
-
|
|
60
|
+
// Merge with a configured static Cookie header (any header-name casing) instead of replacing it.
|
|
61
|
+
const headers = csrfOptions.headers;
|
|
62
|
+
const staticCookieKeys = Object.keys(headers).filter(key => key.toLowerCase() === 'cookie');
|
|
63
|
+
const staticCookie = staticCookieKeys.map(key => headers[key]).join('; ');
|
|
64
|
+
const cookie = this.sessions.buildCookieHeader(staticCookie, sessionCookie);
|
|
65
|
+
staticCookieKeys.forEach(key => delete headers[key]);
|
|
52
66
|
if (cookie) {
|
|
53
|
-
|
|
67
|
+
headers['Cookie'] = cookie;
|
|
54
68
|
}
|
|
55
69
|
// Added for Session reuse - ET-390 - Ends
|
|
56
70
|
if (app_framework_1.Logger.isInfoOn()) {
|
|
57
71
|
const logOptions = JSON.parse(JSON.stringify(csrfOptions));
|
|
58
72
|
logOptions['headers']['Authorization'] = logOptions?.headers?.Authorization.substring(0, 9);
|
|
59
73
|
// Added for Session reuse - ET-390 - Starts
|
|
60
|
-
|
|
61
|
-
logOptions.headers['Cookie'] = '***masked***';
|
|
74
|
+
maskCookieHeaders(logOptions.headers);
|
|
62
75
|
// Added for Session reuse - ET-390 - Ends
|
|
63
76
|
console.info('csrfOptions: ' + JSON.stringify(logOptions));
|
|
64
77
|
}
|
|
@@ -127,7 +140,7 @@ class FlexPLMConnect {
|
|
|
127
140
|
if (this.sessions.options.enabled) {
|
|
128
141
|
sessionCookie = this.sessions.extractSessionCookie(response) ?? sentCookie;
|
|
129
142
|
if (sessionCookie)
|
|
130
|
-
this.sessions.storeSession(sessionCookie);
|
|
143
|
+
this.sessions.storeSession(sessionCookie, sentCookie);
|
|
131
144
|
}
|
|
132
145
|
// Added for Session reuse - ET-390 - Ends
|
|
133
146
|
try {
|
|
@@ -163,8 +176,7 @@ class FlexPLMConnect {
|
|
|
163
176
|
const logOptions = JSON.parse(JSON.stringify(xferOptions));
|
|
164
177
|
if (logOptions?.headers?.Authorization)
|
|
165
178
|
logOptions.headers.Authorization = logOptions?.headers?.Authorization.substring(0, 9);
|
|
166
|
-
|
|
167
|
-
logOptions.headers.Cookie = '***masked***';
|
|
179
|
+
maskCookieHeaders(logOptions?.headers);
|
|
168
180
|
if (csrf?.nonce_key)
|
|
169
181
|
logOptions.headers[csrf.nonce_key] = csrf.nonce ? `***masked(length:${csrf.nonce.length})***` : '***masked(empty)***';
|
|
170
182
|
console.info('csrfOptions: ' + JSON.stringify(logOptions));
|
|
@@ -264,7 +276,7 @@ class FlexPLMConnect {
|
|
|
264
276
|
if (!(sent && this.sessions.options.retryStatuses.includes(res.status))) {
|
|
265
277
|
const cookie = this.sessions.extractSessionCookie(res) ?? sent;
|
|
266
278
|
if (cookie)
|
|
267
|
-
this.sessions.storeSession(cookie);
|
|
279
|
+
this.sessions.storeSession(cookie, sent);
|
|
268
280
|
}
|
|
269
281
|
return { response: res, sent };
|
|
270
282
|
};
|
|
@@ -354,6 +354,60 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
354
354
|
isInfoOn.mockRestore();
|
|
355
355
|
info.mockRestore();
|
|
356
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
|
+
});
|
|
357
411
|
describe('option normalisation', () => {
|
|
358
412
|
const rejectedOnceThenOk = (u, opts) => {
|
|
359
413
|
fetchMock()
|
|
@@ -521,6 +575,41 @@ describe('FlexPLMConnect session reuse', () => {
|
|
|
521
575
|
describe('concurrent session acquisition', () => {
|
|
522
576
|
const flush = () => new Promise(r => setImmediate(r));
|
|
523
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
|
+
});
|
|
524
613
|
it('should share one session across concurrent events on a cold cache', async () => {
|
|
525
614
|
const u = uniqueUser();
|
|
526
615
|
let release;
|
|
@@ -21,9 +21,20 @@ export declare class FlexPLMSessionManager {
|
|
|
21
21
|
runColdAcquisition<T>(acquire: () => Promise<T>): Promise<T>;
|
|
22
22
|
private getSessionKey;
|
|
23
23
|
getCachedSession(): string | undefined;
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Compare-and-set: stores `cookie` only if the cache still holds the cookie this request was sent with
|
|
26
|
+
* (`sentCookie`, undefined for a cold acquisition = nothing cached). A late response must not roll back or
|
|
27
|
+
* resurrect a session that a concurrent event already replaced or cleared.
|
|
28
|
+
* @returns true if the cache was updated
|
|
29
|
+
*/
|
|
30
|
+
storeSession(cookie: string, sentCookie: string | undefined): boolean;
|
|
25
31
|
clearSession(): void;
|
|
26
32
|
/** Returns the session cookie value Windchill issued on this response, if any (last one wins). */
|
|
27
33
|
extractSessionCookie(response: Response): string | undefined;
|
|
28
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Builds the Cookie header value from the configured static Cookie header and the managed session cookie.
|
|
36
|
+
* With session reuse enabled only the managed cookie is replaced/removed; every other static cookie pair is kept.
|
|
37
|
+
* With session reuse disabled the static value is returned unchanged.
|
|
38
|
+
*/
|
|
39
|
+
buildCookieHeader(staticCookie?: string, sessionCookie?: string): string | undefined;
|
|
29
40
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FlexPLMSessionManager = exports.SessionRejectedError = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
4
5
|
// Module-level on purpose: a new FlexPLMConnect (and session manager) is constructed for every event.
|
|
5
6
|
const sessionCache = new Map();
|
|
6
7
|
// Upper bound so users/option combinations that are never requested again cannot accumulate cookies forever.
|
|
@@ -18,6 +19,7 @@ const pruneSessionCache = () => {
|
|
|
18
19
|
};
|
|
19
20
|
// In-flight cold session acquisitions per key, so concurrent events share one FlexPLM session instead of each creating their own.
|
|
20
21
|
const inFlightAcquisitions = new Map();
|
|
22
|
+
const SESSION_CACHE_DIGEST_KEY = (0, crypto_1.randomBytes)(32);
|
|
21
23
|
const DEFAULT_SESSION_COOKIE_NAME = 'JSESSIONID';
|
|
22
24
|
const DEFAULT_SESSION_TTL_SECONDS = 900;
|
|
23
25
|
const DEFAULT_RETRY_STATUSES = [401, 403];
|
|
@@ -98,8 +100,18 @@ class FlexPLMSessionManager {
|
|
|
98
100
|
getSessionKey() {
|
|
99
101
|
// Options that change the cached value are part of the key; JSON avoids '|' collisions between fields.
|
|
100
102
|
const { cookieName, ttlMs } = this.options;
|
|
103
|
+
const staticHeaders = Object.entries(this.config.flexplmConnect?.staticHeaders ?? {})
|
|
104
|
+
.map(([name, value]) => [name.toLowerCase(), value])
|
|
105
|
+
.sort(([nameA, valueA], [nameB, valueB]) => {
|
|
106
|
+
if (nameA !== nameB)
|
|
107
|
+
return nameA < nameB ? -1 : 1;
|
|
108
|
+
return valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
|
|
109
|
+
});
|
|
110
|
+
const authenticationAndRoutingDigest = (0, crypto_1.createHmac)('sha256', SESSION_CACHE_DIGEST_KEY)
|
|
111
|
+
.update(JSON.stringify([this.config.userName(), this.config.password(), staticHeaders]))
|
|
112
|
+
.digest('hex');
|
|
101
113
|
return JSON.stringify([
|
|
102
|
-
this.config.apiHost, this.config.urlContext, this.config.
|
|
114
|
+
this.config.apiHost, this.config.urlContext, this.config.plmEnviornment, cookieName, ttlMs, authenticationAndRoutingDigest,
|
|
103
115
|
]);
|
|
104
116
|
}
|
|
105
117
|
getCachedSession() {
|
|
@@ -115,13 +127,22 @@ class FlexPLMSessionManager {
|
|
|
115
127
|
}
|
|
116
128
|
return cachedSessValue.cookie;
|
|
117
129
|
}
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Compare-and-set: stores `cookie` only if the cache still holds the cookie this request was sent with
|
|
132
|
+
* (`sentCookie`, undefined for a cold acquisition = nothing cached). A late response must not roll back or
|
|
133
|
+
* resurrect a session that a concurrent event already replaced or cleared.
|
|
134
|
+
* @returns true if the cache was updated
|
|
135
|
+
*/
|
|
136
|
+
storeSession(cookie, sentCookie) {
|
|
120
137
|
const key = this.getSessionKey();
|
|
138
|
+
if (sessionCache.get(key)?.cookie !== sentCookie)
|
|
139
|
+
return false;
|
|
140
|
+
// sliding expiry: Windchill's session timeout is also sliding
|
|
121
141
|
// delete first so a refreshed entry moves to the newest position (oldest-first eviction then acts as LRU)
|
|
122
142
|
sessionCache.delete(key);
|
|
123
143
|
pruneSessionCache();
|
|
124
144
|
sessionCache.set(key, { cookie, expiresAt: Date.now() + this.options.ttlMs });
|
|
145
|
+
return true;
|
|
125
146
|
}
|
|
126
147
|
clearSession() {
|
|
127
148
|
sessionCache.delete(this.getSessionKey());
|
|
@@ -150,8 +171,22 @@ class FlexPLMSessionManager {
|
|
|
150
171
|
}
|
|
151
172
|
return jSession;
|
|
152
173
|
}
|
|
153
|
-
|
|
154
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Builds the Cookie header value from the configured static Cookie header and the managed session cookie.
|
|
176
|
+
* With session reuse enabled only the managed cookie is replaced/removed; every other static cookie pair is kept.
|
|
177
|
+
* With session reuse disabled the static value is returned unchanged.
|
|
178
|
+
*/
|
|
179
|
+
buildCookieHeader(staticCookie, sessionCookie) {
|
|
180
|
+
if (!this.options.enabled)
|
|
181
|
+
return staticCookie || undefined;
|
|
182
|
+
const name = this.options.cookieName;
|
|
183
|
+
const pairs = (staticCookie ?? '')
|
|
184
|
+
.split(';')
|
|
185
|
+
.map(pair => pair.trim())
|
|
186
|
+
.filter(pair => pair && pair.split('=')[0].trim() !== name);
|
|
187
|
+
if (sessionCookie)
|
|
188
|
+
pairs.push(`${name}=${sessionCookie}`);
|
|
189
|
+
return pairs.length ? pairs.join('; ') : undefined;
|
|
155
190
|
}
|
|
156
191
|
}
|
|
157
192
|
exports.FlexPLMSessionManager = FlexPLMSessionManager;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const flexplm_session_manager_1 = require("./flexplm-session-manager");
|
|
4
|
+
let userCounter = 0;
|
|
5
|
+
const newManager = (user = 'mgr-user-' + (++userCounter), staticHeaders) => {
|
|
6
|
+
const config = {
|
|
7
|
+
apiHost: 'https://flexplm.example.com',
|
|
8
|
+
urlContext: '/FlexPLM',
|
|
9
|
+
vibeEventEndpoint: '/api/events',
|
|
10
|
+
csrfEndpoint: '/csrf',
|
|
11
|
+
plmEnviornment: 'test',
|
|
12
|
+
userName: () => user,
|
|
13
|
+
password: () => 'pass',
|
|
14
|
+
itemPreDevelopmentLifecycleStages: [],
|
|
15
|
+
flexplmConnect: { staticHeaders, sessionReuse: { enabled: true } },
|
|
16
|
+
};
|
|
17
|
+
return new flexplm_session_manager_1.FlexPLMSessionManager(config);
|
|
18
|
+
};
|
|
19
|
+
describe('FlexPLMSessionManager.storeSession (compare-and-set)', () => {
|
|
20
|
+
it('should store a cold acquisition when nothing is cached', () => {
|
|
21
|
+
const m = newManager();
|
|
22
|
+
expect(m.storeSession('a', undefined)).toBe(true);
|
|
23
|
+
expect(m.getCachedSession()).toBe('a');
|
|
24
|
+
});
|
|
25
|
+
it('should not let a cold acquisition overwrite an entry that appeared meanwhile', () => {
|
|
26
|
+
const m = newManager();
|
|
27
|
+
m.storeSession('a', undefined);
|
|
28
|
+
expect(m.storeSession('b', undefined)).toBe(false);
|
|
29
|
+
expect(m.getCachedSession()).toBe('a');
|
|
30
|
+
});
|
|
31
|
+
it('should replace or refresh the entry while it still holds the cookie that was sent', () => {
|
|
32
|
+
const m = newManager();
|
|
33
|
+
m.storeSession('old', undefined);
|
|
34
|
+
expect(m.storeSession('new', 'old')).toBe(true);
|
|
35
|
+
expect(m.getCachedSession()).toBe('new');
|
|
36
|
+
expect(m.storeSession('new', 'new')).toBe(true); // sliding refresh with the same cookie
|
|
37
|
+
});
|
|
38
|
+
it('should ignore a late response that was sent with an older cookie', () => {
|
|
39
|
+
const m = newManager();
|
|
40
|
+
m.storeSession('old', undefined);
|
|
41
|
+
m.storeSession('new', 'old');
|
|
42
|
+
expect(m.storeSession('old', 'old')).toBe(false);
|
|
43
|
+
expect(m.getCachedSession()).toBe('new');
|
|
44
|
+
});
|
|
45
|
+
it('should not resurrect a session that was cleared meanwhile', () => {
|
|
46
|
+
const m = newManager();
|
|
47
|
+
m.storeSession('old', undefined);
|
|
48
|
+
m.clearSession();
|
|
49
|
+
expect(m.storeSession('old', 'old')).toBe(false);
|
|
50
|
+
expect(m.getCachedSession()).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
it('should isolate cached sessions when static authorization headers differ', () => {
|
|
53
|
+
const firstManager = newManager('shared-user', { Authorization: 'Basic first', PLM_ENV: 'test' });
|
|
54
|
+
const secondManager = newManager('shared-user', { Authorization: 'Basic second', PLM_ENV: 'test' });
|
|
55
|
+
firstManager.storeSession('first-session', undefined);
|
|
56
|
+
expect(secondManager.getCachedSession()).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
it('should isolate cached sessions when static routing headers differ', () => {
|
|
59
|
+
const firstManager = newManager('shared-user', { 'X-Route': 'route-a' });
|
|
60
|
+
const secondManager = newManager('shared-user', { 'X-Route': 'route-b' });
|
|
61
|
+
firstManager.storeSession('first-session', undefined);
|
|
62
|
+
expect(secondManager.getCachedSession()).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
it('should reuse cached sessions for equivalent static headers regardless of header order or casing', () => {
|
|
65
|
+
const firstManager = newManager('shared-user', { 'Authorization': 'Basic same', 'X-Route': 'route-a' });
|
|
66
|
+
const secondManager = newManager('shared-user', { 'x-route': 'route-a', 'authorization': 'Basic same' });
|
|
67
|
+
firstManager.storeSession('shared-session', undefined);
|
|
68
|
+
expect(secondManager.getCachedSession()).toBe('shared-session');
|
|
69
|
+
});
|
|
70
|
+
});
|