Skip to content

Commit 62d4023

Browse files
committed
add initial cache implementation
1 parent af1d0bd commit 62d4023

File tree

4 files changed

+640
-111
lines changed

4 files changed

+640
-111
lines changed
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import { createLogger } from '@sap-cloud-sdk/util';
2+
import { signedJwt } from '../../../../test-resources/test/test-util';
3+
import { iasTokenCache, getCacheKey } from './ias-token-cache';
4+
import type { ClientCredentialsResponse } from './xsuaa-service-types';
5+
6+
const logger = createLogger({
7+
package: 'connectivity',
8+
messageContext: 'ias-token-cache'
9+
});
10+
11+
describe('ias-token-cache', () => {
12+
const mockToken: ClientCredentialsResponse = {
13+
access_token: 'mock-access-token',
14+
token_type: 'Bearer',
15+
expires_in: 3600,
16+
scope: 'openid',
17+
jti: 'mock-jti'
18+
};
19+
20+
afterEach(() => {
21+
iasTokenCache.clear();
22+
jest.restoreAllMocks();
23+
});
24+
25+
describe('getToken', () => {
26+
it('returns cached token if valid', () => {
27+
iasTokenCache.cacheToken('test-client-id', {}, mockToken);
28+
29+
const cachedToken = iasTokenCache.getToken('test-client-id', {});
30+
expect(cachedToken).toEqual(mockToken);
31+
});
32+
33+
it('returns undefined for expired token', () => {
34+
const expiredToken = {
35+
...mockToken,
36+
expires_in: -1
37+
};
38+
iasTokenCache.cacheToken('test-client-id', {}, expiredToken);
39+
40+
// Wait for token to expire
41+
jest.useFakeTimers();
42+
jest.advanceTimersByTime(2000);
43+
44+
const cachedToken = iasTokenCache.getToken('test-client-id', {});
45+
expect(cachedToken).toBeUndefined();
46+
47+
jest.useRealTimers();
48+
});
49+
50+
it('returns undefined if cache key cannot be created', () => {
51+
jest.spyOn(logger, 'warn');
52+
53+
const cachedToken = iasTokenCache.getToken('', {});
54+
expect(cachedToken).toBeUndefined();
55+
expect(logger.warn).toHaveBeenCalledWith(
56+
'Cannot create cache key for IAS token cache. The given client ID is undefined.'
57+
);
58+
});
59+
});
60+
61+
describe('cacheToken', () => {
62+
it('caches token with expiration', () => {
63+
iasTokenCache.cacheToken('test-client-id', {}, mockToken);
64+
65+
const cachedToken = iasTokenCache.getToken('test-client-id', {});
66+
expect(cachedToken).toEqual(mockToken);
67+
});
68+
69+
it('does not cache if cache key cannot be created', () => {
70+
jest.spyOn(logger, 'warn');
71+
72+
iasTokenCache.cacheToken('', {}, mockToken);
73+
74+
expect(logger.warn).toHaveBeenCalledWith(
75+
'Cannot create cache key for IAS token cache. The given client ID is undefined.'
76+
);
77+
});
78+
});
79+
80+
describe('getCacheKey', () => {
81+
describe('technical-user flow', () => {
82+
it('generates cache key with default tenant (provider-tenant)', () => {
83+
const cacheKey = getCacheKey('test-client-id', {});
84+
expect(cacheKey).toBe('provider-tenant:test-client-id:');
85+
});
86+
87+
it('generates cache key with appTenantId', () => {
88+
const cacheKey = getCacheKey('test-client-id', {
89+
appTenantId: 'tenant-123'
90+
});
91+
expect(cacheKey).toBe('tenant-123:test-client-id:');
92+
});
93+
94+
it('includes resource name in cache key', () => {
95+
const cacheKey = getCacheKey('test-client-id', {
96+
resource: { name: 'my-app' }
97+
});
98+
expect(cacheKey).toBe('provider-tenant:test-client-id:name=my-app');
99+
});
100+
101+
it('includes resource clientId in cache key', () => {
102+
const cacheKey = getCacheKey('test-client-id', {
103+
resource: { clientId: 'resource-client-123' }
104+
});
105+
expect(cacheKey).toBe(
106+
'provider-tenant:test-client-id:clientid=resource-client-123'
107+
);
108+
});
109+
110+
it('includes resource clientId and tenantId in cache key', () => {
111+
const cacheKey = getCacheKey('test-client-id', {
112+
resource: { clientId: 'resource-client-123', tenantId: 'tenant-456' }
113+
});
114+
expect(cacheKey).toBe(
115+
'provider-tenant:test-client-id:clientid=resource-client-123:tenantid=tenant-456'
116+
);
117+
});
118+
119+
it('returns undefined if clientId is missing', () => {
120+
jest.spyOn(logger, 'warn');
121+
122+
const cacheKey = getCacheKey('', {});
123+
expect(cacheKey).toBeUndefined();
124+
expect(logger.warn).toHaveBeenCalledWith(
125+
'Cannot create cache key for IAS token cache. The given client ID is undefined.'
126+
);
127+
});
128+
});
129+
130+
describe('business-user flow', () => {
131+
it('generates cache key with user and tenant from assertion', () => {
132+
const assertion = signedJwt({
133+
user_uuid: 'user-123',
134+
app_tid: 'tenant-456'
135+
});
136+
137+
const cacheKey = getCacheKey('test-client-id', {
138+
actAs: 'business-user',
139+
assertion
140+
});
141+
expect(cacheKey).toBe('user-123:tenant-456:test-client-id:');
142+
});
143+
144+
it('generates cache key with resource', () => {
145+
const assertion = signedJwt({
146+
user_uuid: 'user-123',
147+
app_tid: 'tenant-456'
148+
});
149+
150+
const cacheKey = getCacheKey('test-client-id', {
151+
actAs: 'business-user',
152+
assertion,
153+
resource: { name: 'my-app' }
154+
});
155+
expect(cacheKey).toBe('user-123:tenant-456:test-client-id:name=my-app');
156+
});
157+
158+
it('returns undefined if assertion is missing', () => {
159+
jest.spyOn(logger, 'warn');
160+
161+
const cacheKey = getCacheKey('test-client-id', {
162+
actAs: 'business-user'
163+
} as any);
164+
expect(cacheKey).toBeUndefined();
165+
expect(logger.warn).toHaveBeenCalledWith(
166+
'Cannot create cache key for IAS token cache. Business-user flow requires assertion JWT.'
167+
);
168+
});
169+
170+
it('returns undefined if user ID cannot be extracted', () => {
171+
jest.spyOn(logger, 'warn');
172+
const assertion = signedJwt({
173+
app_tid: 'tenant-456'
174+
// Missing user_uuid
175+
});
176+
177+
const cacheKey = getCacheKey('test-client-id', {
178+
actAs: 'business-user',
179+
assertion
180+
});
181+
expect(cacheKey).toBeUndefined();
182+
expect(logger.warn).toHaveBeenCalledWith(
183+
'Cannot create cache key for IAS token cache. User ID could not be extracted from assertion JWT.'
184+
);
185+
});
186+
187+
it('returns undefined if tenant ID cannot be extracted', () => {
188+
jest.spyOn(logger, 'warn');
189+
const assertion = signedJwt({
190+
user_uuid: 'user-123'
191+
// Missing app_tid or zid
192+
});
193+
194+
const cacheKey = getCacheKey('test-client-id', {
195+
actAs: 'business-user',
196+
assertion
197+
});
198+
expect(cacheKey).toBeUndefined();
199+
expect(logger.warn).toHaveBeenCalledWith(
200+
'Cannot create cache key for IAS token cache. Tenant ID could not be extracted from assertion JWT.'
201+
);
202+
});
203+
});
204+
205+
describe('cache isolation', () => {
206+
it('isolates cache by tenant for technical-user', () => {
207+
const key1 = getCacheKey('test-client-id', {
208+
appTenantId: 'tenant-1'
209+
});
210+
const key2 = getCacheKey('test-client-id', {
211+
appTenantId: 'tenant-2'
212+
});
213+
214+
expect(key1).not.toBe(key2);
215+
expect(key1).toBe('tenant-1:test-client-id:');
216+
expect(key2).toBe('tenant-2:test-client-id:');
217+
});
218+
219+
it('isolates cache by resource', () => {
220+
const key1 = getCacheKey('test-client-id', {
221+
resource: { name: 'app-1' }
222+
});
223+
const key2 = getCacheKey('test-client-id', {
224+
resource: { name: 'app-2' }
225+
});
226+
227+
expect(key1).not.toBe(key2);
228+
});
229+
230+
it('isolates cache by user for business-user', () => {
231+
const assertion1 = signedJwt({
232+
user_uuid: 'user-1',
233+
app_tid: 'tenant-123'
234+
});
235+
const assertion2 = signedJwt({
236+
user_uuid: 'user-2',
237+
app_tid: 'tenant-123'
238+
});
239+
240+
const key1 = getCacheKey('test-client-id', {
241+
actAs: 'business-user',
242+
assertion: assertion1
243+
});
244+
const key2 = getCacheKey('test-client-id', {
245+
actAs: 'business-user',
246+
assertion: assertion2
247+
});
248+
249+
expect(key1).not.toBe(key2);
250+
expect(key1).toBe('user-1:tenant-123:test-client-id:');
251+
expect(key2).toBe('user-2:tenant-123:test-client-id:');
252+
});
253+
});
254+
});
255+
256+
describe('clear', () => {
257+
it('clears all cached tokens', () => {
258+
iasTokenCache.cacheToken('test-client-id', {}, mockToken);
259+
expect(iasTokenCache.getToken('test-client-id', {})).toEqual(mockToken);
260+
261+
iasTokenCache.clear();
262+
expect(iasTokenCache.getToken('test-client-id', {})).toBeUndefined();
263+
});
264+
});
265+
});

0 commit comments

Comments
 (0)