Files
api/services/nginx/app/tests/Unit/Cron/CronTaskRegistryTest.php
T
Jeppe B 0060fb45ca Add in-app account deletion (#319)
## Summary
- Add self-service deletion for the authenticated customer or subuser
identity only.
- Preserve shared customer grants, reset keys, bookings, order bookings,
vehicles, invoices, and legally required history.
- Require password/TOTP or a fresh deletion-specific, five-minute,
single-use WebAuthn assertion.
- Reject support impersonation and expired legacy plain-session tokens.
- Use durable database throttling, transactional request processing, a
durable outbox, and terminal `manual_review` state.
- Keep API and worker default-off behind separate
`account_deletion.api_enabled` and `account_deletion.worker_enabled`
module-config flags.

## Safe rollout
1. Keep both flags disabled.
2. Run `php scripts/account-deletion-schema.php check`.
3. If needed, run `php scripts/account-deletion-schema.php apply --yes`,
then rerun `check` until `ready:true`.
4. Deploy the frontend companion PR while the API remains disabled.
5. Enable `api_enabled` for a controlled canary; verify password and
passwordless request flows plus immediate authentication revocation.
6. Inspect queued request/outbox state, then enable `worker_enabled`.
7. Verify anonymization, preserved tenant/history data, outbox delivery,
retries, and manual-review behavior before broad rollout.

## Verification
- Account deletion unit tests: 2 passed, 43 assertions.
- PHP lint, both OpenAPI YAML parses, runtime-DDL scan,
destructive-scope scan, and `git diff --check` passed.
- Full API/unit/integration evidence is required from exact-head CI;
local Docker is unavailable and shared-vendor tests were explicitly
discarded.

## Security notes
- Schema mutation is CLI-only; web and cron paths perform read-only
readiness checks.
- Runtime behavior fails closed when schema/config/throttle/delivery
prerequisites are unavailable.
2026-07-22 19:22:17 +02:00

61 lines
2.1 KiB
PHP

<?php
use classes\cron_schedule;
use classes\cron_task_registry;
it('discovers module-owned cron task definitions', function (): void {
$registry = new cron_task_registry(app_path('modules'));
$definitions = $registry->definitions();
expect($definitions)->toHaveCount(23);
expect(array_keys($definitions))->toContain(
'system.sync_logs',
'backups.process_jobs',
'backups.prune_retention',
'economic.transfer_queue',
'dynamicimages.pre_render',
'weatherapi.preload_department_responses',
'goals.progress_alerts',
'account.process_deletion_requests',
'selfserve.activate_opening_cleaner_relays'
);
$transferQueue = $registry->get('EconomicTransferQueueCron');
expect($transferQueue)->not->toBeNull();
expect($transferQueue->id)->toBe('economic.transfer_queue');
expect($transferQueue->module)->toBe('economic');
expect($transferQueue->schedule)->toBe(['type' => 'interval', 'seconds' => 30]);
});
it('keeps every discovered cron task in a module cron folder', function (): void {
$files = glob(app_path('modules/*/cron/tasks.php')) ?: [];
$modules = array_map(
static fn(string $file): string => basename(dirname(dirname($file))),
$files
);
$registry = new cron_task_registry(app_path('modules'));
foreach ($registry->definitions() as $definition) {
expect($modules)->toContain($definition->module);
}
});
it('normalizes and advances interval schedules without tight loops', function (): void {
expect(cron_schedule::normalize(['type' => 'interval', 'seconds' => 60]))
->toBe(['type' => 'interval', 'seconds' => 60]);
$now = strtotime('2026-07-09 12:10:00');
$next = cron_schedule::nextRunAt(
['type' => 'interval', 'seconds' => 300],
'2026-07-09 12:00:00',
$now
);
expect($next)->toBe('2026-07-09 12:15:00');
});
it('rejects unsafe cron intervals', function (): void {
expect(fn() => cron_schedule::normalize(['type' => 'interval', 'seconds' => 5]))
->toThrow(InvalidArgumentException::class);
});