refactor(realtime): add test case for clean dangling user

Signed-off-by: BoHong Li <a60814billy@gmail.com>
This commit is contained in:
BoHong Li 2019-05-10 18:17:13 +08:00
parent d8b18ee241
commit ab37a33e0b
No known key found for this signature in database
GPG Key ID: 9696D5590D58290F
7 changed files with 169 additions and 26 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -24,7 +24,8 @@ function makeMockSocket (headers, query) {
return broadCastChannelCache[channel]
}
},
disconnect: sinon.fake()
disconnect: sinon.fake(),
rooms: []
}
}

View File

@ -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

View File

@ -0,0 +1,12 @@
'use strict'
class UpdateDirtyNoteJobStub {
start() {
}
stop() {
}
}
exports.UpdateDirtyNoteJobStub = UpdateDirtyNoteJobStub
exports.UpdateDirtyNoteJob = UpdateDirtyNoteJobStub