[document-updater] address zod violations (#36147)

* [editor-core] address zod violation for group_id

* [monorepo] use discriminated union in zod schemas where possible

* [editor-core] v1_source_doc_id is a number

GitOrigin-RevId: 38f591b07cca4e22954bfe30bf855545a6af675d
This commit is contained in:
Jakob Ackermann
2026-09-17 11:46:42 +00:00
committed by Copybot
parent 7895541382
commit 0776357d1a
5 changed files with 201 additions and 9 deletions
@@ -651,7 +651,11 @@ const updateProjectSchema = z.object({
body: z.strictObject({
projectHistoryId: z.union([z.number(), zz.projectHistoryId()]).optional(),
userId: zz.objectId().nullish(),
updates: z.array(renameUpdateSchema.or(addUpdateSchema)).default([]),
updates: z
.array(
z.discriminatedUnion('type', [renameUpdateSchema, addUpdateSchema])
)
.default([]),
version: z.union([z.number(), z.string()]),
// `source` is polymorphic (see Utils.extractOriginOrSource): either a
// plain descriptive string (e.g. 'editor' for live user edits) or the
@@ -680,7 +684,12 @@ const updateProjectFallbackSchema = z.object({
projectHistoryId: z.union([z.number(), z.string()]).optional(),
userId: z.string().nullish(),
updates: z
.array(renameUpdateFallbackSchema.or(addUpdateFallbackSchema))
.array(
z.discriminatedUnion('type', [
renameUpdateFallbackSchema,
addUpdateFallbackSchema,
])
)
.default([]),
version: z.union([z.number(), z.string()]),
// loosened equivalent of editorCoreSchemas.rawOrigin -- see the same
@@ -533,6 +533,54 @@ describe("Applying updates to a project's structure", function () {
})
})
describe('with linked-file metadata for a personal reference library', function () {
before(async function () {
this.project_id = DocUpdaterClient.randomId()
this.metadata = {
provider: 'zotero',
format: 'bibtex',
// web persists the absent group as null, see the rawLinkedFileData
// comment in overleaf-editor-core
group_id: null,
importer_id: this.user_id,
importedAt: new Date().toISOString(),
}
this.fileUpdate = {
type: 'add-file',
id: DocUpdaterClient.randomId(),
pathname: '/references.bib',
url: 'filestore.example.com',
metadata: this.metadata,
}
this.updates = [this.fileUpdate]
await sendProjectUpdateAndWait(
this.project_id,
this.user_id,
this.updates,
this.version
)
})
it('should push the file addition to the project history api with the metadata', function (done) {
rclientProjectHistory.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error) {
return done(error)
}
const update = JSON.parse(updates[0])
update.file.should.equal(this.fileUpdate.id)
expect(update.metadata).to.deep.equal(this.metadata)
done()
}
)
})
})
describe('with invalid metadata', function () {
before(async function () {
this.project_id = DocUpdaterClient.randomId()
@@ -1162,6 +1162,47 @@ describe('HttpController', function () {
})
})
describe('when an add-file update has linked-file metadata with a null group_id', function () {
beforeEach(async function () {
this.req.body.updates[3].metadata = {
provider: 'zotero',
format: 'bibtex',
group_id: null,
importer_id: this.userId,
importedAt: '2024-01-01T00:00:00.000Z',
}
await this.HttpController.updateProject(this.req, this.res, this.next)
})
it('should accept the change', function () {
this.ProjectManager.promises.updateProjectWithLocks.should.have.been.calledWith(
this.project_id,
this.projectHistoryId,
this.userId,
this.updates,
this.version,
this.source
)
})
})
describe('when an update has an unrecognized type', function () {
beforeEach(async function () {
this.req.body.updates[3].type = 'add-folder'
await this.HttpController.updateProject(this.req, this.res, this.next)
})
it('should call next with the error', function () {
this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
})
it('should not update the project', function () {
this.ProjectManager.promises.updateProjectWithLocks.called.should.equal(
false
)
})
})
describe('when an add-file update has a malformed hash', function () {
beforeEach(async function () {
this.req.body.updates[3].hash = 'not-a-valid-hash'