ShareJsUpdateManagerTests.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /* eslint-disable
  2. no-return-assign,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  11. */
  12. const sinon = require('sinon');
  13. const chai = require('chai');
  14. const should = chai.should();
  15. const modulePath = "../../../../app/js/ShareJsUpdateManager.js";
  16. const SandboxedModule = require('sandboxed-module');
  17. const crypto = require('crypto');
  18. describe("ShareJsUpdateManager", function() {
  19. beforeEach(function() {
  20. let Model;
  21. this.project_id = "project-id-123";
  22. this.doc_id = "document-id-123";
  23. this.callback = sinon.stub();
  24. return this.ShareJsUpdateManager = SandboxedModule.require(modulePath, {
  25. requires: {
  26. "./sharejs/server/model":
  27. (Model = class Model {
  28. constructor(db) {
  29. this.db = db;
  30. }
  31. }),
  32. "./ShareJsDB" : (this.ShareJsDB = { mockDB: true }),
  33. "redis-sharelatex" : { createClient: () => { return this.rclient = {auth() {}}; }
  34. },
  35. "logger-sharelatex": (this.logger = { log: sinon.stub() }),
  36. "./RealTimeRedisManager": (this.RealTimeRedisManager = {}),
  37. "./Metrics": (this.metrics = { inc: sinon.stub() })
  38. },
  39. globals: {
  40. clearTimeout: (this.clearTimeout = sinon.stub())
  41. }
  42. }
  43. );
  44. });
  45. describe("applyUpdate", function() {
  46. beforeEach(function() {
  47. this.lines = ["one", "two"];
  48. this.version = 34;
  49. this.updatedDocLines = ["onefoo", "two"];
  50. const content = this.updatedDocLines.join("\n");
  51. this.hash = crypto.createHash('sha1').update("blob " + content.length + "\x00").update(content, 'utf8').digest('hex');
  52. this.update = {p: 4, t: "foo", v:this.version, hash:this.hash};
  53. this.model = {
  54. applyOp: sinon.stub().callsArg(2),
  55. getSnapshot: sinon.stub(),
  56. db: {
  57. appliedOps: {}
  58. }
  59. };
  60. this.ShareJsUpdateManager.getNewShareJsModel = sinon.stub().returns(this.model);
  61. this.ShareJsUpdateManager._listenForOps = sinon.stub();
  62. return this.ShareJsUpdateManager.removeDocFromCache = sinon.stub().callsArg(1);
  63. });
  64. describe("successfully", function() {
  65. beforeEach(function(done) {
  66. this.model.getSnapshot.callsArgWith(1, null, {snapshot: this.updatedDocLines.join("\n"), v: this.version});
  67. this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] = (this.appliedOps = ["mock-ops"]);
  68. return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version, appliedOps) => {
  69. this.callback(err, docLines, version, appliedOps);
  70. return done();
  71. });
  72. });
  73. it("should create a new ShareJs model", function() {
  74. return this.ShareJsUpdateManager.getNewShareJsModel
  75. .calledWith(this.project_id, this.doc_id, this.lines, this.version)
  76. .should.equal(true);
  77. });
  78. it("should listen for ops on the model", function() {
  79. return this.ShareJsUpdateManager._listenForOps
  80. .calledWith(this.model)
  81. .should.equal(true);
  82. });
  83. it("should send the update to ShareJs", function() {
  84. return this.model.applyOp
  85. .calledWith(`${this.project_id}:${this.doc_id}`, this.update)
  86. .should.equal(true);
  87. });
  88. it("should get the updated doc lines", function() {
  89. return this.model.getSnapshot
  90. .calledWith(`${this.project_id}:${this.doc_id}`)
  91. .should.equal(true);
  92. });
  93. return it("should return the updated doc lines, version and ops", function() {
  94. return this.callback.calledWith(null, this.updatedDocLines, this.version, this.appliedOps).should.equal(true);
  95. });
  96. });
  97. describe("when applyOp fails", function() {
  98. beforeEach(function(done) {
  99. this.error = new Error("Something went wrong");
  100. this.model.applyOp = sinon.stub().callsArgWith(2, this.error);
  101. return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version) => {
  102. this.callback(err, docLines, version);
  103. return done();
  104. });
  105. });
  106. return it("should call the callback with the error", function() {
  107. return this.callback.calledWith(this.error).should.equal(true);
  108. });
  109. });
  110. describe("when getSnapshot fails", function() {
  111. beforeEach(function(done) {
  112. this.error = new Error("Something went wrong");
  113. this.model.getSnapshot.callsArgWith(1, this.error);
  114. return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version) => {
  115. this.callback(err, docLines, version);
  116. return done();
  117. });
  118. });
  119. return it("should call the callback with the error", function() {
  120. return this.callback.calledWith(this.error).should.equal(true);
  121. });
  122. });
  123. return describe("with an invalid hash", function() {
  124. beforeEach(function(done) {
  125. this.error = new Error("invalid hash");
  126. this.model.getSnapshot.callsArgWith(1, null, {snapshot: "unexpected content", v: this.version});
  127. this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] = (this.appliedOps = ["mock-ops"]);
  128. return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version, appliedOps) => {
  129. this.callback(err, docLines, version, appliedOps);
  130. return done();
  131. });
  132. });
  133. return it("should call the callback with the error", function() {
  134. return this.callback.calledWith(this.error).should.equal(true);
  135. });
  136. });
  137. });
  138. return describe("_listenForOps", function() {
  139. beforeEach(function() {
  140. this.model = { on: (event, callback) => {
  141. return this.callback = callback;
  142. }
  143. };
  144. sinon.spy(this.model, "on");
  145. return this.ShareJsUpdateManager._listenForOps(this.model);
  146. });
  147. it("should listen to the model for updates", function() {
  148. return this.model.on.calledWith("applyOp")
  149. .should.equal(true);
  150. });
  151. return describe("the callback", function() {
  152. beforeEach(function() {
  153. this.opData = {
  154. op: {t: "foo", p: 1},
  155. meta: { source: "bar"
  156. }
  157. };
  158. this.RealTimeRedisManager.sendData = sinon.stub();
  159. return this.callback(`${this.project_id}:${this.doc_id}`, this.opData);
  160. });
  161. return it("should publish the op to redis", function() {
  162. return this.RealTimeRedisManager.sendData
  163. .calledWith({project_id: this.project_id, doc_id: this.doc_id, op: this.opData})
  164. .should.equal(true);
  165. });
  166. });
  167. });
  168. });