DiffTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import { expect } from 'chai'
  2. import request from 'request'
  3. import crypto from 'node:crypto'
  4. import mongodb from 'mongodb-legacy'
  5. import nock from 'nock'
  6. import * as ProjectHistoryClient from './helpers/ProjectHistoryClient.js'
  7. import * as ProjectHistoryApp from './helpers/ProjectHistoryApp.js'
  8. const { ObjectId } = mongodb
  9. const MockHistoryStore = () => nock('http://127.0.0.1:3100')
  10. const MockWeb = () => nock('http://127.0.0.1:3000')
  11. function createMockBlob(historyId, content) {
  12. const sha = crypto.createHash('sha1').update(content).digest('hex')
  13. MockHistoryStore()
  14. .get(`/api/projects/${historyId}/blobs/${sha}`)
  15. .reply(200, content)
  16. .persist()
  17. return sha
  18. }
  19. describe('Diffs', function () {
  20. beforeEach(function (done) {
  21. ProjectHistoryApp.ensureRunning(error => {
  22. if (error) {
  23. throw error
  24. }
  25. this.historyId = new ObjectId().toString()
  26. this.projectId = new ObjectId().toString()
  27. MockHistoryStore().post('/api/projects').reply(200, {
  28. projectId: this.historyId,
  29. })
  30. MockWeb()
  31. .get(`/project/${this.projectId}/details`)
  32. .reply(200, {
  33. name: 'Test Project',
  34. overleaf: { history: { id: this.historyId } },
  35. })
  36. ProjectHistoryClient.initializeProject(this.historyId, error => {
  37. if (error) {
  38. return done(error)
  39. }
  40. done()
  41. })
  42. })
  43. })
  44. afterEach(function () {
  45. nock.cleanAll()
  46. })
  47. it('should return a diff of the updates to a doc from a single chunk', function (done) {
  48. this.blob = 'one two three five'
  49. this.sha = createMockBlob(this.historyId, this.blob)
  50. this.v2AuthorId = '123456789'
  51. MockHistoryStore()
  52. .get(`/api/projects/${this.historyId}/versions/6/history`)
  53. .reply(200, {
  54. chunk: {
  55. history: {
  56. snapshot: {
  57. files: {
  58. 'foo.tex': {
  59. hash: this.sha,
  60. stringLength: this.blob.length,
  61. },
  62. },
  63. },
  64. changes: [
  65. {
  66. operations: [
  67. {
  68. pathname: 'foo.tex',
  69. textOperation: [13, ' four', 5],
  70. },
  71. ],
  72. timestamp: '2017-12-04T10:29:17.786Z',
  73. authors: [31],
  74. },
  75. {
  76. operations: [
  77. {
  78. pathname: 'foo.tex',
  79. textOperation: [4, -4, 15],
  80. },
  81. ],
  82. timestamp: '2017-12-04T10:29:22.905Z',
  83. authors: [31],
  84. },
  85. {
  86. operations: [
  87. {
  88. pathname: 'foo.tex',
  89. textOperation: [19, ' six'],
  90. },
  91. ],
  92. timestamp: '2017-12-04T10:29:26.120Z',
  93. v2Authors: [this.v2AuthorId],
  94. },
  95. ],
  96. },
  97. startVersion: 3,
  98. },
  99. authors: [31],
  100. })
  101. ProjectHistoryClient.getDiff(
  102. this.projectId,
  103. 'foo.tex',
  104. 3,
  105. 6,
  106. (error, diff) => {
  107. if (error) {
  108. throw error
  109. }
  110. expect(diff).to.deep.equal({
  111. diff: [
  112. {
  113. u: 'one ',
  114. },
  115. {
  116. d: 'two ',
  117. meta: {
  118. users: [31],
  119. start_ts: 1512383362905,
  120. end_ts: 1512383362905,
  121. },
  122. },
  123. {
  124. u: 'three',
  125. },
  126. {
  127. i: ' four',
  128. meta: {
  129. users: [31],
  130. start_ts: 1512383357786,
  131. end_ts: 1512383357786,
  132. },
  133. },
  134. {
  135. u: ' five',
  136. },
  137. {
  138. i: ' six',
  139. meta: {
  140. users: [this.v2AuthorId],
  141. start_ts: 1512383366120,
  142. end_ts: 1512383366120,
  143. },
  144. },
  145. ],
  146. })
  147. done()
  148. }
  149. )
  150. })
  151. it('should return a diff of the updates to a doc across multiple chunks', function (done) {
  152. MockHistoryStore()
  153. .get(`/api/projects/${this.historyId}/versions/5/history`)
  154. .reply(200, {
  155. chunk: {
  156. history: {
  157. snapshot: {
  158. files: {
  159. 'foo.tex': {
  160. hash: createMockBlob(this.historyId, 'one two three five'),
  161. stringLength: 'one three four five'.length,
  162. },
  163. },
  164. },
  165. changes: [
  166. {
  167. operations: [
  168. {
  169. pathname: 'foo.tex',
  170. textOperation: [13, ' four', 5],
  171. },
  172. ],
  173. timestamp: '2017-12-04T10:29:17.786Z',
  174. authors: [31],
  175. },
  176. {
  177. operations: [
  178. {
  179. pathname: 'foo.tex',
  180. textOperation: [4, -4, 15],
  181. },
  182. ],
  183. timestamp: '2017-12-04T10:29:22.905Z',
  184. authors: [31],
  185. },
  186. ],
  187. },
  188. startVersion: 3,
  189. },
  190. authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
  191. })
  192. MockHistoryStore()
  193. .get(`/api/projects/${this.historyId}/versions/6/history`)
  194. .reply(200, {
  195. chunk: {
  196. history: {
  197. snapshot: {
  198. files: {
  199. 'foo.tex': {
  200. hash: createMockBlob(this.historyId, 'one three four five'),
  201. stringLength: 'one three four five'.length,
  202. },
  203. },
  204. },
  205. changes: [
  206. {
  207. operations: [
  208. {
  209. pathname: 'foo.tex',
  210. textOperation: [19, ' six'],
  211. },
  212. ],
  213. timestamp: '2017-12-04T10:29:26.120Z',
  214. authors: [31],
  215. },
  216. {
  217. operations: [
  218. {
  219. pathname: 'foo.tex',
  220. textOperation: [23, ' seven'],
  221. },
  222. ],
  223. timestamp: '2017-12-04T10:29:26.120Z',
  224. authors: [31],
  225. },
  226. ],
  227. },
  228. startVersion: 5,
  229. },
  230. authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
  231. })
  232. ProjectHistoryClient.getDiff(
  233. this.projectId,
  234. 'foo.tex',
  235. 4,
  236. 6,
  237. (error, diff) => {
  238. if (error) {
  239. throw error
  240. }
  241. expect(diff).to.deep.equal({
  242. diff: [
  243. {
  244. u: 'one ',
  245. },
  246. {
  247. d: 'two ',
  248. meta: {
  249. users: [31],
  250. start_ts: 1512383362905,
  251. end_ts: 1512383362905,
  252. },
  253. },
  254. {
  255. u: 'three four five',
  256. },
  257. {
  258. i: ' six',
  259. meta: {
  260. users: [31],
  261. start_ts: 1512383366120,
  262. end_ts: 1512383366120,
  263. },
  264. },
  265. ],
  266. })
  267. done()
  268. }
  269. )
  270. })
  271. it('should return a 404 when there are no changes for the file in the range', function (done) {
  272. this.blob = 'one two three five'
  273. this.sha = createMockBlob(this.historyId, this.blob)
  274. MockHistoryStore()
  275. .get(`/api/projects/${this.historyId}/versions/6/history`)
  276. .reply(200, {
  277. chunk: {
  278. history: {
  279. snapshot: {
  280. files: {
  281. 'foo.tex': {
  282. hash: this.sha,
  283. stringLength: this.blob.length,
  284. },
  285. },
  286. },
  287. changes: [
  288. {
  289. operations: [
  290. {
  291. pathname: 'foo.tex',
  292. textOperation: [13, ' four', 5],
  293. },
  294. ],
  295. timestamp: '2017-12-04T10:29:17.786Z',
  296. authors: [31],
  297. },
  298. ],
  299. },
  300. startVersion: 3,
  301. },
  302. authors: [31],
  303. })
  304. request.get(
  305. {
  306. url: `http://127.0.0.1:3054/project/${this.projectId}/diff`,
  307. qs: {
  308. pathname: 'not_here.tex',
  309. from: 3,
  310. to: 6,
  311. },
  312. json: true,
  313. },
  314. (error, res, body) => {
  315. if (error) {
  316. throw error
  317. }
  318. expect(res.statusCode).to.equal(404)
  319. done()
  320. }
  321. )
  322. })
  323. it('should return a binary flag with a diff of a binary file', function (done) {
  324. this.blob = 'one two three five'
  325. this.sha = createMockBlob(this.historyId, this.blob)
  326. this.binaryBlob = Buffer.from([1, 2, 3, 4])
  327. this.binarySha = createMockBlob(this.historyId, this.binaryBlob)
  328. MockHistoryStore()
  329. .get(`/api/projects/${this.historyId}/versions/6/history`)
  330. .reply(200, {
  331. chunk: {
  332. history: {
  333. snapshot: {
  334. files: {
  335. 'binary.tex': {
  336. hash: this.binarySha,
  337. byteLength: this.binaryBlob.length, // Indicates binary
  338. },
  339. 'foo.tex': {
  340. hash: this.sha,
  341. stringLength: this.blob.length, // Indicates binary
  342. },
  343. },
  344. },
  345. changes: [
  346. {
  347. operations: [
  348. {
  349. pathname: 'foo.tex',
  350. textOperation: [13, ' four', 5],
  351. },
  352. ],
  353. timestamp: '2017-12-04T10:29:17.786Z',
  354. authors: [31],
  355. },
  356. {
  357. operations: [
  358. {
  359. pathname: 'foo.tex',
  360. textOperation: [4, -4, 15],
  361. },
  362. ],
  363. timestamp: '2017-12-04T10:29:22.905Z',
  364. authors: [31],
  365. },
  366. {
  367. operations: [
  368. {
  369. pathname: 'foo.tex',
  370. textOperation: [19, ' six'],
  371. },
  372. ],
  373. timestamp: '2017-12-04T10:29:26.120Z',
  374. authors: [31],
  375. },
  376. ],
  377. },
  378. startVersion: 3,
  379. },
  380. authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
  381. })
  382. ProjectHistoryClient.getDiff(
  383. this.projectId,
  384. 'binary.tex',
  385. 3,
  386. 6,
  387. (error, diff) => {
  388. if (error) {
  389. throw error
  390. }
  391. expect(diff).to.deep.equal({
  392. diff: {
  393. binary: true,
  394. },
  395. })
  396. done()
  397. }
  398. )
  399. })
  400. })