14 — QA Checklist (Scheduler Module)
- 1. Registration & idempotency of defaults
- 2. Missed runs & recovery
- 3. Timezone / DST
- 4. Overlap protection
- 5. Failure path & DLQ
- 6. Idempotency
- 7. Tenant isolation
- 8. Security & RBAC
- 9. Console QA
(proposed) - 10. Perf & load
Test matrix for the scheduler backend + the
(proposed)console. Sources:scheduler.service.ts,jobs/*.job.ts,bullmq.module.ts,dlq.setup.ts,idempotency.service.ts,tenant-context.service.ts. Backend items are testable today; console items are(proposed).
1. Registration & idempotency of defaults
-
Fresh boot registers exactly 10 repeatables, one per row of the table
(01 §2;
scheduler.service.ts:48-119). -
Reboot with existing repeatables → no duplicates (
:128-133skip). -
GET /schedulerreturns 10 rows with correct{queue,name,pattern,tz},tzdefaultingUTC(:152-186). - Redis flush mid-run → next boot re-registers all 10 (journey 3, 03).
-
Custom job with identical name+pattern is silently skipped
(
:128-133) — UI mirrors the warn (08 §F1).
2. Missed runs & recovery
- Simulate outage: kill worker > 1 h, restart → audit-flush resumes; backlog bounded by repeatable semantics (missed fire = skipped, not burst).
-
Manual re-trigger path (S5
(proposed)) enqueues exactly one child job. -
Re-trigger after partial success does not double-deliver
(
IdempotencyService,idempotency.service.ts:11-20). -
Operator can confirm "no run in expected window" from S1 badges
(
missed, C1) — depends on E4(proposed).
3. Timezone / DST
-
All 10 defaults registered with
tz: 'UTC'(scheduler.service.ts:54,62,70,76,82,89,96,103,110,117). -
Custom schedules accept explicit tz (
dto:38-41); cron fires at the wall-clock instant in that tz across a DST boundary. - Console renders UTC patterns without pretending to local run times (09 B8).
-
No 6-field cron accepted (5-field regex
dto:20-24).
4. Overlap protection
-
A long-running child job that outlives its next trigger does not
produce concurrent duplicate work (BullMQ repeatable +
attemptsconfigbullmq.module.ts:60-65; verify overlap behaviour is intended fire-and-forget per queue). -
fee-reminderenqueues exactly onesend-payment-reminderper due invoice (ISSUED/PARTIAL, due ≤ 3 days,fee-reminder.job.ts:16-23,25-41). -
attendance-report-dailypayload carriesreportTypecorrectly (attendance-report.job.ts:13-28).
5. Failure path & DLQ
-
Job fails 1×, 2× → retried with exponential 5 s backoff
(
bullmq.module.ts:60-65). -
Fails 3rd time → DLQ record with
originalQueue, originalJobId, originalJobName, data, failedReason, attemptsMade, failedAt(dlq.setup.ts:8-21). -
DLQ replay preserves
correlationId/tenantId(idempotency keys stay stable,idempotency.service.ts:12). -
Failed-trigger retention: 7 d (
scheduler.service.ts:146); global failed retention 14 d (bullmq.module.ts:63-64) — DLQ viewer shows what actually exists (S6 empty states honest, 09 B2).
6. Idempotency
-
sl:idempotency:{jobId}key set via SET NX PX 300 s (idempotency.service.ts:11-20); second delivery within TTL is dropped. - Expired key (job > 5 min) re-processes — accepted trade-off, documented.
-
QueueBridge event path dedupes on
{correlationId}:{eventType}(queue-bridge.service.ts:44-46).
7. Tenant isolation
-
Workers restore context via
tenantContext.runbefore touching repositories (report.worker.ts:18-30; also finance/attendance/admission/ inapp workers, 01 §7). -
Gap:
FeeReminderJobqueriesInvoiceRepositorydirectly without wrapping intenantContext.run(fee-reminder.job.ts:20-23) while the trigger payload carriestenantId: 'system'(scheduler.service.ts:139) — verify query scoping and either wrap the job or confirm intended cross-tenant scan. Open QA risk. -
Trigger payloads never read
tenantIdfrom operator input; custom create DTO has no tenant field (create-schedule.dto.ts:18-41).
8. Security & RBAC
-
scheduler.read/create/deleteenforced on E1/E2/E3 (scheduler.controller.ts:24,31,38). -
Custom-job payload cannot inject arbitrary queue names (whitelist
dto:4-16). - Console hides actions without permission (09 B4).
9. Console QA (proposed)
- S1 poll loop: 60 s cadence, single-flight, background pause (10 §2, 13 §3).
- S2 run history matches retention reality (1 h/100, 7 d, 14 d — S2 note).
-
S3 duplicate warn; invalid cron inline error matches server message
"Invalid cron pattern (5 fields required)" (
dto:22). - S6 replay button disabled until E8 exists.
- a11y: badges icon+text; live regions; focus traps (11 §6).
10. Perf & load
-
listJobsacross 10 queues stays < 1 s at 100+ repeatables each (scheduler.service.ts:152-186). - Poll of E1 + enrich does not exceed api tier 100 req/min (00-shared/07 §4).
-
DLQ listener attaches once per worker — no duplicate records on
restart (
dlq-listener.service.ts:31-44).