Fix accept_params/reject_params missing a standalone Contract's unknown: strictness - #72
Merged
Merged
Conversation
…n: strictness ParamsBehaviourMatcher replayed a rule against a brand-new generic host instead of one that mirrors Permittable::Contract's own override, which forces top_level: false because standalone input has no router. The matcher's generic host fell through to the module default instead, which exempts routing keys (controller, action, ...) at the top level — so accept_params/reject_params could wrongly agree a routing-shaped key was accepted by a rootless unknown: :error Contract that actually rejects it. Mirror that one override on the throwaway host when @subject is a Contract, leaving the controller path untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The false positive
ParamsBehaviourMatcher#run(accept_params/reject_params's engine) replays the rule against a brand-new, generic throwaway host —Class.new { include Permittable; attr_accessor :params }— instead of one that behaves like@subjectitself.That matters for a standalone
Permittable::Contract.Contract#initializebuilds its own internal host with a deliberate override:— "standalone input has no router and no request... gets no exemption from
unknown:checking." The matcher's generic host has no such override, so it falls through to the module default, which exempts routing/form keys (controller,action,format, ...) whenevertop_levelis true — true for any rootless rule, the common case for aContract.So
accept_params/reject_paramscould disagree withcontract.callon exactly the routing-shaped keys a standaloneContractis documented to not exempt — a false "accept" (or false negative onreject_params) in the one place a spec exists to catch it.The fix
ParamsBehaviourMatcher#runstill builds the same minimal generic host for the general case — it is not safe to subclass@subject's own class, since@subjectmay be a real controller, and instantiating one outside the framework's own dispatch (no request, no response, whatever itsbefore_actions assume) isn't something the matcher can do reliably.@subject.classalso isn't usable for aContract, sincePermittable::Contractdoesn't itselfinclude Permittable— it wraps a private internal host that does.So the one override
Contractmakes is replicated directly on the throwaway host, only when@subjectis aPermittable::Contract:The controller path is untouched — it still gets the plain generic host, exactly as before.
Verification
spec/matchers_spec.rbreproduces the false positive against a rootlessunknown: :errorContractwith acontrollerkey; confirmed it fails onmasterbefore the fixbundle exec rspec)🤖 Generated with Claude Code