167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
buildPrelaunchMaintenanceCacheKey,
|
|
runCachedPrelaunchMaintenanceTask,
|
|
} from '@electron/gateway/prelaunch-maintenance-cache';
|
|
|
|
describe('prelaunch maintenance cache', () => {
|
|
let tempDir: string;
|
|
let cachePath: string;
|
|
|
|
beforeEach(() => {
|
|
tempDir = mkdtempSync(join(tmpdir(), 'clawx-prelaunch-cache-'));
|
|
cachePath = join(tempDir, 'cache.json');
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it('runs a task on cache miss and skips it when the cache key is unchanged', () => {
|
|
const task = vi.fn();
|
|
const cacheKey = buildPrelaunchMaintenanceCacheKey({
|
|
task: 'skills-symlink-cleanup',
|
|
appVersion: '1.0.0',
|
|
rootSignature: 'mtime-a',
|
|
});
|
|
|
|
expect(runCachedPrelaunchMaintenanceTask(
|
|
'skills-symlink-cleanup',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath },
|
|
)).toEqual({ executed: true, reason: 'cache-miss' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
|
|
expect(runCachedPrelaunchMaintenanceTask(
|
|
'skills-symlink-cleanup',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath },
|
|
)).toEqual({ executed: false, reason: 'cache-hit' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('stores the post-task cache key when the task mutates signed inputs', () => {
|
|
let rootSignature = 'dirty';
|
|
const cacheKey = () => buildPrelaunchMaintenanceCacheKey({
|
|
task: 'skills-symlink-cleanup',
|
|
appVersion: '1.0.0',
|
|
rootSignature,
|
|
});
|
|
const task = vi.fn(() => {
|
|
rootSignature = 'clean';
|
|
});
|
|
|
|
expect(runCachedPrelaunchMaintenanceTask(
|
|
'skills-symlink-cleanup',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath },
|
|
)).toEqual({ executed: true, reason: 'cache-miss' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
|
|
const writtenCache = JSON.parse(readFileSync(cachePath, 'utf-8'));
|
|
expect(writtenCache.tasks['skills-symlink-cleanup'].key).toBe(cacheKey());
|
|
|
|
expect(runCachedPrelaunchMaintenanceTask(
|
|
'skills-symlink-cleanup',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath },
|
|
)).toEqual({ executed: false, reason: 'cache-hit' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not cache a task that reports maintenance failure', () => {
|
|
const cacheKey = buildPrelaunchMaintenanceCacheKey({
|
|
task: 'plugin-maintenance',
|
|
appVersion: '1.0.0',
|
|
configuredChannels: ['feishu'],
|
|
});
|
|
const task = vi.fn()
|
|
.mockReturnValueOnce(false)
|
|
.mockReturnValueOnce(true);
|
|
|
|
expect(runCachedPrelaunchMaintenanceTask(
|
|
'plugin-maintenance',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath },
|
|
)).toEqual({ executed: true, reason: 'task-failed' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
|
|
expect(runCachedPrelaunchMaintenanceTask(
|
|
'plugin-maintenance',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath },
|
|
)).toEqual({ executed: true, reason: 'cache-miss' });
|
|
expect(task).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('reruns a task when the cache key changes', () => {
|
|
const task = vi.fn();
|
|
const firstKey = buildPrelaunchMaintenanceCacheKey({
|
|
task: 'runtime-deps-cleanup',
|
|
openclawDir: '/old/openclaw',
|
|
});
|
|
const secondKey = buildPrelaunchMaintenanceCacheKey({
|
|
task: 'runtime-deps-cleanup',
|
|
openclawDir: '/new/openclaw',
|
|
});
|
|
|
|
runCachedPrelaunchMaintenanceTask('runtime-deps-cleanup', firstKey, task, { cachePath });
|
|
const result = runCachedPrelaunchMaintenanceTask('runtime-deps-cleanup', secondKey, task, { cachePath });
|
|
|
|
expect(result).toEqual({ executed: true, reason: 'cache-miss' });
|
|
expect(task).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('treats cache schema changes as misses', () => {
|
|
const task = vi.fn();
|
|
const cacheKey = buildPrelaunchMaintenanceCacheKey({
|
|
task: 'plugin-maintenance',
|
|
configuredChannels: ['feishu'],
|
|
});
|
|
writeFileSync(cachePath, JSON.stringify({
|
|
schemaVersion: 0,
|
|
tasks: {
|
|
'plugin-maintenance': {
|
|
key: cacheKey,
|
|
updatedAt: new Date().toISOString(),
|
|
},
|
|
},
|
|
}), 'utf-8');
|
|
|
|
const result = runCachedPrelaunchMaintenanceTask('plugin-maintenance', cacheKey, task, { cachePath });
|
|
|
|
expect(result).toEqual({ executed: true, reason: 'cache-miss' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
expect(JSON.parse(readFileSync(cachePath, 'utf-8')).schemaVersion).toBe(1);
|
|
});
|
|
|
|
it('runs conservatively when the cache file cannot be read', () => {
|
|
const task = vi.fn();
|
|
const blockedCachePath = join(tempDir, 'blocked-cache');
|
|
mkdirSync(blockedCachePath);
|
|
const cacheKey = buildPrelaunchMaintenanceCacheKey({
|
|
task: 'plugin-maintenance',
|
|
configuredChannels: [],
|
|
});
|
|
|
|
const result = runCachedPrelaunchMaintenanceTask(
|
|
'plugin-maintenance',
|
|
cacheKey,
|
|
task,
|
|
{ cachePath: blockedCachePath },
|
|
);
|
|
|
|
expect(result).toEqual({ executed: true, reason: 'cache-unavailable' });
|
|
expect(task).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|