From f8f62d489d9f2d006a2df77b2b0cc60f4fee4cbd Mon Sep 17 00:00:00 2001 From: Antoine Gomez Date: Tue, 27 Mar 2018 13:33:07 +0200 Subject: [PATCH 1/3] Add shutdown support --- package-lock.json | 130 ++++++++++++++++-------------- package.json | 10 +-- src/module/redis.extension.ts | 17 ++-- test/integration/shutdown.test.ts | 53 ++++++++++++ test/mocks/redis.mock.ts | 5 ++ 5 files changed, 145 insertions(+), 70 deletions(-) create mode 100644 test/integration/shutdown.test.ts diff --git a/package-lock.json b/package-lock.json index 5c6a870..17f4e1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,40 +1,48 @@ { "name": "@hapiness/redis", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@hapiness/core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@hapiness/core/-/core-1.3.0.tgz", - "integrity": "sha512-NgGxkivGOV1jfOYJssXphR5f/iGYmmUAw14uuH9iwmscrjQo1n+gF+Kf+FB1wcRt28ivsi3+QYKRWFhddQCqkA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@hapiness/core/-/core-1.5.0.tgz", + "integrity": "sha512-yDX0TA2TTpaz2nUwUe7wuUS497ffa9GhATLPlzsF7sqVP3iiziKoElX/y3pI/BjJjARK8jVn1+JIC/sBCr8OTA==", "dev": true, "requires": { - "@types/hapi": "16.1.11", + "@types/hapi": "16.1.14", "@types/hoek": "4.1.3", - "@types/joi": "13.0.3", - "@types/node": "8.5.1", - "@types/websocket": "0.0.35", + "@types/joi": "13.0.7", + "@types/node": "9.6.0", + "@types/websocket": "0.0.38", "debug": "3.1.0", - "hapi": "16.6.2", + "hapi": "16.6.3", "injection-js": "2.2.1", - "reflect-metadata": "0.1.10", + "reflect-metadata": "0.1.12", "websocket": "1.0.25" + }, + "dependencies": { + "@types/node": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.0.tgz", + "integrity": "sha512-h3YZbOq2+ZoDFI1z8Zx0Ck/xRWkOESVaLdgLdd/c25mMQ1Y2CAkILu9ny5A15S5f32gGcQdaUIZ2jzYr8D7IFg==", + "dev": true + } } }, "@types/boom": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@types/boom/-/boom-4.3.8.tgz", - "integrity": "sha512-YACq3sVU3MIYXBEdqrRvQvSVgAHNyYdmrbWnEfrftmZXFcdQ1tY9Lv8AHqwdvYceblgRou++zhzUkJ3YOlWPSg==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@types/boom/-/boom-4.3.10.tgz", + "integrity": "sha512-5iXMLKwCxW0FK0G4XgS5kn0VZQv31DhVAeB36YhxzFpWF4QKa6ZLn4XrziIK2j662p9Azs+lFpFrsuUzC8376g==", "dev": true }, "@types/catbox": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/catbox/-/catbox-7.1.4.tgz", - "integrity": "sha512-0WHI0dG3QLPpRdA0hMNClTgyAOgFvN47/wtM/HnQ/zGds8FbWhjWqMUWotY7l5vjT0pjvzPAv8plSGTwPA6teQ==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@types/catbox/-/catbox-7.1.6.tgz", + "integrity": "sha512-xcLIJdHkkqB6dyclgvFee8GjfeVYzTJAoFiOZlAnZ9R5mv/8VCnUuaQ4z/v3GrlKUL4j9YEbhOimtFawGBe4ng==", "dev": true, "requires": { - "@types/boom": "4.3.8" + "@types/boom": "4.3.10" } }, "@types/events": { @@ -52,14 +60,15 @@ } }, "@types/hapi": { - "version": "16.1.11", - "resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-16.1.11.tgz", - "integrity": "sha512-2KU+sErWdRt2sTI82Ql1R3GJKReay9z7iSgeIPPKfY+9hbSRETQyHKVQ/8cFKHK5nU4KmDGg29CIVo+OsGUQjw==", + "version": "16.1.14", + "resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-16.1.14.tgz", + "integrity": "sha512-VeWocvlYrFINg4ZloxSPkg9qtd9aHMkH1Lt4bPqguSLSTrd51D67tcz8+crhsWsClViTylB6deRatxZzOnly7g==", "dev": true, "requires": { - "@types/boom": "4.3.8", - "@types/catbox": "7.1.4", - "@types/joi": "13.0.3", + "@types/boom": "4.3.10", + "@types/catbox": "7.1.6", + "@types/events": "1.1.0", + "@types/joi": "13.0.7", "@types/mimos": "3.0.1", "@types/node": "8.5.1", "@types/podium": "1.0.0", @@ -73,9 +82,9 @@ "dev": true }, "@types/joi": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@types/joi/-/joi-13.0.3.tgz", - "integrity": "sha512-JI7HHhgbh0VLirvu4o7ld1q/BbGMpdkK8312imVcpmSGLOns7jcC4wwo+zKNcOa+I4prZR/1gJx0Mj+B0kcqhQ==", + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/@types/joi/-/joi-13.0.7.tgz", + "integrity": "sha512-x7VMOrIfpqo0pMi5bIuRE+3RwMNlzE3HZLrEpebW2JmuQXeIX69/G8R90Ibs1i/gb1YvBoSlO4pMwH0VUmclGw==", "dev": true }, "@types/mime-db": { @@ -123,11 +132,12 @@ } }, "@types/websocket": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-0.0.35.tgz", - "integrity": "sha512-HaG4p0g1T6qQDgQNPMEeDNTbK2V7L54QsVecMlAYFRK6IHsusBknZ+vQMGAeN8mOTNaCqb0pXZQLAZu7kdifWg==", + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-0.0.38.tgz", + "integrity": "sha512-Z7dRTAiMoIjz9HBa/xb3k+2mx2uJx2sbnbkRRIvM+l/srNLfthHFBW/jD59thOcEa1/ZooKd30G0D+KGH9wU7Q==", "dev": true, "requires": { + "@types/events": "1.1.0", "@types/node": "8.5.1" } }, @@ -579,9 +589,9 @@ "dev": true }, "content": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/content/-/content-3.0.6.tgz", - "integrity": "sha512-tyl3fRp8jOHsQR0X9vrIy0mKQccv0tA9/RlvLl514eA7vHOJr/TnmMTpgQjInwbeW9IOQVy0OECGAuQNUa0nnQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/content/-/content-3.0.7.tgz", + "integrity": "sha512-LXtnSnvE+Z1Cjpa3P9gh9kb396qV4MqpfwKy777BOSF8n6nw2vAi03tHNl0/XRqZUyzVzY/+nMXOZVnEapWzdg==", "dev": true, "requires": { "boom": "5.2.0" @@ -897,9 +907,9 @@ } }, "hapi": { - "version": "16.6.2", - "resolved": "https://registry.npmjs.org/hapi/-/hapi-16.6.2.tgz", - "integrity": "sha512-DBeIsge8nn3rBSFGX/btOwwkkVIMTuWHIkkiWtRAq8IHxhBfmVSewPm4BprU50PQjncQFw44JTN77l/pMKVHlA==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/hapi/-/hapi-16.6.3.tgz", + "integrity": "sha512-Fe1EtSlRWdez9c1sLDrHZYxpsp3IddwtUWp7y65TCBW5CMcBP98X4WnoBJZTGsDZnk/FDkRyEMhUVsC9qysDPg==", "dev": true, "requires": { "accept": "2.1.4", @@ -1296,14 +1306,14 @@ "dev": true, "requires": { "hoek": "4.2.0", - "isemail": "3.0.0", + "isemail": "3.1.1", "topo": "2.0.2" }, "dependencies": { "isemail": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.0.0.tgz", - "integrity": "sha512-rz0ng/c+fX+zACpLgDB8fnUQ845WSU06f4hlhk4K8TJxmR6f5hyvitu9a9JdMD7aq/P4E0XdG1uaab2OiXgHlA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.1.tgz", + "integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==", "dev": true, "requires": { "punycode": "2.1.0" @@ -1491,9 +1501,9 @@ "dev": true }, "mime-db": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.32.0.tgz", - "integrity": "sha512-+ZWo/xZN40Tt6S+HyakUxnSOgff+JEdaneLWIm0Z6LmpCn5DMcZntLyUY5c/rTDog28LhXLKOUZKoTxTCAdBVw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", "dev": true }, "mime-types": { @@ -1520,7 +1530,7 @@ "dev": true, "requires": { "hoek": "4.2.0", - "mime-db": "1.32.0" + "mime-db": "1.33.0" } }, "minimatch": { @@ -1656,9 +1666,9 @@ } }, "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", "dev": true }, "nigel": { @@ -1813,7 +1823,7 @@ "requires": { "b64": "3.0.3", "boom": "5.2.0", - "content": "3.0.6", + "content": "3.0.7", "hoek": "4.2.0", "nigel": "2.0.2" } @@ -1942,9 +1952,9 @@ "dev": true }, "reflect-metadata": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", - "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", "dev": true }, "regenerator-runtime": { @@ -2039,9 +2049,9 @@ } }, "rxjs": { - "version": "5.5.6", - "resolved": "http://nexus.in.tdw/repository/npm/rxjs/-/rxjs-5.5.6.tgz", - "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.8.tgz", + "integrity": "sha512-Bz7qou7VAIoGiglJZbzbXa4vpX5BmTTN2Dj/se6+SwADtw4SihqBIiEa7VmTXJ8pynvq0iFr5Gx9VLyye1rIxQ==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -2306,7 +2316,7 @@ "dev": true, "requires": { "boom": "5.2.0", - "content": "3.0.6", + "content": "3.0.7", "hoek": "4.2.0", "pez": "2.1.5", "wreck": "12.5.1" @@ -2410,7 +2420,7 @@ }, "symbol-observable": { "version": "1.0.1", - "resolved": "http://nexus.in.tdw/repository/npm/symbol-observable/-/symbol-observable-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", "dev": true }, @@ -2613,9 +2623,9 @@ "optional": true }, "typedarray-to-buffer": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz", - "integrity": "sha1-EBezLZhP9VbroQD1AViauhrOLgQ=", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { "is-typedarray": "1.0.0" @@ -2744,8 +2754,8 @@ "dev": true, "requires": { "debug": "2.6.9", - "nan": "2.8.0", - "typedarray-to-buffer": "3.1.2", + "nan": "2.10.0", + "typedarray-to-buffer": "3.1.5", "yaeti": "0.0.6" }, "dependencies": { diff --git a/package.json b/package.json index b6a2463..572fad2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hapiness/redis", - "version": "1.0.2", + "version": "1.1.0", "description": "Hapiness module for redis", "main": "commonjs/index.js", "types": "index.d.ts", @@ -77,7 +77,7 @@ "redis-commands": "^1.3.1" }, "devDependencies": { - "@hapiness/core": "1.3.0", + "@hapiness/core": "~1.5.0", "@types/fs-extra": "^5.0.0", "coveralls": "^3.0.0", "fs-extra": "^5.0.0", @@ -85,15 +85,15 @@ "mocha": "^4.0.1", "mocha-typescript": "^1.1.12", "rimraf": "^2.6.2", - "rxjs": "^5.5.6", + "rxjs": "^5.5.7", "ts-node": "^3.3.0", "tslint": "^5.8.0", "typescript": "^2.6.2", "unit.js": "^2.0.0" }, "peerDependencies": { - "@hapiness/core": "1.3.0", - "rxjs": "^5.5.6" + "@hapiness/core": "^1.5.0", + "rxjs": "^5.5.7" }, "engines": { "node": ">=7.0.0" diff --git a/src/module/redis.extension.ts b/src/module/redis.extension.ts index 946edee..83c3a51 100644 --- a/src/module/redis.extension.ts +++ b/src/module/redis.extension.ts @@ -1,11 +1,11 @@ -import { CoreModule, Extension, ExtensionWithConfig, OnExtensionLoad } from '@hapiness/core'; +import { CoreModule, Extension, ExtensionWithConfig, OnExtensionLoad, ExtensionShutdownPriority, OnShutdown } from '@hapiness/core'; import { Observable } from 'rxjs/Observable'; import { RedisConfig } from './interfaces'; import { RedisClientManager } from './managers'; -export class RedisExt implements OnExtensionLoad { +export class RedisExt implements OnExtensionLoad, OnShutdown { public static setConfig(config: RedisConfig): ExtensionWithConfig { return { @@ -26,11 +26,18 @@ export class RedisExt implements OnExtensionLoad { onExtensionLoad(module: CoreModule, config: RedisConfig): Observable { return Observable .of(new RedisClientManager(config)) - .switchMap(_ => _.createClient().map(__ => _)) - .map(_ => ({ + .switchMap(redisClient => redisClient.createClient().map(() => redisClient)) + .map(redisClient => ({ instance: this, token: RedisExt, - value: _ + value: redisClient })); } + + onShutdown(module, redisClient: RedisClientManager) { + return { + priority: ExtensionShutdownPriority.NORMAL, + resolver: Observable.bindNodeCallback(redisClient.client.quit)() + }; + } } diff --git a/test/integration/shutdown.test.ts b/test/integration/shutdown.test.ts new file mode 100644 index 0000000..ccf15d9 --- /dev/null +++ b/test/integration/shutdown.test.ts @@ -0,0 +1,53 @@ +import { test, suite } from 'mocha-typescript'; + +import { Observable } from 'rxjs'; + +import { Hapiness, HapinessModule, OnStart } from '@hapiness/core'; + +import { FakeRedisClient, mockRedisCreateConnection } from '../mocks'; +import { RedisExt, RedisModule } from '../../src'; + +@suite('- Integration tests of RedisModule') +export class RedisModuleIntegrationTest { + + @test('- Test shutdown') + testRedisModule(done) { + const fakeInst = new FakeRedisClient(); + const redisStub = mockRedisCreateConnection(); + redisStub.returns( fakeInst); + + Observable + .of(fakeInst) + .delay(new Date(Date.now() + 1500)) + .map(_ => _.emit('ready')) + .subscribe(); + + @HapinessModule({ + version: '1.0.0', + providers: [], + imports: [RedisModule] + }) + class RedisShutdownTest implements OnStart { + constructor() {} + + onStart(): void { + const extension = Hapiness['extensions'].find(item => item.token === RedisExt); + extension.instance.onShutdown({}, extension.value).resolver + .do(() => { + redisStub.restore(); + }) + .subscribe(() => done(), err => done(err)); + } + } + + Hapiness.bootstrap(RedisShutdownTest, [ + RedisExt.setConfig( + { + url: '//test.com', + password: 'mdp', + db: '1' + } + ) + ]); + } +} diff --git a/test/mocks/redis.mock.ts b/test/mocks/redis.mock.ts index 9548ebd..4552592 100644 --- a/test/mocks/redis.mock.ts +++ b/test/mocks/redis.mock.ts @@ -11,4 +11,9 @@ export class FakeRedisClient extends EventEmitter { cb(null, param); return true; } + + quit(cb: redis.Callback) { + cb(null, null); + return true; + } } From 949cdbce61ad50661a00c79202fbea76cbd665f0 Mon Sep 17 00:00:00 2001 From: Antoine Gomez Date: Wed, 28 Mar 2018 12:02:22 +0200 Subject: [PATCH 2/3] Refactor module to handle reconnection --- src/module/common/default-values.ts | 8 +- src/module/managers/redis-client.manager.ts | 101 +++++++++++++++++++- src/module/redis.extension.ts | 6 +- src/module/services/redis-client.service.ts | 25 ++--- test/integration/redis.module.test.ts | 6 +- test/integration/shutdown.test.ts | 5 +- test/mocks/redis.mock.ts | 16 ++++ test/unit/redis-client.manager.test.ts | 19 +++- test/unit/redis-client.service.test.ts | 16 +++- tsconfig.json | 6 +- 10 files changed, 171 insertions(+), 37 deletions(-) diff --git a/src/module/common/default-values.ts b/src/module/common/default-values.ts index 44ff895..ff679a7 100644 --- a/src/module/common/default-values.ts +++ b/src/module/common/default-values.ts @@ -1,12 +1,16 @@ import { RetryStrategy, RetryStrategyOptions } from 'redis'; +const debug = require('debug')('hapiness:redis'); + export class DefaultValues { public static RECONNECT_INTERVAL = 5000; public static RETRY_STRATEGY(reconnect_interval?: number): RetryStrategy { - return (opts: RetryStrategyOptions): number | Error => - reconnect_interval || DefaultValues.RECONNECT_INTERVAL; + return (opts: RetryStrategyOptions): number | Error => { + debug('RETRY CONNECT', opts.error, opts.attempt); + return reconnect_interval || DefaultValues.RECONNECT_INTERVAL + }; } } diff --git a/src/module/managers/redis-client.manager.ts b/src/module/managers/redis-client.manager.ts index facd61f..7d57e4e 100644 --- a/src/module/managers/redis-client.manager.ts +++ b/src/module/managers/redis-client.manager.ts @@ -1,13 +1,58 @@ import { createClient, RedisClient } from 'redis'; + +import * as redis_commands from 'redis-commands'; + +import * as EventEmitter from 'events'; + import { Observable } from 'rxjs'; import { RedisConfig } from '../interfaces'; import { DefaultValues } from '../common'; +import { HapinessRedisClient } from '../../custom-typings/redis-types'; + +const ClientProperties = [ + // redis properties, forwarded read-only. + 'connection_id', + 'connected', + 'ready', + 'connections', + 'options', + 'pub_sub_mode', + 'selected_db' +]; + +const ConnectionEvents = [ + 'ready', + 'connect', + 'reconnecting', + 'error', + 'end' +]; + +const MonitorEvents = [ + 'monitor' +]; + +const PubSubEvents = [ + 'message', + 'pmessage', + 'subscribe', + 'psubscribe', + 'unsubscribe', + 'punsubscribe' +]; + +const AllEvents = [ + ...ConnectionEvents, + ...MonitorEvents, + ...PubSubEvents +]; export class RedisClientManager { private _config: any; private _client: RedisClient; + private _redisClientObs: HapinessRedisClient; constructor(config: RedisConfig) { // If no retry strategy provided, we'll use the default one @@ -25,18 +70,70 @@ export class RedisClientManager { return Observable.create( observer => { this._client = createClient(this._config); - this._client.on('ready', () => { + this.createObservableClient(); + this._redisClientObs.on('ready', () => { observer.next(); observer.complete(); }); - this._client.on('error', err => { + this._redisClientObs.on('error', err => { observer.error(err); }); } ); } + createObservableClient() { + const redisClientObs: any = new EventEmitter(); + const sendCommand = this.sendCommand.bind(this); + const client = this._client; + + redis_commands + .list.forEach(command => { + Object.defineProperty(redisClientObs, command, { + configurable: true, + enumerable: false, + writable: true, + value(...args) { + return sendCommand(command, ...args) + } + }); + }); + + AllEvents.forEach((eventName) => { + this._client.on(eventName, redisClientObs.emit.bind(redisClientObs, eventName)) + }, this); + + ClientProperties.forEach((propertyName) => { + Object.defineProperty(redisClientObs, propertyName, { + configurable: true, + enumerable: false, + get() { + return client[propertyName] + } + }) + }); + + this._redisClientObs = redisClientObs; + } + + sendCommand(command, ...args): Observable { + return Observable.create(observer => { + this._client.send_command(command, args, (err, res) => { + if (err) { + observer.error(err); + } else { + observer.next(res); + observer.complete(); + } + }); + }); + } + public get client(): RedisClient { return this._client; } + + public get clientObs(): HapinessRedisClient { + return this._redisClientObs; + } } diff --git a/src/module/redis.extension.ts b/src/module/redis.extension.ts index 83c3a51..3212e20 100644 --- a/src/module/redis.extension.ts +++ b/src/module/redis.extension.ts @@ -5,6 +5,8 @@ import { Observable } from 'rxjs/Observable'; import { RedisConfig } from './interfaces'; import { RedisClientManager } from './managers'; +const debug = require('debug')('hapiness:redis'); + export class RedisExt implements OnExtensionLoad, OnShutdown { public static setConfig(config: RedisConfig): ExtensionWithConfig { @@ -24,6 +26,7 @@ export class RedisExt implements OnExtensionLoad, OnShutdown { * @returns Observable */ onExtensionLoad(module: CoreModule, config: RedisConfig): Observable { + debug('load redis extension', config); return Observable .of(new RedisClientManager(config)) .switchMap(redisClient => redisClient.createClient().map(() => redisClient)) @@ -35,9 +38,10 @@ export class RedisExt implements OnExtensionLoad, OnShutdown { } onShutdown(module, redisClient: RedisClientManager) { + debug('SIGTERM received, shutdown redis'); return { priority: ExtensionShutdownPriority.NORMAL, - resolver: Observable.bindNodeCallback(redisClient.client.quit)() + resolver: redisClient.clientObs.quit() }; } } diff --git a/src/module/services/redis-client.service.ts b/src/module/services/redis-client.service.ts index 902e38a..beb0adf 100644 --- a/src/module/services/redis-client.service.ts +++ b/src/module/services/redis-client.service.ts @@ -1,30 +1,15 @@ -import * as redis_commands from 'redis-commands'; - import { Injectable, Inject } from '@hapiness/core'; import { HapinessRedisClient } from '../../custom-typings/redis-types'; -import { Observable } from 'rxjs/Observable'; import { RedisExt } from '../redis.extension'; import { RedisClientManager } from '../managers'; - +import { RedisClient } from 'redis'; @Injectable() export class RedisClientService { - - private _client: HapinessRedisClient; - - constructor(@Inject(RedisExt) private _redisManager: RedisClientManager) { - this._client = _redisManager.client; - redis_commands - .list - .forEach(command => { - if (typeof this._redisManager.client[command] === 'function') { - this._client[command] = Observable.bindNodeCallback(this._client[command]); - } - }); - } + constructor(@Inject(RedisExt) private _redisManager: RedisClientManager) {} /** * Return the HapinessRedisClient @@ -32,6 +17,10 @@ export class RedisClientService { * See documentation at */ public get connection(): HapinessRedisClient { - return this._client; + return this._redisManager.clientObs; + } + + public get client(): RedisClient { + return this._redisManager.client; } } diff --git a/test/integration/redis.module.test.ts b/test/integration/redis.module.test.ts index 57b7566..1630a8f 100644 --- a/test/integration/redis.module.test.ts +++ b/test/integration/redis.module.test.ts @@ -87,12 +87,14 @@ export class RedisModuleIntegrationTest { .then(__ => done()) .catch(err => done(err)) }, - err => this + err => { + redisStub.restore(); + this ._httpServer .stop() .then(__ => done(err)) .catch(e => done(e)) - ); + }); } } diff --git a/test/integration/shutdown.test.ts b/test/integration/shutdown.test.ts index ccf15d9..6ac4483 100644 --- a/test/integration/shutdown.test.ts +++ b/test/integration/shutdown.test.ts @@ -36,7 +36,10 @@ export class RedisModuleIntegrationTest { .do(() => { redisStub.restore(); }) - .subscribe(() => done(), err => done(err)); + .subscribe(() => done(), err => { + redisStub.restore(); + done(err) + }); } } diff --git a/test/mocks/redis.mock.ts b/test/mocks/redis.mock.ts index 4552592..dc82f82 100644 --- a/test/mocks/redis.mock.ts +++ b/test/mocks/redis.mock.ts @@ -7,11 +7,27 @@ export function mockRedisCreateConnection() { } export class FakeRedisClient extends EventEmitter { + public connected: boolean; + + constructor() { + super(); + this.connected = true; + } + get(param: string, cb: redis.Callback) { cb(null, param); return true; } + setex(param: string, timer: number, value: string, cb) { + cb(new Error('Cannot SETEX'), null); + return false; + } + + send_command(command, params, cb) { + this[command].apply(this, [...params, cb]); + } + quit(cb: redis.Callback) { cb(null, null); return true; diff --git a/test/unit/redis-client.manager.test.ts b/test/unit/redis-client.manager.test.ts index 9ea1dbb..486c62a 100644 --- a/test/unit/redis-client.manager.test.ts +++ b/test/unit/redis-client.manager.test.ts @@ -32,16 +32,24 @@ export class RedisClientManagerTest { manager .createClient() + .flatMap(() => manager.clientObs.setex('param', 60, 'xaxa')) + .catch(err => { + unit.object(err).isInstanceOf(Error).hasProperty('message', 'Cannot SETEX'); + return Observable.of(null); + }) .subscribe( _ => { + unit.bool(manager.clientObs.connected).isTrue(); manager.client.get('param', (err, res) => { unit.string(res).is('param'); redisStub.restore(); done(); }); }, - err => done(err) - ); + err => { + redisStub.restore(); + done(err) + }); } @test('- Dont create the manager and return error') @@ -68,10 +76,13 @@ export class RedisClientManagerTest { manager .createClient() .subscribe( - _ => done(new Error('Should not be there')), + _ => { + redisStub.restore(); + done(new Error('Should not be there')); + }, err => { - unit.string(err.message).is('Thrown by fakeInst'); redisStub.restore(); + unit.string(err.message).is('Thrown by fakeInst'); done(); } ); diff --git a/test/unit/redis-client.service.test.ts b/test/unit/redis-client.service.test.ts index c731ba3..fdeb76a 100644 --- a/test/unit/redis-client.service.test.ts +++ b/test/unit/redis-client.service.test.ts @@ -31,17 +31,23 @@ export class RedisClientServiceTest { .subscribe(); + let redisService: RedisClientService; Observable.of(manager) .flatMap(_ => _.createClient().map(__ => _)) - .map(_ => new RedisClientService( _)) - .flatMap(_ => _.connection.get('param')) + .map(client => new RedisClientService( client)) + .do(_ => redisService = _) + .flatMap(() => redisService.connection.get('param')) + .do(res => unit.string(res).is('param')) + .flatMap(() => Observable.bindNodeCallback(redisService.client.get)('param')) + .do(res => unit.string(res).is('param')) .subscribe( res => { - unit.string(res).is('param'); redisStub.restore(); done(); }, - err => done(err) - ); + err => { + redisStub.restore(); + done(err); + }); } } diff --git a/tsconfig.json b/tsconfig.json index 03ed136..e2c9503 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,9 @@ ], "lib": [ "dom", - "es2015" + "es2015", + "es2016", + "es2017" ] }, "compileOnSave": false, @@ -31,4 +33,4 @@ "dist", "tmp" ] -} \ No newline at end of file +} From a05693592a77f6e4d09c706626cdb9b707951043 Mon Sep 17 00:00:00 2001 From: Antoine Gomez Date: Wed, 28 Mar 2018 12:06:37 +0200 Subject: [PATCH 3/3] Fix debug --- src/module/common/default-values.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/common/default-values.ts b/src/module/common/default-values.ts index ff679a7..c855678 100644 --- a/src/module/common/default-values.ts +++ b/src/module/common/default-values.ts @@ -8,7 +8,7 @@ export class DefaultValues { public static RETRY_STRATEGY(reconnect_interval?: number): RetryStrategy { return (opts: RetryStrategyOptions): number | Error => { - debug('RETRY CONNECT', opts.error, opts.attempt); + debug('RETRY CONNECT'); return reconnect_interval || DefaultValues.RECONNECT_INTERVAL }; }