HistoryManagerTests.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. const { expect } = require('chai')
  2. const sinon = require('sinon')
  3. const SandboxedModule = require('sandboxed-module')
  4. const { ObjectId } = require('mongodb')
  5. const MODULE_PATH = '../../../../app/src/Features/History/HistoryManager'
  6. describe('HistoryManager', function () {
  7. beforeEach(function () {
  8. this.user_id = 'user-id-123'
  9. this.historyId = new ObjectId().toString()
  10. this.AuthenticationController = {
  11. getLoggedInUserId: sinon.stub().returns(this.user_id),
  12. }
  13. this.FetchUtils = {
  14. fetchJson: sinon.stub(),
  15. fetchNothing: sinon.stub().resolves(),
  16. }
  17. this.projectHistoryUrl = 'http://project_history.example.com'
  18. this.v1HistoryUrl = 'http://v1_history.example.com'
  19. this.v1HistoryUser = 'system'
  20. this.v1HistoryPassword = 'verysecret'
  21. this.settings = {
  22. apis: {
  23. project_history: {
  24. url: this.projectHistoryUrl,
  25. },
  26. v1_history: {
  27. url: this.v1HistoryUrl,
  28. user: this.v1HistoryUser,
  29. pass: this.v1HistoryPassword,
  30. },
  31. },
  32. }
  33. this.UserGetter = {
  34. promises: {
  35. getUsersByV1Ids: sinon.stub(),
  36. getUsers: sinon.stub(),
  37. },
  38. }
  39. this.project = {
  40. overleaf: {
  41. history: {
  42. id: this.historyId,
  43. },
  44. },
  45. }
  46. this.ProjectGetter = {
  47. promises: {
  48. getProject: sinon.stub().resolves(this.project),
  49. },
  50. }
  51. this.HistoryManager = SandboxedModule.require(MODULE_PATH, {
  52. requires: {
  53. '@overleaf/fetch-utils': this.FetchUtils,
  54. '@overleaf/settings': this.settings,
  55. '../User/UserGetter': this.UserGetter,
  56. '../Project/ProjectGetter': this.ProjectGetter,
  57. },
  58. })
  59. })
  60. describe('initializeProject', function () {
  61. beforeEach(function () {
  62. this.settings.apis.project_history.initializeHistoryForNewProjects = true
  63. })
  64. describe('project history returns a successful response', function () {
  65. beforeEach(async function () {
  66. this.FetchUtils.fetchJson.resolves({ project: { id: this.historyId } })
  67. this.result = await this.HistoryManager.promises.initializeProject(
  68. this.historyId
  69. )
  70. })
  71. it('should call the project history api', function () {
  72. this.FetchUtils.fetchJson.should.have.been.calledWithMatch(
  73. `${this.settings.apis.project_history.url}/project`,
  74. { method: 'POST' }
  75. )
  76. })
  77. it('should return the overleaf id', function () {
  78. expect(this.result).to.equal(this.historyId)
  79. })
  80. })
  81. describe('project history returns a response without the project id', function () {
  82. it('should throw an error', async function () {
  83. this.FetchUtils.fetchJson.resolves({ project: {} })
  84. await expect(
  85. this.HistoryManager.promises.initializeProject(this.historyId)
  86. ).to.be.rejected
  87. })
  88. })
  89. describe('project history errors', function () {
  90. it('should propagate the error', async function () {
  91. this.FetchUtils.fetchJson.rejects(new Error('problem connecting'))
  92. await expect(
  93. this.HistoryManager.promises.initializeProject(this.historyId)
  94. ).to.be.rejected
  95. })
  96. })
  97. })
  98. describe('injectUserDetails', function () {
  99. beforeEach(function () {
  100. this.user1 = {
  101. _id: (this.user_id1 = '123456'),
  102. first_name: 'Jane',
  103. last_name: 'Doe',
  104. email: 'jane@example.com',
  105. overleaf: { id: 5011 },
  106. }
  107. this.user1_view = {
  108. id: this.user_id1,
  109. first_name: 'Jane',
  110. last_name: 'Doe',
  111. email: 'jane@example.com',
  112. }
  113. this.user2 = {
  114. _id: (this.user_id2 = 'abcdef'),
  115. first_name: 'John',
  116. last_name: 'Doe',
  117. email: 'john@example.com',
  118. }
  119. this.user2_view = {
  120. id: this.user_id2,
  121. first_name: 'John',
  122. last_name: 'Doe',
  123. email: 'john@example.com',
  124. }
  125. this.UserGetter.promises.getUsersByV1Ids.resolves([this.user1])
  126. this.UserGetter.promises.getUsers.resolves([this.user1, this.user2])
  127. })
  128. describe('with a diff', function () {
  129. it('should turn user_ids into user objects', async function () {
  130. const diff = await this.HistoryManager.promises.injectUserDetails({
  131. diff: [
  132. {
  133. i: 'foo',
  134. meta: {
  135. users: [this.user_id1],
  136. },
  137. },
  138. {
  139. i: 'bar',
  140. meta: {
  141. users: [this.user_id2],
  142. },
  143. },
  144. ],
  145. })
  146. expect(diff.diff[0].meta.users).to.deep.equal([this.user1_view])
  147. expect(diff.diff[1].meta.users).to.deep.equal([this.user2_view])
  148. })
  149. it('should handle v1 user ids', async function () {
  150. const diff = await this.HistoryManager.promises.injectUserDetails({
  151. diff: [
  152. {
  153. i: 'foo',
  154. meta: {
  155. users: [5011],
  156. },
  157. },
  158. {
  159. i: 'bar',
  160. meta: {
  161. users: [this.user_id2],
  162. },
  163. },
  164. ],
  165. })
  166. expect(diff.diff[0].meta.users).to.deep.equal([this.user1_view])
  167. expect(diff.diff[1].meta.users).to.deep.equal([this.user2_view])
  168. })
  169. it('should leave user objects', async function () {
  170. const diff = await this.HistoryManager.promises.injectUserDetails({
  171. diff: [
  172. {
  173. i: 'foo',
  174. meta: {
  175. users: [this.user1_view],
  176. },
  177. },
  178. {
  179. i: 'bar',
  180. meta: {
  181. users: [this.user_id2],
  182. },
  183. },
  184. ],
  185. })
  186. expect(diff.diff[0].meta.users).to.deep.equal([this.user1_view])
  187. expect(diff.diff[1].meta.users).to.deep.equal([this.user2_view])
  188. })
  189. it('should handle a binary diff marker', async function () {
  190. const diff = await this.HistoryManager.promises.injectUserDetails({
  191. diff: { binary: true },
  192. })
  193. expect(diff.diff.binary).to.be.true
  194. })
  195. })
  196. describe('with a list of updates', function () {
  197. it('should turn user_ids into user objects', async function () {
  198. const updates = await this.HistoryManager.promises.injectUserDetails({
  199. updates: [
  200. {
  201. fromV: 5,
  202. toV: 8,
  203. meta: {
  204. users: [this.user_id1],
  205. },
  206. },
  207. {
  208. fromV: 4,
  209. toV: 5,
  210. meta: {
  211. users: [this.user_id2],
  212. },
  213. },
  214. ],
  215. })
  216. expect(updates.updates[0].meta.users).to.deep.equal([this.user1_view])
  217. expect(updates.updates[1].meta.users).to.deep.equal([this.user2_view])
  218. })
  219. it('should leave user objects', async function () {
  220. const updates = await this.HistoryManager.promises.injectUserDetails({
  221. updates: [
  222. {
  223. fromV: 5,
  224. toV: 8,
  225. meta: {
  226. users: [this.user1_view],
  227. },
  228. },
  229. {
  230. fromV: 4,
  231. toV: 5,
  232. meta: {
  233. users: [this.user_id2],
  234. },
  235. },
  236. ],
  237. })
  238. expect(updates.updates[0].meta.users).to.deep.equal([this.user1_view])
  239. expect(updates.updates[1].meta.users).to.deep.equal([this.user2_view])
  240. })
  241. })
  242. })
  243. describe('deleteProject', function () {
  244. const projectId = new ObjectId()
  245. const historyId = new ObjectId()
  246. beforeEach(async function () {
  247. await this.HistoryManager.promises.deleteProject(projectId, historyId)
  248. })
  249. it('should call the project-history service', async function () {
  250. expect(this.FetchUtils.fetchNothing).to.have.been.calledWith(
  251. `${this.projectHistoryUrl}/project/${projectId}`,
  252. { method: 'DELETE' }
  253. )
  254. })
  255. it('should call the v1-history service', async function () {
  256. expect(this.FetchUtils.fetchNothing).to.have.been.calledWith(
  257. `${this.v1HistoryUrl}/projects/${historyId}`,
  258. {
  259. method: 'DELETE',
  260. basicAuth: {
  261. user: this.v1HistoryUser,
  262. password: this.v1HistoryPassword,
  263. },
  264. }
  265. )
  266. })
  267. })
  268. })