[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
+13 -6
View File
@@ -28,7 +28,9 @@ const rawRetainOp = z.union([
z.strictObject({
r: z.number().int().min(1),
commentIds: z.array(zz.objectId()).optional(),
tracking: rawTrackingProps.or(rawClearTrackingProps).optional(),
tracking: z
.discriminatedUnion('type', [rawTrackingProps, rawClearTrackingProps])
.optional(),
}),
z.number().int().min(1),
])
@@ -101,14 +103,14 @@ const rawLinkedFileData = z.discriminatedUnion('provider', [
z.strictObject({
provider: z.literal('project_file'),
source_project_id: zz.objectId().optional(),
v1_source_doc_id: z.string().optional(),
v1_source_doc_id: z.number().optional(),
source_entity_path: z.string(),
importedAt: z.iso.datetime().optional(),
}),
z.strictObject({
provider: z.literal('project_output_file'),
source_project_id: zz.objectId().optional(),
v1_source_doc_id: z.string().optional(),
v1_source_doc_id: z.number().optional(),
source_output_file_path: z.string(),
build_id: zz.buildId().optional(),
clsiServerId: z.string().optional(),
@@ -116,7 +118,12 @@ const rawLinkedFileData = z.discriminatedUnion('provider', [
}),
z.strictObject({
provider: z.literal('mendeley'),
group_id: zz.routeSegment().optional(),
// null, not just absent, for an import from a personal library: the web
// agents always build the data object with a group_id key, so no group
// means `group_id: undefined`, and mongoose keeps that key when $set-ing
// the Mixed linkedFileData (upserting over an existing file), where the
// driver serializes it as null. Same for the zotero/papers branches.
group_id: zz.routeSegment().nullish(),
importer_id: z.string().optional(),
v1_importer_id: z.number().optional(),
importedAt: z.iso.datetime().optional(),
@@ -126,14 +133,14 @@ const rawLinkedFileData = z.discriminatedUnion('provider', [
// absent on files imported before the `format` field existed; agents
// default this to 'bibtex' (ZoteroAgent `_getFormat`)
format: z.enum(['bibtex', 'biblatex']).optional(),
group_id: zz.routeSegment().optional(),
group_id: zz.routeSegment().nullish(),
importer_id: z.string().optional(),
v1_importer_id: z.number().optional(),
importedAt: z.iso.datetime().optional(),
}),
z.strictObject({
provider: z.literal('papers'),
group_id: zz.routeSegment().optional(),
group_id: zz.routeSegment().nullish(),
importer_id: z.string().optional(),
v1_importer_id: z.number().optional(),
importedAt: z.iso.datetime().optional(),
@@ -1,7 +1,58 @@
const { expect } = require('chai')
const { rawLinkedFileData, rawFileMetadata } = require('../../lib/schemas')
const {
rawLinkedFileData,
rawFileMetadata,
rawRetainOp,
} = require('../../lib/schemas')
describe('schemas', function () {
describe('rawRetainOp', function () {
it('accepts a bare retain length', function () {
const result = rawRetainOp.safeParse(5)
expect(result.success).to.equal(true)
})
it('accepts a retain with tracked-insert props', function () {
const result = rawRetainOp.safeParse({
r: 5,
tracking: {
type: 'insert',
userId: '507f1f77bcf86cd799439011',
ts: '2024-01-01T00:00:00.000Z',
},
})
expect(result.success).to.equal(true)
})
it('accepts a retain with clear-tracking props', function () {
const result = rawRetainOp.safeParse({
r: 5,
tracking: { type: 'none' },
})
expect(result.success).to.equal(true)
})
it('rejects a retain with an unrecognized tracking type', function () {
const result = rawRetainOp.safeParse({
r: 5,
tracking: { type: 'not-a-tracking-type' },
})
expect(result.success).to.equal(false)
})
it('rejects a retain with a malformed tracking userId', function () {
const result = rawRetainOp.safeParse({
r: 5,
tracking: {
type: 'insert',
userId: 'not-an-object-id',
ts: '2024-01-01T00:00:00.000Z',
},
})
expect(result.success).to.equal(false)
})
})
describe('rawLinkedFileData', function () {
it('rejects an empty object', function () {
const result = rawLinkedFileData.safeParse({})
@@ -41,6 +92,15 @@ describe('schemas', function () {
expect(result.success).to.equal(true)
})
it('accepts a project_file provider with a v1 source doc id', function () {
const result = rawLinkedFileData.safeParse({
provider: 'project_file',
v1_source_doc_id: 1234,
source_entity_path: '/main.tex',
})
expect(result.success).to.equal(true)
})
it('rejects a project_file provider with a malformed source_project_id', function () {
const result = rawLinkedFileData.safeParse({
provider: 'project_file',
@@ -68,6 +128,15 @@ describe('schemas', function () {
expect(result.success).to.equal(false)
})
it('accepts a project_output_file provider with a v1 source doc id', function () {
const result = rawLinkedFileData.safeParse({
provider: 'project_output_file',
v1_source_doc_id: 1234,
source_output_file_path: 'output.pdf',
})
expect(result.success).to.equal(true)
})
it('accepts a project_output_file provider with a valid build_id', function () {
const result = rawLinkedFileData.safeParse({
provider: 'project_output_file',
@@ -100,6 +169,14 @@ describe('schemas', function () {
expect(result.success).to.equal(true)
})
it(`accepts a ${provider} provider with a null group_id`, function () {
const result = rawLinkedFileData.safeParse({
provider,
group_id: null,
})
expect(result.success).to.equal(true)
})
it(`rejects a ${provider} provider with a group_id containing a path separator`, function () {
const result = rawLinkedFileData.safeParse({
provider,
@@ -134,6 +211,16 @@ describe('schemas', function () {
expect(result.success).to.equal(true)
})
it('accepts linked-file metadata for a v1 project_file import', function () {
const result = rawFileMetadata.safeParse({
importedAt: '2024-01-01T00:00:00.000Z',
provider: 'project_file',
v1_source_doc_id: 1234,
source_entity_path: '/main.tex',
})
expect(result.success).to.equal(true)
})
it('accepts an importedAt timestamp with no provider', function () {
const result = rawFileMetadata.safeParse({
importedAt: '2024-01-01T00:00:00.000Z',