toContain('CREATE TABLE IF NOT EXISTS backup_records') ->and($content)->toContain('CREATE TABLE IF NOT EXISTS backup_components') ->and($content)->toContain('CREATE TABLE IF NOT EXISTS backup_jobs') ->and($content)->toContain('CREATE TABLE IF NOT EXISTS backup_restore_audit') ->and($content)->toContain('manifest_sha256') ->and($content)->toContain('encryption_key_id'); }); it('hardens database dumps by keeping passwords out of the mysqldump command', function (): void { $content = file_get_contents(app_path('classes/db.php')); expect($content)->toContain('MYSQL_PWD') ->and($content)->toContain('--single-transaction') ->and($content)->toContain('--routines') ->and($content)->toContain('--triggers') ->and($content)->toContain('--events') ->and($content)->not->toContain('--password=$pass'); }); it('uses encrypted component backups and never stores raw environment dumps', function (): void { $content = file_get_contents(app_path('classes/backup_store.php')); expect($content)->toContain('AES-256-GCM') ->and($content)->toContain('BACKUP_ENCRYPTION_KEY_V1') ->and($content)->toContain('required_runtime_config_keys') ->and($content)->toContain('createObjectBucketComponent') ->and($content)->not->toContain('json_encode($_ENV)') ->and($content)->not->toContain("exec('zip -r"); }); it('normalizes string boolean config values before destructive restore gates', function (): void { $content = file_get_contents(app_path('classes/backup_store.php')); expect($content)->toContain("['1', 'true', 'yes', 'on']") ->and($content)->toContain("['0', 'false', 'no', 'off', '']") ->and($content)->toContain('Backup system is disabled in backup configuration.') ->and($content)->toContain('Direct production restore is disabled in backup configuration.'); }); it('wires backup job, verification, restore preview, restore, and audit routes', function (): void { $content = file_get_contents(app_path('routes/moduleBackupsRoute.php')); expect($content)->toContain('/modules/backup/jobs/{id}') ->and($content)->toContain('/modules/backup/backups/{backup_uuid}/verify') ->and($content)->toContain('/modules/backup/backups/{backup_uuid}/restore/preview') ->and($content)->toContain('/modules/backup/backups/{backup_uuid}/restore') ->and($content)->toContain('/modules/backup/restore-audit') ->and($content)->toContain('modules_backup_restore') ->and($content)->toContain('Subuser sessions cannot manage backup disaster recovery.'); }); it('registers hourly backup enqueue, worker, and retention prune cron tasks', function (): void { $content = file_get_contents(app_path('modules/backups/cron/tasks.php')); $cron = file_get_contents(app_path('cron/Cron.php')); expect($content)->toContain("'seconds' => 3600") ->and($content)->toContain('backups.process_jobs') ->and($content)->toContain('backups.prune_retention') ->and($cron)->toContain('processBackupJobs') ->and($cron)->toContain('pruneBackupRetention'); }); it('documents backup disaster recovery APIs and config variables in openapi', function (): void { $content = file_get_contents(app_path('openapi.yaml')); expect($content)->toContain('/modules/backup/jobs/{id}:') ->and($content)->toContain('/modules/backup/backups/{backup_uuid}/restore/preview:') ->and($content)->toContain('BackupRestoreRequest') ->and($content)->toContain('BackupRestoreAuditListResponse') ->and($content)->toContain('retention_recent_hours') ->and($content)->toContain('verification_required') ->and($content)->toContain('restore_enabled'); });