diff --git a/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js b/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js index b657991dda..f8a5943c43 100644 --- a/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js +++ b/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js @@ -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) { diff --git a/services/history-v1/test/acceptance/js/storage/support/redis.js b/services/history-v1/test/acceptance/js/storage/support/redis.js new file mode 100644 index 0000000000..3f5b9cda27 --- /dev/null +++ b/services/history-v1/test/acceptance/js/storage/support/redis.js @@ -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 } diff --git a/services/history-v1/test/acceptance/js/storage/support/runscript.js b/services/history-v1/test/acceptance/js/storage/support/runscript.js new file mode 100644 index 0000000000..7ff8355566 --- /dev/null +++ b/services/history-v1/test/acceptance/js/storage/support/runscript.js @@ -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 }