Inverte a validação de papel para fail-closed - #91
Merged
Merged
Conversation
User#validar_atribuicao_de_papel começava com `return if ator.blank?`: a regra que impede um líder de virar admin só rodava se alguém tivesse preenchido o ator, um attr_accessor que não é coluna. Os dois controllers de hoje preenchem, e há specs cobrindo isso — mas a invariante valia por disciplina, não por construção. Um job, um importador, uma rake task, um endpoint futuro, um update no console: qualquer caminho novo que esquecesse passava direto e em silêncio. O default escolhido para o caso não previsto era o errado. Agora ator ausente é RECUSA. Os poucos lugares legítimos que escrevem papel fora de uma requisição declaram isso com ator_dispensado: db/seeds.rb, o console e a factory da suíte. O custo recai sobre eles, que é onde ele deve cair — e o esquecimento vira erro visível em vez de brecha. O nome ator_dispensado é incômodo de escrever de propósito. Ele não deve ser o caminho fácil: quem o usa está afirmando "não há requisição aqui, não há fronteira a defender", e isso precisa ser consciente, não um atalho para calar uma validação que apareceu no caminho. Duas decisões que valem registro: A criação também é coberta, não só a alteração. Um caminho que crie um usuário já com role=admin sem ator é tão perigoso quanto um que promova alguém depois. Na factory, ator_dispensado vale só durante a criação — um after(:create) limpa a marca. Sem essa limpeza os specs de spec/models/user_spec.rb passariam por vacuidade, já que todos partem de um usuário criado por ali. Um spec que mude o papel DEPOIS está exercitando a regra e informa o ator, como qualquer requisição informaria. Não criei um caso de uso Users::ChangeRole. Ele não resolveria sozinho: enquanto user.update(role:) continuar acessível, é o model que precisa preservar a invariante — e é o model que a preserva agora. Verificado no container: RuboCop 110 arquivos sem ofensas, zeitwerk:check limpo, RSpec 391 exemplos e 0 falhas (eram 388), sem nenhum ajuste em spec fora do arquivo desta regra. Closes #79 Co-Authored-By: Claude Opus 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.
Fecha a #79.
User#validar_atribuicao_de_papelcomeçava comreturn if ator.blank?. A regra que impede um líder de virar admin só rodava se alguém tivesse preenchido o ator — umattr_accessorque não é coluna.Os dois controllers de hoje preenchem, e há specs cobrindo isso. Mas a invariante valia por disciplina, não por construção: um job, um importador, uma rake task, um endpoint futuro, um
updateno console — qualquer caminho novo que esquecesse passava direto e em silêncio. Como a issue coloca, o problema nunca foi a decisão de ter umator; foi o default escolhido para o caso não previsto.Agora ator ausente é recusa. Os poucos lugares legítimos que escrevem papel fora de uma requisição declaram isso com
ator_dispensado:db/seeds.rb, o console e a factory da suíte.O nome é incômodo de propósito
ator_dispensadonão deve ser o caminho fácil. Quem o usa está afirmando "não há requisição aqui, não há fronteira a defender" — isso precisa ser uma decisão consciente, não um atalho para calar uma validação que apareceu no caminho. Está comentado assim no model.Duas decisões
A criação também é coberta, não só a alteração. Um caminho que crie um usuário já com
role=adminsem ator é tão perigoso quanto um que promova alguém depois.Na factory, a dispensa vale só durante a criação — um
after(:create)limpa a marca. Sem essa limpeza os specs despec/models/user_spec.rbpassariam por vacuidade, já que todos partem de um usuário criado ali. Um spec que mude o papel depois está exercitando a regra e informa o ator, como qualquer requisição informaria.O que não fiz
Não criei um
Users::ChangeRole. A issue já antecipava que ele não resolve sozinho, e concordo: enquantouser.update(role:)continuar acessível, é o model que precisa preservar a invariante — e é o model que a preserva agora. Um caso de uso por cima seria conveniência, não garantia.Verificação
No container: RuboCop 110 arquivos / 0 ofensas,
zeitwerk:checkAll is good!, RSpec 391 exemplos / 0 falhas (eram 388).Vale registrar que nenhum spec fora do arquivo desta regra precisou de ajuste — era o risco real da mudança, e o
after(:create)na factory foi o que o absorveu. O spec que antes afirmava "não roda sem ator (seeds e console seguem funcionando)" foi invertido para afirmar a recusa, com um caso novo cobrindo a saída declarada.Nada de JS, CSS, HAML ou CSP mudou.