Merge pull request #24977 from overleaf/bg-history-buffer-improve-tests

add more tests for chunk buffer in history-v1

GitOrigin-RevId: 3cfa2492efd67597a2782ca7a5671889a67049d5
This commit is contained in:
Brian Gough
2025-04-23 12:38:04 +01:00
committed by Copybot
parent 626416ed02
commit 12b96e40a5
@@ -8,6 +8,11 @@ const {
History,
File,
AddFileOperation,
EditFileOperation,
AddCommentOperation,
TextOperation,
Range,
TrackingProps,
Change,
} = require('overleaf-editor-core')
const cleanup = require('./support/cleanup')
@@ -59,6 +64,9 @@ describe('chunk buffer', function () {
})
it('should load from chunk store and update cache on first access (cache miss)', async function () {
// Load the underlying chunk from the chunk store for verification
const storedChunk = await chunkStore.loadLatest(projectId)
// First access should load from chunk store and populate cache
const firstResult = await chunkBuffer.loadLatest(projectId)
@@ -67,6 +75,9 @@ describe('chunk buffer', function () {
expect(firstResult.getStartVersion()).to.equal(1)
expect(firstResult.getEndVersion()).to.equal(2)
// Verify the chunk is the same as the one in the store
expect(firstResult).to.deep.equal(storedChunk)
// Verify that we got a cache miss metric
expect(
metrics.inc.calledWith('chunk_buffer.loadLatest', 1, {
@@ -85,6 +96,9 @@ describe('chunk buffer', function () {
expect(secondResult.getStartVersion()).to.equal(1)
expect(secondResult.getEndVersion()).to.equal(2)
// Verify the chunk is the same as the one in the store
expect(secondResult).to.deep.equal(storedChunk)
// Verify that we got a cache hit metric
expect(
metrics.inc.calledWith('chunk_buffer.loadLatest', 1, {
@@ -99,6 +113,7 @@ describe('chunk buffer', function () {
expect(secondResult.getEndVersion()).to.equal(
firstResult.getEndVersion()
)
expect(secondResult).to.deep.equal(firstResult)
})
it('should refresh the cache when chunk changes in the store', async function () {
@@ -129,12 +144,17 @@ describe('chunk buffer', function () {
// Store the new chunk directly in the chunk store
await chunkStore.create(projectId, newChunk)
// Load the underlying chunk from the chunk store for verification
const storedChunk = await chunkStore.loadLatest(projectId)
// Access again - should detect the change and refresh cache
const secondResult = await chunkBuffer.loadLatest(projectId)
// Verify we got the updated chunk
expect(secondResult.getStartVersion()).to.equal(2)
expect(secondResult.getEndVersion()).to.equal(3)
// Verify that the chunk content is the same
expect(secondResult).to.deep.equal(storedChunk)
// Verify that we got a cache miss metric (since the cached chunk was invalidated)
expect(
@@ -145,6 +165,9 @@ describe('chunk buffer', function () {
})
it('should continue using cache when chunk in store has not changed', async function () {
// Load the underlying chunk from the chunk store for verification
const storedChunk = await chunkStore.loadLatest(projectId)
// First access to load into cache
await chunkBuffer.loadLatest(projectId)
@@ -157,6 +180,7 @@ describe('chunk buffer', function () {
// Verify we got the same chunk
expect(result.getStartVersion()).to.equal(1)
expect(result.getEndVersion()).to.equal(2)
expect(result).to.deep.equal(storedChunk)
// Verify that we got a cache hit metric
expect(
@@ -167,11 +191,122 @@ describe('chunk buffer', function () {
})
})
it('should handle a chunk with metadata, comments and tracked changes', async function () {
// Create a snapshot and initial file
const snapshot = new Snapshot()
const initialFileOp = new AddFileOperation(
'test.tex',
File.fromString('Initial line.\\nSecond line.', {
meta1: 'abc',
meta2: 'def',
})
)
const initialChange = new Change([initialFileOp], new Date(), [])
// Add a comment
const commentOp = new AddCommentOperation(
'comment1',
[new Range(0, 7)] // Range for "Initial"
)
const commentChange = new Change(
[new EditFileOperation('test.tex', commentOp)],
new Date(),
[]
)
// Tracked insert
const trackedInsertOp = new TextOperation()
.retain(14)
.insert('Hello', {
commentIds: ['comment1'],
tracking: TrackingProps.fromRaw({
ts: '2024-01-01T00:00:00.000Z',
type: 'insert',
userId: 'user1',
}),
})
.retain(12)
const insertChange = new Change(
[new EditFileOperation('test.tex', trackedInsertOp)],
new Date(),
[]
)
// Tracked delete
const trackedDeleteOp = new TextOperation().retain(14, {
tracking: TrackingProps.fromRaw({
ts: '2024-01-01T00:00:00.000Z',
type: 'delete',
userId: 'user1',
}),
})
const deleteChange = new Change(
[new EditFileOperation('test.tex', trackedDeleteOp)],
new Date(),
[]
)
// Combine changes into history and create chunk
const history = new History(snapshot, [
initialChange,
commentChange,
insertChange,
deleteChange,
])
const chunk = new Chunk(history, 1) // Start version 0
// Store the chunk
await chunkStore.create(projectId, chunk)
// Clear the cache
await redisBackend.clearCache(projectId)
metrics.inc.resetHistory()
// Load the underlying chunk from the chunk store for verification
const storedChunk = await chunkStore.loadLatest(projectId)
// Load the chunk via buffer (cache miss)
const firstResult = await chunkBuffer.loadLatest(projectId)
// Verify chunk details
expect(firstResult.getStartVersion()).to.equal(1)
expect(firstResult.getEndVersion()).to.equal(5) // 4 changes
expect(firstResult.history.changes.length).to.equal(4)
expect(firstResult).to.deep.equal(storedChunk)
// Verify cache miss metric
expect(
metrics.inc.calledWith('chunk_buffer.loadLatest', 1, {
status: 'cache-miss',
})
).to.be.true
// Reset metrics
metrics.inc.resetHistory()
// Second access should hit the cache
const secondResult = await chunkBuffer.loadLatest(projectId)
// Verify we got the same chunk
expect(secondResult.getStartVersion()).to.equal(1)
expect(secondResult.getEndVersion()).to.equal(5)
expect(secondResult.history.changes.length).to.equal(4)
expect(secondResult).to.deep.equal(storedChunk)
// Verify cache hit metric
expect(
metrics.inc.calledWith('chunk_buffer.loadLatest', 1, {
status: 'cache-hit',
})
).to.be.true
})
describe('with an empty project', function () {
it('should handle a case with empty chunks (no changes)', async function () {
// Clear the cache
await redisBackend.clearCache(projectId)
// Load the underlying chunk from the chunk store for verification
const storedChunk = await chunkStore.loadLatest(projectId)
// Load the initial empty chunk via buffer
const result = await chunkBuffer.loadLatest(projectId)
@@ -180,6 +315,9 @@ describe('chunk buffer', function () {
expect(result.getEndVersion()).to.equal(0) // Start equals end for empty chunks
expect(result.history.changes.length).to.equal(0)
// Verify that the chunk is the same as the one in the store
expect(result).to.deep.equal(storedChunk)
// Verify cache miss metric
expect(
metrics.inc.calledWith('chunk_buffer.loadLatest', 1, {
@@ -198,6 +336,9 @@ describe('chunk buffer', function () {
expect(secondResult.getEndVersion()).to.equal(0)
expect(secondResult.history.changes.length).to.equal(0)
// Verify that the chunk is the same as the one in the store
expect(secondResult).to.deep.equal(storedChunk)
// Verify cache hit metric
expect(
metrics.inc.calledWith('chunk_buffer.loadLatest', 1, {