convert TpdsUpdateSender to async and cleanup

GitOrigin-RevId: a19830134a13a23775c604a05a7e093d00bbb2de
This commit is contained in:
Ersun Warncke 2020-05-19 17:17:45 -04:00 committed by Copybot
parent 419100d167
commit 9c87ad9801
4 changed files with 349 additions and 463 deletions

View file

@ -1194,13 +1194,18 @@ const ProjectEntityUpdateHandler = {
project.overleaf &&
project.overleaf.history &&
project.overleaf.history.id
TpdsUpdateSender.moveEntity({
// do not wait
TpdsUpdateSender.promises
.moveEntity({
project_id: projectId,
project_name: project.name,
startPath,
endPath,
rev
})
.catch(err => {
logger.error({ err }, 'error sending tpds update')
})
DocumentUpdaterHandler.updateProjectStructure(
projectId,
projectHistoryId,
@ -1243,13 +1248,18 @@ const ProjectEntityUpdateHandler = {
project.overleaf &&
project.overleaf.history &&
project.overleaf.history.id
TpdsUpdateSender.moveEntity({
// do not wait
TpdsUpdateSender.promises
.moveEntity({
project_id: projectId,
project_name: project.name,
startPath,
endPath,
rev
})
.catch(err => {
logger.error({ err }, 'error sending tpds update')
})
DocumentUpdaterHandler.updateProjectStructure(
projectId,
projectHistoryId,

View file

@ -1,306 +1,187 @@
/* eslint-disable
camelcase,
handle-callback-err,
max-len,
no-irregular-whitespace,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let tpdsUrl
const settings = require('settings-sharelatex')
const _ = require('lodash')
const { callbackify } = require('util')
const logger = require('logger-sharelatex')
const path = require('path')
const ProjectGetter = require('../Project/ProjectGetter')
const keys = require('../../infrastructure/Keys')
const metrics = require('metrics-sharelatex')
const request = require('request')
const path = require('path')
const request = require('request-promise-native')
const settings = require('settings-sharelatex')
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
const { promisifyAll } = require('../../util/promises')
.promises
const buildPath = function(user_id, project_name, filePath) {
let projectPath = path.join(project_name, '/', filePath)
projectPath = encodeURIComponent(projectPath)
const fullPath = path.join('/user/', `${user_id}`, '/entity/', projectPath)
return fullPath
}
const tpdsUrl = _.get(settings, ['apis', 'thirdPartyDataStore', 'url'])
const tpdsworkerEnabled = () =>
(settings.apis.tpdsworker != null
? settings.apis.tpdsworker.url
: undefined) != null
if (!tpdsworkerEnabled()) {
logger.log('tpdsworker is not enabled, request will not be sent to it')
}
async function addDoc(options) {
metrics.inc('tpds.add-doc')
if (settings.apis.thirdPartyDataStore.linode_url != null) {
tpdsUrl = settings.apis.thirdPartyDataStore.linode_url
} else {
tpdsUrl = settings.apis.thirdPartyDataStore.url
}
const TpdsUpdateSender = {
_enqueue(group, method, job, callback) {
if (!tpdsworkerEnabled()) {
return callback()
}
const opts = {
uri: `${settings.apis.tpdsworker.url}/enqueue/web_to_tpds_http_requests`,
json: {
group,
method,
job
},
method: 'post',
timeout: 5 * 1000
}
return request(opts, function(err) {
if (err != null) {
logger.err(
{ err },
'error queuing something in the tpdsworker, continuing anyway'
options.streamOrigin =
settings.apis.docstore.pubUrl +
path.join(
`/project/${options.project_id}`,
`/doc/${options.doc_id}`,
'/raw'
)
return callback()
} else {
return callback()
}
})
},
_addEntity(options, callback) {
if (callback == null) {
callback = function(err) {}
return addEntity(options)
}
async function addEntity(options) {
const projectUserIds = await getProjectUsersIds(options.project_id)
if (!projectUserIds.length) {
return
}
return getProjectsUsersIds(options.project_id, function(
err,
user_id,
allUserIds
) {
if (err != null) {
logger.warn({ err, options }, 'error getting projects user ids')
return callback(err)
}
logger.log(
{
project_id: options.project_id,
user_id,
path: options.path,
uri: options.uri,
rev: options.rev
},
'sending file to third party data store'
)
const postOptions = {
const job = {
method: 'post',
headers: {
sl_entity_rev: options.rev,
sl_project_id: options.project_id,
sl_all_user_ids: JSON.stringify(allUserIds)
sl_all_user_ids: JSON.stringify(projectUserIds)
},
uri: `${tpdsUrl}${buildPath(
user_id,
options.project_name,
options.path
)}`,
uri: buildTpdsUrl(projectUserIds[0], options.project_name, options.path),
title: 'addFile',
streamOrigin: options.streamOrigin
}
return TpdsUpdateSender._enqueue(
options.project_id,
'pipeStreamFrom',
postOptions,
function(err) {
if (err != null) {
logger.warn(
{
err,
project_id: options.project_id,
user_id,
path: options.path,
uri: options.uri,
rev: options.rev
},
'error sending file to third party data store queued up for processing'
)
return callback(err)
}
logger.log(
{
project_id: options.project_id,
user_id,
path: options.path,
uri: options.uri,
rev: options.rev
},
'sending file to third party data store queued up for processing'
)
return callback(err)
}
)
})
},
addFile(options, callback) {
if (callback == null) {
callback = function(err) {}
}
return enqueue(options.project_id, 'pipeStreamFrom', job)
}
async function addFile(options) {
metrics.inc('tpds.add-file')
options.streamOrigin =
(settings.apis.filestore.linode_url || settings.apis.filestore.url) +
path.join(`/project/${options.project_id}/file/`, `${options.file_id}`)
return this._addEntity(options, callback)
},
addDoc(options, callback) {
if (callback == null) {
callback = function(err) {}
}
metrics.inc('tpds.add-doc')
options.streamOrigin =
(settings.apis.docstore.linode_url || settings.apis.docstore.pubUrl) +
path.join(`/project/${options.project_id}/doc/`, `${options.doc_id}/raw`)
return this._addEntity(options, callback)
},
moveEntity(options, callback) {
let endPath, startPath
if (callback == null) {
callback = function(err) {}
settings.apis.filestore.url +
path.join(`/project/${options.project_id}`, `/file/${options.file_id}`)
return addEntity(options)
}
function buildMovePaths(options) {
if (options.newProjectName) {
return {
startPath: path.join('/', options.project_name, '/'),
endPath: path.join('/', options.newProjectName, '/')
}
metrics.inc('tpds.move-entity')
if (options.newProjectName != null) {
startPath = path.join(`/${options.project_name}/`)
endPath = path.join(`/${options.newProjectName}/`)
} else {
startPath = mergeProjectNameAndPath(
options.project_name,
options.startPath
)
endPath = mergeProjectNameAndPath(options.project_name, options.endPath)
return {
startPath: path.join('/', options.project_name, '/', options.startPath),
endPath: path.join('/', options.project_name, '/', options.endPath)
}
return getProjectsUsersIds(options.project_id, function(
err,
user_id,
allUserIds
) {
const moveOptions = {
}
}
function buildTpdsUrl(userId, projectName, filePath) {
const projectPath = encodeURIComponent(path.join(projectName, '/', filePath))
return `${tpdsUrl}/user/${userId}/entity/${projectPath}`
}
async function deleteEntity(options) {
metrics.inc('tpds.delete-entity')
const projectUserIds = await getProjectUsersIds(options.project_id)
if (!projectUserIds.length) {
return
}
const job = {
method: 'delete',
headers: {
sl_project_id: options.project_id,
sl_all_user_ids: JSON.stringify(projectUserIds)
},
uri: buildTpdsUrl(projectUserIds[0], options.project_name, options.path),
title: 'deleteEntity',
sl_all_user_ids: JSON.stringify(projectUserIds)
}
return enqueue(options.project_id, 'standardHttpRequest', job)
}
async function enqueue(group, method, job) {
const tpdsWorkerUrl = _.get(settings, ['apis', 'tpdsworker', 'url'])
// silently do nothing if worker url is not in settings
if (!tpdsWorkerUrl) {
return
}
try {
return request({
uri: `${tpdsWorkerUrl}/enqueue/web_to_tpds_http_requests`,
json: { group, job, method },
method: 'post',
timeout: 5 * 1000
})
} catch (err) {
// log error and continue
logger.error({ err, group, job, method }, 'error enqueueing tpdsworker job')
}
}
async function getProjectUsersIds(projectId) {
// get list of all user ids with access to project. project owner
// will always be the first entry in the list.
// TODO: filter this list to only return users with dropbox linked
return CollaboratorsGetter.getInvitedMemberIds(projectId)
}
async function moveEntity(options) {
metrics.inc('tpds.move-entity')
const projectUserIds = await getProjectUsersIds(options.project_id)
if (!projectUserIds.length) {
return
}
const { endPath, startPath } = buildMovePaths(options)
const job = {
method: 'put',
title: 'moveEntity',
uri: `${tpdsUrl}/user/${user_id}/entity`,
uri: `${tpdsUrl}/user/${projectUserIds[0]}/entity`,
headers: {
sl_project_id: options.project_id,
sl_entity_rev: options.rev,
sl_all_user_ids: JSON.stringify(allUserIds)
sl_all_user_ids: JSON.stringify(projectUserIds)
},
json: {
user_id,
user_id: projectUserIds[0],
endPath,
startPath
}
}
return TpdsUpdateSender._enqueue(
options.project_id,
'standardHttpRequest',
moveOptions,
callback
)
})
},
deleteEntity(options, callback) {
if (callback == null) {
callback = function(err) {}
}
metrics.inc('tpds.delete-entity')
return getProjectsUsersIds(options.project_id, function(
err,
user_id,
allUserIds
) {
const deleteOptions = {
method: 'DELETE',
headers: {
sl_project_id: options.project_id,
sl_all_user_ids: JSON.stringify(allUserIds)
},
uri: `${tpdsUrl}${buildPath(
user_id,
options.project_name,
options.path
)}`,
title: 'deleteEntity',
sl_all_user_ids: JSON.stringify(allUserIds)
}
return TpdsUpdateSender._enqueue(
options.project_id,
'standardHttpRequest',
deleteOptions,
callback
)
})
},
return enqueue(options.project_id, 'standardHttpRequest', job)
}
pollDropboxForUser(user_id, callback) {
if (callback == null) {
callback = function(err) {}
}
async function pollDropboxForUser(userId) {
metrics.inc('tpds.poll-dropbox')
const options = {
method: 'POST',
const job = {
method: 'post',
uri: `${tpdsUrl}/user/poll`,
json: {
user_ids: [user_id]
user_ids: [userId]
}
}
return TpdsUpdateSender._enqueue(
`poll-dropbox:${user_id}`,
'standardHttpRequest',
options,
callback
)
return enqueue(`poll-dropbox:${userId}`, 'standardHttpRequest', job)
}
const TpdsUpdateSender = {
addDoc: callbackify(addDoc),
addEntity: callbackify(addEntity),
addFile: callbackify(addFile),
deleteEntity: callbackify(deleteEntity),
enqueue: callbackify(enqueue),
moveEntity: callbackify(moveEntity),
pollDropboxForUser: callbackify(pollDropboxForUser),
promises: {
addDoc,
addEntity,
addFile,
deleteEntity,
enqueue,
moveEntity,
pollDropboxForUser
}
}
var getProjectsUsersIds = function(project_id, callback) {
if (callback == null) {
callback = function(err, owner_id, allUserIds) {}
}
return ProjectGetter.getProject(
project_id,
{ _id: true, owner_ref: true },
function(err, project) {
if (err != null) {
return callback(err)
}
return CollaboratorsGetter.getInvitedMemberIds(project_id, function(
err,
member_ids
) {
if (err != null) {
return callback(err)
}
return callback(
err,
project != null ? project.owner_ref : undefined,
member_ids
)
})
}
)
}
var mergeProjectNameAndPath = function(project_name, path) {
if (path.indexOf('/') === 0) {
path = path.substring(1)
}
const fullPath = `/${project_name}/${path}`
return fullPath
}
TpdsUpdateSender.promises = promisifyAll(TpdsUpdateSender)
module.exports = TpdsUpdateSender

View file

@ -133,7 +133,10 @@ describe('ProjectEntityUpdateHandler', function() {
addFile: sinon.stub().yields(),
addDoc: sinon.stub(),
deleteEntity: sinon.stub().yields(),
moveEntity: sinon.stub()
moveEntity: sinon.stub(),
promises: {
moveEntity: sinon.stub().resolves()
}
}
this.FileStoreHandler = {
copyFile: sinon.stub(),
@ -1672,7 +1675,7 @@ describe('ProjectEntityUpdateHandler', function() {
})
it('notifies tpds', function() {
this.TpdsUpdateSender.moveEntity
this.TpdsUpdateSender.promises.moveEntity
.calledWith({
project_id: projectId,
project_name: this.project_name,
@ -1731,7 +1734,7 @@ describe('ProjectEntityUpdateHandler', function() {
})
it('notifies tpds', function() {
this.TpdsUpdateSender.moveEntity
this.TpdsUpdateSender.promises.moveEntity
.calledWith({
project_id: projectId,
project_name: this.project_name,

View file

@ -1,31 +1,20 @@
/* eslint-disable
camelcase,
handle-callback-err,
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const assert = require('assert')
require('chai').should()
const modulePath = require('path').join(
const chai = require('chai')
const path = require('path')
const sinon = require('sinon')
chai.should()
const modulePath = path.join(
__dirname,
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js'
)
const sinon = require('sinon')
const ath = require('path')
const project_id = 'project_id_here'
const user_id = 'user_id_here'
const read_only_ref_1 = 'read_only_ref_1_id_here'
const collaberator_ref_1 = 'collaberator_ref_1_here'
const project_name = 'project_name_here'
const projectId = 'project_id_here'
const userId = 'user_id_here'
const readOnlyRef = 'read_only_ref_1_id_here'
const collaberatorRef = 'collaberator_ref_1_here'
const projectName = 'project_name_here'
const thirdPartyDataStoreApiUrl = 'http://third-party-json-store.herokuapp.com'
const httpUsername = 'user'
@ -37,16 +26,14 @@ const filestoreUrl = 'filestore.sharelatex.com'
describe('TpdsUpdateSender', function() {
beforeEach(function() {
this.requestQueuer = function(queue, meth, opts, callback) {}
const project = { owner_ref: user_id }
const member_ids = [collaberator_ref_1, read_only_ref_1, user_id]
const memberIds = [userId, collaberatorRef, readOnlyRef]
this.CollaboratorsGetter = {
getInvitedMemberIds: sinon.stub().yields(null, member_ids)
promises: {
getInvitedMemberIds: sinon.stub().resolves(memberIds)
}
this.ProjectGetter = {
getProject: sinon.stub().callsArgWith(2, null, project)
}
this.docstoreUrl = 'docstore.sharelatex.env'
this.request = sinon.stub().returns({ pipe() {} })
this.request = sinon.stub().resolves()
this.settings = {
siteUrl,
httpAuthSiteUrl,
@ -60,182 +47,187 @@ describe('TpdsUpdateSender', function() {
}
}
}
return (this.updateSender = SandboxedModule.require(modulePath, {
this.updateSender = SandboxedModule.require(modulePath, {
globals: {
console: console
},
requires: {
'settings-sharelatex': this.settings,
'logger-sharelatex': { log() {} },
'../Project/ProjectGetter': this.ProjectGetter,
request: this.request,
'request-promise-native': this.request,
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
'metrics-sharelatex': {
inc() {}
}
}
}))
})
describe('_enqueue', function() {
it('should not call request if there is no tpdsworker url', function(done) {
return this.updateSender._enqueue(null, null, null, err => {
this.request.called.should.equal(false)
return done()
})
})
it('should post the message to the tpdsworker', function(done) {
describe('enqueue', function() {
it('should not call request if there is no tpdsworker url', async function() {
await this.updateSender.promises.enqueue(null, null, null)
this.request.should.not.have.been.called
})
it('should post the message to the tpdsworker', async function() {
this.settings.apis.tpdsworker = { url: 'www.tpdsworker.env' }
const group = 'myproject'
const method = 'somemethod'
const job = 'do something'
this.request.callsArgWith(1)
return this.updateSender._enqueue(group, method, job, err => {
const args = this.request.args[0][0]
await this.updateSender.promises.enqueue(group, method, job)
const args = this.request.firstCall.args[0]
args.json.group.should.equal(group)
args.json.job.should.equal(job)
args.json.method.should.equal(method)
args.uri.should.equal(
'www.tpdsworker.env/enqueue/web_to_tpds_http_requests'
)
return done()
})
})
})
describe('sending updates', function() {
it('queues a post the file with user and file id', function(done) {
const file_id = '4545345'
beforeEach(function() {
this.settings.apis.tpdsworker = { url: 'www.tpdsworker.env' }
})
it('queues a post the file with user and file id', async function() {
const fileId = '4545345'
const path = '/some/path/here.jpg'
this.updateSender._enqueue = function(uid, method, job, callback) {
uid.should.equal(project_id)
await this.updateSender.promises.addFile({
project_id: projectId,
file_id: fileId,
path,
project_name: projectName
})
const { group, job, method } = this.request.firstCall.args[0].json
group.should.equal(projectId)
method.should.equal('pipeStreamFrom')
job.method.should.equal('post')
job.streamOrigin.should.equal(
`${filestoreUrl}/project/${project_id}/file/${file_id}`
`${filestoreUrl}/project/${projectId}/file/${fileId}`
)
const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${user_id}/entity/${encodeURIComponent(
project_name
const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
projectName
)}${encodeURIComponent(path)}`
job.uri.should.equal(expectedUrl)
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([collaberator_ref_1, read_only_ref_1, user_id])
)
return done()
}
return this.updateSender.addFile(
{ project_id, file_id, path, project_name },
() => {}
job.headers.sl_all_user_ids.should.equal(
JSON.stringify([userId, collaberatorRef, readOnlyRef])
)
})
it('post doc with stream origin of docstore', function(done) {
const doc_id = '4545345'
it('post doc with stream origin of docstore', async function() {
const docId = '4545345'
const path = '/some/path/here.tex'
const lines = ['line1', 'line2', 'line3']
this.updateSender._enqueue = (uid, method, job, callback) => {
uid.should.equal(project_id)
await this.updateSender.promises.addDoc({
project_id: projectId,
doc_id: docId,
path,
docLines: lines,
project_name: projectName
})
const { group, job, method } = this.request.firstCall.args[0].json
group.should.equal(projectId)
method.should.equal('pipeStreamFrom')
job.method.should.equal('post')
const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${user_id}/entity/${encodeURIComponent(
project_name
const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
projectName
)}${encodeURIComponent(path)}`
job.uri.should.equal(expectedUrl)
job.streamOrigin.should.equal(
`${this.docstoreUrl}/project/${project_id}/doc/${doc_id}/raw`
`${this.docstoreUrl}/project/${projectId}/doc/${docId}/raw`
)
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([collaberator_ref_1, read_only_ref_1, user_id])
JSON.stringify([userId, collaberatorRef, readOnlyRef])
)
return done()
}
return this.updateSender.addDoc({
project_id,
doc_id,
path,
docLines: lines,
project_name
})
})
it('deleting entity', function(done) {
it('deleting entity', async function() {
const path = '/path/here/t.tex'
this.updateSender._enqueue = function(uid, method, job, callback) {
uid.should.equal(project_id)
job.method.should.equal('DELETE')
const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${user_id}/entity/${encodeURIComponent(
project_name
await this.updateSender.promises.deleteEntity({
project_id: projectId,
path,
project_name: projectName
})
const { group, job, method } = this.request.firstCall.args[0].json
group.should.equal(projectId)
method.should.equal('standardHttpRequest')
job.method.should.equal('delete')
const expectedUrl = `${thirdPartyDataStoreApiUrl}/user/${userId}/entity/${encodeURIComponent(
projectName
)}${encodeURIComponent(path)}`
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([collaberator_ref_1, read_only_ref_1, user_id])
JSON.stringify([userId, collaberatorRef, readOnlyRef])
)
job.uri.should.equal(expectedUrl)
return done()
}
return this.updateSender.deleteEntity({ project_id, path, project_name })
})
it('moving entity', function(done) {
it('moving entity', async function() {
const startPath = 'staring/here/file.tex'
const endPath = 'ending/here/file.tex'
this.updateSender._enqueue = function(uid, method, job, callback) {
uid.should.equal(project_id)
job.method.should.equal('put')
job.uri.should.equal(
`${thirdPartyDataStoreApiUrl}/user/${user_id}/entity`
)
job.json.startPath.should.equal(`/${project_name}/${startPath}`)
job.json.endPath.should.equal(`/${project_name}/${endPath}`)
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([collaberator_ref_1, read_only_ref_1, user_id])
)
return done()
}
return this.updateSender.moveEntity({
project_id,
await this.updateSender.promises.moveEntity({
project_id: projectId,
startPath,
endPath,
project_name
})
project_name: projectName
})
it('should be able to rename a project using the move entity func', function(done) {
const { group, job, method } = this.request.firstCall.args[0].json
group.should.equal(projectId)
method.should.equal('standardHttpRequest')
job.method.should.equal('put')
job.uri.should.equal(`${thirdPartyDataStoreApiUrl}/user/${userId}/entity`)
job.json.startPath.should.equal(`/${projectName}/${startPath}`)
job.json.endPath.should.equal(`/${projectName}/${endPath}`)
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([userId, collaberatorRef, readOnlyRef])
)
})
it('should be able to rename a project using the move entity func', async function() {
const oldProjectName = '/oldProjectName/'
const newProjectName = '/newProjectName/'
this.updateSender._enqueue = function(uid, method, job, callback) {
uid.should.equal(project_id)
job.method.should.equal('put')
job.uri.should.equal(
`${thirdPartyDataStoreApiUrl}/user/${user_id}/entity`
)
job.json.startPath.should.equal(oldProjectName)
job.json.endPath.should.equal(newProjectName)
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([collaberator_ref_1, read_only_ref_1, user_id])
)
return done()
}
return this.updateSender.moveEntity({
project_id,
await this.updateSender.promises.moveEntity({
project_id: projectId,
project_name: oldProjectName,
newProjectName
})
const { group, job, method } = this.request.firstCall.args[0].json
group.should.equal(projectId)
method.should.equal('standardHttpRequest')
job.method.should.equal('put')
job.uri.should.equal(`${thirdPartyDataStoreApiUrl}/user/${userId}/entity`)
job.json.startPath.should.equal(oldProjectName)
job.json.endPath.should.equal(newProjectName)
job.headers.sl_all_user_ids.should.eql(
JSON.stringify([userId, collaberatorRef, readOnlyRef])
)
})
it('pollDropboxForUser', function(done) {
this.updateSender._enqueue = sinon.stub().callsArg(3)
return this.updateSender.pollDropboxForUser(user_id, error => {
this.updateSender._enqueue
.calledWith(`poll-dropbox:${user_id}`, 'standardHttpRequest', {
method: 'POST',
uri: `${thirdPartyDataStoreApiUrl}/user/poll`,
json: {
user_ids: [user_id]
}
})
.should.equal(true)
return done()
})
it('pollDropboxForUser', async function() {
await this.updateSender.promises.pollDropboxForUser(userId)
const { group, job, method } = this.request.firstCall.args[0].json
group.should.equal(`poll-dropbox:${userId}`)
method.should.equal('standardHttpRequest')
job.method.should.equal('post')
job.uri.should.equal(`${thirdPartyDataStoreApiUrl}/user/poll`)
job.json.user_ids[0].should.equal(userId)
})
})
})