backup:configure could point automatic backups at any writable directory (e.g. a statically served one) and set a cron of every second; an invalid expression deleted the running job before failing, silently ending backups. - A path entered in the UI is resolved under BACKUP_ROOT (default ./backups) and refused if it leaves it; env values stay unrestricted. - The schedule must be a five-field cron that runs at most hourly. - reload() validates before replacing the running job. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { BadRequestException } from '@nestjs/common';
|
|
import * as path from 'node:path';
|
|
import {
|
|
assertBackupCron,
|
|
assertGuiBackupPath,
|
|
resolveGuiBackupPath,
|
|
} from './backup-schedule-rules';
|
|
|
|
describe('backup-schedule-rules', () => {
|
|
const originalRoot = process.env.BACKUP_ROOT;
|
|
|
|
beforeEach(() => {
|
|
process.env.BACKUP_ROOT = path.resolve('/data/backups');
|
|
});
|
|
|
|
afterAll(() => {
|
|
process.env.BACKUP_ROOT = originalRoot;
|
|
});
|
|
|
|
it('keeps a GUI path under the backup root', () => {
|
|
expect(resolveGuiBackupPath('nightly')).toBe(
|
|
path.resolve('/data/backups/nightly'),
|
|
);
|
|
expect(() => assertGuiBackupPath('nightly')).not.toThrow();
|
|
});
|
|
|
|
it('refuses a GUI path that leaves the backup root', () => {
|
|
expect(() => assertGuiBackupPath('../../app/public')).toThrow(
|
|
BadRequestException,
|
|
);
|
|
expect(() => assertGuiBackupPath('/etc')).toThrow(BadRequestException);
|
|
expect(() => assertGuiBackupPath('/data/backups-evil')).toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
|
|
it('accepts a daily schedule', () => {
|
|
expect(() => assertBackupCron('0 3 * * *')).not.toThrow();
|
|
});
|
|
|
|
it('refuses a schedule that runs more than hourly or has seconds', () => {
|
|
expect(() => assertBackupCron('* * * * *')).toThrow(BadRequestException);
|
|
expect(() => assertBackupCron('*/10 * * * *')).toThrow(BadRequestException);
|
|
expect(() => assertBackupCron('* * * * * *')).toThrow(BadRequestException);
|
|
});
|
|
|
|
it('refuses an invalid expression', () => {
|
|
expect(() => assertBackupCron('every day')).toThrow(BadRequestException);
|
|
});
|
|
});
|