DiffTests.js 11 KB

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