Mutare.Ecto.ValidationBoundary (mutare_ecto v0.3.0)

Copy Markdown View Source

Swap a validate_number/3 bound between its strict and non-strict form — the changeset counterpart of the in-query :comparison swap. validate_number(:age, greater_than: 0) → validate_number(:age, greater_than_or_equal_to: 0). "Is the bound itself tested?" — the two validations accept the same values except one exactly on the bound, so the mutant survives unless a test builds a changeset whose value equals it and asserts the validation result. Family :validation_boundary; equivalence-sensitive (Mutare.Ecto.Equivalence), because a kill needs exactly that fixture.

Four swaps, in two pairs: greater_than ↔ greater_than_or_equal_to and less_than ↔ less_than_or_equal_to. equal_to/not_equal_to are deliberately not swapped: the mutant rejects the one value the written validation accepts (or the reverse), which any happy-path test kills — no signal beyond the whole-call :validation_drop. validate_length's min/max/is are inclusive with no strict counterpart, so core's integer family provides their off-by-one mutations (the value stays an ordinary expression — Mutare.Ecto.Changeset.Routing).

One mutant per swappable option; every other option (message:, a second bound) is kept as written. Matched like the stage drops (Mutare.Ecto.Changeset): the call resolves to Ecto.Changeset in any spelling, and the options are the trailing argument in the direct and piped forms alike. A non-keyword options argument (validate_number(cs, :age, opts)) yields nothing — there is no written key to swap. Delivered in place (the call rebuilt in its written form), reported at and tagged with the written key so # mutare:ignore[ecto:greater_than] on a bound's line suppresses its swap.

Summary

Functions

The finer # mutare:ignore label for a bound swap: the written key (# mutare:ignore[ecto:greater_than] leaves a greater_than: bound's swap alone). Total over any key; swap/1 returns the replacement key or nil when no swap exists.

Bound-swap mutations for a validate_number/3 call as :validation_boundary tags, or [] for any other call, for a call without a written keyword options list, or for options without a swappable key.

The strict/non-strict counterpart of a validate_number option key, or nil for any other key.

Functions

label(key)

@spec label(atom()) :: String.t()

The finer # mutare:ignore label for a bound swap: the written key (# mutare:ignore[ecto:greater_than] leaves a greater_than: bound's swap alone). Total over any key; swap/1 returns the replacement key or nil when no swap exists.

mutations(node, context)

@spec mutations(Macro.t(), Mutare.Ecto.Context.t()) :: [Mutare.Ecto.Tag.t()]

Bound-swap mutations for a validate_number/3 call as :validation_boundary tags, or [] for any other call, for a call without a written keyword options list, or for options without a swappable key.

swap(key)

@spec swap(atom()) :: atom() | nil

The strict/non-strict counterpart of a validate_number option key, or nil for any other key.