Files
ApiCore/ApiCore-Backend/src/backup/backup-schedule-rules.spec.ts
T
Nick SzaunigandClaude Opus 5.5 ebeb499a18 fix(backup): keep GUI backup paths under BACKUP_ROOT and limit the schedule
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]>
2026-10-01 02:14:18 +02:00

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);
});
});