mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-08 09:13:20 +00:00
Merge pull request #6 from heukirne/s3-archive
Add S3 archive track changes feature
This commit is contained in:
commit
a10dc4f898
22 changed files with 992 additions and 13 deletions
services/track-changes
app.coffee
app/coffee
DocArchiveManager.coffeeDocstoreHandler.coffeeHttpController.coffeeMongoAWS.coffeeMongoAWSexternal.coffeeMongoManager.coffeeUpdatesManager.coffee
config
package.jsontest
acceptance/coffee
ArchivingUpdatesTests.coffeeGettingADiffTests.coffeeGettingUpdatesTests.coffeeRestoringVersions.coffee
helpers
unit/coffee
DocArchive
HttpController
MongoManager
|
@ -27,6 +27,9 @@ app.post "/project/:project_id/doc/:doc_id/version/:version/restore", HttpContro
|
|||
|
||||
app.post "/doc/:doc_id/pack", HttpController.packDoc
|
||||
|
||||
app.get '/project/:project_id/archive', HttpController.archiveProject
|
||||
app.get '/project/:project_id/unarchive', HttpController.unArchiveProject
|
||||
|
||||
packWorker = null # use a single packing worker
|
||||
|
||||
app.post "/pack", (req, res, next) ->
|
||||
|
|
74
services/track-changes/app/coffee/DocArchiveManager.coffee
Normal file
74
services/track-changes/app/coffee/DocArchiveManager.coffee
Normal file
|
@ -0,0 +1,74 @@
|
|||
MongoManager = require "./MongoManager"
|
||||
MongoAWS = require "./MongoAWS"
|
||||
LockManager = require "./LockManager"
|
||||
DocstoreHandler = require "./DocstoreHandler"
|
||||
logger = require "logger-sharelatex"
|
||||
_ = require "underscore"
|
||||
async = require "async"
|
||||
settings = require("settings-sharelatex")
|
||||
|
||||
# increase lock timeouts because archiving can be slow
|
||||
LockManager.LOCK_TEST_INTERVAL = 500 # 500ms between each test of the lock
|
||||
LockManager.MAX_LOCK_WAIT_TIME = 30000 # 30s maximum time to spend trying to get the lock
|
||||
LockManager.LOCK_TTL = 30 # seconds
|
||||
|
||||
module.exports = DocArchiveManager =
|
||||
|
||||
archiveAllDocsChanges: (project_id, callback = (error, docs) ->) ->
|
||||
DocstoreHandler.getAllDocs project_id, (error, docs) ->
|
||||
if error?
|
||||
return callback(error)
|
||||
else if !docs?
|
||||
return callback new Error("No docs for project #{project_id}")
|
||||
jobs = _.map docs, (doc) ->
|
||||
(cb)-> DocArchiveManager.archiveDocChangesWithLock project_id, doc._id, cb
|
||||
async.series jobs, callback
|
||||
|
||||
archiveDocChangesWithLock: (project_id, doc_id, callback = (error) ->) ->
|
||||
job = (releaseLock) ->
|
||||
DocArchiveManager.archiveDocChanges project_id, doc_id, releaseLock
|
||||
LockManager.runWithLock("HistoryLock:#{doc_id}", job, callback)
|
||||
|
||||
archiveDocChanges: (project_id, doc_id, callback)->
|
||||
MongoManager.getDocChangesCount doc_id, (error, count) ->
|
||||
return callback(error) if error?
|
||||
if count == 0
|
||||
logger.log {project_id, doc_id}, "document history is empty, not archiving"
|
||||
return callback()
|
||||
else
|
||||
MongoManager.getLastCompressedUpdate doc_id, (error, update) ->
|
||||
return callback(error) if error?
|
||||
MongoAWS.archiveDocHistory project_id, doc_id, (error) ->
|
||||
return callback(error) if error?
|
||||
logger.log doc_id:doc_id, project_id:project_id, "exported document to S3"
|
||||
MongoManager.markDocHistoryAsArchived doc_id, update, (error) ->
|
||||
return callback(error) if error?
|
||||
callback()
|
||||
|
||||
unArchiveAllDocsChanges: (project_id, callback = (error, docs) ->) ->
|
||||
DocstoreHandler.getAllDocs project_id, (error, docs) ->
|
||||
if error?
|
||||
return callback(error)
|
||||
else if !docs?
|
||||
return callback new Error("No docs for project #{project_id}")
|
||||
jobs = _.map docs, (doc) ->
|
||||
(cb)-> DocArchiveManager.unArchiveDocChangesWithLock project_id, doc._id, cb
|
||||
async.parallelLimit jobs, 4, callback
|
||||
|
||||
unArchiveDocChangesWithLock: (project_id, doc_id, callback = (error) ->) ->
|
||||
job = (releaseLock) ->
|
||||
DocArchiveManager.unArchiveDocChanges project_id, doc_id, releaseLock
|
||||
LockManager.runWithLock("HistoryLock:#{doc_id}", job, callback)
|
||||
|
||||
unArchiveDocChanges: (project_id, doc_id, callback)->
|
||||
MongoManager.getArchivedDocChanges doc_id, (error, count) ->
|
||||
return callback(error) if error?
|
||||
if count == 0
|
||||
return callback()
|
||||
else
|
||||
MongoAWS.unArchiveDocHistory project_id, doc_id, (error) ->
|
||||
return callback(error) if error?
|
||||
logger.log doc_id:doc_id, project_id:project_id, "imported document from S3"
|
||||
MongoManager.markDocHistoryAsUnarchived doc_id, (error) ->
|
||||
return callback(error) if error?
|
||||
callback()
|
21
services/track-changes/app/coffee/DocstoreHandler.coffee
Normal file
21
services/track-changes/app/coffee/DocstoreHandler.coffee
Normal file
|
@ -0,0 +1,21 @@
|
|||
request = require("request").defaults(jar: false)
|
||||
logger = require "logger-sharelatex"
|
||||
settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = DocstoreHandler =
|
||||
|
||||
getAllDocs: (project_id, callback = (error) ->) ->
|
||||
logger.log project_id: project_id, "getting all docs for project in docstore api"
|
||||
url = "#{settings.apis.docstore.url}/project/#{project_id}/doc"
|
||||
request.get {
|
||||
url: url
|
||||
json: true
|
||||
}, (error, res, docs) ->
|
||||
return callback(error) if error?
|
||||
logger.log {error, res, docs: if docs?.length then docs.map (d) -> d._id else []}, "docstore response"
|
||||
if 200 <= res.statusCode < 300
|
||||
callback(null, docs)
|
||||
else
|
||||
error = new Error("docstore api responded with non-success code: #{res.statusCode}")
|
||||
logger.error err: error, project_id: project_id, "error getting all docs from docstore"
|
||||
callback(error)
|
|
@ -3,6 +3,7 @@ DiffManager = require "./DiffManager"
|
|||
PackManager = require "./PackManager"
|
||||
RestoreManager = require "./RestoreManager"
|
||||
logger = require "logger-sharelatex"
|
||||
DocArchiveManager = require "./DocArchiveManager"
|
||||
|
||||
module.exports = HttpController =
|
||||
flushDoc: (req, res, next = (error) ->) ->
|
||||
|
@ -66,3 +67,17 @@ module.exports = HttpController =
|
|||
RestoreManager.restoreToBeforeVersion project_id, doc_id, version, user_id, (error) ->
|
||||
return next(error) if error?
|
||||
res.send 204
|
||||
|
||||
archiveProject: (req, res, next = (error) ->) ->
|
||||
project_id = req.params.project_id
|
||||
logger.log project_id: project_id, "archiving all track changes to s3"
|
||||
DocArchiveManager.archiveAllDocsChanges project_id, (error) ->
|
||||
return next(error) if error?
|
||||
res.send 204
|
||||
|
||||
unArchiveProject: (req, res, next = (error) ->) ->
|
||||
project_id = req.params.project_id
|
||||
logger.log project_id: project_id, "unarchiving all track changes from s3"
|
||||
DocArchiveManager.unArchiveAllDocsChanges project_id, (error) ->
|
||||
return next(error) if error?
|
||||
res.send 204
|
105
services/track-changes/app/coffee/MongoAWS.coffee
Normal file
105
services/track-changes/app/coffee/MongoAWS.coffee
Normal file
|
@ -0,0 +1,105 @@
|
|||
settings = require "settings-sharelatex"
|
||||
logger = require "logger-sharelatex"
|
||||
AWS = require 'aws-sdk'
|
||||
S3S = require 's3-streams'
|
||||
{db, ObjectId} = require "./mongojs"
|
||||
JSONStream = require "JSONStream"
|
||||
ReadlineStream = require "readline-stream"
|
||||
|
||||
module.exports = MongoAWS =
|
||||
|
||||
MAX_SIZE: 1024*1024 # almost max size
|
||||
MAX_COUNT: 1024 # almost max count
|
||||
|
||||
archiveDocHistory: (project_id, doc_id, _callback = (error) ->) ->
|
||||
|
||||
callback = (args...) ->
|
||||
_callback(args...)
|
||||
_callback = () ->
|
||||
|
||||
query = {
|
||||
doc_id: ObjectId(doc_id)
|
||||
expiresAt: {$exists : false}
|
||||
}
|
||||
|
||||
AWS.config.update {
|
||||
accessKeyId: settings.filestore.s3.key
|
||||
secretAccessKey: settings.filestore.s3.secret
|
||||
}
|
||||
|
||||
upload = S3S.WriteStream new AWS.S3(), {
|
||||
"Bucket": settings.filestore.stores.user_files,
|
||||
"Key": project_id+"/changes-"+doc_id
|
||||
}
|
||||
|
||||
db.docHistory.find(query)
|
||||
.on 'error', (err) ->
|
||||
callback(err)
|
||||
.pipe JSONStream.stringify()
|
||||
.pipe upload
|
||||
.on 'error', (err) ->
|
||||
callback(err)
|
||||
.on 'finish', () ->
|
||||
return callback(null)
|
||||
|
||||
unArchiveDocHistory: (project_id, doc_id, _callback = (error) ->) ->
|
||||
|
||||
callback = (args...) ->
|
||||
_callback(args...)
|
||||
_callback = () ->
|
||||
|
||||
AWS.config.update {
|
||||
accessKeyId: settings.filestore.s3.key
|
||||
secretAccessKey: settings.filestore.s3.secret
|
||||
}
|
||||
|
||||
download = S3S.ReadStream new AWS.S3(), {
|
||||
"Bucket": settings.filestore.stores.user_files,
|
||||
"Key": project_id+"/changes-"+doc_id
|
||||
}, {
|
||||
encoding: "utf8"
|
||||
}
|
||||
|
||||
lineStream = new ReadlineStream();
|
||||
ops = []
|
||||
sz = 0
|
||||
|
||||
download
|
||||
.on 'open', (obj) ->
|
||||
return 1
|
||||
.on 'error', (err) ->
|
||||
callback(err)
|
||||
.pipe lineStream
|
||||
.on 'data', (line) ->
|
||||
if line.length > 2
|
||||
ops.push(JSON.parse(line))
|
||||
sz += line.length
|
||||
if ops.length >= MongoAWS.MAX_COUNT || sz >= MongoAWS.MAX_SIZE
|
||||
download.pause()
|
||||
MongoAWS.handleBulk ops.slice(0), sz, () ->
|
||||
download.resume()
|
||||
ops.splice(0,ops.length)
|
||||
sz = 0
|
||||
.on 'end', () ->
|
||||
MongoAWS.handleBulk ops, sz, callback
|
||||
.on 'error', (err) ->
|
||||
return callback(err)
|
||||
|
||||
handleBulk: (ops, size, cb) ->
|
||||
bulk = db.docHistory.initializeUnorderedBulkOp();
|
||||
|
||||
for op in ops
|
||||
op._id = ObjectId(op._id)
|
||||
op.doc_id = ObjectId(op.doc_id)
|
||||
op.project_id = ObjectId(op.project_id)
|
||||
bulk.find({_id:op._id}).upsert().updateOne(op)
|
||||
|
||||
if ops.length > 0
|
||||
bulk.execute (err, result) ->
|
||||
if err?
|
||||
logger.error err:err, "error bulking ReadlineStream"
|
||||
else
|
||||
logger.log count:ops.length, result:result, size: size, "bulked ReadlineStream"
|
||||
cb(err)
|
||||
else
|
||||
cb()
|
123
services/track-changes/app/coffee/MongoAWSexternal.coffee
Normal file
123
services/track-changes/app/coffee/MongoAWSexternal.coffee
Normal file
|
@ -0,0 +1,123 @@
|
|||
settings = require "settings-sharelatex"
|
||||
child_process = require "child_process"
|
||||
mongoUri = require "mongo-uri";
|
||||
logger = require "logger-sharelatex"
|
||||
AWS = require 'aws-sdk'
|
||||
fs = require 'fs'
|
||||
S3S = require 's3-streams'
|
||||
|
||||
module.exports = MongoAWSexternal =
|
||||
|
||||
archiveDocHistory: (project_id, doc_id, callback = (error) ->) ->
|
||||
MongoAWS.mongoExportDocHistory doc_id, (error, filepath) ->
|
||||
MongoAWS.s3upStream project_id, doc_id, filepath, callback
|
||||
#delete temp file?
|
||||
|
||||
|
||||
unArchiveDocHistory: (project_id, doc_id, callback = (error) ->) ->
|
||||
MongoAWS.s3downStream project_id, doc_id, (error, filepath) ->
|
||||
if error == null
|
||||
MongoAWS.mongoImportDocHistory filepath, callback
|
||||
#delete temp file?
|
||||
else
|
||||
callback
|
||||
|
||||
mongoExportDocHistory: (doc_id, callback = (error, filepath) ->) ->
|
||||
uriData = mongoUri.parse(settings.mongo.url);
|
||||
filepath = settings.path.dumpFolder + '/' + doc_id + '.jsonUp'
|
||||
|
||||
args = []
|
||||
args.push '-h'
|
||||
args.push uriData.hosts[0]
|
||||
args.push '-d'
|
||||
args.push uriData.database
|
||||
args.push '-c'
|
||||
args.push 'docHistory'
|
||||
args.push '-q'
|
||||
args.push "{doc_id: ObjectId('#{doc_id}') , expiresAt: {$exists : false} }"
|
||||
args.push '-o'
|
||||
args.push filepath
|
||||
|
||||
proc = child_process.spawn "mongoexport", args
|
||||
|
||||
proc.on "error", callback
|
||||
|
||||
stderr = ""
|
||||
proc.stderr.on "data", (chunk) -> stderr += chunk.toString()
|
||||
|
||||
proc.on "close", (code) ->
|
||||
if code == 0
|
||||
return callback(null,filepath)
|
||||
else
|
||||
return callback(new Error("mongodump failed: #{stderr}"),null)
|
||||
|
||||
mongoImportDocHistory: (filepath, callback = (error) ->) ->
|
||||
|
||||
uriData = mongoUri.parse(settings.mongo.url);
|
||||
|
||||
args = []
|
||||
args.push '-h'
|
||||
args.push uriData.hosts[0]
|
||||
args.push '-d'
|
||||
args.push uriData.database
|
||||
args.push '-c'
|
||||
args.push 'docHistory'
|
||||
args.push '--file'
|
||||
args.push filepath
|
||||
|
||||
proc = child_process.spawn "mongoimport", args
|
||||
|
||||
proc.on "error", callback
|
||||
|
||||
stderr = ""
|
||||
proc.stderr.on "data", (chunk) -> stderr += chunk.toString()
|
||||
|
||||
proc.on "close", (code) ->
|
||||
if code == 0
|
||||
return callback(null,filepath)
|
||||
else
|
||||
return callback(new Error("mongodump failed: #{stderr}"),null)
|
||||
|
||||
s3upStream: (project_id, doc_id, filepath, callback = (error) ->) ->
|
||||
|
||||
AWS.config.update {
|
||||
accessKeyId: settings.filestore.s3.key
|
||||
secretAccessKey: settings.filestore.s3.secret
|
||||
}
|
||||
|
||||
upload = S3S.WriteStream new AWS.S3(), {
|
||||
"Bucket": settings.filestore.stores.user_files,
|
||||
"Key": project_id+"/changes-"+doc_id
|
||||
}
|
||||
|
||||
fs.createReadStream(filepath)
|
||||
.on 'open', (obj) ->
|
||||
return 1
|
||||
.pipe(upload)
|
||||
.on 'finish', () ->
|
||||
return callback(null)
|
||||
.on 'error', (err) ->
|
||||
return callback(err)
|
||||
|
||||
s3downStream: (project_id, doc_id, callback = (error, filepath) ->) ->
|
||||
|
||||
filepath = settings.path.dumpFolder + '/' + doc_id + '.jsonDown'
|
||||
|
||||
AWS.config.update {
|
||||
accessKeyId: settings.filestore.s3.key
|
||||
secretAccessKey: settings.filestore.s3.secret
|
||||
}
|
||||
|
||||
download = S3S.ReadStream new AWS.S3(), {
|
||||
"Bucket": settings.filestore.stores.user_files,
|
||||
"Key": project_id+"/changes-"+doc_id
|
||||
}
|
||||
|
||||
download
|
||||
.on 'open', (obj) ->
|
||||
return 1
|
||||
.pipe(fs.createWriteStream(filepath))
|
||||
.on 'finish', () ->
|
||||
return callback(null, filepath)
|
||||
.on 'error', (err) ->
|
||||
return callback(err, null)
|
|
@ -1,6 +1,7 @@
|
|||
{db, ObjectId} = require "./mongojs"
|
||||
PackManager = require "./PackManager"
|
||||
async = require "async"
|
||||
logger = require "logger-sharelatex"
|
||||
|
||||
module.exports = MongoManager =
|
||||
getLastCompressedUpdate: (doc_id, callback = (error, update) ->) ->
|
||||
|
@ -47,6 +48,7 @@ module.exports = MongoManager =
|
|||
|
||||
|
||||
insertCompressedUpdate: (project_id, doc_id, update, temporary, callback = (error) ->) ->
|
||||
inS3 = update.inS3?
|
||||
update = {
|
||||
doc_id: ObjectId(doc_id.toString())
|
||||
project_id: ObjectId(project_id.toString())
|
||||
|
@ -54,6 +56,9 @@ module.exports = MongoManager =
|
|||
meta: update.meta
|
||||
v: update.v
|
||||
}
|
||||
if inS3
|
||||
update.inS3 = true
|
||||
|
||||
if temporary
|
||||
seconds = 1000
|
||||
minutes = 60 * seconds
|
||||
|
@ -126,3 +131,20 @@ module.exports = MongoManager =
|
|||
# For finding documents which need packing
|
||||
db.docHistoryStats.ensureIndex { doc_id: 1 }, { background: true }
|
||||
db.docHistoryStats.ensureIndex { updates: -1, doc_id: 1 }, { background: true }
|
||||
|
||||
getDocChangesCount: (doc_id, callback)->
|
||||
db.docHistory.count { doc_id : ObjectId(doc_id.toString()), inS3 : { $exists : false }}, {}, callback
|
||||
|
||||
getArchivedDocChanges: (doc_id, callback)->
|
||||
db.docHistory.count { doc_id: ObjectId(doc_id.toString()) , inS3: true }, {}, callback
|
||||
|
||||
markDocHistoryAsArchived: (doc_id, update, callback)->
|
||||
db.docHistory.update { _id: update._id }, { $set : { inS3 : true } }, (error)->
|
||||
return callback(error) if error?
|
||||
db.docHistory.remove { doc_id : ObjectId(doc_id.toString()), inS3 : { $exists : false }, v: { $lt : update.v }, expiresAt: {$exists : false} }, (error)->
|
||||
return callback(error) if error?
|
||||
callback(error)
|
||||
|
||||
markDocHistoryAsUnarchived: (doc_id, callback)->
|
||||
db.docHistory.update { doc_id: ObjectId(doc_id.toString()) }, { $unset : { inS3 : true } }, { multi: true }, (error)->
|
||||
callback(error)
|
||||
|
|
|
@ -6,6 +6,8 @@ WebApiManager = require "./WebApiManager"
|
|||
UpdateTrimmer = require "./UpdateTrimmer"
|
||||
logger = require "logger-sharelatex"
|
||||
async = require "async"
|
||||
DocArchiveManager = require "./DocArchiveManager"
|
||||
_ = require "underscore"
|
||||
|
||||
module.exports = UpdatesManager =
|
||||
compressAndSaveRawUpdates: (project_id, doc_id, rawUpdates, temporary, callback = (error) ->) ->
|
||||
|
@ -32,6 +34,9 @@ module.exports = UpdatesManager =
|
|||
return
|
||||
|
||||
compressedUpdates = UpdateCompressor.compressRawUpdates lastCompressedUpdate, rawUpdates
|
||||
if lastCompressedUpdate?.inS3? and not _.some(compressedUpdates, (update) -> update.inS3)
|
||||
compressedUpdates[compressedUpdates.length-1].inS3 = lastCompressedUpdate.inS3
|
||||
|
||||
MongoManager.insertCompressedUpdates project_id, doc_id, compressedUpdates, temporary,(error) ->
|
||||
return callback(error) if error?
|
||||
logger.log project_id: project_id, doc_id: doc_id, rawUpdatesLength: length, compressedUpdatesLength: compressedUpdates.length, "compressed doc updates"
|
||||
|
@ -94,7 +99,17 @@ module.exports = UpdatesManager =
|
|||
getProjectUpdates: (project_id, options = {}, callback = (error, updates) ->) ->
|
||||
UpdatesManager.processUncompressedUpdatesForProject project_id, (error) ->
|
||||
return callback(error) if error?
|
||||
MongoManager.getProjectUpdates project_id, options, callback
|
||||
MongoManager.getProjectUpdates project_id, options, (error, updates) ->
|
||||
jobs = []
|
||||
for update in updates
|
||||
if update.inS3?
|
||||
do (update) ->
|
||||
jobs.push (callback) -> DocArchiveManager.unArchiveDocChanges update.project_id, update.doc_id, callback
|
||||
if jobs.length?
|
||||
async.series jobs, (err) ->
|
||||
MongoManager.getProjectUpdates project_id, options, callback
|
||||
else
|
||||
callback(error, updates)
|
||||
|
||||
getProjectUpdatesWithUserInfo: (project_id, options = {}, callback = (error, updates) ->) ->
|
||||
UpdatesManager.getProjectUpdates project_id, options, (error, updates) ->
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
Path = require('path')
|
||||
TMP_DIR = Path.resolve(Path.join(__dirname, "../../", "tmp"))
|
||||
|
||||
module.exports =
|
||||
mongo:
|
||||
url: 'mongodb://127.0.0.1/sharelatex'
|
||||
|
@ -19,3 +22,14 @@ module.exports =
|
|||
host: "localhost"
|
||||
port: 6379
|
||||
pass: ""
|
||||
|
||||
filestore:
|
||||
backend: "s3"
|
||||
stores:
|
||||
user_files: ""
|
||||
s3:
|
||||
key: ""
|
||||
secret: ""
|
||||
|
||||
path:
|
||||
dumpFolder: Path.join(TMP_DIR, "dumpFolder")
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
"request": "~2.33.0",
|
||||
"redis-sharelatex": "~0.0.4",
|
||||
"redis": "~0.10.1",
|
||||
"underscore": "~1.7.0"
|
||||
"underscore": "~1.7.0",
|
||||
"mongo-uri": "^0.1.2",
|
||||
"s3-streams": "^0.3.0",
|
||||
"JSONStream": "^1.0.4",
|
||||
"readline-stream": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "~1.9.0",
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
sinon = require "sinon"
|
||||
chai = require("chai")
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
mongojs = require "../../../app/js/mongojs"
|
||||
db = mongojs.db
|
||||
ObjectId = mongojs.ObjectId
|
||||
Settings = require "settings-sharelatex"
|
||||
request = require "request"
|
||||
rclient = require("redis").createClient() # Only works locally for now
|
||||
|
||||
TrackChangesClient = require "./helpers/TrackChangesClient"
|
||||
MockDocStoreApi = require "./helpers/MockDocStoreApi"
|
||||
MockWebApi = require "./helpers/MockWebApi"
|
||||
|
||||
describe "Archiving updates", ->
|
||||
before (done) ->
|
||||
@now = Date.now()
|
||||
@to = @now
|
||||
@user_id = ObjectId().toString()
|
||||
@doc_id = ObjectId().toString()
|
||||
@project_id = ObjectId().toString()
|
||||
|
||||
@minutes = 60 * 1000
|
||||
@hours = 60 * @minutes
|
||||
|
||||
MockWebApi.projects[@project_id] =
|
||||
features:
|
||||
versioning: true
|
||||
sinon.spy MockWebApi, "getProjectDetails"
|
||||
|
||||
MockWebApi.users[@user_id] = @user =
|
||||
email: "user@sharelatex.com"
|
||||
first_name: "Leo"
|
||||
last_name: "Lion"
|
||||
id: @user_id
|
||||
sinon.spy MockWebApi, "getUserInfo"
|
||||
|
||||
MockDocStoreApi.docs[@doc_id] = @doc =
|
||||
_id: @doc_id
|
||||
project_id: @project_id
|
||||
sinon.spy MockDocStoreApi, "getAllDoc"
|
||||
|
||||
@updates = []
|
||||
for i in [0..9]
|
||||
@updates.push {
|
||||
op: [{ i: "a", p: 0 }]
|
||||
meta: { ts: @now - (9 - i) * @hours - 2 * @minutes, user_id: @user_id }
|
||||
v: 2 * i + 1
|
||||
}
|
||||
@updates.push {
|
||||
op: [{ i: "b", p: 0 }]
|
||||
meta: { ts: @now - (9 - i) * @hours, user_id: @user_id }
|
||||
v: 2 * i + 2
|
||||
}
|
||||
|
||||
TrackChangesClient.pushRawUpdates @project_id, @doc_id, @updates, (error) =>
|
||||
throw error if error?
|
||||
TrackChangesClient.flushDoc @project_id, @doc_id, (error) ->
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
after (done) ->
|
||||
MockWebApi.getUserInfo.restore()
|
||||
db.docHistory.remove {project_id: ObjectId(@project_id)}
|
||||
TrackChangesClient.removeS3Doc @project_id, @doc_id, done
|
||||
|
||||
describe "archiving a doc's updates", ->
|
||||
before (done) ->
|
||||
TrackChangesClient.archiveProject @project_id, (error) ->
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
it "should remain one doc change", (done) ->
|
||||
db.docHistory.count { doc_id: ObjectId(@doc_id) }, (error, count) ->
|
||||
throw error if error?
|
||||
count.should.equal 1
|
||||
done()
|
||||
|
||||
it "should remained doc marked as inS3", (done) ->
|
||||
db.docHistory.findOne { doc_id: ObjectId(@doc_id) }, (error, doc) ->
|
||||
throw error if error?
|
||||
doc.inS3.should.equal true
|
||||
done()
|
||||
|
||||
it "should remained doc have last version", (done) ->
|
||||
db.docHistory.findOne { doc_id: ObjectId(@doc_id) }, (error, doc) ->
|
||||
throw error if error?
|
||||
doc.v.should.equal 20
|
||||
done()
|
||||
|
||||
it "should store twenty doc changes in S3", (done) ->
|
||||
TrackChangesClient.getS3Doc @project_id, @doc_id, (error, res, doc) =>
|
||||
doc.length.should.equal 20
|
||||
done()
|
||||
|
||||
describe "unarchiving a doc's updates", ->
|
||||
before (done) ->
|
||||
TrackChangesClient.unarchiveProject @project_id, (error) ->
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
it "should restore doc changes", (done) ->
|
||||
db.docHistory.count { doc_id: ObjectId(@doc_id) }, (error, count) ->
|
||||
throw error if error?
|
||||
count.should.equal 20
|
||||
done()
|
||||
|
||||
it "should remove doc marked as inS3", (done) ->
|
||||
db.docHistory.count { doc_id: ObjectId(@doc_id), inS3 : true }, (error, count) ->
|
||||
throw error if error?
|
||||
count.should.equal 0
|
||||
done()
|
|
@ -28,7 +28,7 @@ describe "Getting a diff", ->
|
|||
first_name: "Leo"
|
||||
last_name: "Lion"
|
||||
id: @user_id
|
||||
sinon.spy MockWebApi, "getUser"
|
||||
sinon.spy MockWebApi, "getUserInfo"
|
||||
|
||||
twoMinutes = 2 * 60 * 1000
|
||||
|
||||
|
@ -68,7 +68,7 @@ describe "Getting a diff", ->
|
|||
|
||||
after () ->
|
||||
MockDocUpdaterApi.getDoc.restore()
|
||||
MockWebApi.getUser.restore()
|
||||
MockWebApi.getUserInfo.restore()
|
||||
|
||||
it "should return the diff", ->
|
||||
expect(@diff).to.deep.equal @expected_diff
|
||||
|
|
|
@ -31,7 +31,7 @@ describe "Getting updates", ->
|
|||
first_name: "Leo"
|
||||
last_name: "Lion"
|
||||
id: @user_id
|
||||
sinon.spy MockWebApi, "getUser"
|
||||
sinon.spy MockWebApi, "getUserInfo"
|
||||
|
||||
@updates = []
|
||||
for i in [0..9]
|
||||
|
@ -52,7 +52,7 @@ describe "Getting updates", ->
|
|||
done()
|
||||
|
||||
after: () ->
|
||||
MockWebApi.getUser.restore()
|
||||
MockWebApi.getUserInfo.restore()
|
||||
|
||||
describe "getting updates up to the limit", ->
|
||||
before (done) ->
|
||||
|
@ -62,7 +62,7 @@ describe "Getting updates", ->
|
|||
done()
|
||||
|
||||
it "should fetch the user details from the web api", ->
|
||||
MockWebApi.getUser
|
||||
MockWebApi.getUserInfo
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ describe "Restoring a version", ->
|
|||
@doc_id = ObjectId().toString()
|
||||
@project_id = ObjectId().toString()
|
||||
MockWebApi.projects[@project_id] = features: versioning: true
|
||||
|
||||
|
||||
minutes = 60 * 1000
|
||||
|
||||
@updates = [{
|
||||
|
@ -49,6 +49,7 @@ describe "Restoring a version", ->
|
|||
first_name: "Leo"
|
||||
last_name: "Lion"
|
||||
id: @user_id
|
||||
|
||||
MockDocUpdaterApi.docs[@doc_id] =
|
||||
lines: @lines
|
||||
version: 7
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
express = require("express")
|
||||
app = express()
|
||||
|
||||
module.exports = MockDocUpdaterApi =
|
||||
docs: {}
|
||||
|
||||
getAllDoc: (project_id, callback = (error) ->) ->
|
||||
callback null, @docs
|
||||
|
||||
run: () ->
|
||||
app.get "/project/:project_id/doc", (req, res, next) =>
|
||||
@getAllDoc req.params.project_id, (error, docs) ->
|
||||
if error?
|
||||
res.send 500
|
||||
if !docs?
|
||||
res.send 404
|
||||
else
|
||||
res.send JSON.stringify docs
|
||||
|
||||
app.listen 3016, (error) ->
|
||||
throw error if error?
|
||||
|
||||
MockDocUpdaterApi.run()
|
||||
|
|
@ -6,15 +6,15 @@ module.exports = MockWebApi =
|
|||
|
||||
projects: {}
|
||||
|
||||
getUser: (user_id, callback = (error) ->) ->
|
||||
getUserInfo: (user_id, callback = (error) ->) ->
|
||||
callback null, @users[user_id] or null
|
||||
|
||||
getProject: (project_id, callback = (error, project) ->) ->
|
||||
getProjectDetails: (project_id, callback = (error, project) ->) ->
|
||||
callback null, @projects[project_id]
|
||||
|
||||
run: () ->
|
||||
app.get "/user/:user_id/personal_info", (req, res, next) =>
|
||||
@getUser req.params.user_id, (error, user) ->
|
||||
@getUserInfo req.params.user_id, (error, user) ->
|
||||
if error?
|
||||
res.send 500
|
||||
if !user?
|
||||
|
@ -23,7 +23,7 @@ module.exports = MockWebApi =
|
|||
res.send JSON.stringify user
|
||||
|
||||
app.get "/project/:project_id/details", (req, res, next) =>
|
||||
@getProject req.params.project_id, (error, project) ->
|
||||
@getProjectDetails req.params.project_id, (error, project) ->
|
||||
if error?
|
||||
res.send 500
|
||||
if !project?
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
request = require "request"
|
||||
rclient = require("redis").createClient() # Only works locally for now
|
||||
{db, ObjectId} = require "../../../../app/js/mongojs"
|
||||
Settings = require "settings-sharelatex"
|
||||
|
||||
module.exports = TrackChangesClient =
|
||||
flushAndGetCompressedUpdates: (project_id, doc_id, callback = (error, updates) ->) ->
|
||||
|
@ -71,4 +72,37 @@ module.exports = TrackChangesClient =
|
|||
"X-User-Id": user_id
|
||||
}, (error, response, body) =>
|
||||
response.statusCode.should.equal 204
|
||||
callback null
|
||||
callback null
|
||||
|
||||
archiveProject: (project_id, callback = (error) ->) ->
|
||||
request.get {
|
||||
url: "http://localhost:3015/project/#{project_id}/archive"
|
||||
}, (error, response, body) =>
|
||||
response.statusCode.should.equal 204
|
||||
callback(error)
|
||||
|
||||
unarchiveProject: (project_id, callback = (error) ->) ->
|
||||
request.get {
|
||||
url: "http://localhost:3015/project/#{project_id}/unarchive"
|
||||
}, (error, response, body) =>
|
||||
response.statusCode.should.equal 204
|
||||
callback(error)
|
||||
|
||||
buildS3Options: (content, key)->
|
||||
return {
|
||||
aws:
|
||||
key: Settings.filestore.s3.key
|
||||
secret: Settings.filestore.s3.secret
|
||||
bucket: Settings.filestore.stores.user_files
|
||||
timeout: 30 * 1000
|
||||
json: content
|
||||
uri:"https://#{Settings.filestore.stores.user_files}.s3.amazonaws.com/#{key}"
|
||||
}
|
||||
|
||||
getS3Doc: (project_id, doc_id, callback = (error, res, body) ->) ->
|
||||
options = TrackChangesClient.buildS3Options(true, project_id+"/changes-"+doc_id)
|
||||
request.get options, callback
|
||||
|
||||
removeS3Doc: (project_id, doc_id, callback = (error, res, body) ->) ->
|
||||
options = TrackChangesClient.buildS3Options(true, project_id+"/changes-"+doc_id)
|
||||
request.del options, callback
|
|
@ -0,0 +1,120 @@
|
|||
chai = require('chai')
|
||||
sinon = require("sinon")
|
||||
should = chai.should()
|
||||
modulePath = "../../../../app/js/DocArchiveManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
|
||||
describe "DocArchiveManager", ->
|
||||
beforeEach ->
|
||||
@DocArchiveManager = SandboxedModule.require modulePath, requires:
|
||||
"./MongoManager" : @MongoManager = sinon.stub()
|
||||
"./MongoAWS" : @MongoAWS = sinon.stub()
|
||||
"./LockManager" : @LockManager = sinon.stub()
|
||||
"./DocstoreHandler" : @DocstoreHandler = sinon.stub()
|
||||
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub(), err:->}
|
||||
"settings-sharelatex": @settings =
|
||||
filestore:
|
||||
backend: 's3'
|
||||
|
||||
@mongoDocs = [{
|
||||
_id: ObjectId()
|
||||
}, {
|
||||
_id: ObjectId()
|
||||
}, {
|
||||
_id: ObjectId()
|
||||
}]
|
||||
|
||||
@project_id = "project-id-123"
|
||||
@doc_id = "doc-id-123"
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "archiveAllDocsChanges", ->
|
||||
it "should archive all project docs change", (done)->
|
||||
@DocstoreHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @mongoDocs)
|
||||
@DocArchiveManager.archiveDocChangesWithLock = sinon.stub().callsArgWith(2, null)
|
||||
|
||||
@DocArchiveManager.archiveAllDocsChanges @project_id, (err)=>
|
||||
@DocArchiveManager.archiveDocChangesWithLock.calledWith(@project_id, @mongoDocs[0]._id).should.equal true
|
||||
@DocArchiveManager.archiveDocChangesWithLock.calledWith(@project_id, @mongoDocs[1]._id).should.equal true
|
||||
@DocArchiveManager.archiveDocChangesWithLock.calledWith(@project_id, @mongoDocs[2]._id).should.equal true
|
||||
should.not.exist err
|
||||
done()
|
||||
|
||||
describe "archiveDocChangesWithLock", ->
|
||||
beforeEach ->
|
||||
@DocArchiveManager.archiveDocChanges = sinon.stub().callsArg(2)
|
||||
@LockManager.runWithLock = sinon.stub().callsArg(2)
|
||||
@DocArchiveManager.archiveDocChangesWithLock @project_id, @doc_id, @callback
|
||||
|
||||
it "should run archiveDocChangesWithLock with the lock", ->
|
||||
@LockManager.runWithLock
|
||||
.calledWith(
|
||||
"HistoryLock:#{@doc_id}"
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "archiveDocChanges", ->
|
||||
beforeEach ->
|
||||
@update = { _id: ObjectId(), op: "op", meta: "meta", v: "v"}
|
||||
@MongoManager.getDocChangesCount = sinon.stub().callsArg(1)
|
||||
@MongoManager.getLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @update)
|
||||
@MongoAWS.archiveDocHistory = sinon.stub().callsArg(2)
|
||||
@MongoManager.markDocHistoryAsArchived = sinon.stub().callsArg(2)
|
||||
@DocArchiveManager.archiveDocChanges @project_id, @doc_id, @callback
|
||||
|
||||
it "should run markDocHistoryAsArchived with doc_id and update", ->
|
||||
@MongoManager.markDocHistoryAsArchived
|
||||
.calledWith(
|
||||
@doc_id, @update
|
||||
)
|
||||
.should.equal true
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "unArchiveAllDocsChanges", ->
|
||||
it "should unarchive all project docs change", (done)->
|
||||
@DocstoreHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @mongoDocs)
|
||||
@DocArchiveManager.unArchiveDocChangesWithLock = sinon.stub().callsArgWith(2, null)
|
||||
|
||||
@DocArchiveManager.unArchiveAllDocsChanges @project_id, (err)=>
|
||||
@DocArchiveManager.unArchiveDocChangesWithLock.calledWith(@project_id, @mongoDocs[0]._id).should.equal true
|
||||
@DocArchiveManager.unArchiveDocChangesWithLock.calledWith(@project_id, @mongoDocs[1]._id).should.equal true
|
||||
@DocArchiveManager.unArchiveDocChangesWithLock.calledWith(@project_id, @mongoDocs[2]._id).should.equal true
|
||||
should.not.exist err
|
||||
done()
|
||||
|
||||
describe "unArchiveDocChangesWithLock", ->
|
||||
beforeEach ->
|
||||
@DocArchiveManager.unArchiveDocChanges = sinon.stub().callsArg(2)
|
||||
@LockManager.runWithLock = sinon.stub().callsArg(2)
|
||||
@DocArchiveManager.unArchiveDocChangesWithLock @project_id, @doc_id, @callback
|
||||
|
||||
it "should run unArchiveDocChangesWithLock with the lock", ->
|
||||
@LockManager.runWithLock
|
||||
.calledWith(
|
||||
"HistoryLock:#{@doc_id}"
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "unArchiveDocChanges", ->
|
||||
beforeEach ->
|
||||
@MongoManager.getArchivedDocChanges = sinon.stub().callsArg(1)
|
||||
@MongoAWS.unArchiveDocHistory = sinon.stub().callsArg(2)
|
||||
@MongoManager.markDocHistoryAsUnarchived = sinon.stub().callsArg(1)
|
||||
@DocArchiveManager.unArchiveDocChanges @project_id, @doc_id, @callback
|
||||
|
||||
it "should run markDocHistoryAsUnarchived with doc_id", ->
|
||||
@MongoManager.markDocHistoryAsUnarchived
|
||||
.calledWith(
|
||||
@doc_id
|
||||
)
|
||||
.should.equal true
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
|
@ -0,0 +1,55 @@
|
|||
chai = require('chai')
|
||||
chai.should()
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/DocstoreHandler.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe "DocstoreHandler", ->
|
||||
beforeEach ->
|
||||
@requestDefaults = sinon.stub().returns(@request = sinon.stub())
|
||||
@DocstoreHandler = SandboxedModule.require modulePath, requires:
|
||||
"request" : defaults: @requestDefaults
|
||||
"settings-sharelatex": @settings =
|
||||
apis:
|
||||
docstore:
|
||||
url: "docstore.sharelatex.com"
|
||||
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub(), err:->}
|
||||
|
||||
@requestDefaults.calledWith(jar: false).should.equal true
|
||||
|
||||
@project_id = "project-id-123"
|
||||
@doc_id = "doc-id-123"
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "getAllDocs", ->
|
||||
describe "with a successful response code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, statusCode: 204, @docs = [{ _id: "mock-doc-id" }])
|
||||
@DocstoreHandler.getAllDocs @project_id, @callback
|
||||
|
||||
it "should get all the project docs in the docstore api", ->
|
||||
@request.get
|
||||
.calledWith({
|
||||
url: "#{@settings.apis.docstore.url}/project/#{@project_id}/doc"
|
||||
json: true
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback with the docs", ->
|
||||
@callback.calledWith(null, @docs).should.equal true
|
||||
|
||||
describe "with a failed response code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, statusCode: 500, "")
|
||||
@DocstoreHandler.getAllDocs @project_id, @callback
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback.calledWith(new Error("docstore api responded with non-success code: 500")).should.equal true
|
||||
|
||||
it "should log the error", ->
|
||||
@logger.error
|
||||
.calledWith({
|
||||
err: new Error("docstore api responded with a non-success code: 500")
|
||||
project_id: @project_id
|
||||
}, "error getting all docs from docstore")
|
||||
.should.equal true
|
|
@ -0,0 +1,113 @@
|
|||
chai = require('chai')
|
||||
chai.should()
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../../app/js/MongoAWS.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
{ObjectId} = require("mongojs")
|
||||
|
||||
describe "MongoAWS", ->
|
||||
beforeEach ->
|
||||
@MongoAWS = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex": @settings =
|
||||
filestore:
|
||||
s3:
|
||||
secret: "s3-secret"
|
||||
key: "s3-key"
|
||||
stores:
|
||||
user_files: "s3-bucket"
|
||||
"child_process": @child_process = {}
|
||||
"mongo-uri": @mongouri = {}
|
||||
"logger-sharelatex": @logger = {log: sinon.stub(), error: sinon.stub(), err:->}
|
||||
"aws-sdk": @awssdk = {}
|
||||
"fs": @fs = {}
|
||||
"s3-streams": @s3streams = {}
|
||||
"./mongojs" : { db: @db = {}, ObjectId: ObjectId }
|
||||
"JSONStream": @JSONStream = {}
|
||||
"readline-stream": @readline = sinon.stub()
|
||||
|
||||
@project_id = ObjectId().toString()
|
||||
@doc_id = ObjectId().toString()
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "archiveDocHistory", ->
|
||||
|
||||
beforeEach ->
|
||||
@awssdk.config = { update: sinon.stub() }
|
||||
@awssdk.S3 = sinon.stub()
|
||||
@s3streams.WriteStream = sinon.stub()
|
||||
@db.docHistory = {}
|
||||
@db.docHistory.on = sinon.stub()
|
||||
@db.docHistory.find = sinon.stub().returns @db.docHistory
|
||||
@db.docHistory.on.returns
|
||||
pipe:->
|
||||
pipe:->
|
||||
on: (type, cb)->
|
||||
on: (type, cb)->
|
||||
cb()
|
||||
@JSONStream.stringify = sinon.stub()
|
||||
|
||||
@MongoAWS.archiveDocHistory @project_id, @doc_id, @callback
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "unArchiveDocHistory", ->
|
||||
|
||||
beforeEach ->
|
||||
@awssdk.config = { update: sinon.stub() }
|
||||
@awssdk.S3 = sinon.stub()
|
||||
@s3streams.ReadStream = sinon.stub()
|
||||
|
||||
@s3streams.ReadStream.returns
|
||||
#describe on 'open' behavior
|
||||
on: (type, cb)->
|
||||
#describe on 'error' behavior
|
||||
on: (type, cb)->
|
||||
pipe:->
|
||||
#describe on 'data' behavior
|
||||
on: (type, cb)->
|
||||
cb([])
|
||||
#describe on 'end' behavior
|
||||
on: (type, cb)->
|
||||
cb()
|
||||
#describe on 'error' behavior
|
||||
on: sinon.stub()
|
||||
|
||||
@MongoAWS.handleBulk = sinon.stub()
|
||||
@MongoAWS.unArchiveDocHistory @project_id, @doc_id, @callback
|
||||
|
||||
it "should call handleBulk", ->
|
||||
@MongoAWS.handleBulk.called.should.equal true
|
||||
|
||||
describe "handleBulk", ->
|
||||
beforeEach ->
|
||||
@bulkOps = [{
|
||||
_id: ObjectId()
|
||||
doc_id: ObjectId()
|
||||
project_id: ObjectId()
|
||||
}, {
|
||||
_id: ObjectId()
|
||||
doc_id: ObjectId()
|
||||
project_id: ObjectId()
|
||||
}, {
|
||||
_id: ObjectId()
|
||||
doc_id: ObjectId()
|
||||
project_id: ObjectId()
|
||||
}]
|
||||
@bulk =
|
||||
find: sinon.stub().returns
|
||||
upsert: sinon.stub().returns
|
||||
updateOne: sinon.stub()
|
||||
execute: sinon.stub().callsArgWith(0, null, {})
|
||||
@db.docHistory = {}
|
||||
@db.docHistory.initializeUnorderedBulkOp = sinon.stub().returns @bulk
|
||||
@MongoAWS.handleBulk @bulkOps, @bulkOps.length, @callback
|
||||
|
||||
it "should call updateOne for each operation", ->
|
||||
@bulk.find.calledWith({_id:@bulkOps[0]._id}).should.equal true
|
||||
@bulk.find.calledWith({_id:@bulkOps[1]._id}).should.equal true
|
||||
@bulk.find.calledWith({_id:@bulkOps[2]._id}).should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.calledWith(null).should.equal true
|
||||
|
|
@ -13,6 +13,7 @@ describe "HttpController", ->
|
|||
"./DiffManager": @DiffManager = {}
|
||||
"./RestoreManager": @RestoreManager = {}
|
||||
"./PackManager": @PackManager = {}
|
||||
"./DocArchiveManager": @DocArchiveManager = {}
|
||||
@doc_id = "doc-id-123"
|
||||
@project_id = "project-id-123"
|
||||
@next = sinon.stub()
|
||||
|
@ -130,3 +131,39 @@ describe "HttpController", ->
|
|||
|
||||
it "should return a success code", ->
|
||||
@res.send.calledWith(204).should.equal true
|
||||
|
||||
describe "archiveProject", ->
|
||||
beforeEach ->
|
||||
@req =
|
||||
params:
|
||||
project_id: @project_id
|
||||
@res =
|
||||
send: sinon.stub()
|
||||
@DocArchiveManager.archiveAllDocsChanges = sinon.stub().callsArg(1)
|
||||
@HttpController.archiveProject @req, @res, @next
|
||||
|
||||
it "should process archive doc changes", ->
|
||||
@DocArchiveManager.archiveAllDocsChanges
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
it "should return a success code", ->
|
||||
@res.send.calledWith(204).should.equal true
|
||||
|
||||
describe "unArchiveProject", ->
|
||||
beforeEach ->
|
||||
@req =
|
||||
params:
|
||||
project_id: @project_id
|
||||
@res =
|
||||
send: sinon.stub()
|
||||
@DocArchiveManager.unArchiveAllDocsChanges = sinon.stub().callsArg(1)
|
||||
@HttpController.unArchiveProject @req, @res, @next
|
||||
|
||||
it "should process unarchive doc changes", ->
|
||||
@DocArchiveManager.unArchiveAllDocsChanges
|
||||
.calledWith(@project_id)
|
||||
.should.equal true
|
||||
|
||||
it "should return a success code", ->
|
||||
@res.send.calledWith(204).should.equal true
|
||||
|
|
|
@ -398,3 +398,89 @@ describe "MongoManager", ->
|
|||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "getDocChangesCount", ->
|
||||
beforeEach ->
|
||||
@db.docHistory =
|
||||
count: sinon.stub().callsArg(2)
|
||||
@MongoManager.getDocChangesCount @doc_id, @callback
|
||||
|
||||
it "should return if there is any doc changes", ->
|
||||
@db.docHistory.count
|
||||
.calledWith({
|
||||
doc_id: ObjectId(@doc_id)
|
||||
inS3 : { $exists : false }
|
||||
}, {
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "getArchivedDocChanges", ->
|
||||
beforeEach ->
|
||||
@db.docHistory =
|
||||
count: sinon.stub().callsArg(2)
|
||||
@MongoManager.getArchivedDocChanges @doc_id, @callback
|
||||
|
||||
it "should return if there is any archived doc changes", ->
|
||||
@db.docHistory.count
|
||||
.calledWith({
|
||||
doc_id: ObjectId(@doc_id)
|
||||
inS3 : true
|
||||
}, {
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "markDocHistoryAsArchived", ->
|
||||
beforeEach ->
|
||||
@update = { _id: ObjectId(), op: "op", meta: "meta", v: "v"}
|
||||
@db.docHistory =
|
||||
update: sinon.stub().callsArg(2)
|
||||
remove: sinon.stub().callsArg(1)
|
||||
@MongoManager.markDocHistoryAsArchived @doc_id, @update, @callback
|
||||
|
||||
it "should update last doc change with inS3 flag", ->
|
||||
@db.docHistory.update
|
||||
.calledWith({
|
||||
_id: ObjectId(@update._id)
|
||||
},{
|
||||
$set : { inS3 : true }
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should remove any other doc changes before last update", ->
|
||||
@db.docHistory.remove
|
||||
.calledWith({
|
||||
doc_id: ObjectId(@doc_id)
|
||||
inS3 : { $exists : false }
|
||||
v: { $lt : @update.v }
|
||||
expiresAt: {$exists : false}
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "markDocHistoryAsUnarchived", ->
|
||||
beforeEach ->
|
||||
@db.docHistory =
|
||||
update: sinon.stub().callsArg(3)
|
||||
@MongoManager.markDocHistoryAsUnarchived @doc_id, @callback
|
||||
|
||||
it "should remove any doc changes inS3 flag", ->
|
||||
@db.docHistory.update
|
||||
.calledWith({
|
||||
doc_id: ObjectId(@doc_id)
|
||||
},{
|
||||
$unset : { inS3 : true }
|
||||
},{
|
||||
multi: true
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
Loading…
Add table
Reference in a new issue