We test the layer's conventions, not the libraries
Redis and SAQ as libraries are trusted to their authors — what's
checked here are our rules on top of them: task
uniqueness, cleanup of stale runs, retries and backoff, cache
policy, lane isolation. We don't duplicate the broker's and
driver's tests.
Uniqueness and cleanup — on a real DB and fake
time
The lock of a single active run is held by a partial-UNIQUE
Postgres index, not a check in code — so the test brings up a DB
and hits the index. Expiry of heartbeat_at and the
TTL/cron windows are driven through a stubbed clock
(time-machine), not by real waiting.
Rate-limit Lua — under concurrent load
Token-bucket / sliding-window atomicity is only visible under a
race: the test fires parallel requests and checks that not a single
one leaked past the limit — a sequential check wouldn't catch that
bug.
Stack and infrastructure
In place donepytest, pytest-asyncio
Redisfakeredis for unit key logic; a running Redis for Lua atomicity
and restart survival
Timetime-machine — cache TTL, heartbeat_at expiry,
cron windows and next-tick recomputation
Workerthe SAQ handler by direct call — without bringing up a worker or
really polling the queue
Markers@pytest.mark.unit /
@pytest.mark.integration — by type;
p0 / p1 — by priority, orthogonal to
type
Unit · P0Enqueue idempotencydeterministic · every PR
test_enqueue_idempotency.py
A repeat of the same job_id is dropped, window after terminal
duplicate in queuea repeat enqueue of the same job_id while the
task is still queued or running is dropped — no second task
appears
window after successthe job_id of a completed
succeeded task is held for ~24h (time-machine) —
a repeat within the window is dropped, past the window it goes
through
window after failureafter failed the window is short — the task can
be re-enqueued sooner than after a success
Integration · P0Uniqueness and stale cleanupDB · separate CI step
test_uniqueness_reaping.py
A second active run is rejected by the index, a stale heartbeat is
cleaned up
second active rejectedwith a run still active, an attempt at a second is turned
away by the partial-UNIQUE index — the race is absorbed by the
database itself, not by a check in code alone
stale heartbeat → stalea worker died without closing the run → on
heartbeat_at expiry (time-machine) the run is
cleaned up to
failed/stale
(cleanup)
lock released after cleanupafter a stale run is cleaned up the lock is released and the
next run starts — a zombie doesn't lock the task forever
reaping stalls with the singleton, drains on returnthe reaper lives on the singleton — while it's down, reaping
stalls too and stale runs pile up; on return several
accumulated stale runs are cleaned up in one pass, not one per
tick
(reaper on the singleton)
graceful drain on SIGTERMa normal stop (not a crash): the worker finishes what it
started, and an unfinished task returns to the queue and is
picked up again — the repeat is safe by job_id
idempotency, not via reaping
(graceful drain)
Unit · P0Retries and backofflogic is deterministic · every PR
test_retry_backoff.py
Short vs long retry, classification, exp-backoff + jitter
short retry in-memorydelay ≤60s — a retry within the same slot, without returning
to the queue
long — defer/re-enqueuea delay beyond the threshold → the task is deferred and
re-enqueued, the worker slot is freed for the wait
transient vs permanenta temporary failure is classified as
transient and retried; a permanent one
(permanent) goes to terminal at once, without
attempts
exp-backoff + full jitterthe delay grows exponentially with full jitter — two series
of attempts don't coincide in timing
cap and attempt ceilingthe delay hits an upper cap; on exhausting the attempt count
the task goes to terminal, it doesn't retry forever
Integration · P1Terminal and DLQDB
test_terminal_dlq.py
Outcome in the consumer's row, failure by policy (not in Redis)
outcome isn't lostthe run's result is written to the Postgres row of the
consumer module — durable, surviving the task's completion in
the broker
failure by policya failure that has exhausted its attempts is recorded by the
consumer's policy — a DLQ table / a “not delivered” status /
failed-items — not into the durable Redis
queue
the queue doesn't accumulate dead workthe Redis queue doesn't accumulate terminal tasks — after
terminal the record of it lives in Postgres, not in the
broker
Integration · P1Rate-limit under loadrunning Redis · concurrent
nothing leaks past the limita batch of parallel requests on token-bucket /
sliding-window — exactly the limit is let through, none over:
Lua counts atomically
one shared primitive for allone primitive serves different keys (provider, source) — the
counters don't cross between keys
survives restartlimit state lives in durable Redis — after a process restart
the counter isn't reset, the window continues
failure mode on unavailabilityredis-durable unavailable at decision time → the primitive
doesn't swallow the failure but hands it to the consumer; the
safe default is fail-closed (protection blocks the guessing),
a relaxation to fail-open is something the consumer declares
deliberately
(failure mode)
Unit · P1Cache — TTL and keyfakeredis + time-machine
hit / missthe first request is a miss, it computes and stores; a repeat
with the same key is a hit, the source isn't touched
key from query and identitya different identity or a different query yields a different
key → miss: one user's candidates don't leak to another
through the shared cache
(key composition)
TTL expiryon TTL expiry (time-machine) the entry disappears — the next
request misses and recomputes
the cache doesn't insistthe cache is a hint, not the source of truth: on a
discrepancy the trim re-checks against the source, the cached
value isn't imposed
Integration · P1Cache — single-flightrunning Redis · concurrent miss
one computes, the rest waita batch of parallel misses on a hot key — the expensive
retrieval runs exactly once, the rest wait for the ready
result, the source isn't rebuilt N times
lock in durable, not in cachethe rebuild lock (lock:) lives in
redis-durable — the LRU of the evictable cache
won't drop it under memory, single-flight doesn't fall
apart
the lock is releasedafter a rebuild (success or failure) the lock is released —
the next miss after TTL takes it anew, the key doesn't get
stuck
the lane cap holdsthe agents lane, with its own cap, is saturated
with tasks — the interactive lane is still served, it doesn't
starve
a lane failure stays locala worker failure in one lane doesn't bring down the workers of
another — the topology keeps the lanes apart
(worker topology)
Unit · P1Cron windows — timezone and ticktime-machine · deterministic
test_cron_timezone.py
Naive window → UTC by zone, NULL → organization's zone
org-zone window → UTCa naive HH:MM is unfolded to UTC via
platform_settings.timezone (IANA) — the tick
lands on the right moment, not on local hours taken as
UTC
owner's zone, NULL → organ agent schedule is computed by the owner's
timezone; NULL falls back to the
organization's zone — not UTC and not the server's zone
DST boundaryrecomputing the next tick across a daylight-saving transition
(time-machine) doesn't shift the window by an hour or lose a
tick
Integration · P1Singleton schedulerseveral replicas · one tick
test_scheduler_singleton.py
Cron tick and next_run_at scan: once per N replicas, batch of due
runs
one tick — one taska cron tick with N scheduler replicas publishes the task
once, not N times (time-machine on the cron window)
double run absorbeda race of two replicas on one tick is absorbed by the
uniqueness lock — the second publish is turned away
(uniqueness)
next_run_at scan — all duethe second trigger source: one pass over agent schedules
publishes every run whose next_run_at
has already arrived — not the first one found and not one per
tick
(two sources)
a missed tick isn't caught upa tick that fell on the singleton's downtime is
not published retroactively on return — we don't
backfill, we wait for the next window (time-machine skips the
scheduled window)
(missed tick)
Integration · P1pub/sub for push notificationsfan-out to subscribers · poll fallback
test_pubsub_push.py
The signal reaches all subscribers, a miss is harmless
fan-out to subscribersa published signal is received by all hanging subscribers — an
event from one api replica reaches a connection on another
a miss is harmlessa subscriber missed a publish (no connection at the moment of
the event) — the next poll catches up the counter, the truth in
Postgres isn't lost
Integration · P1Dedup of inbound deliveriesrunning Redis · time-machine
test_webhook_dedup.py
One event — one delivery; receipt window, key in durable
a repeat in the window is droppedthe same inbound delivery repeated (dedup:webhook:
/ dedup:slack-event: /
dedup:telegram-update:) within the window is
dropped — the event is processed exactly once
after TTL it goes through againon the receipt window's expiry (time-machine) the mark fades —
the same delivery goes through again, it doesn't get stuck
forever
key in the durable instancethe dedup key lives in redis-durable, not in the
cache — the LRU won't drop it within the receipt window and a
repeat won't leak through
orthogonal to dedup:job:inbound-delivery dedup (an event) and the idempotency of
dedup:job: (enqueuing a task) — different
namespaces, they don't overlap
(one event — one delivery)
cap → rejection, not evictionon reaching maxmemory on
redis-durable a new write is rejected with an
error (noeviction) — data isn't dropped
silently
producer backpressurethe producer hits the write rejection and slows down — the
instance doesn't grow unbounded and doesn't run into the OOM
killer
durable survives pressure, cache goes under LRUunder the same memory pressure a durable key stays put, while
a cache key is evicted by LRU — the roles are separated
physically, not by the policy of one instance
StructureTest file structure
The split is by type. unit/ runs on every PR
deterministically — fakeredis and a stubbed clock, without a running
DB or real waiting: cache key logic, idempotency, backoff.
integration/ — as a separate, rarer step on a running
DB and Redis: uniqueness and cleanup hit the index, rate-limit is
raced concurrently, lanes and the scheduler are checked on real
pools. Priority (P0–P1) is orthogonal to the directories and set by
markers (pytest -m p0).
tests/cache_workers/module directory
conftest.pyfakeredis / running Redis · time-machine · task and run
factories · SAQ handler directly
unit/deterministic, every PR — without a DB or real
waiting
test_enqueue_idempotency.pyjob_id repeat and the window after terminal
test_retry_backoff.pyclassification, exp-backoff + jitter, cap and
ceiling
test_cache.pykey composition, isolation by identity, TTL
test_cron_timezone.pyunfolding a cron window to UTC, NULL → org zone, DST
integration/running DB and Redis
test_uniqueness_reaping.pyuniqueness on the partial-UNIQUE index, stale cleanup
by heartbeat, graceful drain on SIGTERM
test_terminal_dlq.py · test_rate_limit.pyconsumer terminal/DLQ, rate-limit under concurrent
load and failure mode
test_cache_singleflight.pyone rebuild per batch of misses, lock in
durable
test_lane_isolation.py ·
test_scheduler_singleton.pylane isolation, singleton scheduler across N
replicas
test_webhook_dedup.py ·
test_memory_policy.pydedup of inbound deliveries within the receipt
window, noeviction on durable and backpressure
test_pubsub_push.pyfan-out of a signal to subscribers, poll catch-up on
a miss