mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
174 lines
6.7 KiB
TypeScript
174 lines
6.7 KiB
TypeScript
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
|
|
import type { Expires } from '../../../../src/storage/keyvalue/WrappedExpiringStorage';
|
|
import { WrappedExpiringStorage } from '../../../../src/storage/keyvalue/WrappedExpiringStorage';
|
|
import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
|
|
import clearAllTimers = jest.clearAllTimers;
|
|
|
|
type Internal = Expires<string>;
|
|
|
|
function createExpires(payload: string, expires?: Date): Internal {
|
|
return { payload, expires: expires?.toISOString() };
|
|
}
|
|
|
|
jest.useFakeTimers();
|
|
|
|
describe('A WrappedExpiringStorage', (): void => {
|
|
const tomorrow = new Date();
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
const yesterday = new Date();
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
let source: jest.Mocked<KeyValueStorage<string, Internal>>;
|
|
let storage: WrappedExpiringStorage<string, string>;
|
|
|
|
beforeEach(async(): Promise<void> => {
|
|
source = {
|
|
get: jest.fn(),
|
|
has: jest.fn(),
|
|
set: jest.fn(),
|
|
delete: jest.fn(),
|
|
entries: jest.fn(),
|
|
};
|
|
storage = new WrappedExpiringStorage(source);
|
|
});
|
|
|
|
afterEach(async(): Promise<void> => {
|
|
clearAllTimers();
|
|
});
|
|
|
|
it('does not return data if there is no result.', async(): Promise<void> => {
|
|
await expect(storage.get('key')).resolves.toBeUndefined();
|
|
expect(source.get).toHaveBeenCalledTimes(1);
|
|
expect(source.get).toHaveBeenLastCalledWith('key');
|
|
});
|
|
|
|
it('returns data if it has not expired.', async(): Promise<void> => {
|
|
source.get.mockResolvedValueOnce(createExpires('data!', tomorrow));
|
|
await expect(storage.get('key')).resolves.toEqual('data!');
|
|
});
|
|
|
|
it('deletes expired data when trying to get it.', async(): Promise<void> => {
|
|
source.get.mockResolvedValueOnce(createExpires('data!', yesterday));
|
|
await expect(storage.get('key')).resolves.toBeUndefined();
|
|
expect(source.delete).toHaveBeenCalledTimes(1);
|
|
expect(source.delete).toHaveBeenLastCalledWith('key');
|
|
});
|
|
|
|
it('returns false on `has` checks if there is no data.', async(): Promise<void> => {
|
|
await expect(storage.has('key')).resolves.toBe(false);
|
|
expect(source.get).toHaveBeenCalledTimes(1);
|
|
expect(source.get).toHaveBeenLastCalledWith('key');
|
|
});
|
|
|
|
it('true on `has` checks if there is non-expired data.', async(): Promise<void> => {
|
|
source.get.mockResolvedValueOnce(createExpires('data!', tomorrow));
|
|
await expect(storage.has('key')).resolves.toBe(true);
|
|
});
|
|
|
|
it('deletes expired data when checking if it exists.', async(): Promise<void> => {
|
|
source.get.mockResolvedValueOnce(createExpires('data!', yesterday));
|
|
await expect(storage.has('key')).resolves.toBe(false);
|
|
expect(source.delete).toHaveBeenCalledTimes(1);
|
|
expect(source.delete).toHaveBeenLastCalledWith('key');
|
|
});
|
|
|
|
it('converts the expiry date to a string when storing data.', async(): Promise<void> => {
|
|
await storage.set('key', 'data!', tomorrow);
|
|
expect(source.set).toHaveBeenCalledTimes(1);
|
|
expect(source.set).toHaveBeenLastCalledWith('key', createExpires('data!', tomorrow));
|
|
});
|
|
|
|
it('can store data with an expiration duration.', async(): Promise<void> => {
|
|
await storage.set('key', 'data!', tomorrow.getTime() - Date.now());
|
|
expect(source.set).toHaveBeenCalledTimes(1);
|
|
expect(source.set).toHaveBeenLastCalledWith('key', createExpires('data!', tomorrow));
|
|
});
|
|
|
|
it('can store data without expiry date.', async(): Promise<void> => {
|
|
await storage.set('key', 'data!');
|
|
expect(source.set).toHaveBeenCalledTimes(1);
|
|
expect(source.set).toHaveBeenLastCalledWith('key', createExpires('data!'));
|
|
});
|
|
|
|
it('errors when trying to store expired data.', async(): Promise<void> => {
|
|
await expect(storage.set('key', 'data!', yesterday)).rejects.toThrow(InternalServerError);
|
|
});
|
|
|
|
it('directly calls delete on the source when deleting.', async(): Promise<void> => {
|
|
await expect(storage.delete('key')).resolves.toBeUndefined();
|
|
expect(source.delete).toHaveBeenCalledTimes(1);
|
|
expect(source.delete).toHaveBeenLastCalledWith('key');
|
|
});
|
|
|
|
it('only iterates over non-expired entries.', async(): Promise<void> => {
|
|
const data = [
|
|
[ 'key1', createExpires('data1', tomorrow) ],
|
|
[ 'key2', createExpires('data2', yesterday) ],
|
|
[ 'key3', createExpires('data3') ],
|
|
];
|
|
source.entries.mockImplementationOnce(function* (): any {
|
|
yield* data;
|
|
});
|
|
const it = storage.entries();
|
|
await expect(it.next()).resolves.toEqual(
|
|
expect.objectContaining({ value: [ 'key1', 'data1' ]}),
|
|
);
|
|
await expect(it.next()).resolves.toEqual(
|
|
expect.objectContaining({ value: [ 'key3', 'data3' ]}),
|
|
);
|
|
});
|
|
|
|
it('removes expired entries after a given time.', async(): Promise<void> => {
|
|
// Disable interval function and simply check it was called with the correct parameters
|
|
// Otherwise it gets quite difficult to verify the async interval function gets executed
|
|
const mockInterval = jest.spyOn(global, 'setInterval');
|
|
mockInterval.mockImplementation(jest.fn());
|
|
// Timeout of 1 minute
|
|
storage = new WrappedExpiringStorage(source, 1);
|
|
const data = [
|
|
[ 'key1', createExpires('data1', tomorrow) ],
|
|
[ 'key2', createExpires('data2', yesterday) ],
|
|
[ 'key3', createExpires('data3') ],
|
|
];
|
|
source.entries.mockImplementationOnce(function* (): any {
|
|
yield* data;
|
|
});
|
|
|
|
// Make sure interval is created correctly
|
|
expect(mockInterval.mock.calls).toHaveLength(1);
|
|
expect(mockInterval.mock.calls[0]).toHaveLength(2);
|
|
expect(mockInterval.mock.calls[0][1]).toBe(60 * 1000);
|
|
|
|
// Await the function that should have been executed by the interval
|
|
await (mockInterval.mock.calls[0][0] as () => Promise<void>)();
|
|
|
|
expect(source.delete).toHaveBeenCalledTimes(1);
|
|
expect(source.delete).toHaveBeenLastCalledWith('key2');
|
|
mockInterval.mockRestore();
|
|
});
|
|
|
|
it('can stop the timer.', async(): Promise<void> => {
|
|
const mockInterval = jest.spyOn(global, 'setInterval');
|
|
const mockClear = jest.spyOn(global, 'clearInterval');
|
|
// Timeout of 1 minute
|
|
storage = new WrappedExpiringStorage(source, 1);
|
|
const data = [
|
|
[ 'key1', createExpires('data1', tomorrow) ],
|
|
[ 'key2', createExpires('data2', yesterday) ],
|
|
[ 'key3', createExpires('data3') ],
|
|
];
|
|
source.entries.mockImplementationOnce(function* (): any {
|
|
yield* data;
|
|
});
|
|
|
|
await expect(storage.finalize()).resolves.toBeUndefined();
|
|
|
|
// Make sure clearInterval was called with the interval timer
|
|
expect(mockClear.mock.calls).toHaveLength(1);
|
|
expect(mockClear.mock.calls[0]).toHaveLength(1);
|
|
expect(mockClear.mock.calls[0][0]).toBe(mockInterval.mock.results[0].value);
|
|
|
|
mockInterval.mockRestore();
|
|
mockClear.mockRestore();
|
|
});
|
|
});
|