Merge pull request #25928 from overleaf/bg-history-redis-move-test-script-helpers

move test script helpers in history-v1

GitOrigin-RevId: cc2e5d8b1baea7396f948883a12a91846f77836c
This commit is contained in:
Brian Gough
2025-06-04 08:06:02 +00:00
committed by Copybot
parent ee23e8f49f
commit b2b676249d
3 changed files with 114 additions and 82 deletions
@@ -1,91 +1,13 @@
'use strict'
const { expect } = require('chai')
const { promisify } = require('node:util')
const { execFile } = require('node:child_process')
const { Snapshot, Author, Change } = require('overleaf-editor-core')
const { Author, Change } = require('overleaf-editor-core')
const cleanup = require('./support/cleanup')
const redisBackend = require('../../../../storage/lib/chunk_store/redis')
const redis = require('../../../../storage/lib/redis')
const rclient = redis.rclientHistory
const keySchema = redisBackend.keySchema
const { setupProjectState, rclient, keySchema } = require('./support/redis')
const { runScript } = require('./support/runscript')
const SCRIPT_PATH = 'storage/scripts/expire_redis_chunks.js'
async function runExpireScript() {
const TIMEOUT = 10 * 1000 // 10 seconds
let result
try {
result = await promisify(execFile)('node', [SCRIPT_PATH], {
encoding: 'utf-8',
timeout: TIMEOUT,
env: {
...process.env,
LOG_LEVEL: 'debug', // Override LOG_LEVEL for script output
},
})
result.status = 0
} catch (err) {
const { stdout, stderr, code } = err
if (typeof code !== 'number') {
console.error('Error running expire script:', err)
throw err
}
result = { stdout, stderr, status: code }
}
// The script might exit with status 1 if it finds no keys to process, which is ok
if (result.status !== 0 && result.status !== 1) {
console.error('Expire script failed:', result.stderr)
throw new Error(`expire script failed with status ${result.status}`)
}
return result
}
// Helper to set up a basic project state in Redis
async function setupProjectState(
projectId,
{
headVersion = 0,
persistedVersion = null,
expireTime = null,
persistTime = null,
changes = [],
}
) {
const headSnapshot = new Snapshot()
await rclient.set(
keySchema.head({ projectId }),
JSON.stringify(headSnapshot.toRaw())
)
await rclient.set(
keySchema.headVersion({ projectId }),
headVersion.toString()
)
if (persistedVersion !== null) {
await rclient.set(
keySchema.persistedVersion({ projectId }),
persistedVersion.toString()
)
}
if (expireTime !== null) {
await rclient.set(
keySchema.expireTime({ projectId }),
expireTime.toString()
)
}
if (persistTime !== null) {
await rclient.set(
keySchema.persistTime({ projectId }),
persistTime.toString()
)
}
if (changes.length > 0) {
const rawChanges = changes.map(c => JSON.stringify(c.toRaw()))
await rclient.rpush(keySchema.changes({ projectId }), ...rawChanges)
}
}
function makeChange() {
const timestamp = new Date()
const author = new Author(123, 'test@example.com', 'Test User')
@@ -150,7 +72,7 @@ describe('expire_redis_chunks script', function () {
})
// Run the expire script once after all projects are set up
await runExpireScript()
await runScript(SCRIPT_PATH)
})
async function checkProjectStatus(projectId) {
@@ -0,0 +1,75 @@
'use strict'
const { Snapshot } = require('overleaf-editor-core')
const redis = require('../../../../../storage/lib/redis')
const redisBackend = require('../../../../../storage/lib/chunk_store/redis')
const rclient = redis.rclientHistory
const keySchema = redisBackend.keySchema
// Helper to set up a basic project state in Redis
async function setupProjectState(
projectId,
{
headVersion = 0,
persistedVersion = null,
expireTime = null,
persistTime = null,
changes = [],
expireTimeFuture = false, // Default to not setting future expire time unless specified
}
) {
const headSnapshot = new Snapshot()
await rclient.set(
keySchema.head({ projectId }),
JSON.stringify(headSnapshot.toRaw())
)
await rclient.set(
keySchema.headVersion({ projectId }),
headVersion.toString()
)
if (persistedVersion !== null) {
await rclient.set(
keySchema.persistedVersion({ projectId }),
persistedVersion.toString()
)
} else {
await rclient.del(keySchema.persistedVersion({ projectId }))
}
if (expireTime !== null) {
await rclient.set(
keySchema.expireTime({ projectId }),
expireTime.toString()
)
} else {
// If expireTimeFuture is true, set it to a future time, otherwise delete it if null
if (expireTimeFuture) {
const futureExpireTime = Date.now() + 5 * 60 * 1000 // 5 minutes in the future
await rclient.set(
keySchema.expireTime({ projectId }),
futureExpireTime.toString()
)
} else {
await rclient.del(keySchema.expireTime({ projectId }))
}
}
if (persistTime !== null) {
await rclient.set(
keySchema.persistTime({ projectId }),
persistTime.toString()
)
} else {
await rclient.del(keySchema.persistTime({ projectId }))
}
if (changes.length > 0) {
const rawChanges = changes.map(c => JSON.stringify(c.toRaw()))
await rclient.rpush(keySchema.changes({ projectId }), ...rawChanges)
} else {
await rclient.del(keySchema.changes({ projectId }))
}
}
module.exports = { setupProjectState, rclient, keySchema }
@@ -0,0 +1,35 @@
'use strict'
const { promisify } = require('node:util')
const { execFile } = require('node:child_process')
async function runScript(scriptPath, options = {}) {
const TIMEOUT = options.timeout || 10 * 1000 // 10 seconds default
let result
try {
result = await promisify(execFile)('node', [scriptPath], {
encoding: 'utf-8',
timeout: TIMEOUT,
env: {
...process.env,
LOG_LEVEL: 'debug', // Override LOG_LEVEL for script output
},
})
result.status = 0
} catch (err) {
const { stdout, stderr, code } = err
if (typeof code !== 'number') {
console.error(`Error running script ${scriptPath}:`, err)
throw err
}
result = { stdout, stderr, status: code }
}
// The script might exit with status 1 if it finds no keys to process, which is ok
if (result.status !== 0 && result.status !== 1) {
console.error(`Script ${scriptPath} failed:`, result.stderr)
throw new Error(`Script ${scriptPath} failed with status ${result.status}`)
}
return result
}
module.exports = { runScript }