Skip to main content

Critérios de Aceitação

Os requisitos funcionais dizem o que o sistema faz. Este documento diz como saber que faz — cada critério é escrito na forma BDD (Behavior-Driven Development):

Dado: a situação de partida Quando: a ação do usuário Então: a resposta e o comportamento do sistema

A escolha do formato não é estética. Um critério nessa forma tem três propriedades que uma frase solta não tem:

  1. É verificável. Cada bloco vira um teste automatizado quase sem tradução — o "Dado" monta o cenário, o "Quando" executa, o "Então" é a asserção.
  2. Separa causa de efeito. Fica explícito o que é pré-condição e o que é consequência, e isso expõe requisitos que dependiam de contexto não dito.
  3. Serve às duas pontas. Quem escreve o código e quem valida o produto leem o mesmo texto, sem intermediários.

Cada cenário aponta o requisito funcional (RF) que confirma. Os cenários marcados como Planejado descrevem comportamento ainda não implementado — estão aqui porque o critério vem antes do código, não depois.


Autenticação e conta​

CA01 · Cadastro completo — RF01, RF32​

Dado: um visitante na tela de criação de conta, com nome, sobrenome, data de nascimento, nacionalidade, e-mail, senha e @ preenchidos, e a caixa de aceite da política de privacidade marcada; Quando: ele confirma a criação da conta; Então: o sistema cria a conta com o e-mail pendente de confirmação, não devolve token, envia um código de seis dígitos ao e-mail informado e leva o usuário à tela de confirmação.

CA02 · Cadastro sem aceite da política — RF32, RF33​

Dado: o mesmo visitante, com todos os campos preenchidos, mas sem marcar o aceite da política de privacidade; Quando: ele tenta concluir o cadastro; Então: a interface bloqueia o envio, destaca a caixa de aceite e informa que é preciso aceitar a política.

CA03 · Aceite forjado fora da interface — RF33​

Dado: um cliente HTTP qualquer, fora do formulário da aplicação; Quando: ele envia POST /auth/signup sem o campo acceptedPolicy, com false, ou com a string "true"; Então: o servidor responde 400, nenhuma conta é criada, e a mensagem informa que o aceite é obrigatório.

Por que a string "true" também é recusada: em JavaScript toda string não vazia é verdadeira. A comparação é estrita (=== true), de modo que um formulário que serialize o booleano sem cuidado não passe por engano.

CA04 · Confirmação de e-mail — RF34​

Dado: uma conta recém-criada, com o código de seis dígitos recebido no e-mail e ainda dentro dos quinze minutos de validade; Quando: o usuário digita o código na tela de confirmação; Então: o sistema marca o e-mail como confirmado, devolve o token e leva o usuário ao feed — sem exigir um login logo em seguida.

CA05 · Login com conta não confirmada — RF34​

Dado: uma conta cujo e-mail ainda não foi confirmado; Quando: o usuário tenta entrar com a senha correta; Então: o sistema responde 403 (e não 401), e a interface o leva à tela de código em vez de sugerir que a senha esteja errada.

Por que 403 e não 401: os dois status dizem coisas diferentes — 401 é "não sabemos quem é você", 403 é "sabemos, mas falta um passo". A distinção é o que permite à interface escolher para onde levar o usuário.

CA06 · Ordem das checagens no login — RF34​

Dado: uma conta não confirmada; Quando: alguém tenta entrar com a senha errada; Então: a resposta é 401, e não 403.

Por quê: a confirmação é checada depois da senha. Se fosse antes, bastaria digitar um e-mail qualquer para descobrir se ele tem conta aqui e em que estado ela está.

CA07 · Código errado — RF34​

Dado: uma conta pendente de confirmação; Quando: o usuário digita um código incorreto; Então: o sistema responde 400, contabiliza a tentativa e mantém a conta pendente. Após cinco tentativas erradas, o código deixa de valer e um novo precisa ser solicitado.

CA08 · Código expirado ou reutilizado — RF34​

Dado: um código emitido há mais de quinze minutos, ou já usado uma vez; Quando: o usuário o informa; Então: o sistema responde 400 com a mesma mensagem de código inválido, sem distinguir entre expirado, usado e incorreto.

Por que a mensagem é a mesma nos três casos: diferenciá-los diria a quem está tentando adivinhar se vale a pena continuar. Para o usuário legítimo, a ação é idêntica nos três casos — pedir um novo código.

CA09 · Reenvio de código — RF35​

Dado: um usuário que não recebeu o código e já esperou sessenta segundos desde o último pedido; Quando: ele aciona "Reenviar código"; Então: um novo código é emitido e enviado, e o botão volta a ficar indisponível por sessenta segundos.

CA10 · Reenvio pedido cedo demais — RF35​

Dado: um código pedido há menos de sessenta segundos; Quando: o usuário pede outro; Então: o sistema responde 429 e informa quantos segundos faltam.

Por que existe o limite: sem ele, quem conhecesse um endereço de e-mail poderia usar a rota para encher a caixa de entrada de outra pessoa.

CA11 · Resposta uniforme para e-mail desconhecido — RF35, RF36​

Dado: um e-mail que não tem conta na plataforma; Quando: alguém pede reenvio de confirmação ou código de troca de senha para ele; Então: o sistema responde 200, exatamente como responderia a um e-mail cadastrado, e nenhum e-mail é enviado.

Por quê: uma resposta diferente por caso transformaria a rota em um verificador de quem tem conta aqui — informação que não é nossa para distribuir. Por isso a interface diz "se houver uma conta, o código foi enviado", e nunca "enviamos".

CA12 · Troca de senha por código — RF36​

Dado: um usuário com o código de troca de senha em mãos; Quando: ele informa o código e uma senha com no mínimo oito caracteres, uma maiúscula e um caractere especial; Então: a senha é substituída, o e-mail passa a constar como confirmado, e o usuário é levado ao login para entrar com a nova senha.

Por que a troca também confirma o e-mail: o usuário acabou de provar que tem acesso a ele. Exigir a mesma prova duas vezes não acrescenta segurança.

CA13 · Senha fraca na troca — RF36​

Dado: um código válido; Quando: a nova senha não atende à regra de força; Então: o servidor responde 400 e a senha anterior permanece — a regra é validada na API, e não apenas no formulário.


Privacidade​

CA14 · Perfil público sem dados pessoais — RF38​

Dado: um usuário autenticado visitando o perfil de outra pessoa; Quando: a página do perfil é carregada; Então: são exibidos nome, @, foto, bio, seguidores, seguindo e o ano de criação da conta. E-mail, data de nascimento e nacionalidade não aparecem, e a API os devolve como null.

Por que a seção some inteira, em vez de mostrar um traço: uma caixa vazia com um travessão anuncia que existe algo escondido ali. O perfil público termina na bio.

CA15 · Dados pessoais no próprio perfil — RF38​

Dado: o mesmo usuário acessando o seu próprio perfil; Quando: a página é carregada; Então: e-mail, data de nascimento e nacionalidade aparecem, cada um com a opção de edição.

CA16 · Acesso à política de privacidade — RF41​

Dado: um usuário autenticado com o modal "Conta e segurança" aberto; Quando: ele aciona "Política de privacidade"; Então: a política abre em uma nova aba, com o conteúdo completo — coleta e tratamento de dados, sobre a plataforma, criptografias, direitos do titular e os serviços de hospedagem no rodapé — e nada muda na tela em que ele estava.

CA17 · Leitura da política antes do cadastro — RF32​

Dado: um visitante ainda sem conta, na etapa final do cadastro; Quando: ele aciona o link "política de privacidade" ao lado da caixa de aceite; Então: a política abre em nova aba e o formulário já preenchido permanece intacto.

Por que a página é pública: o cadastro exige aceitá-la, e exigir login para ler aquilo que se está aceitando seria um ciclo impossível de fechar.


Publicações​

CA18 · Página dedicada da publicação — RF37​

Dado: uma publicação existente; Quando: o usuário clica no horário do cartão, no feed ou no perfil; Então: a página /post/:id abre com os dados do autor, o conteúdo, os contadores e os comentários já expandidos.

CA19 · Publicação inexistente ou de conta excluída — RF37​

Dado: um endereço de publicação que não corresponde a nada visível; Quando: a página é aberta; Então: a API responde 404 e a interface mostra o estado de erro com a opção de tentar de novo.

CA20 · Publicações no perfil — RF39​

Dado: um perfil, próprio ou de terceiro, com publicações; Quando: a página do perfil é carregada; Então: logo abaixo das informações aparece a seção "Publicações", em ordem cronológica decrescente, com o total e o botão "Carregar mais" enquanto houver páginas.

CA21 · Perfil sem publicações — RF39​

Dado: um perfil cujo dono nunca publicou; Quando: a página é carregada; Então: a seção exibe "Nenhuma publicação por aqui ainda" — e não uma lista vazia sem explicação nem um erro.

CA22 · Perfil inexistente — RF39​

Dado: um @ que não corresponde a nenhum usuário; Quando: as publicações são solicitadas; Então: a API responde 404, e não uma lista vazia.

Por quê: lista vazia afirma "esse usuário existe e não publicou nada", que é uma informação diferente de "esse usuário não existe".

CA23 · Edição e exclusão no perfil de terceiros — RF25, RF28​

Dado: um usuário visitando o perfil de outra pessoa; Quando: ele abre o menu de contexto de uma publicação alheia; Então: as opções de editar e excluir não são oferecidas — a decisão é tomada comparando o autor da publicação com o usuário do token, e não pela tela em que se está.

CA24 · Busca leva à publicação — RF42​

Dado: um usuário com a busca aberta na aba "Posts" e ao menos um resultado em tela; Quando: ele clica em um resultado; Então: ele vai para a página da publicação, e não para o perfil do autor.

CA25 · Termo de busca curto demais — RF31​

Dado: a aba "Posts" da busca; Quando: o usuário digita menos de três caracteres; Então: nenhuma consulta é disparada e a interface pede um termo maior.

Por quê: o índice FULLTEXT do InnoDB ignora palavras menores que innodb_ft_min_token_size (padrão 3). Consultar assim devolveria vazio sem explicação.


Exclusão de conta​

CA26 · Etapa 1 — entender o efeito — RF16​

Dado: um usuário autenticado que acionou "Excluir conta" em Conta e segurança; Quando: o fluxo abre; Então: a primeira etapa lista as consequências permanentes e avisa que um código será enviado ao e-mail no passo seguinte. Nada é excluído ainda.

CA27 · Etapa 2 — provar presença — RF40​

Dado: o usuário na primeira etapa; Quando: ele aciona "Continuar"; Então: o sistema envia um código ao e-mail da conta — lido do banco a partir do token, nunca aceito da requisição — e exibe o endereço mascarado (a*****@gmail.com) junto ao campo do código.

Por que mascarado: confirma ao usuário para onde o código foi sem escrever o endereço inteiro em uma tela que pode estar sendo vista por outra pessoa.

Por que código e não senha: a senha costuma estar salva no navegador e não distingue o dono de quem se sentou na cadeira dele. O código exige acesso à caixa de entrada naquele momento.

CA28 · Etapa 3 — declarar intenção — RF16​

Dado: o código correto informado; Quando: o usuário avança; Então: o sistema pede que ele digite a palavra EXCLUIR, em maiúsculas, e só então libera o botão de exclusão.

CA29 · Exclusão concluída — RF40, RF43​

Dado: as três etapas cumpridas; Quando: o usuário confirma; Então: as publicações, comentários, curtidas e relações de seguir são apagados; o cadastro é anonimizado — e-mail e @ viram valores derivados do id em domínio reservado, o nome vira "Conta encerrada" e os demais campos pessoais viram nulos; a conta some do feed, da busca, dos perfis e das listas de seguidores; a sessão local é apagada e o usuário volta ao login.

Por que anonimizar e não apagar a linha: um DELETE em user dispararia ON DELETE CASCADE sobre like_post e removeria as curtidas que a pessoa deu em publicações de terceiros — contadores alheios cairiam retroativamente. Encerrar a própria conta não pode mexer no conteúdo de outras pessoas. O artigo 12 da LGPD coloca dado anonimizado fora do alcance da lei, então a obrigação de eliminação é cumprida.

CA29b · E-mail liberado após a exclusão — RF43​

Dado: uma conta excluída; Quando: a mesma pessoa tenta se cadastrar de novo com o mesmo e-mail e o mesmo @; Então: o cadastro é aceito.

Por quê: email e alias são únicos. Enquanto a exclusão apenas marcava deleted_at, a linha antiga continuava ocupando os dois valores e trancava a pessoa para fora do próprio endereço. A anonimização os devolve.

CA30 · Exclusão sem código — RF40​

Dado: um usuário autenticado; Quando: ele envia DELETE /users/me sem código, ou com um código inválido; Então: o servidor responde 400 e a conta permanece ativa.

Por quê: para uma ação irreversível, o token sozinho é pouco — ele prova que a sessão existe, não que o dono está diante da tela.

CA31 · Exclusão sem sessão — RF40​

Dado: uma requisição sem token; Quando: ela chega a DELETE /users/me ou à rota de código de exclusão; Então: o servidor responde 401 e nenhum e-mail é enviado.


Segurança dos códigos​

CA32 · Código guardado como hash​

Dado: um código emitido para qualquer um dos três fluxos; Quando: ele é gravado; Então: o banco recebe apenas o hash do código, e nunca o valor em texto — o mesmo tratamento dado à senha.

Por quê: se a tabela vazar, os hashes não permitem entrar em conta alguma. O texto puro permitiria, ainda que por poucos minutos.

CA33 · Uso único garantido sob concorrência​

Dado: duas requisições chegando ao mesmo tempo com o mesmo código válido; Quando: ambas tentam consumi-lo; Então: apenas uma é aceita; a outra recebe 400.

Como: o consumo é um UPDATE ... WHERE id = ? AND used_at IS NULL, e o serviço só prossegue se ele afetou uma linha. Sem isso existiria uma janela entre verificar e marcar, e as duas passariam.


Defesa e mensagens de erro​

CA34 · Limite de requisições por rota — RNF-B24​

Dado: uma origem que já esgotou o limite de tentativas de login na janela corrente; Quando: ela tenta fazer login de novo; Então: o servidor responde 429, sem consultar o banco nem calcular hash de senha.

Por que baldes separados por rota: com um balde único, cada usuário novo gastaria cadastro, verificação e login do mesmo saldo. Atrás de uma rede compartilhada — universidade, escritório, operadora móvel —, onde centenas de pessoas saem pelo mesmo endereço, isso trancaria a quinta pessoa a se cadastrar. Estourar o limite de login não pode impedir alguém de criar conta.

CA35 · Mensagem de erro que chega ao usuário — RNF-B26​

Dado: a aplicação em produção; Quando: uma requisição falha com erro 4xx — por exemplo, cadastro sem o aceite da política; Então: o usuário lê a mensagem exata escrita para aquele caso.

E dado o mesmo ambiente, quando a falha é 5xx, então o usuário lê apenas "Erro interno do servidor".

Por quê: a distinção é de origem, não de gravidade. Os 4xx são frases escritas para alguém ler e agir; os 5xx carregam mensagem de exceção, que pode expor nome de tabela ou caminho de arquivo. Mascarar os dois, como a primeira versão fazia, transformava "É necessário aceitar a política de privacidade" em "Erro interno do servidor" — e o usuário não tinha como saber o que corrigir.


Edição do perfil​

CA36 · Dados agrupados por visibilidade — RF29, RF44​

Dado: o usuário na tela "Informações pessoais"; Quando: ela é exibida; Então: os campos aparecem em dois grupos rotulados — "Perfil", visível a qualquer pessoa, com nome e bio; e "Dados pessoais", visível só ao dono, com e-mail, nascimento e nacionalidade.

Por quê: o agrupamento é por quem enxerga, e não por tipo de dado, porque é essa a informação que falta a quem edita. Sem o rótulo, é fácil escrever o telefone na bio achando que é campo privado.

CA37 · Um só lugar para editar — RF29, RF44​

Dado: o usuário no próprio perfil; Quando: ele procura alterar qualquer dado; Então: o caminho é sempre "Conta e segurança" → "Informações pessoais" — não há botões de edição espalhados pelo perfil. A foto é a única exceção, e continua sendo trocada tocando no avatar.

Por quê: dois caminhos para a mesma alteração são duas telas para manter em acordo. A foto fica de fora porque recorte de imagem não cabe num formulário de texto.


Fluxos planejados​

CA38 · Feed em tempo real — RF10 · Planejado​

Dado: dois usuários com o feed aberto; Quando: um deles publica; Então: a publicação aparece no feed do outro sem recarregar a página, entregue por WebSocket.

CA39 · Conversa privada — RF09, RF11, RF12 · Planejado​

Dado: dois usuários cadastrados; Quando: um inicia uma conversa e envia uma mensagem; Então: a mensagem é entregue em tempo real, fica gravada no histórico e exibe status de entrega — pendente, enviada, recebida e lida.