| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- import { expect } from 'chai'
- import crypto from 'node:crypto'
- import mongodb from 'mongodb-legacy'
- import nock from 'nock'
- import {
- fetchJsonWithResponse,
- RequestFailedError,
- } from '@overleaf/fetch-utils'
- import * as ProjectHistoryClient from './helpers/ProjectHistoryClient.js'
- import * as ProjectHistoryApp from './helpers/ProjectHistoryApp.js'
- const { ObjectId } = mongodb
- const MockHistoryStore = () => nock('http://127.0.0.1:3100')
- const MockWeb = () => nock('http://127.0.0.1:3000')
- function createMockBlob(historyId, content) {
- const sha = crypto.createHash('sha1').update(content).digest('hex')
- MockHistoryStore()
- .get(`/api/projects/${historyId}/blobs/${sha}`)
- .reply(200, content)
- .persist()
- return sha
- }
- describe('Diffs', function () {
- beforeEach(async function () {
- await ProjectHistoryApp.ensureRunning()
- this.historyId = new ObjectId().toString()
- this.projectId = new ObjectId().toString()
- MockHistoryStore().post('/api/projects').reply(200, {
- projectId: this.historyId,
- })
- MockWeb()
- .get(`/project/${this.projectId}/details`)
- .reply(200, {
- name: 'Test Project',
- overleaf: { history: { id: this.historyId } },
- })
- await ProjectHistoryClient.initializeProject(this.historyId)
- })
- afterEach(function () {
- nock.cleanAll()
- })
- it('should return a diff of the updates to a doc from a single chunk', async function () {
- this.blob = 'one two three five'
- this.sha = createMockBlob(this.historyId, this.blob)
- this.v2AuthorId = '123456789'
- MockHistoryStore()
- .get(`/api/projects/${this.historyId}/versions/6/history`)
- .reply(200, {
- chunk: {
- history: {
- snapshot: {
- files: {
- 'foo.tex': {
- hash: this.sha,
- stringLength: this.blob.length,
- },
- },
- },
- changes: [
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [13, ' four', 5],
- },
- ],
- timestamp: '2017-12-04T10:29:17.786Z',
- authors: [31],
- },
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [4, -4, 15],
- },
- ],
- timestamp: '2017-12-04T10:29:22.905Z',
- authors: [31],
- },
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [19, ' six'],
- },
- ],
- timestamp: '2017-12-04T10:29:26.120Z',
- v2Authors: [this.v2AuthorId],
- },
- ],
- },
- startVersion: 3,
- },
- authors: [31],
- })
- const diff = await ProjectHistoryClient.getDiff(
- this.projectId,
- 'foo.tex',
- 3,
- 6
- )
- expect(diff).to.deep.equal({
- diff: [
- {
- u: 'one ',
- },
- {
- d: 'two ',
- meta: {
- users: [31],
- start_ts: 1512383362905,
- end_ts: 1512383362905,
- },
- },
- {
- u: 'three',
- },
- {
- i: ' four',
- meta: {
- users: [31],
- start_ts: 1512383357786,
- end_ts: 1512383357786,
- },
- },
- {
- u: ' five',
- },
- {
- i: ' six',
- meta: {
- users: [this.v2AuthorId],
- start_ts: 1512383366120,
- end_ts: 1512383366120,
- },
- },
- ],
- })
- })
- it('should return a diff of the updates to a doc across multiple chunks', async function () {
- MockHistoryStore()
- .get(`/api/projects/${this.historyId}/versions/5/history`)
- .reply(200, {
- chunk: {
- history: {
- snapshot: {
- files: {
- 'foo.tex': {
- hash: createMockBlob(this.historyId, 'one two three five'),
- stringLength: 'one three four five'.length,
- },
- },
- },
- changes: [
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [13, ' four', 5],
- },
- ],
- timestamp: '2017-12-04T10:29:17.786Z',
- authors: [31],
- },
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [4, -4, 15],
- },
- ],
- timestamp: '2017-12-04T10:29:22.905Z',
- authors: [31],
- },
- ],
- },
- startVersion: 3,
- },
- authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
- })
- MockHistoryStore()
- .get(`/api/projects/${this.historyId}/versions/6/history`)
- .reply(200, {
- chunk: {
- history: {
- snapshot: {
- files: {
- 'foo.tex': {
- hash: createMockBlob(this.historyId, 'one three four five'),
- stringLength: 'one three four five'.length,
- },
- },
- },
- changes: [
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [19, ' six'],
- },
- ],
- timestamp: '2017-12-04T10:29:26.120Z',
- authors: [31],
- },
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [23, ' seven'],
- },
- ],
- timestamp: '2017-12-04T10:29:26.120Z',
- authors: [31],
- },
- ],
- },
- startVersion: 5,
- },
- authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
- })
- const diff = await ProjectHistoryClient.getDiff(
- this.projectId,
- 'foo.tex',
- 4,
- 6
- )
- expect(diff).to.deep.equal({
- diff: [
- {
- u: 'one ',
- },
- {
- d: 'two ',
- meta: {
- users: [31],
- start_ts: 1512383362905,
- end_ts: 1512383362905,
- },
- },
- {
- u: 'three four five',
- },
- {
- i: ' six',
- meta: {
- users: [31],
- start_ts: 1512383366120,
- end_ts: 1512383366120,
- },
- },
- ],
- })
- })
- it('should return a 404 when there are no changes for the file in the range', async function () {
- this.blob = 'one two three five'
- this.sha = createMockBlob(this.historyId, this.blob)
- MockHistoryStore()
- .get(`/api/projects/${this.historyId}/versions/6/history`)
- .reply(200, {
- chunk: {
- history: {
- snapshot: {
- files: {
- 'foo.tex': {
- hash: this.sha,
- stringLength: this.blob.length,
- },
- },
- },
- changes: [
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [13, ' four', 5],
- },
- ],
- timestamp: '2017-12-04T10:29:17.786Z',
- authors: [31],
- },
- ],
- },
- startVersion: 3,
- },
- authors: [31],
- })
- try {
- await fetchJsonWithResponse(
- `http://127.0.0.1:3054/project/${this.projectId}/diff?pathname=not_here.tex&from=3&to=6`
- )
- expect.fail('Expected a 404 error')
- } catch (error) {
- expect(error).to.be.instanceOf(RequestFailedError)
- expect(error.response.status).to.equal(404)
- }
- })
- it('should return a binary flag with a diff of a binary file', async function () {
- this.blob = 'one two three five'
- this.sha = createMockBlob(this.historyId, this.blob)
- this.binaryBlob = Buffer.from([1, 2, 3, 4])
- this.binarySha = createMockBlob(this.historyId, this.binaryBlob)
- MockHistoryStore()
- .get(`/api/projects/${this.historyId}/versions/6/history`)
- .reply(200, {
- chunk: {
- history: {
- snapshot: {
- files: {
- 'binary.tex': {
- hash: this.binarySha,
- byteLength: this.binaryBlob.length, // Indicates binary
- },
- 'foo.tex': {
- hash: this.sha,
- stringLength: this.blob.length, // Indicates binary
- },
- },
- },
- changes: [
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [13, ' four', 5],
- },
- ],
- timestamp: '2017-12-04T10:29:17.786Z',
- authors: [31],
- },
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [4, -4, 15],
- },
- ],
- timestamp: '2017-12-04T10:29:22.905Z',
- authors: [31],
- },
- {
- operations: [
- {
- pathname: 'foo.tex',
- textOperation: [19, ' six'],
- },
- ],
- timestamp: '2017-12-04T10:29:26.120Z',
- authors: [31],
- },
- ],
- },
- startVersion: 3,
- },
- authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
- })
- const diff = await ProjectHistoryClient.getDiff(
- this.projectId,
- 'binary.tex',
- 3,
- 6
- )
- expect(diff).to.deep.equal({
- diff: {
- binary: true,
- },
- })
- })
- })
|