mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
267 lines
9.7 KiB
JavaScript
267 lines
9.7 KiB
JavaScript
/* eslint-disable
|
|
camelcase,
|
|
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
|
|
*/
|
|
require('chai').should();
|
|
const sinon = require("sinon");
|
|
const SandboxedModule = require('sandboxed-module');
|
|
const path = require("path");
|
|
const modulePath = '../../../app/js/DocumentUpdaterManager';
|
|
|
|
describe('DocumentUpdaterManager', function() {
|
|
beforeEach(function() {
|
|
let Timer;
|
|
this.project_id = "project-id-923";
|
|
this.doc_id = "doc-id-394";
|
|
this.lines = ["one", "two", "three"];
|
|
this.version = 42;
|
|
this.settings = {
|
|
apis: { documentupdater: {url: "http://doc-updater.example.com"}
|
|
},
|
|
redis: { documentupdater: {
|
|
key_schema: {
|
|
pendingUpdates({doc_id}) { return `PendingUpdates:${doc_id}`; }
|
|
}
|
|
}
|
|
},
|
|
maxUpdateSize: 7 * 1024 * 1024
|
|
};
|
|
this.rclient = {auth() {}};
|
|
|
|
return this.DocumentUpdaterManager = SandboxedModule.require(modulePath, {
|
|
requires: {
|
|
'settings-sharelatex':this.settings,
|
|
'logger-sharelatex': (this.logger = {log: sinon.stub(), error: sinon.stub(), warn: sinon.stub()}),
|
|
'request': (this.request = {}),
|
|
'redis-sharelatex' : { createClient: () => this.rclient
|
|
},
|
|
'metrics-sharelatex': (this.Metrics = {
|
|
summary: sinon.stub(),
|
|
Timer: (Timer = class Timer {
|
|
done() {}
|
|
})
|
|
})
|
|
},
|
|
globals: {
|
|
JSON: (this.JSON = Object.create(JSON))
|
|
}
|
|
}
|
|
);
|
|
}); // avoid modifying JSON object directly
|
|
|
|
describe("getDocument", function() {
|
|
beforeEach(function() {
|
|
return this.callback = sinon.stub();
|
|
});
|
|
|
|
describe("successfully", function() {
|
|
beforeEach(function() {
|
|
this.body = JSON.stringify({
|
|
lines: this.lines,
|
|
version: this.version,
|
|
ops: (this.ops = ["mock-op-1", "mock-op-2"]),
|
|
ranges: (this.ranges = {"mock": "ranges"})});
|
|
this.fromVersion = 2;
|
|
this.request.get = sinon.stub().callsArgWith(1, null, {statusCode: 200}, this.body);
|
|
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
});
|
|
|
|
it('should get the document from the document updater', function() {
|
|
const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}/doc/${this.doc_id}?fromVersion=${this.fromVersion}`;
|
|
return this.request.get.calledWith(url).should.equal(true);
|
|
});
|
|
|
|
return it("should call the callback with the lines, version, ranges and ops", function() {
|
|
return this.callback.calledWith(null, this.lines, this.version, this.ranges, this.ops).should.equal(true);
|
|
});
|
|
});
|
|
|
|
describe("when the document updater API returns an error", function() {
|
|
beforeEach(function() {
|
|
this.request.get = sinon.stub().callsArgWith(1, (this.error = new Error("something went wrong")), null, null);
|
|
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
});
|
|
|
|
return it("should return an error to the callback", function() {
|
|
return this.callback.calledWith(this.error).should.equal(true);
|
|
});
|
|
});
|
|
|
|
[404, 422].forEach(statusCode => describe(`when the document updater returns a ${statusCode} status code`, function() {
|
|
beforeEach(function() {
|
|
this.request.get = sinon.stub().callsArgWith(1, null, { statusCode }, "");
|
|
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
});
|
|
|
|
return it("should return the callback with an error", function() {
|
|
this.callback.called.should.equal(true);
|
|
const err = this.callback.getCall(0).args[0];
|
|
err.should.have.property('statusCode', statusCode);
|
|
err.should.have.property('message', "doc updater could not load requested ops");
|
|
this.logger.error.called.should.equal(false);
|
|
return this.logger.warn.called.should.equal(true);
|
|
});
|
|
}));
|
|
|
|
return describe("when the document updater returns a failure error code", function() {
|
|
beforeEach(function() {
|
|
this.request.get = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "");
|
|
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
|
});
|
|
|
|
return it("should return the callback with an error", function() {
|
|
this.callback.called.should.equal(true);
|
|
const err = this.callback.getCall(0).args[0];
|
|
err.should.have.property('statusCode', 500);
|
|
err.should.have.property('message', "doc updater returned a non-success status code: 500");
|
|
return this.logger.error.called.should.equal(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('flushProjectToMongoAndDelete', function() {
|
|
beforeEach(function() {
|
|
return this.callback = sinon.stub();
|
|
});
|
|
|
|
describe("successfully", function() {
|
|
beforeEach(function() {
|
|
this.request.del = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "");
|
|
return this.DocumentUpdaterManager.flushProjectToMongoAndDelete(this.project_id, this.callback);
|
|
});
|
|
|
|
it('should delete the project from the document updater', function() {
|
|
const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}?background=true`;
|
|
return this.request.del.calledWith(url).should.equal(true);
|
|
});
|
|
|
|
return it("should call the callback with no error", function() {
|
|
return this.callback.calledWith(null).should.equal(true);
|
|
});
|
|
});
|
|
|
|
describe("when the document updater API returns an error", function() {
|
|
beforeEach(function() {
|
|
this.request.del = sinon.stub().callsArgWith(1, (this.error = new Error("something went wrong")), null, null);
|
|
return this.DocumentUpdaterManager.flushProjectToMongoAndDelete(this.project_id, this.callback);
|
|
});
|
|
|
|
return it("should return an error to the callback", function() {
|
|
return this.callback.calledWith(this.error).should.equal(true);
|
|
});
|
|
});
|
|
|
|
return describe("when the document updater returns a failure error code", function() {
|
|
beforeEach(function() {
|
|
this.request.del = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "");
|
|
return this.DocumentUpdaterManager.flushProjectToMongoAndDelete(this.project_id, this.callback);
|
|
});
|
|
|
|
return it("should return the callback with an error", function() {
|
|
this.callback.called.should.equal(true);
|
|
const err = this.callback.getCall(0).args[0];
|
|
err.should.have.property('statusCode', 500);
|
|
return err.should.have.property('message', "document updater returned a failure status code: 500");
|
|
});
|
|
});
|
|
});
|
|
|
|
return describe('queueChange', function() {
|
|
beforeEach(function() {
|
|
this.change = {
|
|
"doc":"1234567890",
|
|
"op":[{"d":"test", "p":345}],
|
|
"v": 789
|
|
};
|
|
this.rclient.rpush = sinon.stub().yields();
|
|
return this.callback = sinon.stub();
|
|
});
|
|
|
|
describe("successfully", function() {
|
|
beforeEach(function() {
|
|
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
|
});
|
|
|
|
it("should push the change", function() {
|
|
return this.rclient.rpush
|
|
.calledWith(`PendingUpdates:${this.doc_id}`, JSON.stringify(this.change))
|
|
.should.equal(true);
|
|
});
|
|
|
|
return it("should notify the doc updater of the change via the pending-updates-list queue", function() {
|
|
return this.rclient.rpush
|
|
.calledWith("pending-updates-list", `${this.project_id}:${this.doc_id}`)
|
|
.should.equal(true);
|
|
});
|
|
});
|
|
|
|
describe("with error talking to redis during rpush", function() {
|
|
beforeEach(function() {
|
|
this.rclient.rpush = sinon.stub().yields(new Error("something went wrong"));
|
|
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
|
});
|
|
|
|
return it("should return an error", function() {
|
|
return this.callback.calledWithExactly(sinon.match(Error)).should.equal(true);
|
|
});
|
|
});
|
|
|
|
describe("with null byte corruption", function() {
|
|
beforeEach(function() {
|
|
this.JSON.stringify = () => '["bad bytes! \u0000 <- here"]';
|
|
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
|
});
|
|
|
|
it("should return an error", function() {
|
|
return this.callback.calledWithExactly(sinon.match(Error)).should.equal(true);
|
|
});
|
|
|
|
return it("should not push the change onto the pending-updates-list queue", function() {
|
|
return this.rclient.rpush.called.should.equal(false);
|
|
});
|
|
});
|
|
|
|
describe("when the update is too large", function() {
|
|
beforeEach(function() {
|
|
this.change = {op: {p: 12,t: "update is too large".repeat(1024 * 400)}};
|
|
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
|
});
|
|
|
|
it("should return an error", function() {
|
|
return this.callback.calledWithExactly(sinon.match(Error)).should.equal(true);
|
|
});
|
|
|
|
it("should add the size to the error", function() {
|
|
return this.callback.args[0][0].updateSize.should.equal(7782422);
|
|
});
|
|
|
|
return it("should not push the change onto the pending-updates-list queue", function() {
|
|
return this.rclient.rpush.called.should.equal(false);
|
|
});
|
|
});
|
|
|
|
return describe("with invalid keys", function() {
|
|
beforeEach(function() {
|
|
this.change = {
|
|
"op":[{"d":"test", "p":345}],
|
|
"version": 789 // not a valid key
|
|
};
|
|
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
|
});
|
|
|
|
return it("should remove the invalid keys from the change", function() {
|
|
return this.rclient.rpush
|
|
.calledWith(`PendingUpdates:${this.doc_id}`, JSON.stringify({op:this.change.op}))
|
|
.should.equal(true);
|
|
});
|
|
});
|
|
});
|
|
});
|