n8n/packages/cli/test/unit/services/cache.service.test.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

241 lines
6.8 KiB
TypeScript
Raw Normal View History

import { CacheService } from '@/services/cache/cache.service';
import config from '@/config';
import { sleep } from 'n8n-workflow';
jest.mock('ioredis', () => {
const Redis = require('ioredis-mock');
return function (...args: unknown[]) {
return new Redis(args);
};
});
for (const backend of ['memory', 'redis'] as const) {
describe(backend, () => {
let cacheService: CacheService;
beforeAll(async () => {
config.set('cache.backend', backend);
cacheService = new CacheService();
await cacheService.init();
});
afterEach(async () => {
await cacheService.reset();
config.load(config.default);
});
describe('init', () => {
test('should select backend based on config', () => {
expect(cacheService.isMemory()).toBe(backend === 'memory');
expect(cacheService.isRedis()).toBe(backend === 'redis');
});
if (backend === 'redis') {
test('with auto backend and queue mode, should select redis', async () => {
config.set('executions.mode', 'queue');
await cacheService.init();
expect(cacheService.isRedis()).toBe(true);
});
}
if (backend === 'memory') {
test('should honor max size when enough', async () => {
config.set('cache.memory.maxSize', 16); // enough bytes for "withoutUnicode"
await cacheService.init();
await cacheService.set('key', 'withoutUnicode');
await expect(cacheService.get('key')).resolves.toBe('withoutUnicode');
// restore
config.set('cache.memory.maxSize', 3 * 1024 * 1024);
await cacheService.init();
});
test('should honor max size when not enough', async () => {
config.set('cache.memory.maxSize', 16); // not enough bytes for "withUnicodeԱԲԳ"
await cacheService.init();
await cacheService.set('key', 'withUnicodeԱԲԳ');
await expect(cacheService.get('key')).resolves.toBeUndefined();
// restore
config.set('cache.memory.maxSize', 3 * 1024 * 1024);
await cacheService.init();
});
}
});
describe('set', () => {
test('should set a string value', async () => {
await cacheService.set('key', 'value');
await expect(cacheService.get('key')).resolves.toBe('value');
});
test('should set a number value', async () => {
await cacheService.set('key', 123);
await expect(cacheService.get('key')).resolves.toBe(123);
});
test('should set an object value', async () => {
const object = { a: { b: { c: { d: 1 } } } };
await cacheService.set('key', object);
await expect(cacheService.get('key')).resolves.toMatchObject(object);
});
test('should not cache `null` or `undefined` values', async () => {
await cacheService.set('key1', null);
await cacheService.set('key2', undefined);
await cacheService.set('key3', 'value');
await expect(cacheService.get('key1')).resolves.toBeUndefined();
await expect(cacheService.get('key2')).resolves.toBeUndefined();
await expect(cacheService.get('key3')).resolves.toBe('value');
});
test('should disregard zero-length keys', async () => {
await cacheService.set('', 'value');
await expect(cacheService.get('')).resolves.toBeUndefined();
});
test('should honor ttl', async () => {
await cacheService.set('key', 'value', 100);
await expect(cacheService.get('key')).resolves.toBe('value');
await sleep(200);
await expect(cacheService.get('key')).resolves.toBeUndefined();
});
});
describe('get', () => {
test('should fall back to fallback value', async () => {
const promise = cacheService.get('key', { fallbackValue: 'fallback' });
await expect(promise).resolves.toBe('fallback');
});
test('should refresh value', async () => {
const promise = cacheService.get('testString', {
refreshFn: async () => 'refreshValue',
});
await expect(promise).resolves.toBe('refreshValue');
});
test('should handle non-ASCII key', async () => {
const nonAsciiKey = 'ԱԲԳ';
await cacheService.set(nonAsciiKey, 'value');
await expect(cacheService.get(nonAsciiKey)).resolves.toBe('value');
});
});
describe('delete', () => {
test('should delete a key', async () => {
await cacheService.set('key', 'value');
await cacheService.delete('key');
await expect(cacheService.get('key')).resolves.toBeUndefined();
});
});
describe('setMany', () => {
test('should set multiple string values', async () => {
await cacheService.setMany([
['key1', 'value1'],
['key2', 'value2'],
]);
const promise = cacheService.getMany(['key1', 'key2']);
await expect(promise).resolves.toStrictEqual(['value1', 'value2']);
});
test('should set multiple number values', async () => {
await cacheService.setMany([
['key1', 123],
['key2', 456],
]);
const promise = cacheService.getMany(['key1', 'key2']);
await expect(promise).resolves.toStrictEqual([123, 456]);
});
test('should disregard zero-length keys', async () => {
await cacheService.setMany([['', 'value1']]);
await expect(cacheService.get('')).resolves.toBeUndefined();
});
});
describe('getMany', () => {
test('should return undefined on missing result', async () => {
await cacheService.setMany([
['key1', 123],
['key2', 456],
]);
const promise = cacheService.getMany(['key2', 'key3']);
await expect(promise).resolves.toStrictEqual([456, undefined]);
});
});
describe('delete', () => {
test('should handle non-ASCII key', async () => {
const nonAsciiKey = 'ԱԲԳ';
await cacheService.set(nonAsciiKey, 'value');
await expect(cacheService.get(nonAsciiKey)).resolves.toBe('value');
await cacheService.delete(nonAsciiKey);
await expect(cacheService.get(nonAsciiKey)).resolves.toBeUndefined();
});
});
describe('setHash', () => {
test('should set a hash if non-existing', async () => {
await cacheService.setHash('keyW', { field: 'value' });
await expect(cacheService.getHash('keyW')).resolves.toStrictEqual({ field: 'value' });
});
test('should add to a hash value if existing', async () => {
await cacheService.setHash('key', { field1: 'value1' });
await cacheService.setHash('key', { field2: 'value2' });
await expect(cacheService.getHash('key')).resolves.toStrictEqual({
field1: 'value1',
field2: 'value2',
});
});
});
describe('deleteFromHash', () => {
test('should delete a hash field', async () => {
await cacheService.setHash('key', { field1: 'value1', field2: 'value2' });
await cacheService.deleteFromHash('key', 'field1');
await expect(cacheService.getHash('key')).resolves.toStrictEqual({ field2: 'value2' });
});
});
describe('getHashValue', () => {
test('should return a hash field value', async () => {
await cacheService.setHash('key', { field1: 'value1', field2: 'value2' });
await expect(cacheService.getHashValue('key', 'field1')).resolves.toBe('value1');
});
});
});
}