Skip to content

Commit

Permalink
fix: mset does not add cache key prefix.
Browse files Browse the repository at this point in the history
Integration tests with real Redis instance was added.
  • Loading branch information
alexkvak committed Jan 28, 2020
1 parent 71d60a3 commit 8d58233
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ node_js:
- '10'
- '8'

services:
- redis-server

script:
- npm run test:ci
- npm run test:integration
- npm run build
- npm run check

Expand Down
13 changes: 13 additions & 0 deletions integration/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
globals: {
'ts-jest': {
tsConfig: '<rootDir>/../tsconfig.json',
diagnostics: true,
},
},
testMatch: ['<rootDir>/tests/**/*.spec.ts'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.ts$': 'ts-jest',
},
};
213 changes: 213 additions & 0 deletions integration/tests/redis.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import faker from 'faker';
import Redis, { Redis as RedisType } from 'ioredis';
import RedisStorageAdapter, { CACHE_PREFIX } from '../../src/adapters/redis';
import { ConnectionStatus } from '../../src/connection-status';

let redis: RedisType;
let adapter: RedisStorageAdapter;

function delay(duration: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, duration));
}

const expireTimeout = 5;

describe('Redis adapter', () => {
beforeAll(() => {
redis = new Redis();
adapter = new RedisStorageAdapter(redis, { lockExpireTimeout: expireTimeout });
});

afterAll(() => {
redis.disconnect();
});

it('Sets connection status to "connected" if redis executes some command', async () => {
await redis.get('');
expect(adapter.getConnectionStatus()).toEqual(ConnectionStatus.CONNECTED);
});

describe('set', () => {
it('set returns true if operation is successful', async () => {
const key = faker.random.uuid();
const value = faker.random.uuid();

await expect(adapter.set(key, value)).resolves.toEqual(true);
await expect(adapter.get(key)).resolves.toEqual(value);
});

it('set adds cache prefix', async () => {
const key = faker.random.uuid();
const value = faker.random.uuid();

await adapter.set(key, value);

await expect(redis.get(`${CACHE_PREFIX}:${key}`)).resolves.toEqual(value);
});

it('set calls set with cache prefix and PX mode when expires set', async () => {
const key = faker.random.uuid();
const value = faker.random.uuid();

await adapter.set(key, value, expireTimeout);
await delay(expireTimeout);
await expect(redis.get(`${CACHE_PREFIX}:${key}`)).resolves.toBeNull();
});
});

describe('get', () => {
it('get returns value', async () => {
const key = faker.random.uuid();
const value = faker.random.uuid();

await expect(adapter.set(key, value));
await expect(adapter.get(key)).resolves.toEqual(value);
});

it('get returns null if key does not set', async () => {
const key = faker.random.uuid();
await expect(adapter.get(key)).resolves.toBeNull();
});

it('get adds cache prefix', async () => {
const key = faker.random.uuid();
const value = faker.random.uuid();
await redis.set(`${CACHE_PREFIX}:${key}`, value);

await expect(adapter.get(key)).resolves.toEqual(value);
});
});

describe('del', () => {
it('del calls del with cache prefix', async () => {
const key = faker.random.uuid();
const value = faker.random.uuid();

await redis.set(`${CACHE_PREFIX}:${key}`, value);
await adapter.del(key);

await expect(redis.get(`${CACHE_PREFIX}:${key}`)).resolves.toBeNull();
});

it('del does nothing if key does not exist', async () => {
const key = faker.random.uuid();
const keyWithPrefix = `${CACHE_PREFIX}:${key}`;

await expect(redis.get(keyWithPrefix)).resolves.toBeNull();
await adapter.del(key);
await expect(redis.get(keyWithPrefix)).resolves.toBeNull();
});
});

describe('acquireLock', () => {
it('acquireLock returns true if lock is successful', async () => {
const key = faker.random.uuid();
const lockResult = await adapter.acquireLock(key);

expect(lockResult).toEqual(true);
});

it('acquireLock calls set with generated key name and in NX mode', async () => {
const key = faker.random.uuid();
const lockResult = await adapter.acquireLock(key);

expect(lockResult).toEqual(true);
await delay(expireTimeout);

await (expect(redis.get(`${key}_lock`))).resolves.toBeNull();
});
});

describe('releaseLock', () => {
it('releaseLock returns false if lock does not exist', async () => {
const key = faker.random.uuid();
const releaseLockResult = await adapter.releaseLock(key);
expect(releaseLockResult).toEqual(false);
});

it('releaseLock delete lock record with appropriate key, and returns true on success', async () => {
const key = faker.random.uuid();

await redis.set(`${key}_lock`, '');
const releaseLockResult = await adapter.releaseLock(key);
expect(releaseLockResult).toEqual(true);
});

it('releaseLock delete lock record set by acquireLock', async () => {
const key = faker.random.uuid();

await adapter.acquireLock(key);

await expect(adapter.releaseLock(key)).resolves.toEqual(true);
});
});

describe('isLockExists', () => {
it('isLockExists returns true if lock exists', async () => {
const key = faker.random.uuid();

await adapter.acquireLock(key);
await expect(adapter.isLockExists(key)).resolves.toEqual(true);
});

it('isLockExists returns false if lock does not exist', async () => {
const key = faker.random.uuid();

await expect(adapter.isLockExists(key)).resolves.toEqual(false);
});
});

describe('mset', () => {
it('mset sets values', async () => {
const values = new Map([
[faker.random.word(), faker.random.uuid()],
[faker.random.word(), faker.random.uuid()]
]);
await adapter.mset(values);

for (const [key, value] of values.entries()) {
await expect(redis.get(`${CACHE_PREFIX}:${key}`)).resolves.toEqual(value);
}
});

it('mset throws error on empty values', async () => {
await expect(adapter.mset(new Map<string, any>())).rejects.toThrowError('ERR wrong number of arguments for \'mset\' command');
});
});

describe('mget', () => {
it('mget gets values', async () => {
const values = new Map([
[faker.random.word(), faker.random.uuid()],
[faker.random.word(), faker.random.uuid()]
]);

for (const [key, value] of values.entries()) {
await redis.set(`${CACHE_PREFIX}:${key}`, value);
}

const result = await adapter.mget(Array.from(values.keys()));

expect(result).toEqual(Array.from(values.values()));
});

it('mget returns null for non-existing keys', async () => {
const values = new Map([
[faker.random.word(), faker.random.uuid()],
[faker.random.word(), faker.random.uuid()]
]);

for (const [key, value] of values.entries()) {
await redis.set(`${CACHE_PREFIX}:${key}`, value);
}

const keys = Array.from(values.keys());
const nonExistingKey = faker.random.word();
keys.push(nonExistingKey);

const result = await adapter.mget(keys);

expect(result).toEqual([...Array.from(values.values()), null]);
});
});
});
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"test": "npm run test:unit",
"test:unit": "jest --coverage --verbose --passWithNoTests",
"test:ci": "npm run test:unit -- --coverageReporters=text-lcov | coveralls",
"test:integration": "jest --config ./integration/jest.config.js --forceExit --detectOpenHandles",
"test:unit:watch": "jest --watch",
"prepublishOnly": "npm run check && npm run build",
"semantic-release": "semantic-release"
Expand All @@ -43,12 +44,14 @@
"devDependencies": {
"@semantic-release/changelog": "^3.0.4",
"@semantic-release/git": "^7.0.16",
"@types/faker": "^4.1.9",
"@types/ioredis": "^4.14.4",
"@types/jest": "^24.0.15",
"@types/lodash": "^4.14.149",
"@types/node": "^8",
"coveralls": "^3.0.4",
"cz-conventional-changelog": "^3.0.2",
"faker": "^4.1.0",
"ioredis": "^4.14.1",
"jest": "^24.8.0",
"semantic-release": "^15.13.24",
Expand Down
6 changes: 5 additions & 1 deletion src/adapters/redis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ export class RedisStorageAdapter implements StorageAdapter {
* Set multiple values to redis storage
*/
public async mset(values: Map<string, any>): Promise<void> {
await withTimeout(this.redisInstance.mset(values), this.options.operationTimeout);
const data = new Map<string, any>();
for (const [key, value] of values.entries()) {
data.set(`${CACHE_PREFIX}:${key}`, value);
}
await withTimeout(this.redisInstance.mset(data), this.options.operationTimeout);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/redis/redis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ describe('Redis adapter', () => {
await adapter.mset(values);

expect((mock as any).mset).toHaveBeenCalledTimes(1);
expect((mock as any).mset).toHaveBeenCalledWith(values);
expect((mock as any).mset).toHaveBeenCalledWith(new Map([['cache:some1', 'value1'], ['cache:some2', 'value2']]));
});

it('mget gets values', async () => {
Expand Down

0 comments on commit 8d58233

Please sign in to comment.