Um 500 escondido dentro das rotas isoladas de uma engine montada
MissionControl::Jobs é a interface web que uso para acompanhar os jobs do Solid Queue rodando. Está montada em /jobs e fica atrás do mesmo login do resto do blog-manager. Só que não estava, acessava sem sessão e você recebia um 500, não uma página de login. O tipo de bug que fica invisível a menos que você vá procurar, porque quem já está logado nunca vê.
O que eu construí
Uma linha, no fim das contas: redirect_to new_session_path virou redirect_to main_app.new_session_path na concern de autenticação compartilhada. Mas chegar nessa linha exigiu entender algo sobre engines do Rails que eu não tinha internalizado antes.
O que me surpreendeu
MissionControl::Jobs se monta como uma engine com seu próprio namespace de rotas isolado. Está configurada para herdar o ApplicationController da minha aplicação, então recebe minha lógica de autenticação de graça, login obrigatório, igual em todo lugar. O que eu não sabia: quando esse código de autenticação compartilhado roda dentro de uma requisição para a engine, chamar um helper de rota sem qualificação como new_session_path não resolve contra as rotas da minha aplicação. Resolve contra as rotas da própria engine. E a engine não tem nenhuma action sessions#new, então em vez de um redirect você recebe um UrlGenerationError, um 500, bem no meio da camada de autenticação que devia ser justamente o que mantém as pessoas de fora.
O fix, main_app.new_session_path, é o idioma padrão para “não, eu quero dizer as rotas da aplicação de verdade, não seja lá qual contexto eu estou executando agora.” Depois que entendi o mecanismo, ficou óbvio. Antes disso, a mensagem de erro (No route matches {:action=>"new", :controller=>"sessions", :server_id=>nil}) só parecia sem sentido, por que sessions precisaria de um server_id?
Escrevi o teste antes do fix, especificamente para ter uma falha real, reproduzida, para comparar em vez de confiar só no meu próprio diagnóstico. Ainda bem, porque o teste em si acabou esbarrando na mesma classe de bug em outro lugar. Depois que o teste visita /jobs, chamar o helper de caminho de login de dentro do teste, mesmo qualificado com main_app., resolve contra o contexto de roteamento residual da engine e afirma a coisa errada. Testes de integração do Rails carregam uma noção de “a página atual” entre requisições dentro de um mesmo teste, e depois que você visita um caminho montado por uma engine, esse contexto persiste para as próprias chamadas de helper do teste. O fix ali foi diferente: capturar o caminho esperado antes de fazer a requisição, não depois.
A revisão de código encontrou mais uma coisa que eu tinha deixado passar: a mesma concern compartilhada tem um segundo helper de rota sem qualificação duas linhas abaixo, no código que decide para onde te mandar depois de um login bem-sucedido. No momento inalcançável a partir da engine, nada chama esse trecho dali hoje, mas é exatamente o mesmo formato de bug sentado no mesmo arquivo, só que dormente. Consertei também, já que eu estava ali e já tinha provado que o fix não muda comportamento em nenhum outro lugar.
Uma coisa que deliberadamente não consertei: revisar esse mesmo código de redirecionamento de login revelou um problema real e separado, ele guarda qualquer URL que você estava tentando acessar quando sua sessão expirou, incluindo o método, mas sempre redireciona de volta com GET depois que você faz login de novo. Se essa URL guardada fosse uma action só de POST, você receberia um 404 na volta em vez de chegar aonde queria. Verifiquei o histórico do git e confirmei que isso é anterior a hoje por completo, é comportamento original do scaffold do Rails 8, não algo que eu introduzi. É real, mas consertar direito significa decidir como a aplicação deve se comportar nesse caso, não colar mais uma linha num PR que devia ser um fix pontual para outra coisa. Registrei separado.
O que vem a seguir
Verifiquei ponta a ponta em vez de confiar só na suíte de testes: confirmei que o bug estava ativo em produção (testei com curl, recebi o 500 eu mesmo), fiz o deploy do fix, testei de novo com curl e recebi o redirect que esperava. Coisa pequena, mas “os testes passam” e “a coisa quebrada de verdade na frente de usuários reais agora está consertada” não são a mesma afirmação, e é fácil confundir as duas.
Leitura relacionada
Uma correção de desvio de documentação que não foi tão chata quanto parecia
Três itens da auditoria que cada um virou outra coisa: uma alegação meio corrigida, uma recuperação de senha silenciosamente morta, e um e-mail de staging que apontaria para produção.
Apagando código morto, e pegando um motivo errado para uma resposta certa
Uma issue de limpeza com uma justificativa errada, uma varredura de documentação que não era necessária, e a credencial órfã que uma revisão pegou.
Adicionando links dos posts ao vivo no painel
Fazendo os títulos do painel linkarem para os posts ao vivo: um prefixo de caminho configurável, uma trava por pub_date contra links quebrados, e uma coluna do spec que descartei.
Você também pode achar útil
NordPass
Gerenciador de senhas da equipe por trás da NordVPN, com um plano gratuito.
Como afiliado da NordPass, ganho com compras qualificadas.
Saiba maisProton Drive
Armazenamento em nuvem criptografado, da equipe por trás do Proton Mail.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisRackNerd VPS
Hospedagem VPS econômica para serviços leves que funcionam continuamente.
Como afiliado da RackNerd, ganho com compras qualificadas.
Saiba mais