# Plano de testes — `PP-5` Bloqueio do palpite no horário oficial do jogo

**Story:** `docs/stories/PP-5-bloqueio-no-apito.md`

Por que esta story concentra risco: o prazo é a única barreira entre o bolão e
a fraude óbvia (palpitar depois do gol). Tudo aqui é decidido pelo **relógio do
servidor** — nenhum caso abaixo pode passar confiando no relógio do aparelho.

| ID | Título | Tipo | Prioridade |
| -- | ------ | ---- | ---------- |
| PP-5-01 | Palpite entra com prazo aberto | caminho feliz | alta |
| PP-5-02 | Palpite é recusado no segundo seguinte ao apito | borda | alta |
| PP-5-03 | Bypass pela API com prazo vencido | segurança | alta |
| PP-5-04 | Admin não tem exceção de prazo | autorização | alta |
| PP-5-05 | Device adiantado 3h não abre prazo fechado | borda | alta |
| PP-5-06 | Device atrasado 5h não fecha prazo aberto | borda | alta |
| PP-5-07 | Lote com jogo aberto e jogo fechado | borda | alta |
| PP-5-08 | Campo desabilitado traz o motivo | caminho feliz | média |
| PP-5-09 | App não monta envio para jogo fechado | caminho feliz | média |
| PP-5-10 | Prazo fecha com a tela aberta | concorrência | média |
| PP-5-11 | Jogo de outro bolão | autorização | alta |

---

### PP-5-01 — Palpite entra com prazo aberto
**Tipo:** caminho feliz · **Prioridade:** alta
**Pré-condições:** participante em bolão ativo; jogo `Flamengo x Vasco` com
kickoff em 2 dias.
**Dados de teste:** `2 x 1`
**Passos:**
1. Abrir a rodada do jogo.
2. Preencher `2` e `1`.
3. Enviar.
**Resultado esperado:** HTTP 201, `savedCount: 1`, `rejectedCount: 0`; a rodada
relida traz `myPrediction: {homeScore: 2, awayScore: 1}`.

### PP-5-02 — Palpite é recusado no segundo seguinte ao apito
**Tipo:** borda · **Prioridade:** alta
**Pré-condições:** jogo com `kickoffAt` = agora **menos 1 segundo**.
**Dados de teste:** `1 x 0`
**Passos:**
1. Enviar palpite para esse jogo pela API.
**Resultado esperado:** HTTP 201 com `results[0].saved: false` e
`reason: "PRAZO_FECHADO"`. O palpite **não** existe no banco.
_Nota: a comparação é `kickoff <= agora`. Kickoff exatamente igual a agora é
fechado — na dúvida, o prazo fecha, nunca abre._

### PP-5-03 — Bypass pela API com prazo vencido
**Tipo:** segurança · **Prioridade:** alta
**Pré-condições:** jogo de rodada passada (kickoff há semanas), participante
válido com token válido.
**Passos:**
1. `POST /pools/:id/predictions` direto, sem passar pelo app.
**Resultado esperado:** recusado com `PRAZO_FECHADO`. A UI não é a barreira —
se este caso passar, todo o resto do épico é decorativo.

### PP-5-04 — Admin não tem exceção de prazo
**Tipo:** autorização · **Prioridade:** alta
**Pré-condições:** o **criador** do bolão, que é admin permanente; jogo com
prazo vencido.
**Passos:**
1. Enviar palpite como admin pela API.
**Resultado esperado:** mesma recusa `PRAZO_FECHADO`. Admin que palpita depois
do apito é o cenário que destrói a confiança no bolão inteiro.

### PP-5-05 — Device adiantado 3h não abre prazo fechado
**Tipo:** borda · **Prioridade:** alta
**Pré-condições:** jogo que começou há 1h; relógio do aparelho **3h à frente**.
**Passos:**
1. Abrir a rodada no app com o relógio alterado.
2. Tentar palpitar.
**Resultado esperado:** etiqueta `prazo fechado`, campos desabilitados, e o
envio nem é montado. A contagem vem do `serverTime` da resposta, não de
`DateTime.now()`.

### PP-5-06 — Device atrasado 5h não fecha prazo aberto
**Tipo:** borda · **Prioridade:** alta
**Pré-condições:** jogo que começa em 30 min; relógio do aparelho **5h atrás**.
**Passos:**
1. Abrir a rodada e palpitar.
**Resultado esperado:** prazo aberto com contagem coerente (~30 min), palpite
aceito. O oposto de PP-5-05: o desvio não pode **tirar** prazo de quem tem.

### PP-5-07 — Lote com jogo aberto e jogo fechado
**Tipo:** borda · **Prioridade:** alta
**Pré-condições:** um jogo com prazo aberto e um vencido, no mesmo bolão.
**Passos:**
1. Enviar os dois no mesmo `POST`.
**Resultado esperado:** HTTP 201 com `savedCount: 1`, `rejectedCount: 1`; o
aberto salvo, o fechado com `PRAZO_FECHADO`. Um item ruim **não** derruba o
lote — derrubar faria o participante perder a rodada inteira por um jogo.

### PP-5-08 — Campo desabilitado traz o motivo
**Tipo:** caminho feliz · **Prioridade:** média
**Pré-condições:** rodada com jogo já iniciado.
**Passos:**
1. Abrir a rodada no app.
**Resultado esperado:** o cartão do jogo mostra `prazo fechado` (ou
`bolão encerrado`, se for o caso) junto do campo desabilitado. Campo cinza sem
motivo é indistinguível de app quebrado.

### PP-5-09 — App não monta envio para jogo fechado
**Tipo:** caminho feliz · **Prioridade:** média
**Pré-condições:** rodada só com jogos fechados; nenhum rascunho pendente.
**Passos:**
1. Tocar em enviar.
**Resultado esperado:** aviso "Nada para enviar: preencha o placar de algum jogo
com prazo aberto" e **nenhuma** chamada ao servidor. Tentar e falhar seria
fingir que ainda dava.

### PP-5-10 — Prazo fecha com a tela aberta
**Tipo:** concorrência · **Prioridade:** média
**Pré-condições:** jogo com kickoff em ~1 min; tela de palpite aberta e
preenchida, sem enviar.
**Passos:**
1. Esperar a contagem zerar sem recarregar.
2. Tocar em enviar.
**Resultado esperado:** o cartão passa a `prazo fechado` sozinho (tique de 30s);
se o envio ocorrer, vem `PRAZO_FECHADO` com a mensagem "vale o palpite que
estava salvo", e o campo volta ao valor **do servidor** — não ao digitado.

### PP-5-11 — Jogo de outro bolão
**Tipo:** autorização · **Prioridade:** alta
**Pré-condições:** dois bolões; `matchId` que existe mas **não** pertence ao
bolão da rota.
**Passos:**
1. Enviar palpite com esse `matchId`.
**Resultado esperado:** recusado com `JOGO_FORA_DO_BOLAO`, mensagem própria e
distinta de prazo. Sem isso, dá para pontuar em jogo que o bolão não escolheu.
