mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
ee85d948e2
GitOrigin-RevId: ef2ef77e26df59d1af3df6dc664e284d3c70102d
856 lines
22 KiB
JavaScript
856 lines
22 KiB
JavaScript
/* eslint-disable
|
|
camelcase,
|
|
no-undef,
|
|
no-unused-vars,
|
|
*/
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
// Fix any style issues and re-enable lint.
|
|
/*
|
|
* decaffeinate suggestions:
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* DS207: Consider shorter variations of null checks
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
*/
|
|
import sinon from 'sinon'
|
|
import { expect } from 'chai'
|
|
import Settings from '@overleaf/settings'
|
|
import request from 'request'
|
|
import assert from 'assert'
|
|
import Path from 'path'
|
|
import crypto from 'crypto'
|
|
import { ObjectId } from 'mongodb'
|
|
import nock from 'nock'
|
|
import * as ProjectHistoryClient from './helpers/ProjectHistoryClient.js'
|
|
import * as ProjectHistoryApp from './helpers/ProjectHistoryApp.js'
|
|
import * as HistoryId from './helpers/HistoryId.js'
|
|
|
|
const MockHistoryStore = () => nock('http://localhost:3100')
|
|
const MockFileStore = () => nock('http://localhost:3009')
|
|
const MockWeb = () => nock('http://localhost:3000')
|
|
|
|
const sha = data => crypto.createHash('sha1').update(data).digest('hex')
|
|
|
|
describe('FileTree Diffs', function () {
|
|
beforeEach(function (done) {
|
|
return ProjectHistoryApp.ensureRunning(error => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
|
|
this.historyId = ObjectId().toString()
|
|
this.projectId = 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 } },
|
|
})
|
|
|
|
return ProjectHistoryClient.initializeProject(
|
|
this.historyId,
|
|
(error, ol_project) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
afterEach(function () {
|
|
return nock.cleanAll()
|
|
})
|
|
|
|
it('should return a diff of the updates to a doc from a single chunk', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/7/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'foo.tex': {
|
|
hash: sha('mock-sha-foo'),
|
|
stringLength: 42,
|
|
},
|
|
'renamed.tex': {
|
|
hash: sha('mock-sha-renamed'),
|
|
stringLength: 42,
|
|
},
|
|
'deleted.tex': {
|
|
hash: sha('mock-sha-deleted'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'renamed.tex',
|
|
newPathname: 'newName.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'foo.tex',
|
|
textOperation: ['lorem ipsum'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'deleted.tex',
|
|
newPathname: '',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
file: {
|
|
hash: sha('new-sha'),
|
|
},
|
|
pathname: 'added.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
7,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
pathname: 'foo.tex',
|
|
operation: 'edited',
|
|
},
|
|
{
|
|
pathname: 'deleted.tex',
|
|
operation: 'removed',
|
|
deletedAtV: 5,
|
|
},
|
|
{
|
|
newPathname: 'newName.tex',
|
|
pathname: 'renamed.tex',
|
|
operation: 'renamed',
|
|
},
|
|
{
|
|
pathname: 'added.tex',
|
|
operation: 'added',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should return a diff of the updates to a doc across multiple chunks', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'foo.tex': {
|
|
// Updated in this chunk
|
|
hash: sha('mock-sha-foo'),
|
|
stringLength: 42,
|
|
},
|
|
'bar.tex': {
|
|
// Updated in the next chunk
|
|
hash: sha('mock-sha-bar'),
|
|
stringLength: 42,
|
|
},
|
|
'baz.tex': {
|
|
// Not updated
|
|
hash: sha('mock-sha-bar'),
|
|
stringLength: 42,
|
|
},
|
|
'renamed.tex': {
|
|
hash: sha('mock-sha-renamed'),
|
|
stringLength: 42,
|
|
},
|
|
'deleted.tex': {
|
|
hash: sha('mock-sha-deleted'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'renamed.tex',
|
|
newPathname: 'newName.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'foo.tex',
|
|
textOperation: ['lorem ipsum'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:19.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'deleted.tex',
|
|
newPathname: '',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 2,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/7/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'foo.tex': {
|
|
hash: sha('mock-sha-foo'),
|
|
stringLength: 42,
|
|
},
|
|
'baz.tex': {
|
|
hash: sha('mock-sha-bar'),
|
|
stringLength: 42,
|
|
},
|
|
'newName.tex': {
|
|
hash: sha('mock-sha-renamed'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
file: {
|
|
hash: sha('new-sha'),
|
|
},
|
|
pathname: 'added.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'bar.tex',
|
|
textOperation: ['lorem ipsum'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:23.786Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 5,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
2,
|
|
7,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
pathname: 'foo.tex',
|
|
operation: 'edited',
|
|
},
|
|
{
|
|
pathname: 'bar.tex',
|
|
operation: 'edited',
|
|
},
|
|
{
|
|
pathname: 'baz.tex',
|
|
},
|
|
{
|
|
pathname: 'deleted.tex',
|
|
operation: 'removed',
|
|
deletedAtV: 4,
|
|
},
|
|
{
|
|
newPathname: 'newName.tex',
|
|
pathname: 'renamed.tex',
|
|
operation: 'renamed',
|
|
},
|
|
{
|
|
pathname: 'added.tex',
|
|
operation: 'added',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should return a diff that includes multiple renames', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'one.tex': {
|
|
hash: sha('mock-sha'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'one.tex',
|
|
newPathname: 'two.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'two.tex',
|
|
newPathname: 'three.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
newPathname: 'three.tex',
|
|
pathname: 'one.tex',
|
|
operation: 'renamed',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should handle deleting the re-adding a file', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'one.tex': {
|
|
hash: sha('mock-sha'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'one.tex',
|
|
newPathname: '',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'one.tex',
|
|
file: {
|
|
hash: sha('mock-sha'),
|
|
},
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
pathname: 'one.tex',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should handle deleting the renaming a file to the same place', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'one.tex': {
|
|
hash: sha('mock-sha-one'),
|
|
stringLength: 42,
|
|
},
|
|
'two.tex': {
|
|
hash: sha('mock-sha-two'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'one.tex',
|
|
newPathname: '',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'two.tex',
|
|
newPathname: 'one.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
pathname: 'two.tex',
|
|
newPathname: 'one.tex',
|
|
operation: 'renamed',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should handle adding then renaming a file', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'one.tex',
|
|
file: {
|
|
hash: sha('mock-sha'),
|
|
},
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'one.tex',
|
|
newPathname: 'two.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:22.905Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
pathname: 'two.tex',
|
|
operation: 'added',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should return 422 with a chunk with an invalid rename', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/6/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'foo.tex': {
|
|
hash: sha('mock-sha-foo'),
|
|
stringLength: 42,
|
|
},
|
|
'bar.tex': {
|
|
hash: sha('mock-sha-bar'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'foo.tex',
|
|
newPathname: 'bar.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 5,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
5,
|
|
6,
|
|
(error, diff, statusCode) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(statusCode).to.equal(422)
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should return 422 with a chunk with an invalid add', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/6/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {
|
|
'foo.tex': {
|
|
hash: sha('mock-sha-foo'),
|
|
stringLength: 42,
|
|
},
|
|
},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
file: {
|
|
hash: sha('new-sha'),
|
|
},
|
|
pathname: 'foo.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 5,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
5,
|
|
6,
|
|
(error, diff, statusCode) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(statusCode).to.equal(422)
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should handle edits of missing/invalid files ', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'new.tex',
|
|
textOperation: ['lorem ipsum'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:18.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: '',
|
|
textOperation: ['lorem ipsum'],
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [
|
|
{
|
|
operation: 'edited',
|
|
pathname: 'new.tex',
|
|
},
|
|
],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('should handle deletions of missing/invalid files ', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'missing.tex',
|
|
newPathname: '',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: '',
|
|
newPathname: '',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
|
|
return it('should handle renames of missing/invalid files ', function (done) {
|
|
MockHistoryStore()
|
|
.get(`/api/projects/${this.historyId}/versions/5/history`)
|
|
.reply(200, {
|
|
chunk: {
|
|
history: {
|
|
snapshot: {
|
|
files: {},
|
|
},
|
|
changes: [
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: 'missing.tex',
|
|
newPathname: 'missing-renamed.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
{
|
|
operations: [
|
|
{
|
|
pathname: '',
|
|
newPathname: 'missing-renamed-other.tex',
|
|
},
|
|
],
|
|
timestamp: '2017-12-04T10:29:17.786Z',
|
|
authors: [31],
|
|
},
|
|
],
|
|
},
|
|
startVersion: 3,
|
|
},
|
|
authors: [{ id: 31, email: 'james.allen@overleaf.com', name: 'James' }],
|
|
})
|
|
|
|
return ProjectHistoryClient.getFileTreeDiff(
|
|
this.projectId,
|
|
3,
|
|
5,
|
|
(error, diff) => {
|
|
if (error != null) {
|
|
throw error
|
|
}
|
|
expect(diff).to.deep.equal({
|
|
diff: [],
|
|
})
|
|
return done()
|
|
}
|
|
)
|
|
})
|
|
})
|