From ab37a33e0b4e16a59dbc7bd5ddac43b0dcf9a639 Mon Sep 17 00:00:00 2001 From: BoHong Li Date: Fri, 10 May 2019 18:17:13 +0800 Subject: [PATCH] refactor(realtime): add test case for clean dangling user Signed-off-by: BoHong Li --- lib/realtime.js | 26 +------ lib/realtimeCleanDanglingUserJob.js | 49 +++++++++++++ test/realtime/cleanDanglingUser.test.js | 68 +++++++++++++++++++ test/realtime/dirtyNoteUpdate.test.js | 2 - test/realtime/utils.js | 3 +- test/testDoubles/ProcessQueueFake.js | 35 ++++++++++ .../realtimeUpdateDirtyNoteJobStub.js | 12 ++++ 7 files changed, 169 insertions(+), 26 deletions(-) create mode 100644 lib/realtimeCleanDanglingUserJob.js create mode 100644 test/realtime/cleanDanglingUser.test.js create mode 100644 test/testDoubles/ProcessQueueFake.js create mode 100644 test/testDoubles/realtimeUpdateDirtyNoteJobStub.js diff --git a/lib/realtime.js b/lib/realtime.js index 8b0da9e3..8bf25cd3 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -24,6 +24,7 @@ const ot = require('./ot') const { ProcessQueue } = require('./processQueue') const { RealtimeClientConnection } = require('./realtimeClientConnection') const { UpdateDirtyNoteJob } = require('./realtimeUpdateDirtyNoteJob') +const { CleanDanglingUserJob } = require('./realtimeCleanDanglingUserJob') // public const realtime = { @@ -164,29 +165,8 @@ function finishUpdateNote (note, _note, callback) { }) } -// clean when user not in any rooms or user not in connected list -setInterval(function () { - async.each(Object.keys(users), function (key, callback) { - var socket = realtime.io.sockets.connected[key] - if ((!socket && users[key]) || - (socket && (!socket.rooms || socket.rooms.length <= 0))) { - if (config.debug) { - logger.info('cleaner found redundant user: ' + key) - } - if (!socket) { - socket = { - id: key - } - } - if (!disconnectProcessQueue.checkTaskIsInQueue(socket.id)) { - exports.queueForDisconnect(socket) - } - } - return callback(null, null) - }, function (err) { - if (err) return logger.error('cleaner error', err) - }) -}, 60000) +const cleanDanglingUserJob = new CleanDanglingUserJob(realtime) +cleanDanglingUserJob.start() var saverSleep = false // save note revision in interval diff --git a/lib/realtimeCleanDanglingUserJob.js b/lib/realtimeCleanDanglingUserJob.js new file mode 100644 index 00000000..916e436c --- /dev/null +++ b/lib/realtimeCleanDanglingUserJob.js @@ -0,0 +1,49 @@ +'use strict' + +const async = require('async') +const config = require('./config') +const logger = require('./logger') + +/** + * clean when user not in any rooms or user not in connected list + */ +class CleanDanglingUserJob { + constructor (realtime) { + this.realtime = realtime + } + + start () { + if (this.timer) return + this.timer = setInterval(this.cleanDanglingUser.bind(this), 60000) + } + + stop () { + if (!this.timer) return + clearInterval(this.timer) + this.timer = undefined + } + + cleanDanglingUser () { + const users = this.realtime.getUserPool() + async.each(Object.keys(users), (key, callback) => { + const socket = this.realtime.io.sockets.connected[key] + if ((!socket && users[key]) || + (socket && (!socket.rooms || socket.rooms.length <= 0))) { + if (config.debug) { + logger.info('cleaner found redundant user: ' + key) + } + if (!socket) { + return callback(null, null) + } + if (!this.realtime.disconnectProcessQueue.checkTaskIsInQueue(socket.id)) { + this.realtime.queueForDisconnect(socket) + } + } + return callback(null, null) + }, function (err) { + if (err) return logger.error('cleaner error', err) + }) + } +} + +exports.CleanDanglingUserJob = CleanDanglingUserJob diff --git a/test/realtime/cleanDanglingUser.test.js b/test/realtime/cleanDanglingUser.test.js new file mode 100644 index 00000000..725f888d --- /dev/null +++ b/test/realtime/cleanDanglingUser.test.js @@ -0,0 +1,68 @@ +/* eslint-env node, mocha */ +'use strict' + +const assert = require('assert') +const mock = require('mock-require') +const sinon = require('sinon') +const {removeModuleFromRequireCache, makeMockSocket} = require('./utils') + +describe('cleanDanglingUser', function () { + let clock + beforeEach(() => { + clock = sinon.useFakeTimers() + mock('../../lib/processQueue', require('../testDoubles/ProcessQueueFake')) + mock('../../lib/logger', { + error: () => {}, + info: () => {} + }) + mock('../../lib/history', {}) + mock('../../lib/models', { + Revision: { + saveAllNotesRevision: () => { + } + } + }) + mock('../../lib/config', { + debug: true + }) + mock('../../lib/realtimeUpdateDirtyNoteJob', require('../testDoubles/realtimeUpdateDirtyNoteJobStub')) + }) + + afterEach(() => { + clock.restore() + removeModuleFromRequireCache('../../lib/realtime') + mock.stopAll() + sinon.restore() + }) + + it('should ', (done) => { + const realtime = require('../../lib/realtime') + const queueForDisconnectSpy = sinon.spy(realtime, 'queueForDisconnect') + realtime.io = { + to: sinon.stub().callsFake(function () { + return { + emit: sinon.fake() + } + }), + sockets: { + connected: {} + } + } + let user1Socket = makeMockSocket() + let user2Socket = makeMockSocket() + + user1Socket.rooms.push('room1') + + realtime.io.sockets.connected[user1Socket.id] = user1Socket + realtime.io.sockets.connected[user2Socket.id] = user2Socket + + realtime.users[user1Socket.id] = user1Socket + realtime.users[user2Socket.id] = user2Socket + clock.tick(60000) + clock.restore() + setTimeout(() => { + assert(queueForDisconnectSpy.called) + done() + }, 50) + }) +}) diff --git a/test/realtime/dirtyNoteUpdate.test.js b/test/realtime/dirtyNoteUpdate.test.js index eb04e7f4..7bb029d9 100644 --- a/test/realtime/dirtyNoteUpdate.test.js +++ b/test/realtime/dirtyNoteUpdate.test.js @@ -66,7 +66,6 @@ describe('realtime#update note is dirty timer', function () { realtime.notes['note2'] = note2 clock.tick(1000) - setTimeout(() => { assert(note2.server.isDirty === false) done() @@ -126,5 +125,4 @@ describe('realtime#update note is dirty timer', function () { done() }, 50) }) - }) diff --git a/test/realtime/utils.js b/test/realtime/utils.js index 55f4f9b5..57cced0e 100644 --- a/test/realtime/utils.js +++ b/test/realtime/utils.js @@ -24,7 +24,8 @@ function makeMockSocket (headers, query) { return broadCastChannelCache[channel] } }, - disconnect: sinon.fake() + disconnect: sinon.fake(), + rooms: [] } } diff --git a/test/testDoubles/ProcessQueueFake.js b/test/testDoubles/ProcessQueueFake.js new file mode 100644 index 00000000..5305a163 --- /dev/null +++ b/test/testDoubles/ProcessQueueFake.js @@ -0,0 +1,35 @@ +'use strict' + +class ProcessQueueFake { + constructor () { + this.taskMap = new Map() + this.queue = [] + } + + start () { + + } + + stop () { + + } + + checkTaskIsInQueue (id) { + return this.taskMap.has(id) + } + + push (id, processFunc) { + this.queue.push({ + id: id, + processFunc: processFunc + }) + this.taskMap.set(id, true) + } + + process () { + + } +} + +exports.ProcessQueueFake = ProcessQueueFake +exports.ProcessQueue = ProcessQueueFake diff --git a/test/testDoubles/realtimeUpdateDirtyNoteJobStub.js b/test/testDoubles/realtimeUpdateDirtyNoteJobStub.js new file mode 100644 index 00000000..5b7655dd --- /dev/null +++ b/test/testDoubles/realtimeUpdateDirtyNoteJobStub.js @@ -0,0 +1,12 @@ +'use strict' + +class UpdateDirtyNoteJobStub { + start() { + } + + stop() { + } +} + +exports.UpdateDirtyNoteJobStub = UpdateDirtyNoteJobStub +exports.UpdateDirtyNoteJob = UpdateDirtyNoteJobStub