Merge pull request #32892 from overleaf/mj-history-file-metadata

[history-v1] Add missing properties to zod schemas

GitOrigin-RevId: e16faf56a9b294516152383585493b38685b8d15
This commit is contained in:
Mathias Jakobsen
2026-04-20 09:54:16 +01:00
committed by Copybot
parent 4ce5620b1d
commit 4173c98c48
2 changed files with 130 additions and 4 deletions

View File

@@ -8,21 +8,28 @@ const hexHashPattern = new RegExp(Blob.HEX_HASH_RX_STRING)
const fileSchema = z
.object({
hash: z.string().optional(),
rangesHash: z.string().optional(),
byteLength: z.number().int().nullable().optional(),
stringLength: z.number().int().nullable().optional(),
metadata: z.object({}).passthrough().optional(),
})
.passthrough()
const snapshotSchema = z.object({
files: z.record(z.string(), fileSchema),
})
const v2DocVersionsSchema = z.object({
pathname: z.string().optional(),
v: z.number().int().optional(),
})
const snapshotSchema = z.object({
files: z.record(z.string(), fileSchema),
projectVersion: z.string().optional(),
v2DocVersions: z
.record(z.string(), v2DocVersionsSchema)
.nullable()
.optional(),
timestamp: z.string().optional(),
})
const operationSchema = z
.object({
pathname: z.string().optional(),

View File

@@ -22,6 +22,7 @@ const TextOperation = core.TextOperation
const V2DocVersions = core.V2DocVersions
const knex = require('../../../../storage').knex
const blobHash = require('../../../../storage/lib/blob_hash')
describe('history import', function () {
beforeEach(cleanup.everything)
@@ -513,6 +514,124 @@ describe('history import', function () {
})
})
it('preserves rangesHash when importing a file', async function () {
const testProjectId = '1'
const testFilePathname = 'main.tex'
// hello.txt contains "Olá mundo\n" (10 UTF-8 chars).
const rangesContent = JSON.stringify({
comments: [
{
id: 'comment-1',
ranges: [{ pos: 0, length: 3 }],
resolved: false,
},
],
trackedChanges: [
{
range: { pos: 4, length: 5 },
tracking: {
type: 'insert',
userId: 'user-1',
ts: '2024-01-01T00:00:00.000Z',
},
},
],
})
const testRangesHash = blobHash.fromString(rangesContent)
const testFile = File.fromHash(testFiles.HELLO_TXT_HASH, testRangesHash)
const [contentResponse, rangesResponse] = await Promise.all([
fetch(
testServer.url(
`/api/projects/${testProjectId}/blobs/${testFiles.HELLO_TXT_HASH}`
),
{
method: 'PUT',
body: fs.createReadStream(testFiles.path('hello.txt')),
headers: { Authorization: testServer.basicAuthHeader },
}
),
fetch(
testServer.url(
`/api/projects/${testProjectId}/blobs/${testRangesHash}`
),
{
method: 'PUT',
body: rangesContent,
headers: { Authorization: testServer.basicAuthHeader },
}
),
])
expect(contentResponse.ok).to.be.true
expect(rangesResponse.ok).to.be.true
const testSnapshot = new Snapshot()
testSnapshot.addFile(testFilePathname, testFile)
const importResponse =
await basicAuthClient.apis.ProjectImport.importSnapshot1({
project_id: testProjectId,
snapshot: testSnapshot.toRaw(),
})
expect(importResponse.obj.projectId).to.equal(testProjectId)
const historyResponse =
await clientForProject.apis.Project.getLatestHistory({
project_id: testProjectId,
})
const chunk = ChunkResponse.fromRaw(historyResponse.obj).getChunk()
const file = chunk.getSnapshot().getFile(testFilePathname)
expect(file.getRangesHash()).to.equal(testRangesHash)
})
it('preserves projectVersion, v2DocVersions and timestamp on snapshot import', async function () {
const testProjectId = '1'
const testFilePathname = 'empty.tex'
const testDocId = '000000000000000000000001'
const testProjectVersion = '12345.0'
const testV2DocVersions = new V2DocVersions({
[testDocId]: { pathname: testFilePathname, v: 123 },
})
const testTimestamp = new Date('2024-01-01T00:00:00.000Z')
const response = await fetch(
testServer.url(
`/api/projects/${testProjectId}/blobs/${File.EMPTY_FILE_HASH}`
),
{
method: 'PUT',
body: fs.createReadStream(testFiles.path('empty.tex')),
headers: { Authorization: testServer.basicAuthHeader },
}
)
expect(response.ok).to.be.true
const testSnapshot = new Snapshot()
testSnapshot.addFile(testFilePathname, File.fromHash(File.EMPTY_FILE_HASH))
testSnapshot.setProjectVersion(testProjectVersion)
testSnapshot.setV2DocVersions(testV2DocVersions)
testSnapshot.setTimestamp(testTimestamp)
const importResponse =
await basicAuthClient.apis.ProjectImport.importSnapshot1({
project_id: testProjectId,
snapshot: testSnapshot.toRaw(),
})
expect(importResponse.obj.projectId).to.equal(testProjectId)
const historyResponse =
await clientForProject.apis.Project.getLatestHistory({
project_id: testProjectId,
})
const snapshot = ChunkResponse.fromRaw(historyResponse.obj)
.getChunk()
.getSnapshot()
expect(snapshot.getProjectVersion()).to.equal(testProjectVersion)
expect(snapshot.getV2DocVersions()).to.deep.equal(testV2DocVersions)
expect(snapshot.getTimestamp()?.toISOString()).to.equal(
testTimestamp.toISOString()
)
})
it('rejects text operations on binary files', function () {
const testProjectId = '1'
const testFilePathname = 'main.tex'