mirror of https://github.com/status-im/codimd.git
refactor(realtime): separate test case to individually file
Signed-off-by: BoHong Li <a60814billy@gmail.com>
This commit is contained in:
parent
b17f417af4
commit
79666aeec3
|
@ -1,709 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
/* eslint-env node, mocha */
|
|
||||||
|
|
||||||
const mock = require('mock-require')
|
|
||||||
const assert = require('assert')
|
|
||||||
const sinon = require('sinon')
|
|
||||||
|
|
||||||
function makeMockSocket (headers, query) {
|
|
||||||
const broadCastChannelCache = {}
|
|
||||||
return {
|
|
||||||
id: Math.round(Math.random() * 10000),
|
|
||||||
handshake: {
|
|
||||||
headers: Object.assign({}, headers),
|
|
||||||
query: Object.assign({}, query)
|
|
||||||
},
|
|
||||||
on: sinon.fake(),
|
|
||||||
emit: sinon.fake(),
|
|
||||||
broadCastChannelCache: {},
|
|
||||||
broadcast: {
|
|
||||||
to: (channel) => {
|
|
||||||
if (!broadCastChannelCache[channel]) {
|
|
||||||
broadCastChannelCache[channel] = {
|
|
||||||
channel: channel,
|
|
||||||
emit: sinon.fake()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return broadCastChannelCache[channel]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeModuleFromRequireCache (modulePath) {
|
|
||||||
delete require.cache[require.resolve(modulePath)]
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('realtime', function () {
|
|
||||||
describe('extractNoteIdFromSocket', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
delete require.cache[require.resolve('../lib/realtime')]
|
|
||||||
mock.stopAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('urlPath not set', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/config', {})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
})
|
|
||||||
|
|
||||||
let realtime
|
|
||||||
|
|
||||||
it('return false if socket or socket.handshake not exists', function () {
|
|
||||||
let noteId = realtime.extractNoteIdFromSocket()
|
|
||||||
assert.strictEqual(false, noteId)
|
|
||||||
|
|
||||||
noteId = realtime.extractNoteIdFromSocket({})
|
|
||||||
assert.strictEqual(false, noteId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('return false if query not set and referer not set', function () {
|
|
||||||
let noteId = realtime.extractNoteIdFromSocket(makeMockSocket({
|
|
||||||
otherHeader: 1
|
|
||||||
}, {
|
|
||||||
otherQuery: 1
|
|
||||||
}))
|
|
||||||
assert.strictEqual(false, noteId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('return noteId from query', function () {
|
|
||||||
// Arrange
|
|
||||||
const incomingNoteId = 'myNoteId'
|
|
||||||
const incomingSocket = makeMockSocket(undefined, { noteId: incomingNoteId })
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const noteId = realtime.extractNoteIdFromSocket(incomingSocket)
|
|
||||||
// Assert
|
|
||||||
assert.strictEqual(noteId, incomingNoteId)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('return noteId from old method (referer)', function () {
|
|
||||||
// Arrange
|
|
||||||
const incomingNoteId = 'myNoteId'
|
|
||||||
const incomingSocket = makeMockSocket({
|
|
||||||
referer: `https://localhost:3000/${incomingNoteId}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const noteId = realtime.extractNoteIdFromSocket(incomingSocket)
|
|
||||||
// Assert
|
|
||||||
assert.strictEqual(noteId, incomingNoteId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('urlPath is set', function () {
|
|
||||||
let realtime
|
|
||||||
it('return noteId from old method (referer) and urlPath set', function () {
|
|
||||||
// Arrange
|
|
||||||
const urlPath = 'hello'
|
|
||||||
mock('../lib/config', {
|
|
||||||
urlPath: urlPath
|
|
||||||
})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const incomingNoteId = 'myNoteId'
|
|
||||||
const incomingSocket = makeMockSocket({
|
|
||||||
referer: `https://localhost:3000/${urlPath}/${incomingNoteId}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const noteId = realtime.extractNoteIdFromSocket(incomingSocket)
|
|
||||||
// Assert
|
|
||||||
assert.strictEqual(noteId, incomingNoteId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('parseNoteIdFromSocket', function () {
|
|
||||||
let realtime
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteId: function (noteId, callback) {
|
|
||||||
callback(null, noteId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/config', {})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
removeModuleFromRequireCache('../lib/realtime')
|
|
||||||
mock.stopAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return null when socket not send noteId', function () {
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const mockSocket = makeMockSocket()
|
|
||||||
const fakeCallback = sinon.fake()
|
|
||||||
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
|
||||||
assert(fakeCallback.called)
|
|
||||||
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, null])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('noteId exists', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteId: function (noteId, callback) {
|
|
||||||
callback(null, noteId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should return noteId when noteId exists', function () {
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const noteId = '123456'
|
|
||||||
const mockSocket = makeMockSocket(undefined, {
|
|
||||||
noteId: noteId
|
|
||||||
})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const fakeCallback = sinon.fake()
|
|
||||||
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
|
||||||
assert(fakeCallback.called)
|
|
||||||
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, noteId])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('noteId not exists', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteId: function (noteId, callback) {
|
|
||||||
callback(null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should return null when noteId not exists', function () {
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const noteId = '123456'
|
|
||||||
const mockSocket = makeMockSocket(undefined, {
|
|
||||||
noteId: noteId
|
|
||||||
})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const fakeCallback = sinon.fake()
|
|
||||||
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
|
||||||
assert(fakeCallback.called)
|
|
||||||
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, null])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('parse note error', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteId: function (noteId, callback) {
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
callback('error', null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should return error when noteId parse error', function () {
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const noteId = '123456'
|
|
||||||
const mockSocket = makeMockSocket(undefined, {
|
|
||||||
noteId: noteId
|
|
||||||
})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
const fakeCallback = sinon.fake()
|
|
||||||
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
|
||||||
assert(fakeCallback.called)
|
|
||||||
assert.deepStrictEqual(fakeCallback.getCall(0).args, ['error', null])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('update note is dirty timer', function () {
|
|
||||||
let realtime
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {
|
|
||||||
error: () => {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {
|
|
||||||
Revision: {
|
|
||||||
saveAllNotesRevision: () => {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/config', {})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
removeModuleFromRequireCache('../lib/realtime')
|
|
||||||
mock.stopAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should update note when note is dirty', (done) => {
|
|
||||||
const clock = sinon.useFakeTimers()
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
sinon.stub(realtime, 'updateNote').callsFake(function (note, callback) {
|
|
||||||
callback(null, null)
|
|
||||||
})
|
|
||||||
const socketIoEmitFake = sinon.fake()
|
|
||||||
realtime.io = {
|
|
||||||
to: sinon.stub().callsFake(function () {
|
|
||||||
return {
|
|
||||||
emit: socketIoEmitFake
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
realtime.notes['note1'] = {
|
|
||||||
server: {
|
|
||||||
isDirty: false
|
|
||||||
},
|
|
||||||
socks: []
|
|
||||||
}
|
|
||||||
let note2 = {
|
|
||||||
server: {
|
|
||||||
isDirty: true
|
|
||||||
},
|
|
||||||
socks: []
|
|
||||||
}
|
|
||||||
realtime.notes['note2'] = note2
|
|
||||||
|
|
||||||
clock.tick(1000)
|
|
||||||
clock.restore()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
assert(note2.server.isDirty === false)
|
|
||||||
done()
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('updateNote', function () {
|
|
||||||
let realtime, fakeNote
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {
|
|
||||||
error: () => {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
findOne: async function () {
|
|
||||||
return fakeNote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/config', {})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mock.stopAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return null when note not found', function (done) {
|
|
||||||
fakeNote = null
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
|
|
||||||
sinon.stub(realtime, 'finishUpdateNote').callsFake(function (a, b, callback) {
|
|
||||||
callback(null, b)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fakeCallback = sinon.fake()
|
|
||||||
realtime.updateNote({ id: '123' }, fakeCallback)
|
|
||||||
setTimeout(() => {
|
|
||||||
assert.ok(fakeCallback.called)
|
|
||||||
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, null])
|
|
||||||
sinon.restore()
|
|
||||||
done()
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('finishUpdateNote', function () {
|
|
||||||
let realtime
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteTitle: (data) => (data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/config', {})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
removeModuleFromRequireCache('../lib/realtime')
|
|
||||||
mock.stopAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('return null when note is null', () => {
|
|
||||||
const fakeCallback = sinon.fake()
|
|
||||||
|
|
||||||
realtime.finishUpdateNote(null, {}, fakeCallback)
|
|
||||||
|
|
||||||
assert.ok(fakeCallback.calledOnce)
|
|
||||||
assert.deepStrictEqual(fakeCallback.lastCall.args, [null, null])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('connection', function () {
|
|
||||||
let realtime
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {
|
|
||||||
error: () => {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteTitle: (data) => (data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/config', {})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
removeModuleFromRequireCache('../lib/realtime')
|
|
||||||
mock.stopAll()
|
|
||||||
sinon.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('fail', function () {
|
|
||||||
it('should fast return when server not start', () => {
|
|
||||||
const mockSocket = makeMockSocket()
|
|
||||||
realtime.maintenance = true
|
|
||||||
const spy = sinon.spy(realtime, 'parseNoteIdFromSocket')
|
|
||||||
realtime.connection(mockSocket)
|
|
||||||
assert(!spy.called)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should failed when parse noteId occur error', () => {
|
|
||||||
const mockSocket = makeMockSocket()
|
|
||||||
realtime.maintenance = false
|
|
||||||
const parseNoteIdFromSocketSpy = sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
callback('error', null)
|
|
||||||
})
|
|
||||||
|
|
||||||
const failConnectionSpy = sinon.stub(realtime, 'failConnection')
|
|
||||||
|
|
||||||
realtime.connection(mockSocket)
|
|
||||||
|
|
||||||
assert(parseNoteIdFromSocketSpy.called)
|
|
||||||
assert(failConnectionSpy.calledOnce)
|
|
||||||
assert.deepStrictEqual(failConnectionSpy.lastCall.args, [500, 'error', mockSocket])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should failed when noteId not exists', () => {
|
|
||||||
const mockSocket = makeMockSocket()
|
|
||||||
realtime.maintenance = false
|
|
||||||
const parseNoteIdFromSocketSpy = sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
callback(null, null)
|
|
||||||
})
|
|
||||||
|
|
||||||
const failConnectionSpy = sinon.stub(realtime, 'failConnection')
|
|
||||||
|
|
||||||
realtime.connection(mockSocket)
|
|
||||||
|
|
||||||
assert(parseNoteIdFromSocketSpy.called)
|
|
||||||
assert(failConnectionSpy.calledOnce)
|
|
||||||
assert.deepStrictEqual(failConnectionSpy.lastCall.args, [404, 'note id not found', mockSocket])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should success connect', function () {
|
|
||||||
const mockSocket = makeMockSocket()
|
|
||||||
const noteId = 'note123'
|
|
||||||
realtime.maintenance = false
|
|
||||||
const parseNoteIdFromSocketSpy = sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
callback(null, noteId)
|
|
||||||
})
|
|
||||||
const failConnectionStub = sinon.stub(realtime, 'failConnection')
|
|
||||||
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
|
||||||
const startConnectionStub = sinon.stub(realtime, 'startConnection')
|
|
||||||
|
|
||||||
realtime.connection(mockSocket)
|
|
||||||
|
|
||||||
assert.ok(parseNoteIdFromSocketSpy.calledOnce)
|
|
||||||
|
|
||||||
assert(failConnectionStub.called === false)
|
|
||||||
assert(updateUserDataStub.calledOnce)
|
|
||||||
assert(startConnectionStub.calledOnce)
|
|
||||||
assert(mockSocket.on.callCount === 11)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('socket event', function () {
|
|
||||||
let realtime
|
|
||||||
const noteId = 'note123'
|
|
||||||
let clientSocket
|
|
||||||
const eventFuncMap = new Map()
|
|
||||||
beforeEach(() => {
|
|
||||||
mock('../lib/logger', {
|
|
||||||
error: () => {
|
|
||||||
},
|
|
||||||
info: () => {}
|
|
||||||
})
|
|
||||||
mock('../lib/history', {})
|
|
||||||
mock('../lib/models', {
|
|
||||||
Note: {
|
|
||||||
parseNoteTitle: (data) => (data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mock('../lib/config', {
|
|
||||||
fullversion: '1.5.0',
|
|
||||||
minimumCompatibleVersion: '1.0.0'
|
|
||||||
})
|
|
||||||
realtime = require('../lib/realtime')
|
|
||||||
|
|
||||||
// get all socket event handler
|
|
||||||
clientSocket = makeMockSocket()
|
|
||||||
clientSocket.on = function (event, func) {
|
|
||||||
eventFuncMap.set(event, func)
|
|
||||||
}
|
|
||||||
realtime.maintenance = false
|
|
||||||
sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
callback(null, noteId)
|
|
||||||
})
|
|
||||||
const wrappedFuncs = []
|
|
||||||
wrappedFuncs.push(sinon.stub(realtime, 'failConnection'))
|
|
||||||
wrappedFuncs.push(sinon.stub(realtime, 'updateUserData'))
|
|
||||||
wrappedFuncs.push(sinon.stub(realtime, 'startConnection'))
|
|
||||||
realtime.connection(clientSocket)
|
|
||||||
|
|
||||||
wrappedFuncs.forEach((wrappedFunc) => {
|
|
||||||
wrappedFunc.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
removeModuleFromRequireCache('../lib/realtime')
|
|
||||||
mock.stopAll()
|
|
||||||
sinon.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('refresh', function () {
|
|
||||||
it('should call refresh', () => {
|
|
||||||
const refreshFunc = eventFuncMap.get('refresh')
|
|
||||||
const emitRefreshStub = sinon.stub(realtime, 'emitRefresh')
|
|
||||||
refreshFunc()
|
|
||||||
assert(emitRefreshStub.calledOnce)
|
|
||||||
assert.deepStrictEqual(emitRefreshStub.lastCall.args[0], clientSocket)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('user status', function () {
|
|
||||||
it('should call emitUserStatus and update user data', () => {
|
|
||||||
const userStatusFunc = eventFuncMap.get('user status')
|
|
||||||
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
|
||||||
realtime.notes[noteId] = {}
|
|
||||||
|
|
||||||
const userData = {
|
|
||||||
idle: true,
|
|
||||||
type: 'xs'
|
|
||||||
}
|
|
||||||
userStatusFunc(userData)
|
|
||||||
assert(emitUserStatusStub.calledOnce)
|
|
||||||
assert.deepStrictEqual(emitUserStatusStub.lastCall.args[0], clientSocket)
|
|
||||||
assert(realtime.users[clientSocket.id].idle === true)
|
|
||||||
assert(realtime.users[clientSocket.id].type === 'xs')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should call emitUserStatus without userdata', () => {
|
|
||||||
const userStatusFunc = eventFuncMap.get('user status')
|
|
||||||
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
|
||||||
realtime.notes[noteId] = {}
|
|
||||||
userStatusFunc()
|
|
||||||
assert(emitUserStatusStub.calledOnce)
|
|
||||||
assert.deepStrictEqual(emitUserStatusStub.lastCall.args[0], clientSocket)
|
|
||||||
assert(realtime.users[clientSocket.id].idle === false)
|
|
||||||
assert(realtime.users[clientSocket.id].type === null)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not call emitUserStatus when user not exists', () => {
|
|
||||||
const userStatusFunc = eventFuncMap.get('user status')
|
|
||||||
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
|
||||||
realtime.notes[noteId] = {}
|
|
||||||
delete realtime.users[clientSocket.id]
|
|
||||||
const userData = {
|
|
||||||
idle: true,
|
|
||||||
type: 'xs'
|
|
||||||
}
|
|
||||||
userStatusFunc(userData)
|
|
||||||
assert(emitUserStatusStub.called === false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not call emitUserStatus when note not exists', () => {
|
|
||||||
const userStatusFunc = eventFuncMap.get('user status')
|
|
||||||
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
|
||||||
realtime.notes = {}
|
|
||||||
realtime.users[clientSocket.id] = {}
|
|
||||||
const userData = {
|
|
||||||
idle: true,
|
|
||||||
type: 'xs'
|
|
||||||
}
|
|
||||||
userStatusFunc(userData)
|
|
||||||
assert(emitUserStatusStub.called === false)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('disconnect', function () {
|
|
||||||
it('should push socket to disconnect queue and call disconnect function', () => {
|
|
||||||
const disconnectFunc = eventFuncMap.get('disconnect')
|
|
||||||
const disconnectStub = sinon.stub(realtime, 'disconnect')
|
|
||||||
disconnectFunc()
|
|
||||||
assert(realtime.disconnectSocketQueue.length === 1)
|
|
||||||
assert(disconnectStub.calledOnce)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should quick return when socket is in disconnect queue', () => {
|
|
||||||
const disconnectFunc = eventFuncMap.get('disconnect')
|
|
||||||
const disconnectStub = sinon.stub(realtime, 'disconnect')
|
|
||||||
realtime.disconnectSocketQueue.push(clientSocket)
|
|
||||||
disconnectFunc()
|
|
||||||
assert(disconnectStub.called === false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
;['cursor focus', 'cursor activity', 'cursor blur'].forEach((event) => {
|
|
||||||
describe(event, function () {
|
|
||||||
let cursorFocusFunc
|
|
||||||
|
|
||||||
const cursorData = {
|
|
||||||
cursor: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cursorFocusFunc = eventFuncMap.get(event)
|
|
||||||
realtime.notes[noteId] = {}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should broadcast to all client', () => {
|
|
||||||
cursorFocusFunc(cursorData)
|
|
||||||
const broadChannelEmitFake = clientSocket.broadcast.to(noteId).emit
|
|
||||||
assert(broadChannelEmitFake.calledOnce)
|
|
||||||
assert(broadChannelEmitFake.lastCall.args[0] === event)
|
|
||||||
if (event === 'cursor blur') {
|
|
||||||
assert(broadChannelEmitFake.lastCall.args[1].id === clientSocket.id)
|
|
||||||
} else {
|
|
||||||
assert.deepStrictEqual(broadChannelEmitFake.lastCall.args[1].cursor, cursorData)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not broadcast when note not exists', () => {
|
|
||||||
delete realtime.notes[noteId]
|
|
||||||
cursorFocusFunc(cursorData)
|
|
||||||
const broadChannelEmitFake = clientSocket.broadcast.to(noteId).emit
|
|
||||||
assert(broadChannelEmitFake.called === false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not broadcast when user not exists', () => {
|
|
||||||
delete realtime.users[clientSocket.id]
|
|
||||||
cursorFocusFunc(cursorData)
|
|
||||||
const broadChannelEmitFake = clientSocket.broadcast.to(noteId).emit
|
|
||||||
assert(broadChannelEmitFake.called === false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('version', function () {
|
|
||||||
it('should emit server version ', () => {
|
|
||||||
const versionFunc = eventFuncMap.get('version')
|
|
||||||
versionFunc()
|
|
||||||
assert(clientSocket.emit.called)
|
|
||||||
assert(clientSocket.emit.lastCall.args[0], 'version')
|
|
||||||
assert.deepStrictEqual(clientSocket.emit.lastCall.args[1], {
|
|
||||||
version: '1.5.0',
|
|
||||||
minimumCompatibleVersion: '1.0.0'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('online users', function () {
|
|
||||||
it('should return online user list', () => {
|
|
||||||
const onlineUsersFunc = eventFuncMap.get('online users')
|
|
||||||
realtime.notes[noteId] = {
|
|
||||||
users: {
|
|
||||||
10: {
|
|
||||||
id: 10
|
|
||||||
},
|
|
||||||
20: {
|
|
||||||
id: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onlineUsersFunc()
|
|
||||||
assert(clientSocket.emit.called)
|
|
||||||
assert(clientSocket.emit.lastCall.args[0] === 'online users')
|
|
||||||
let returnUserList = clientSocket.emit.lastCall.args[1].users
|
|
||||||
assert(returnUserList.length === 2)
|
|
||||||
assert(returnUserList[0].id === 10)
|
|
||||||
assert(returnUserList[1].id === 20)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not return user list when note not exists', () => {
|
|
||||||
const onlineUsersFunc = eventFuncMap.get('online users')
|
|
||||||
onlineUsersFunc()
|
|
||||||
assert(clientSocket.emit.called === false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('user changed', function () {
|
|
||||||
it('should call updateUserData', () => {
|
|
||||||
const userChangedFunc = eventFuncMap.get('user changed')
|
|
||||||
realtime.notes[noteId] = {
|
|
||||||
users: {
|
|
||||||
[clientSocket.id]: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
|
||||||
const emitOnlineUsersStub = sinon.stub(realtime, 'emitOnlineUsers')
|
|
||||||
userChangedFunc()
|
|
||||||
assert(updateUserDataStub.calledOnce)
|
|
||||||
assert(emitOnlineUsersStub.calledOnce)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should direct return when note not exists', () => {
|
|
||||||
const userChangedFunc = eventFuncMap.get('user changed')
|
|
||||||
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
|
||||||
const emitOnlineUsersStub = sinon.stub(realtime, 'emitOnlineUsers')
|
|
||||||
userChangedFunc()
|
|
||||||
assert(updateUserDataStub.called === false)
|
|
||||||
assert(emitOnlineUsersStub.called === false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should direct return when note not exists', () => {
|
|
||||||
const userChangedFunc = eventFuncMap.get('user changed')
|
|
||||||
realtime.notes[noteId] = {
|
|
||||||
users: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete realtime.users[clientSocket.id]
|
|
||||||
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
|
||||||
const emitOnlineUsersStub = sinon.stub(realtime, 'emitOnlineUsers')
|
|
||||||
userChangedFunc()
|
|
||||||
assert(updateUserDataStub.called === false)
|
|
||||||
assert(emitOnlineUsersStub.called === false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('permission', function () {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* eslint-env node, mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const mock = require('mock-require')
|
||||||
|
const assert = require('assert')
|
||||||
|
|
||||||
|
const { makeMockSocket } = require('./utils')
|
||||||
|
|
||||||
|
describe('realtime#extractNoteIdFromSocket', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/logger', {})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete require.cache[require.resolve('../../lib/realtime')]
|
||||||
|
mock.stopAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('urlPath not set', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/config', {})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
})
|
||||||
|
|
||||||
|
let realtime
|
||||||
|
|
||||||
|
it('return false if socket or socket.handshake not exists', function () {
|
||||||
|
let noteId = realtime.extractNoteIdFromSocket()
|
||||||
|
assert.strictEqual(false, noteId)
|
||||||
|
|
||||||
|
noteId = realtime.extractNoteIdFromSocket({})
|
||||||
|
assert.strictEqual(false, noteId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('return false if query not set and referer not set', function () {
|
||||||
|
let noteId = realtime.extractNoteIdFromSocket(makeMockSocket({
|
||||||
|
otherHeader: 1
|
||||||
|
}, {
|
||||||
|
otherQuery: 1
|
||||||
|
}))
|
||||||
|
assert.strictEqual(false, noteId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('return noteId from query', function () {
|
||||||
|
// Arrange
|
||||||
|
const incomingNoteId = 'myNoteId'
|
||||||
|
const incomingSocket = makeMockSocket(undefined, { noteId: incomingNoteId })
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const noteId = realtime.extractNoteIdFromSocket(incomingSocket)
|
||||||
|
// Assert
|
||||||
|
assert.strictEqual(noteId, incomingNoteId)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('return noteId from old method (referer)', function () {
|
||||||
|
// Arrange
|
||||||
|
const incomingNoteId = 'myNoteId'
|
||||||
|
const incomingSocket = makeMockSocket({
|
||||||
|
referer: `https://localhost:3000/${incomingNoteId}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const noteId = realtime.extractNoteIdFromSocket(incomingSocket)
|
||||||
|
// Assert
|
||||||
|
assert.strictEqual(noteId, incomingNoteId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('urlPath is set', function () {
|
||||||
|
let realtime
|
||||||
|
it('return noteId from old method (referer) and urlPath set', function () {
|
||||||
|
// Arrange
|
||||||
|
const urlPath = 'hello'
|
||||||
|
mock('../../lib/config', {
|
||||||
|
urlPath: urlPath
|
||||||
|
})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const incomingNoteId = 'myNoteId'
|
||||||
|
const incomingSocket = makeMockSocket({
|
||||||
|
referer: `https://localhost:3000/${urlPath}/${incomingNoteId}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const noteId = realtime.extractNoteIdFromSocket(incomingSocket)
|
||||||
|
// Assert
|
||||||
|
assert.strictEqual(noteId, incomingNoteId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,112 @@
|
||||||
|
/* eslint-env node, mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const assert = require('assert')
|
||||||
|
const mock = require('mock-require')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
const { makeMockSocket, removeModuleFromRequireCache } = require('./utils')
|
||||||
|
|
||||||
|
describe('realtime#parseNoteIdFromSocket', function () {
|
||||||
|
let realtime
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/logger', {})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
parseNoteId: function (noteId, callback) {
|
||||||
|
callback(null, noteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/config', {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
removeModuleFromRequireCache('../../lib/realtime')
|
||||||
|
mock.stopAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return null when socket not send noteId', function () {
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const mockSocket = makeMockSocket()
|
||||||
|
const fakeCallback = sinon.fake()
|
||||||
|
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
||||||
|
assert(fakeCallback.called)
|
||||||
|
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, null])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('noteId exists', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
parseNoteId: function (noteId, callback) {
|
||||||
|
callback(null, noteId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should return noteId when noteId exists', function () {
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const noteId = '123456'
|
||||||
|
const mockSocket = makeMockSocket(undefined, {
|
||||||
|
noteId: noteId
|
||||||
|
})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const fakeCallback = sinon.fake()
|
||||||
|
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
||||||
|
assert(fakeCallback.called)
|
||||||
|
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, noteId])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('noteId not exists', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
parseNoteId: function (noteId, callback) {
|
||||||
|
callback(null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should return null when noteId not exists', function () {
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const noteId = '123456'
|
||||||
|
const mockSocket = makeMockSocket(undefined, {
|
||||||
|
noteId: noteId
|
||||||
|
})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const fakeCallback = sinon.fake()
|
||||||
|
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
||||||
|
assert(fakeCallback.called)
|
||||||
|
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, null])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('parse note error', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
parseNoteId: function (noteId, callback) {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
callback('error', null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should return error when noteId parse error', function () {
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const noteId = '123456'
|
||||||
|
const mockSocket = makeMockSocket(undefined, {
|
||||||
|
noteId: noteId
|
||||||
|
})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
const fakeCallback = sinon.fake()
|
||||||
|
realtime.parseNoteIdFromSocket(mockSocket, fakeCallback)
|
||||||
|
assert(fakeCallback.called)
|
||||||
|
assert.deepStrictEqual(fakeCallback.getCall(0).args, ['error', null])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,261 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* eslint-env node, mocha */
|
||||||
|
|
||||||
|
const mock = require('mock-require')
|
||||||
|
const assert = require('assert')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
function makeMockSocket (headers, query) {
|
||||||
|
const broadCastChannelCache = {}
|
||||||
|
return {
|
||||||
|
id: Math.round(Math.random() * 10000),
|
||||||
|
handshake: {
|
||||||
|
headers: Object.assign({}, headers),
|
||||||
|
query: Object.assign({}, query)
|
||||||
|
},
|
||||||
|
on: sinon.fake(),
|
||||||
|
emit: sinon.fake(),
|
||||||
|
broadCastChannelCache: {},
|
||||||
|
broadcast: {
|
||||||
|
to: (channel) => {
|
||||||
|
if (!broadCastChannelCache[channel]) {
|
||||||
|
broadCastChannelCache[channel] = {
|
||||||
|
channel: channel,
|
||||||
|
emit: sinon.fake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return broadCastChannelCache[channel]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disconnect: sinon.fake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeModuleFromRequireCache (modulePath) {
|
||||||
|
delete require.cache[require.resolve(modulePath)]
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('realtime', function () {
|
||||||
|
|
||||||
|
describe('update note is dirty timer', function () {
|
||||||
|
let realtime
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/logger', {
|
||||||
|
error: () => {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Revision: {
|
||||||
|
saveAllNotesRevision: () => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/config', {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
removeModuleFromRequireCache('../../lib/realtime')
|
||||||
|
mock.stopAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update note when note is dirty', (done) => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
sinon.stub(realtime, 'updateNote').callsFake(function (note, callback) {
|
||||||
|
callback(null, null)
|
||||||
|
})
|
||||||
|
const socketIoEmitFake = sinon.fake()
|
||||||
|
realtime.io = {
|
||||||
|
to: sinon.stub().callsFake(function () {
|
||||||
|
return {
|
||||||
|
emit: socketIoEmitFake
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
realtime.notes['note1'] = {
|
||||||
|
server: {
|
||||||
|
isDirty: false
|
||||||
|
},
|
||||||
|
socks: []
|
||||||
|
}
|
||||||
|
let note2 = {
|
||||||
|
server: {
|
||||||
|
isDirty: true
|
||||||
|
},
|
||||||
|
socks: []
|
||||||
|
}
|
||||||
|
realtime.notes['note2'] = note2
|
||||||
|
|
||||||
|
clock.tick(1000)
|
||||||
|
clock.restore()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
assert(note2.server.isDirty === false)
|
||||||
|
done()
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateNote', function () {
|
||||||
|
let realtime, fakeNote
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/logger', {
|
||||||
|
error: () => {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
findOne: async function () {
|
||||||
|
return fakeNote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/config', {})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mock.stopAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return null when note not found', function (done) {
|
||||||
|
fakeNote = null
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
|
||||||
|
sinon.stub(realtime, 'finishUpdateNote').callsFake(function (a, b, callback) {
|
||||||
|
callback(null, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
const fakeCallback = sinon.fake()
|
||||||
|
realtime.updateNote({ id: '123' }, fakeCallback)
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.ok(fakeCallback.called)
|
||||||
|
assert.deepStrictEqual(fakeCallback.getCall(0).args, [null, null])
|
||||||
|
sinon.restore()
|
||||||
|
done()
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('finishUpdateNote', function () {
|
||||||
|
let realtime
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/logger', {})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
parseNoteTitle: (data) => (data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/config', {})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
removeModuleFromRequireCache('../../lib/realtime')
|
||||||
|
mock.stopAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('return null when note is null', () => {
|
||||||
|
const fakeCallback = sinon.fake()
|
||||||
|
|
||||||
|
realtime.finishUpdateNote(null, {}, fakeCallback)
|
||||||
|
|
||||||
|
assert.ok(fakeCallback.calledOnce)
|
||||||
|
assert.deepStrictEqual(fakeCallback.lastCall.args, [null, null])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('connection', function () {
|
||||||
|
let realtime
|
||||||
|
beforeEach(() => {
|
||||||
|
mock('../../lib/logger', {
|
||||||
|
error: () => {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', {
|
||||||
|
Note: {
|
||||||
|
parseNoteTitle: (data) => (data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/config', {})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
removeModuleFromRequireCache('../../lib/realtime')
|
||||||
|
mock.stopAll()
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('fail', function () {
|
||||||
|
it('should fast return when server not start', () => {
|
||||||
|
const mockSocket = makeMockSocket()
|
||||||
|
realtime.maintenance = true
|
||||||
|
const spy = sinon.spy(realtime, 'parseNoteIdFromSocket')
|
||||||
|
realtime.connection(mockSocket)
|
||||||
|
assert(!spy.called)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should failed when parse noteId occur error', () => {
|
||||||
|
const mockSocket = makeMockSocket()
|
||||||
|
realtime.maintenance = false
|
||||||
|
const parseNoteIdFromSocketSpy = sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
callback('error', null)
|
||||||
|
})
|
||||||
|
|
||||||
|
const failConnectionSpy = sinon.stub(realtime, 'failConnection')
|
||||||
|
|
||||||
|
realtime.connection(mockSocket)
|
||||||
|
|
||||||
|
assert(parseNoteIdFromSocketSpy.called)
|
||||||
|
assert(failConnectionSpy.calledOnce)
|
||||||
|
assert.deepStrictEqual(failConnectionSpy.lastCall.args, [500, 'error', mockSocket])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should failed when noteId not exists', () => {
|
||||||
|
const mockSocket = makeMockSocket()
|
||||||
|
realtime.maintenance = false
|
||||||
|
const parseNoteIdFromSocketSpy = sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
callback(null, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
const failConnectionSpy = sinon.stub(realtime, 'failConnection')
|
||||||
|
|
||||||
|
realtime.connection(mockSocket)
|
||||||
|
|
||||||
|
assert(parseNoteIdFromSocketSpy.called)
|
||||||
|
assert(failConnectionSpy.calledOnce)
|
||||||
|
assert.deepStrictEqual(failConnectionSpy.lastCall.args, [404, 'note id not found', mockSocket])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should success connect', function () {
|
||||||
|
const mockSocket = makeMockSocket()
|
||||||
|
const noteId = 'note123'
|
||||||
|
realtime.maintenance = false
|
||||||
|
const parseNoteIdFromSocketSpy = sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
callback(null, noteId)
|
||||||
|
})
|
||||||
|
const failConnectionStub = sinon.stub(realtime, 'failConnection')
|
||||||
|
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
||||||
|
const startConnectionStub = sinon.stub(realtime, 'startConnection')
|
||||||
|
|
||||||
|
realtime.connection(mockSocket)
|
||||||
|
|
||||||
|
assert.ok(parseNoteIdFromSocketSpy.calledOnce)
|
||||||
|
|
||||||
|
assert(failConnectionStub.called === false)
|
||||||
|
assert(updateUserDataStub.calledOnce)
|
||||||
|
assert(startConnectionStub.calledOnce)
|
||||||
|
assert(mockSocket.on.callCount === 11)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -0,0 +1,270 @@
|
||||||
|
/* eslint-env node, mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const assert = require('assert')
|
||||||
|
const mock = require('mock-require')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
const { makeMockSocket, removeModuleFromRequireCache } = require('./utils')
|
||||||
|
|
||||||
|
describe('realtime#socket event', function () {
|
||||||
|
const noteId = 'note123'
|
||||||
|
let realtime
|
||||||
|
let clientSocket
|
||||||
|
let modelsMock
|
||||||
|
let eventFuncMap
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
eventFuncMap = new Map()
|
||||||
|
modelsMock = {
|
||||||
|
Note: {
|
||||||
|
parseNoteTitle: (data) => (data),
|
||||||
|
destroy: sinon.stub().returns(Promise.resolve(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mock('../../lib/logger', {
|
||||||
|
error: () => {
|
||||||
|
},
|
||||||
|
info: () => {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mock('../../lib/history', {})
|
||||||
|
mock('../../lib/models', modelsMock)
|
||||||
|
mock('../../lib/config', {
|
||||||
|
fullversion: '1.5.0',
|
||||||
|
minimumCompatibleVersion: '1.0.0'
|
||||||
|
})
|
||||||
|
realtime = require('../../lib/realtime')
|
||||||
|
|
||||||
|
// get all socket event handler
|
||||||
|
clientSocket = makeMockSocket()
|
||||||
|
clientSocket.on = function (event, func) {
|
||||||
|
eventFuncMap.set(event, func)
|
||||||
|
}
|
||||||
|
realtime.maintenance = false
|
||||||
|
sinon.stub(realtime, 'parseNoteIdFromSocket').callsFake((socket, callback) => {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
callback(null, noteId)
|
||||||
|
})
|
||||||
|
const wrappedFuncs = []
|
||||||
|
wrappedFuncs.push(sinon.stub(realtime, 'failConnection'))
|
||||||
|
wrappedFuncs.push(sinon.stub(realtime, 'updateUserData'))
|
||||||
|
wrappedFuncs.push(sinon.stub(realtime, 'startConnection'))
|
||||||
|
realtime.connection(clientSocket)
|
||||||
|
|
||||||
|
wrappedFuncs.forEach((wrappedFunc) => {
|
||||||
|
wrappedFunc.restore()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
removeModuleFromRequireCache('../../lib/realtime')
|
||||||
|
mock.stopAll()
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('refresh', function () {
|
||||||
|
it('should call refresh', () => {
|
||||||
|
const refreshFunc = eventFuncMap.get('refresh')
|
||||||
|
const emitRefreshStub = sinon.stub(realtime, 'emitRefresh')
|
||||||
|
refreshFunc()
|
||||||
|
assert(emitRefreshStub.calledOnce)
|
||||||
|
assert.deepStrictEqual(emitRefreshStub.lastCall.args[0], clientSocket)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user status', function () {
|
||||||
|
it('should call emitUserStatus and update user data', () => {
|
||||||
|
const userStatusFunc = eventFuncMap.get('user status')
|
||||||
|
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
||||||
|
realtime.notes[noteId] = {}
|
||||||
|
|
||||||
|
const userData = {
|
||||||
|
idle: true,
|
||||||
|
type: 'xs'
|
||||||
|
}
|
||||||
|
userStatusFunc(userData)
|
||||||
|
assert(emitUserStatusStub.calledOnce)
|
||||||
|
assert.deepStrictEqual(emitUserStatusStub.lastCall.args[0], clientSocket)
|
||||||
|
assert(realtime.users[clientSocket.id].idle === true)
|
||||||
|
assert(realtime.users[clientSocket.id].type === 'xs')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should call emitUserStatus without userdata', () => {
|
||||||
|
const userStatusFunc = eventFuncMap.get('user status')
|
||||||
|
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
||||||
|
realtime.notes[noteId] = {}
|
||||||
|
userStatusFunc()
|
||||||
|
assert(emitUserStatusStub.calledOnce)
|
||||||
|
assert.deepStrictEqual(emitUserStatusStub.lastCall.args[0], clientSocket)
|
||||||
|
assert(realtime.users[clientSocket.id].idle === false)
|
||||||
|
assert(realtime.users[clientSocket.id].type === null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not call emitUserStatus when user not exists', () => {
|
||||||
|
const userStatusFunc = eventFuncMap.get('user status')
|
||||||
|
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
||||||
|
realtime.notes[noteId] = {}
|
||||||
|
delete realtime.users[clientSocket.id]
|
||||||
|
const userData = {
|
||||||
|
idle: true,
|
||||||
|
type: 'xs'
|
||||||
|
}
|
||||||
|
userStatusFunc(userData)
|
||||||
|
assert(emitUserStatusStub.called === false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not call emitUserStatus when note not exists', () => {
|
||||||
|
const userStatusFunc = eventFuncMap.get('user status')
|
||||||
|
const emitUserStatusStub = sinon.stub(realtime, 'emitUserStatus')
|
||||||
|
realtime.notes = {}
|
||||||
|
realtime.users[clientSocket.id] = {}
|
||||||
|
const userData = {
|
||||||
|
idle: true,
|
||||||
|
type: 'xs'
|
||||||
|
}
|
||||||
|
userStatusFunc(userData)
|
||||||
|
assert(emitUserStatusStub.called === false)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('disconnect', function () {
|
||||||
|
it('should push socket to disconnect queue and call disconnect function', () => {
|
||||||
|
const disconnectFunc = eventFuncMap.get('disconnect')
|
||||||
|
const disconnectStub = sinon.stub(realtime, 'disconnect')
|
||||||
|
disconnectFunc()
|
||||||
|
assert(realtime.disconnectSocketQueue.length === 1)
|
||||||
|
assert(disconnectStub.calledOnce)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should quick return when socket is in disconnect queue', () => {
|
||||||
|
const disconnectFunc = eventFuncMap.get('disconnect')
|
||||||
|
const disconnectStub = sinon.stub(realtime, 'disconnect')
|
||||||
|
realtime.disconnectSocketQueue.push(clientSocket)
|
||||||
|
disconnectFunc()
|
||||||
|
assert(disconnectStub.called === false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
;['cursor focus', 'cursor activity', 'cursor blur'].forEach((event) => {
|
||||||
|
describe(event, function () {
|
||||||
|
let cursorFocusFunc
|
||||||
|
|
||||||
|
const cursorData = {
|
||||||
|
cursor: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cursorFocusFunc = eventFuncMap.get(event)
|
||||||
|
realtime.notes[noteId] = {}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should broadcast to all client', () => {
|
||||||
|
cursorFocusFunc(cursorData)
|
||||||
|
const broadChannelEmitFake = clientSocket.broadcast.to(noteId).emit
|
||||||
|
assert(broadChannelEmitFake.calledOnce)
|
||||||
|
assert(broadChannelEmitFake.lastCall.args[0] === event)
|
||||||
|
if (event === 'cursor blur') {
|
||||||
|
assert(broadChannelEmitFake.lastCall.args[1].id === clientSocket.id)
|
||||||
|
} else {
|
||||||
|
assert.deepStrictEqual(broadChannelEmitFake.lastCall.args[1].cursor, cursorData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not broadcast when note not exists', () => {
|
||||||
|
delete realtime.notes[noteId]
|
||||||
|
cursorFocusFunc(cursorData)
|
||||||
|
const broadChannelEmitFake = clientSocket.broadcast.to(noteId).emit
|
||||||
|
assert(broadChannelEmitFake.called === false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not broadcast when user not exists', () => {
|
||||||
|
delete realtime.users[clientSocket.id]
|
||||||
|
cursorFocusFunc(cursorData)
|
||||||
|
const broadChannelEmitFake = clientSocket.broadcast.to(noteId).emit
|
||||||
|
assert(broadChannelEmitFake.called === false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('version', function () {
|
||||||
|
it('should emit server version ', () => {
|
||||||
|
const versionFunc = eventFuncMap.get('version')
|
||||||
|
versionFunc()
|
||||||
|
assert(clientSocket.emit.called)
|
||||||
|
assert(clientSocket.emit.lastCall.args[0], 'version')
|
||||||
|
assert.deepStrictEqual(clientSocket.emit.lastCall.args[1], {
|
||||||
|
version: '1.5.0',
|
||||||
|
minimumCompatibleVersion: '1.0.0'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('online users', function () {
|
||||||
|
it('should return online user list', function () {
|
||||||
|
const onlineUsersFunc = eventFuncMap.get('online users')
|
||||||
|
realtime.notes[noteId] = {
|
||||||
|
users: {
|
||||||
|
10: {
|
||||||
|
id: 10
|
||||||
|
},
|
||||||
|
20: {
|
||||||
|
id: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onlineUsersFunc()
|
||||||
|
assert(clientSocket.emit.called)
|
||||||
|
assert(clientSocket.emit.lastCall.args[0] === 'online users')
|
||||||
|
let returnUserList = clientSocket.emit.lastCall.args[1].users
|
||||||
|
assert(returnUserList.length === 2)
|
||||||
|
assert(returnUserList[0].id === 10)
|
||||||
|
assert(returnUserList[1].id === 20)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not return user list when note not exists', function () {
|
||||||
|
const onlineUsersFunc = eventFuncMap.get('online users')
|
||||||
|
onlineUsersFunc()
|
||||||
|
assert(clientSocket.emit.called === false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user changed', function () {
|
||||||
|
it('should call updateUserData', () => {
|
||||||
|
const userChangedFunc = eventFuncMap.get('user changed')
|
||||||
|
realtime.notes[noteId] = {
|
||||||
|
users: {
|
||||||
|
[clientSocket.id]: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
||||||
|
const emitOnlineUsersStub = sinon.stub(realtime, 'emitOnlineUsers')
|
||||||
|
userChangedFunc()
|
||||||
|
assert(updateUserDataStub.calledOnce)
|
||||||
|
assert(emitOnlineUsersStub.calledOnce)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should direct return when note not exists', function () {
|
||||||
|
const userChangedFunc = eventFuncMap.get('user changed')
|
||||||
|
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
||||||
|
const emitOnlineUsersStub = sinon.stub(realtime, 'emitOnlineUsers')
|
||||||
|
userChangedFunc()
|
||||||
|
assert(updateUserDataStub.called === false)
|
||||||
|
assert(emitOnlineUsersStub.called === false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should direct return when note not exists', function () {
|
||||||
|
const userChangedFunc = eventFuncMap.get('user changed')
|
||||||
|
realtime.notes[noteId] = {
|
||||||
|
users: {}
|
||||||
|
}
|
||||||
|
delete realtime.users[clientSocket.id]
|
||||||
|
const updateUserDataStub = sinon.stub(realtime, 'updateUserData')
|
||||||
|
const emitOnlineUsersStub = sinon.stub(realtime, 'emitOnlineUsers')
|
||||||
|
userChangedFunc()
|
||||||
|
assert(updateUserDataStub.called === false)
|
||||||
|
assert(emitOnlineUsersStub.called === false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,36 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
function makeMockSocket (headers, query) {
|
||||||
|
const broadCastChannelCache = {}
|
||||||
|
return {
|
||||||
|
id: Math.round(Math.random() * 10000),
|
||||||
|
handshake: {
|
||||||
|
headers: Object.assign({}, headers),
|
||||||
|
query: Object.assign({}, query)
|
||||||
|
},
|
||||||
|
on: sinon.fake(),
|
||||||
|
emit: sinon.fake(),
|
||||||
|
broadCastChannelCache: {},
|
||||||
|
broadcast: {
|
||||||
|
to: (channel) => {
|
||||||
|
if (!broadCastChannelCache[channel]) {
|
||||||
|
broadCastChannelCache[channel] = {
|
||||||
|
channel: channel,
|
||||||
|
emit: sinon.fake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return broadCastChannelCache[channel]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disconnect: sinon.fake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeModuleFromRequireCache (modulePath) {
|
||||||
|
delete require.cache[require.resolve(modulePath)]
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.makeMockSocket = makeMockSocket
|
||||||
|
exports.removeModuleFromRequireCache = removeModuleFromRequireCache
|
Loading…
Reference in New Issue