Mutare.Oban.WorkerReturn (mutare_oban v0.1.0)

Copy Markdown View Source

Swaps the return value of an Oban worker callback (perform/1, and Pro's process/1) for a different but still valid Oban outcome — the higher-signal analogue of Mutare.Mutators.ReturnValue's sentinel.

It is behaviour-gated: it fires only inside a module that implements Oban.Worker (or Oban.Pro.Worker), read from the enclosing module's behaviour set (context.behaviours, gathered by Mutare from a use Oban.Worker's injected @behaviour). Everywhere else it is inert.

Because every swap is itself a valid Oban return, the mutant runs as a legitimate job that behaves differently — so a survivor pinpoints a precise gap: no test checks this job's success / failure / retry / cancel / snooze semantics.

The swaps

:ok                ->  {:error, :mutare}     a completed job is now retryable
{:ok, value}       ->  {:error, :mutare}     a completed job is now retryable
{:error, reason}   ->  :ok                    a failure is silently swallowed
{:error, reason}   ->  {:cancel, reason}     a transient failure becomes a permanent cancel
{:cancel, reason}  ->  {:error, reason}      a permanent cancel becomes retryable
{:cancel, reason}  ->  :ok                   a cancel becomes a quiet success
{:snooze, seconds} ->  :ok                   a reschedule is dropped
{:discard, reason} ->  :ok                   (legacy discard) silently succeeds
{:discard, reason} ->  {:cancel, reason}
:discard           ->  :ok                   (legacy bare discard) silently succeeds

The headline is {:error, reason}:ok: the canonical Oban survivor-finder. If a test inserts a job that should fail and never asserts the job ends up retryable / discarded, that mutant lives.

Ignore variants

Each mutant is labelled by the Oban return it becomesok, error, or cancel — so a # mutare:ignore[oban_worker_return:<label>] directive can suppress one kind of swap without silencing the family:

{:error, reason} # mutare:ignore[oban_worker_return:ok] best-effort, failure unasserted

keeps the {:error, reason}{:cancel, reason} mutant while dropping the failure-swallowing :ok swap on that line. See Mutare.Ignore.

Each mutant reuses the original reason operand and injects only literal control atoms, so every one is a valid return that compiles — the single metamutant build is never at risk. Return tails are delivered by Mutare's structural return_replacements/2 hook, so the swaps also reach a worker that returns from a branch tail of a case/cond/if/with in tail position, not just the clause body.

Deliberately left alone

  • The {:ok, value}'s value and a {:snooze, _}'s seconds are dropped (they don't survive the tag change), never rewritten — a value/seconds swap is Mutare.Mutators territory and rarely an Oban-semantics question.
  • A bare :ok carries no payload to preserve, so its only swap is to a sentinel error.
  • A bare :cancel tail is not matched: it is not a member of Oban's t:Oban.Worker.result/0 union (only :discard has a bare legacy form). Oban logs an unknown return and completes the job anyway, so an :ok swap there would differ only by a log line — an unkillable no-op.

Summary

Functions

The deployment requirement: Oban.Worker must be loadable in the Mutare process, or the use Oban.Worker expansion that surfaces @behaviour Oban.Worker cannot run and this mutator silently never fires. Declaring it turns that into a loud Mutare.EnvironmentError at startup. Oban.Pro.Worker is deliberately not listed — Pro is optional, and its absence only narrows the gate.

Offer the alternative Oban return(s) for tail, but only inside a worker module (read from context.behaviours). The context-aware form of Mutare.Mutator.Structural.return_replacements/1.

Classifies an emitted swap by its replacement's return tag (Mutare.Mutator.variant/2).

The swap-target vocabulary for # mutare:ignore[oban_worker_return:<label>] — each mutant is labelled by the Oban return it becomes: "ok", "error", or "cancel".

Functions

required_modules()

The deployment requirement: Oban.Worker must be loadable in the Mutare process, or the use Oban.Worker expansion that surfaces @behaviour Oban.Worker cannot run and this mutator silently never fires. Declaring it turns that into a loud Mutare.EnvironmentError at startup. Oban.Pro.Worker is deliberately not listed — Pro is optional, and its absence only narrows the gate.

return_replacements(tail, map)

Offer the alternative Oban return(s) for tail, but only inside a worker module (read from context.behaviours). The context-aware form of Mutare.Mutator.Structural.return_replacements/1.

variant(original, mutated)

Classifies an emitted swap by its replacement's return tag (Mutare.Mutator.variant/2).

Every mutant this family produces is exactly one of :ok, {:error, _}, or {:cancel, _}, so the label reads directly off the mutated node.

variants()

The swap-target vocabulary for # mutare:ignore[oban_worker_return:<label>] — each mutant is labelled by the Oban return it becomes: "ok", "error", or "cancel".