[document-updater] add track-changes support to setDoc (#35820)

GitOrigin-RevId: 5d39dd1945e710ec3f7068830fb2476e59f0f2a5
This commit is contained in:
Jakob Ackermann
2026-09-17 11:39:12 +00:00
committed by Copybot
parent a77a4782cc
commit 00a2765d01
14 changed files with 901 additions and 34 deletions
@@ -1,12 +1,20 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { Project } from '../../../app/src/models/Project.mjs'
import ProjectGetter from '../../../app/src/Features/Project/ProjectGetter.mjs'
import ProjectOptionsHandler from '../../../app/src/Features/Project/ProjectOptionsHandler.mjs'
import ProjectRootDocManager from '../../../app/src/Features/Project/ProjectRootDocManager.mjs'
import MockDocUpdaterApiClass from './mocks/MockDocUpdaterApi.mjs'
import request from './helpers/request.js'
import User from './helpers/User.mjs'
import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.mjs'
let MockDocUpdaterApi
before(function () {
MockDocUpdaterApi = MockDocUpdaterApiClass.instance()
})
describe('TpdsUpdateTests', function () {
beforeEach(function () {
// Creating a project via TPDS schedules a root doc reset on a 30s timer,
@@ -78,6 +86,170 @@ describe('TpdsUpdateTests', function () {
})
})
describe('updating an existing file as a user', function () {
function updateMainTex(userId, done) {
request(
{
method: 'POST',
url: `/user/${userId}/update/test-project/main.tex`,
auth: {
username: 'overleaf',
password: 'password',
sendImmediately: true,
},
body: 'test one two',
},
(error, response, body) => {
if (error) {
throw error
}
expect(response.statusCode).to.equal(200)
expect(JSON.parse(response.body).status).to.equal('applied')
done()
}
)
}
describe('with track changes disabled', function () {
beforeEach(function (done) {
updateMainTex(this.owner._id, done)
})
it('should send the update to document-updater without track changes', function () {
const requests = MockDocUpdaterApi.getReceivedSetDocRequests(
this.projectId
)
expect(requests).to.have.length(1)
expect(requests[0].body.lines).to.deep.equal(['test one two'])
expect(requests[0].body.user_id).to.equal(this.owner._id.toString())
expect(requests[0].body.trackChanges).to.equal(false)
})
})
describe('with track changes enabled for the user', function () {
beforeEach(function (done) {
Project.updateOne(
{ _id: this.projectId },
{ track_changes: { [this.owner._id.toString()]: true } }
)
.then(() => updateMainTex(this.owner._id, done))
.catch(done)
})
it('should ask document-updater to record the update as tracked changes', function () {
const requests = MockDocUpdaterApi.getReceivedSetDocRequests(
this.projectId
)
expect(requests).to.have.length(1)
expect(requests[0].body.user_id).to.equal(this.owner._id.toString())
expect(requests[0].body.trackChanges).to.equal(true)
})
})
describe('with track changes enabled for another user', function () {
beforeEach(function (done) {
Project.updateOne(
{ _id: this.projectId },
{ track_changes: { '5c41deb2b4ca500153340809': true } }
)
.then(() => updateMainTex(this.owner._id, done))
.catch(done)
})
it('should not ask document-updater to record the update as tracked changes', function () {
const requests = MockDocUpdaterApi.getReceivedSetDocRequests(
this.projectId
)
expect(requests).to.have.length(1)
expect(requests[0].body.trackChanges).to.equal(false)
})
})
})
describe('updating an existing file as a user by project id', function () {
beforeEach(function (done) {
Project.updateOne(
{ _id: this.projectId },
{ track_changes: { [this.owner._id.toString()]: true } }
)
.then(() => {
request(
{
method: 'POST',
url: `/project/${this.projectId}/user/${this.owner._id}/update/main.tex`,
auth: {
username: 'overleaf',
password: 'password',
sendImmediately: true,
},
body: 'test one two',
},
(error, response, body) => {
if (error) {
throw error
}
expect(response.statusCode).to.equal(200)
expect(JSON.parse(response.body).status).to.equal('applied')
done()
}
)
})
.catch(done)
})
it('should ask document-updater to record the update as tracked changes', function () {
const requests = MockDocUpdaterApi.getReceivedSetDocRequests(
this.projectId
)
expect(requests).to.have.length(1)
expect(requests[0].body.user_id).to.equal(this.owner._id.toString())
expect(requests[0].body.trackChanges).to.equal(true)
})
})
describe('updating an existing file via the project contents route', function () {
// Updates via the project contents route (used by github-sync) carry no
// user id: the synced commits can come from any GitHub user, so they are
// never recorded as tracked changes.
beforeEach(function (done) {
Project.updateOne(
{ _id: this.projectId },
{ track_changes: { [this.owner._id.toString()]: true } }
)
.then(() => {
request(
{
method: 'POST',
url: `/project/${this.projectId}/contents/main.tex`,
auth: {
username: 'overleaf',
password: 'password',
sendImmediately: true,
},
body: 'test one two',
},
(error, response, body) => {
if (error) {
throw error
}
expect(response.statusCode).to.equal(200)
done()
}
)
})
.catch(done)
})
it('should send the update to document-updater without track changes', function () {
const requests = MockDocUpdaterApi.getReceivedSetDocRequests(
this.projectId
)
expect(requests).to.have.length(1)
expect(requests[0].body.user_id).to.equal(null)
expect(requests[0].body.trackChanges).to.equal(false)
})
})
describe('deleting a file', function () {
beforeEach(function (done) {
request(
@@ -4,6 +4,13 @@ class MockDocUpdaterApi extends AbstractMockApi {
reset() {
this.updates = {}
this.docsByProject = new Map()
this.receivedSetDocRequests = []
}
getReceivedSetDocRequests(projectId) {
return this.receivedSetDocRequests.filter(
request => request.projectId === projectId
)
}
getProjectStructureUpdates(projectId) {
@@ -63,6 +70,8 @@ class MockDocUpdaterApi extends AbstractMockApi {
)
this.app.post('/project/:projectId/doc/:doc_id', (req, res) => {
const { projectId, doc_id: docId } = req.params
this.receivedSetDocRequests.push({ projectId, docId, body: req.body })
res.sendStatus(204)
})