mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
35f57639e5
* fix(core): dispose tenant cache on demand * chore: add comments for the tenant cache invalidation mechanism * refactor(core): refactor dispose tenant cache implementation per review comments * refactor(core): change `invalidateCache` to a class member method * test(core): add test cases for Tenant class
168 lines
6 KiB
TypeScript
168 lines
6 KiB
TypeScript
import { TtlCache } from '@logto/shared';
|
|
import { pick } from '@silverhand/essentials';
|
|
|
|
import { mockConnector0 } from '#src/__mocks__/connector-base-data.js';
|
|
import { mockSignInExperience } from '#src/__mocks__/sign-in-experience.js';
|
|
|
|
import { WellKnownCache } from './well-known.js';
|
|
|
|
const { jest } = import.meta;
|
|
|
|
const tenantId = 'mock_id';
|
|
const cacheStore = new TtlCache<string, string>(60_000);
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
cacheStore.clear();
|
|
});
|
|
|
|
describe('Well-known cache basics', () => {
|
|
it('should be able to get, set, and delete values', async () => {
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
|
|
await cache.set('sie', WellKnownCache.defaultKey, mockSignInExperience);
|
|
expect(await cache.get('sie', WellKnownCache.defaultKey)).toStrictEqual(mockSignInExperience);
|
|
|
|
await cache.set('connectors-well-known', '123', [mockConnector0]);
|
|
expect(await cache.get('connectors-well-known', '123')).toStrictEqual([
|
|
pick(mockConnector0, 'connectorId', 'id', 'metadata'),
|
|
]);
|
|
|
|
await cache.set('tenant-cache-expires-at', WellKnownCache.defaultKey, 123);
|
|
expect(await cache.get('tenant-cache-expires-at', WellKnownCache.defaultKey)).toBe(123);
|
|
|
|
await cache.delete('sie', WellKnownCache.defaultKey);
|
|
expect(await cache.get('sie', WellKnownCache.defaultKey)).toBe(undefined);
|
|
});
|
|
|
|
it('should NOT be able to set the value with wrong structure', async () => {
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
|
|
// @ts-expect-error
|
|
await cache.set('custom-phrases-tags', WellKnownCache.defaultKey, 1);
|
|
expect(await cache.get('custom-phrases-tags', WellKnownCache.defaultKey)).toBe(undefined);
|
|
});
|
|
|
|
it('should NOT be able to set and get when cache type is wrong', async () => {
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
|
|
// @ts-expect-error
|
|
await cache.set('custom-phrases-tags-', WellKnownCache.defaultKey, []);
|
|
|
|
expect(
|
|
// @ts-expect-error
|
|
await cache.get('custom-phrases-tags-', WellKnownCache.defaultKey)
|
|
).toBe(undefined);
|
|
});
|
|
});
|
|
|
|
describe('Well-known cache function wrappers', () => {
|
|
it('can memoize function and cache the promise', async () => {
|
|
jest.useFakeTimers();
|
|
const runResult = Object.freeze({ foo: 'bar' });
|
|
const run = jest.fn(
|
|
async () =>
|
|
new Promise<Record<string, unknown>>((resolve) => {
|
|
setTimeout(() => {
|
|
resolve(runResult);
|
|
}, 0);
|
|
jest.runOnlyPendingTimers(); // Ensure this runs in fake timers
|
|
})
|
|
);
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
const memoized = cache.memoize(run, ['custom-phrases']);
|
|
|
|
const [result1, result2] = await Promise.all([memoized(), memoized()]);
|
|
expect(result1).toStrictEqual(runResult);
|
|
expect(result2).toStrictEqual(runResult);
|
|
expect(await cache.get('custom-phrases', WellKnownCache.defaultKey)).toStrictEqual(runResult);
|
|
expect(run).toBeCalledTimes(1);
|
|
|
|
// Second call
|
|
expect(await memoized()).toStrictEqual(runResult);
|
|
expect(run).toBeCalledTimes(1);
|
|
|
|
// Cache expired
|
|
jest.setSystemTime(Date.now() + 100_000); // Ensure cache is expired
|
|
|
|
expect(await memoized()).toStrictEqual(runResult);
|
|
expect(run).toBeCalledTimes(2);
|
|
expect(await cache.get('custom-phrases', WellKnownCache.defaultKey)).toStrictEqual(runResult);
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('can memoize function with customized cache key builder', async () => {
|
|
const run = jest.fn(
|
|
async (foo: string, bar: number) =>
|
|
new Promise<Record<string, unknown>>((resolve) => {
|
|
setTimeout(() => {
|
|
resolve({ foo, bar });
|
|
}, 0);
|
|
})
|
|
);
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
const memoized = cache.memoize(run, ['custom-phrases', (foo, bar) => `${foo}+${bar}`]);
|
|
|
|
const [result1, result2] = await Promise.all([memoized('1', 1), memoized('2', 2)]);
|
|
expect(result1).toStrictEqual({ foo: '1', bar: 1 });
|
|
expect(result2).toStrictEqual({ foo: '2', bar: 2 });
|
|
|
|
expect(
|
|
await Promise.all([cache.get('custom-phrases', '1+1'), cache.get('custom-phrases', '2+2')])
|
|
).toStrictEqual([
|
|
{ foo: '1', bar: 1 },
|
|
{ foo: '2', bar: 2 },
|
|
]);
|
|
});
|
|
|
|
it('can create mutate function wrapper with default cache key builder', async () => {
|
|
const run = jest.fn(
|
|
async () =>
|
|
new Promise<Record<string, unknown>>((resolve) => {
|
|
setTimeout(() => {
|
|
resolve({});
|
|
}, 0);
|
|
})
|
|
);
|
|
const update = jest.fn(async () => true);
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
const memoized = cache.memoize(run, ['custom-phrases']);
|
|
const mutate = cache.mutate(update, ['custom-phrases']);
|
|
|
|
await memoized();
|
|
await mutate();
|
|
|
|
expect(await cache.get('custom-phrases', WellKnownCache.defaultKey)).toBeUndefined();
|
|
await memoized();
|
|
expect(run).toBeCalledTimes(2);
|
|
});
|
|
|
|
it('can create mutate function wrapper with customized cache key builder', async () => {
|
|
const run = jest.fn(
|
|
async (foo: string, bar: number) =>
|
|
new Promise<Record<string, unknown>>((resolve) => {
|
|
setTimeout(() => {
|
|
resolve({ foo, bar });
|
|
}, 0);
|
|
})
|
|
);
|
|
const update = jest.fn(async (value: number) => value);
|
|
const cache = new WellKnownCache(tenantId, cacheStore);
|
|
const memoized = cache.memoize(run, ['custom-phrases', (foo, bar) => `${foo}+${bar}`]);
|
|
const mutate = cache.mutate(update, ['custom-phrases', (value) => `1+${value}`]);
|
|
|
|
await Promise.all([memoized('1', 1), memoized('2', 2)]);
|
|
await Promise.all([mutate(1), mutate(2)]);
|
|
|
|
expect(
|
|
await Promise.all([cache.get('custom-phrases', '1+1'), cache.get('custom-phrases', '2+2')])
|
|
).toStrictEqual([undefined, { foo: '2', bar: 2 }]);
|
|
expect(run).toBeCalledTimes(2);
|
|
|
|
const mutate2 = cache.mutate(update, ['custom-phrases', () => `2+2`]);
|
|
await mutate2(1);
|
|
|
|
expect(await cache.get('custom-phrases', '2+2')).toBeUndefined();
|
|
expect(run).toBeCalledTimes(2);
|
|
});
|
|
});
|