From 4a1f95000fda2fb9fe4b7deff3ea67167b93b8e1 Mon Sep 17 00:00:00 2001 From: ThomasTDS Date: Fri, 2 Oct 2026 14:32:53 -0300 Subject: [PATCH] =?UTF-8?q?Escreve=20os=20cen=C3=A1rios=20Gherkin=20em=20p?= =?UTF-8?q?ortugu=C3=AAs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Os arquivos .feature passam a usar # language: pt (Funcionalidade, Cenário, Dado, Quando, Então), e os passos trocam o sujeito "ele" por "o usuário", que era ambíguo logo após "que existe um booking criado". Os links da matriz em docs/test-cases.md foram recalculados para apontar para a linha de cada Cenário. --- README.md | 4 +- docs/test-cases.md | 56 +++++++-------- features/auth.feature | 15 ++-- features/create_booking.feature | 49 ++++++------- features/delete_booking.feature | 31 ++++---- features/get_booking.feature | 29 ++++---- features/update_booking.feature | 121 ++++++++++++++++---------------- steps/common_steps.py | 6 +- steps/test_auth.py | 6 +- steps/test_create_booking.py | 12 ++-- steps/test_delete_booking.py | 6 +- steps/test_get_booking.py | 8 +-- steps/test_update_booking.py | 20 +++--- 13 files changed, 184 insertions(+), 179 deletions(-) diff --git a/README.md b/README.md index 2b0c8b8..b68ea34 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Autenticação: `POST /auth` com `{ "username": "admin", "password": "password12 ### Estrutura de Testes e Padrões Aplicados -- BDD / Gherkin: cenários claros e legíveis em `.feature`, executados via `pytest-bdd`. +- BDD / Gherkin: cenários claros e legíveis em `.feature`, escritos em português (`# language: pt`, com `Funcionalidade`, `Cenário`, `Dado`, `Quando` e `Então`) e executados via `pytest-bdd`. - API Client Objects: `AuthApiClient` e `BookingApiClient` encapsulam as chamadas HTTP, do mesmo jeito que Page Objects encapsulam elementos de UI. - Timeout em todas as chamadas HTTP: os dois clients herdam de `BaseApiClient`, que aplica 5 segundos de limite para conectar e 30 para receber a resposta. Sem isso, o `requests` pode esperar indefinidamente, e uma API travada deixaria o CI parado até o limite de execução do GitHub Actions. - Pirâmide de testes: além dos cenários de BDD (`steps/`), que são testes de integração reais contra a `restful-booker`, `tests/unit/` cobre `api/` e `models/` de forma isolada, com `requests-mock` simulando as respostas HTTP — sem rede, sem depender da API pública estar no ar. @@ -162,7 +162,7 @@ Autenticação: `POST /auth` com `{ "username": "admin", "password": "password12 - Integração contínua via GitHub Actions, em dois jobs: `unit` roda as checagens estáticas e os testes unitários, sem rede, e `integration` só começa se o `unit` passar, rodando a suíte completa contra a API pública. Assim, uma falha no `integration` com o `unit` verde aponta para a API ou para um cenário, e não para o código dos clients. Os dois rodam a cada push e pull request para `master`, em Python 3.12 e 3.13, e também diariamente às 06:00 UTC (ver [.github/workflows/tests.yml](.github/workflows/tests.yml)) para detectar quebras causadas pela própria API pública, com o relatório HTML publicado como artifact do workflow. - Dependências atualizadas automaticamente pelo Dependabot (pip e GitHub Actions, semanal — ver [.github/dependabot.yml](.github/dependabot.yml)). PRs de patch/minor com CI verde são mergeados automaticamente ([.github/workflows/dependabot-auto-merge.yml](.github/workflows/dependabot-auto-merge.yml)); bumps de major exigem revisão manual. - Auditoria de vulnerabilidades conhecidas nas dependências instaladas a cada execução do CI, via [`pip-audit`](https://github.com/pypa/pip-audit); encontrar uma vulnerabilidade quebra o build. -- Rastreabilidade de QA: matriz de test cases em [docs/test-cases.md](docs/test-cases.md), com tags `@TC-XXX` em cada `Scenario` e um subconjunto `@smoke` (`pytest -m smoke`) cobrindo os fluxos ponta-a-ponta mais críticos. Bugs reais encontrados são documentados como GitHub Issues usando o template em [.github/ISSUE_TEMPLATE/bug_report.md](.github/ISSUE_TEMPLATE/bug_report.md). +- Rastreabilidade de QA: matriz de test cases em [docs/test-cases.md](docs/test-cases.md), com tags `@TC-XXX` em cada `Cenário` e um subconjunto `@smoke` (`pytest -m smoke`) cobrindo os fluxos ponta-a-ponta mais críticos. Bugs reais encontrados são documentados como GitHub Issues usando o template em [.github/ISSUE_TEMPLATE/bug_report.md](.github/ISSUE_TEMPLATE/bug_report.md). --- diff --git a/docs/test-cases.md b/docs/test-cases.md index 56b8c0f..9aa5bdf 100644 --- a/docs/test-cases.md +++ b/docs/test-cases.md @@ -1,8 +1,8 @@ # Matriz de Test Cases -Rastreabilidade dos casos de teste do projeto. Esta tabela **não duplica** os passos dos cenários — eles já estão documentados em Gherkin nos arquivos `.feature`, com o ID do test case como tag (`@TC-XXX`) em cada `Scenario`, garantindo rastreabilidade bidirecional. +Rastreabilidade dos casos de teste do projeto. Esta tabela **não duplica** os passos dos cenários — eles já estão documentados em Gherkin nos arquivos `.feature`, com o ID do test case como tag (`@TC-XXX`) em cada `Cenário`, garantindo rastreabilidade bidirecional. -Um `Scenario Outline` com `Examples` conta como um único test case (a variação de dados não infla a contagem). +Um `Esquema do Cenário` com `Exemplos` conta como um único test case (a variação de dados não infla a contagem). Legenda: @@ -11,32 +11,32 @@ Legenda: - **Automação**: `Automatizado` / `Manual` / `Planejado` (identificado, ainda não implementado) | ID | Módulo | Título | Tipo | Prioridade | Automação | Cenário | -| ------ | ------------ | -------------------------------------------------------- | --------- | ---------- | ------------ | ---------------------------------------------------------------------- | -| TC-001 | Autenticação | Gerar token com credenciais válidas | Funcional | Crítica | Automatizado | [auth.feature:3](../features/auth.feature#L3) | -| TC-002 | Autenticação | Tentar gerar token com credenciais inválidas | Negativo | Alta | Automatizado | [auth.feature:7](../features/auth.feature#L7) | -| TC-003 | Criação | Criar booking sem nenhum token de autenticação | Funcional | Alta | Automatizado | [create_booking.feature:7](../features/create_booking.feature#L7) | -| TC-004 | Consulta | Buscar um booking existente pelo id | Funcional | Alta | Automatizado | [get_booking.feature:3](../features/get_booking.feature#L3) | -| TC-005 | Consulta | Buscar um booking inexistente | Negativo | Média | Automatizado | [get_booking.feature:8](../features/get_booking.feature#L8) | -| TC-006 | Consulta | Buscar bookings filtrando por firstname e lastname | Funcional | Média | Automatizado | [get_booking.feature:15](../features/get_booking.feature#L15) | -| TC-007 | Atualização | Atualizar booking com token válido (PUT) | Funcional | Crítica | Automatizado | [update_booking.feature:4](../features/update_booking.feature#L4) | -| TC-008 | Atualização | Tentar atualizar booking sem token de autenticação | Negativo | Alta | Automatizado | [update_booking.feature:10](../features/update_booking.feature#L10) | -| TC-009 | Atualização | Atualizar parcialmente o booking com PATCH | Funcional | Média | Automatizado | [update_booking.feature:27](../features/update_booking.feature#L27) | -| TC-010 | Remoção | Remover booking com token válido | Funcional | Crítica | Automatizado | [delete_booking.feature:3](../features/delete_booking.feature#L3) | -| TC-011 | Remoção | Tentar remover booking sem token de autenticação | Negativo | Alta | Automatizado | [delete_booking.feature:9](../features/delete_booking.feature#L9) | -| TC-012 | Atualização | Tentar atualizar parcialmente booking sem token (PATCH) | Negativo | Alta | Automatizado | [update_booking.feature:36](../features/update_booking.feature#L36) | -| TC-013 | Atualização | Tentar atualizar booking com token inválido (PUT) | Negativo | Alta | Automatizado | [update_booking.feature:17](../features/update_booking.feature#L17) | -| TC-014 | Remoção | Tentar remover um booking que não existe | Negativo | Baixa | Automatizado | [delete_booking.feature:16](../features/delete_booking.feature#L16) | -| TC-015 | Criação | Tentar criar booking sem um campo obrigatório (bug) | Negativo | Média | Automatizado | [create_booking.feature:13](../features/create_booking.feature#L13) | -| TC-016 | Criação | Criar booking com totalprice em formato inválido | Negativo | Média | Automatizado | [create_booking.feature:24](../features/create_booking.feature#L24) | -| TC-017 | Criação | Criar booking com depositpaid em formato inválido | Negativo | Média | Automatizado | [create_booking.feature:39](../features/create_booking.feature#L39) | -| TC-018 | Criação | Criar booking com checkin em formato inválido | Negativo | Média | Automatizado | [create_booking.feature:49](../features/create_booking.feature#L49) | -| TC-019 | Criação | Criar booking com totalprice negativo | Negativo | Baixa | Automatizado | [create_booking.feature:58](../features/create_booking.feature#L58) | -| TC-020 | Atualização | Tentar atualizar booking com corpo vazio (PUT) | Negativo | Alta | Automatizado | [update_booking.feature:47](../features/update_booking.feature#L47) | -| TC-021 | Atualização | Atualizar booking com totalprice em formato inválido | Negativo | Média | Automatizado | [update_booking.feature:59](../features/update_booking.feature#L59) | -| TC-022 | Atualização | Tentar atualizar booking sem o campo bookingdates | Negativo | Alta | Automatizado | [update_booking.feature:67](../features/update_booking.feature#L67) | -| TC-023 | Atualização | Atualizar parcialmente com lastname em formato inválido | Negativo | Média | Automatizado | [update_booking.feature:77](../features/update_booking.feature#L77) | -| TC-024 | Atualização | Atualizar parcialmente com corpo vazio não altera nada | Funcional | Baixa | Automatizado | [update_booking.feature:87](../features/update_booking.feature#L87) | -| TC-025 | Atualização | Tentar atualizar (PUT/PATCH) um booking inexistente | Negativo | Média | Automatizado | [update_booking.feature:100](../features/update_booking.feature#L100) | +| ------ | ------------ | ------------------------------------------------------- | --------- | ---------- | ------------ | --------------------------------------------------------------------- | +| TC-001 | Autenticação | Gerar token com credenciais válidas | Funcional | Crítica | Automatizado | [auth.feature:5](../features/auth.feature#L5) | +| TC-002 | Autenticação | Tentar gerar token com credenciais inválidas | Negativo | Alta | Automatizado | [auth.feature:10](../features/auth.feature#L10) | +| TC-003 | Criação | Criar booking sem nenhum token de autenticação | Funcional | Alta | Automatizado | [create_booking.feature:9](../features/create_booking.feature#L9) | +| TC-004 | Consulta | Buscar um booking existente pelo id | Funcional | Alta | Automatizado | [get_booking.feature:5](../features/get_booking.feature#L5) | +| TC-005 | Consulta | Buscar um booking inexistente | Negativo | Média | Automatizado | [get_booking.feature:11](../features/get_booking.feature#L11) | +| TC-006 | Consulta | Buscar bookings filtrando por firstname e lastname | Funcional | Média | Automatizado | [get_booking.feature:16](../features/get_booking.feature#L16) | +| TC-007 | Atualização | Atualizar booking com token válido (PUT) | Funcional | Crítica | Automatizado | [update_booking.feature:6](../features/update_booking.feature#L6) | +| TC-008 | Atualização | Tentar atualizar booking sem token de autenticação | Negativo | Alta | Automatizado | [update_booking.feature:13](../features/update_booking.feature#L13) | +| TC-009 | Atualização | Atualizar parcialmente o booking com PATCH | Funcional | Média | Automatizado | [update_booking.feature:31](../features/update_booking.feature#L31) | +| TC-010 | Remoção | Remover booking com token válido | Funcional | Crítica | Automatizado | [delete_booking.feature:5](../features/delete_booking.feature#L5) | +| TC-011 | Remoção | Tentar remover booking sem token de autenticação | Negativo | Alta | Automatizado | [delete_booking.feature:12](../features/delete_booking.feature#L12) | +| TC-012 | Atualização | Tentar atualizar parcialmente booking sem token (PATCH) | Negativo | Alta | Automatizado | [update_booking.feature:39](../features/update_booking.feature#L39) | +| TC-013 | Atualização | Tentar atualizar booking com token inválido (PUT) | Negativo | Alta | Automatizado | [update_booking.feature:23](../features/update_booking.feature#L23) | +| TC-014 | Remoção | Tentar remover um booking que não existe | Negativo | Baixa | Automatizado | [delete_booking.feature:22](../features/delete_booking.feature#L22) | +| TC-015 | Criação | Tentar criar booking sem um campo obrigatório (bug) | Negativo | Média | Automatizado | [create_booking.feature:22](../features/create_booking.feature#L22) | +| TC-016 | Criação | Criar booking com totalprice em formato inválido | Negativo | Média | Automatizado | [create_booking.feature:32](../features/create_booking.feature#L32) | +| TC-017 | Criação | Criar booking com depositpaid em formato inválido | Negativo | Média | Automatizado | [create_booking.feature:41](../features/create_booking.feature#L41) | +| TC-018 | Criação | Criar booking com checkin em formato inválido | Negativo | Média | Automatizado | [create_booking.feature:51](../features/create_booking.feature#L51) | +| TC-019 | Criação | Criar booking com totalprice negativo | Negativo | Baixa | Automatizado | [create_booking.feature:60](../features/create_booking.feature#L60) | +| TC-020 | Atualização | Tentar atualizar booking com corpo vazio (PUT) | Negativo | Alta | Automatizado | [update_booking.feature:49](../features/update_booking.feature#L49) | +| TC-021 | Atualização | Atualizar booking com totalprice em formato inválido | Negativo | Média | Automatizado | [update_booking.feature:61](../features/update_booking.feature#L61) | +| TC-022 | Atualização | Tentar atualizar booking sem o campo bookingdates | Negativo | Alta | Automatizado | [update_booking.feature:69](../features/update_booking.feature#L69) | +| TC-023 | Atualização | Atualizar parcialmente com lastname em formato inválido | Negativo | Média | Automatizado | [update_booking.feature:79](../features/update_booking.feature#L79) | +| TC-024 | Atualização | Atualizar parcialmente com corpo vazio não altera nada | Funcional | Baixa | Automatizado | [update_booking.feature:89](../features/update_booking.feature#L89) | +| TC-025 | Atualização | Tentar atualizar (PUT/PATCH) um booking inexistente | Negativo | Média | Automatizado | [update_booking.feature:102](../features/update_booking.feature#L102) | `BookingApiClient.get_booking_ids` também aceita filtro por `checkin`/`checkout`, mas o TC-006 cobre só `firstname`/`lastname`: o filtro por data na API pública de demonstração é conhecido por ser instável, e testá-lo arriscaria um teste flaky em vez de validar um comportamento real. diff --git a/features/auth.feature b/features/auth.feature index 75f288e..d2f9644 100644 --- a/features/auth.feature +++ b/features/auth.feature @@ -1,11 +1,12 @@ -Feature: Autenticação na API restful-booker +# language: pt +Funcionalidade: Autenticação na API restful-booker @TC-001 @smoke - Scenario: Gerar token com credenciais válidas - When ele solicita um token com credenciais válidas - Then ele deve receber um token de autenticação válido + Cenário: Gerar token com credenciais válidas + Quando o usuário solicita um token com credenciais válidas + Então o usuário deve receber um token de autenticação válido @TC-002 - Scenario: Tentar gerar token com credenciais inválidas - When ele solicita um token com o usuário "admin" e a senha "senha_errada" - Then a resposta deve indicar credenciais inválidas + Cenário: Tentar gerar token com credenciais inválidas + Quando o usuário solicita um token com o login "admin" e a senha "senha_errada" + Então a resposta deve indicar credenciais inválidas diff --git a/features/create_booking.feature b/features/create_booking.feature index dd50cd6..e13c2e4 100644 --- a/features/create_booking.feature +++ b/features/create_booking.feature @@ -1,15 +1,16 @@ -Feature: Criação de booking +# language: pt +Funcionalidade: Criação de booking # A documentação da API indica que POST /booking não exige autenticação. # Ou seja, qualquer pessoa consegue criar um booking sem token — uma falha # de controle de acesso do próprio app de demonstração, e vale documentar # esse comportamento em vez de presumir que a criação seria bloqueada. @TC-003 @smoke - Scenario: Criar booking sem nenhum token de autenticação - Given que ele não possui nenhum token de autenticação - When ele cria um booking com dados válidos - Then o booking deve ser criado com sucesso - And o id do booking criado deve ser retornado + Cenário: Criar booking sem nenhum token de autenticação + Dado que o usuário não possui nenhum token de autenticação + Quando o usuário cria um booking com dados válidos + Então o booking deve ser criado com sucesso + E o id do booking criado deve ser retornado # Bug real da API, documentado em # https://github.com/ThomasTDS/qa-api-python/issues/8: em vez de rejeitar @@ -18,9 +19,9 @@ Feature: Criação de booking # é sinal de que o bug foi corrigido do lado de lá, e cabe atualizar o # teste (e fechar a issue). @TC-015 - Scenario: Tentar criar booking sem um campo obrigatório - When ele tenta criar um booking sem informar o firstname - Then a resposta deve indicar um erro interno do servidor + Cenário: Tentar criar booking sem um campo obrigatório + Quando o usuário tenta criar um booking sem informar o firstname + Então a resposta deve indicar um erro interno do servidor # A API aceita um totalprice em formato errado (texto em vez de número) e # devolve 200, gravando null no lugar em silêncio, em vez de rejeitar o @@ -28,35 +29,35 @@ Feature: Criação de booking # essa corrupção: a resposta não bate com o modelo Booking, e o teste # acusa o problema em vez de aceitar o dado corrompido como válido. @TC-016 - Scenario: Criar booking com totalprice em formato inválido - When ele cria um booking com totalprice em formato inválido - Then a resposta da API não deve corresponder ao schema esperado + Cenário: Criar booking com totalprice em formato inválido + Quando o usuário cria um booking com totalprice em formato inválido + Então a resposta da API não deve corresponder ao schema esperado # A API não valida o tipo de depositpaid: qualquer string não vazia é # coagida para true, então não existe forma de a API rejeitar um valor # inválido nesse campo. Documentado em # https://github.com/ThomasTDS/qa-api-python/issues/19. @TC-017 - Scenario: Criar booking com depositpaid em formato inválido - When ele cria um booking com depositpaid em formato inválido - Then o booking deve ser criado com sucesso - And o depositpaid do booking criado deve ser true + Cenário: Criar booking com depositpaid em formato inválido + Quando o usuário cria um booking com depositpaid em formato inválido + Então o booking deve ser criado com sucesso + E o depositpaid do booking criado deve ser true # Uma data de check-in com formato inválido não é validada nem rejeitada: # o valor é processado e gravado corrompido (formato "NaN"), em vez de a # API retornar 400. Documentado em # https://github.com/ThomasTDS/qa-api-python/issues/20. @TC-018 - Scenario: Criar booking com checkin em formato inválido - When ele cria um booking com checkin em formato inválido - Then o booking deve ser criado com sucesso - And o checkin do booking criado deve estar corrompido + Cenário: Criar booking com checkin em formato inválido + Quando o usuário cria um booking com checkin em formato inválido + Então o booking deve ser criado com sucesso + E o checkin do booking criado deve estar corrompido # Não há validação de regra de negócio para totalprice: valores negativos # são aceitos normalmente, sem nenhuma restrição. Documentado em # https://github.com/ThomasTDS/qa-api-python/issues/21. @TC-019 - Scenario: Criar booking com totalprice negativo - When ele cria um booking com totalprice negativo - Then o booking deve ser criado com sucesso - And o totalprice do booking criado deve ser negativo + Cenário: Criar booking com totalprice negativo + Quando o usuário cria um booking com totalprice negativo + Então o booking deve ser criado com sucesso + E o totalprice do booking criado deve ser negativo diff --git a/features/delete_booking.feature b/features/delete_booking.feature index 4cde0d3..e0aa2e8 100644 --- a/features/delete_booking.feature +++ b/features/delete_booking.feature @@ -1,24 +1,25 @@ -Feature: Remoção de booking +# language: pt +Funcionalidade: Remoção de booking @TC-010 @smoke - Scenario: Remover booking com token válido - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele remove o booking - Then o booking deve ser removido com sucesso + Cenário: Remover booking com token válido + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário remove o booking + Então o booking deve ser removido com sucesso @TC-011 - Scenario: Tentar remover booking sem token de autenticação - Given que existe um booking criado - And que ele não possui nenhum token de autenticação - When ele tenta remover o booking - Then a resposta deve indicar acesso não autorizado + Cenário: Tentar remover booking sem token de autenticação + Dado que existe um booking criado + E que o usuário não possui nenhum token de autenticação + Quando o usuário tenta remover o booking + Então a resposta deve indicar acesso não autorizado # Comportamento observado na API pública: remover um id que nunca existiu # retorna 405 (Method Not Allowed), não 404 como seria de se esperar. # Documentado como comportamento real da API, não como bug da automação. @TC-014 - Scenario: Tentar remover um booking que não existe - Given que ele possui um token de autenticação válido - When ele tenta remover o booking pelo id "999999999" - Then a resposta deve indicar que o método não é permitido + Cenário: Tentar remover um booking que não existe + Dado que o usuário possui um token de autenticação válido + Quando o usuário tenta remover o booking pelo id "999999999" + Então a resposta deve indicar que o método não é permitido diff --git a/features/get_booking.feature b/features/get_booking.feature index 2b42336..8cf0607 100644 --- a/features/get_booking.feature +++ b/features/get_booking.feature @@ -1,20 +1,21 @@ -Feature: Consulta de booking +# language: pt +Funcionalidade: Consulta de booking @TC-004 @smoke - Scenario: Buscar um booking existente pelo id - Given que existe um booking criado - When ele busca o booking pelo id - Then os dados retornados devem corresponder ao booking criado + Cenário: Buscar um booking existente pelo id + Dado que existe um booking criado + Quando o usuário busca o booking pelo id + Então os dados retornados devem corresponder ao booking criado @TC-005 - Scenario: Buscar um booking inexistente - When ele busca o booking pelo id "999999999" - Then a resposta deve indicar que o booking não foi encontrado + Cenário: Buscar um booking inexistente + Quando o usuário busca o booking pelo id "999999999" + Então a resposta deve indicar que o booking não foi encontrado @TC-006 - Scenario: Buscar bookings filtrando por firstname e lastname - Given que existe um booking criado - When ele busca bookings filtrando pelo firstname e lastname do booking criado - Then o id do booking criado deve estar entre os resultados - When ele busca bookings filtrando por um firstname e lastname que não correspondem a nenhum booking - Then o id do booking criado não deve estar entre os resultados + Cenário: Buscar bookings filtrando por firstname e lastname + Dado que existe um booking criado + Quando o usuário busca bookings filtrando pelo firstname e lastname do booking criado + Então o id do booking criado deve estar entre os resultados + Quando o usuário busca bookings filtrando por um firstname e lastname que não correspondem a nenhum booking + Então o id do booking criado não deve estar entre os resultados diff --git a/features/update_booking.feature b/features/update_booking.feature index c9d9738..62770e5 100644 --- a/features/update_booking.feature +++ b/features/update_booking.feature @@ -1,55 +1,56 @@ -Feature: Atualização de booking (PUT e PATCH) +# language: pt +Funcionalidade: Atualização de booking (PUT e PATCH) # PUT substitui todos os campos do booking; exige todos os campos obrigatórios. @TC-007 @smoke - Scenario: Atualizar booking com token válido - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza o booking com novos dados - Then o booking deve ser atualizado com sucesso + Cenário: Atualizar booking com token válido + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza o booking com novos dados + Então o booking deve ser atualizado com sucesso @TC-008 - Scenario: Tentar atualizar booking sem token de autenticação - Given que existe um booking criado - And que ele não possui nenhum token de autenticação - When ele tenta atualizar o booking com novos dados - Then a resposta deve indicar acesso não autorizado + Cenário: Tentar atualizar booking sem token de autenticação + Dado que existe um booking criado + E que o usuário não possui nenhum token de autenticação + Quando o usuário tenta atualizar o booking com novos dados + Então a resposta deve indicar acesso não autorizado # Diferente do TC-008 (token ausente): aqui o token existe, mas é inválido. # Validado manualmente que PATCH e DELETE se comportam da mesma forma (403), # então não repetimos esse caso pros outros dois verbos. @TC-013 - Scenario: Tentar atualizar booking com token inválido - Given que existe um booking criado - And que ele possui um token de autenticação inválido - When ele tenta atualizar o booking com novos dados - Then a resposta deve indicar acesso não autorizado + Cenário: Tentar atualizar booking com token inválido + Dado que existe um booking criado + E que o usuário possui um token de autenticação inválido + Quando o usuário tenta atualizar o booking com novos dados + Então a resposta deve indicar acesso não autorizado # PATCH atualiza somente os campos enviados, mantendo o restante do booking. @TC-009 - Scenario: Atualizar parcialmente o booking com PATCH - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza parcialmente o booking alterando o sobrenome para "Silva" - Then o booking deve ser atualizado com sucesso - And o sobrenome do booking deve ser "Silva" + Cenário: Atualizar parcialmente o booking com PATCH + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza parcialmente o booking alterando o sobrenome para "Silva" + Então o booking deve ser atualizado com sucesso + E o sobrenome do booking deve ser "Silva" @TC-012 - Scenario: Tentar atualizar parcialmente booking sem token de autenticação - Given que existe um booking criado - And que ele não possui nenhum token de autenticação - When ele tenta atualizar parcialmente o booking alterando o sobrenome para "NaoDeveriaFuncionar" - Then a resposta deve indicar acesso não autorizado + Cenário: Tentar atualizar parcialmente booking sem token de autenticação + Dado que existe um booking criado + E que o usuário não possui nenhum token de autenticação + Quando o usuário tenta atualizar parcialmente o booking alterando o sobrenome para "NaoDeveriaFuncionar" + Então a resposta deve indicar acesso não autorizado # PUT valida corretamente a ausência de campos obrigatórios (ao contrário # do POST de criação, que derruba com 500 no mesmo cenário — ver TC-015): # aqui a API responde com 400, como esperado. @TC-020 - Scenario: Tentar atualizar booking com corpo vazio - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza o booking com corpo vazio - Then a resposta deve indicar requisição inválida + Cenário: Tentar atualizar booking com corpo vazio + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza o booking com corpo vazio + Então a resposta deve indicar requisição inválida # Mesmo defeito do TC-016 (totalprice de tipo inválido não é validado), # agora observado também no PUT: a API aceita e grava totalprice como @@ -57,40 +58,40 @@ Feature: Atualização de booking (PUT e PATCH) # https://github.com/ThomasTDS/qa-api-python/issues/22, que cobre os # dois verbos. @TC-021 - Scenario: Atualizar booking com totalprice em formato inválido - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza o booking com totalprice em formato inválido - Then o booking deve ser atualizado com sucesso - And o totalprice do booking atualizado deve ser nulo + Cenário: Atualizar booking com totalprice em formato inválido + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza o booking com totalprice em formato inválido + Então o booking deve ser atualizado com sucesso + E o totalprice do booking atualizado deve ser nulo @TC-022 - Scenario: Tentar atualizar booking sem o campo bookingdates - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza o booking sem o campo bookingdates - Then a resposta deve indicar requisição inválida + Cenário: Tentar atualizar booking sem o campo bookingdates + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza o booking sem o campo bookingdates + Então a resposta deve indicar requisição inválida # PATCH não valida o tipo de nenhum campo enviado: um lastname numérico é # aceito e gravado como número, quebrando o contrato do schema. Documentado # em https://github.com/ThomasTDS/qa-api-python/issues/23. @TC-023 - Scenario: Atualizar parcialmente o booking com lastname em formato inválido - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza parcialmente o booking com lastname em formato inválido - Then o booking deve ser atualizado com sucesso - And o lastname do booking atualizado deve ser o valor numérico enviado + Cenário: Atualizar parcialmente o booking com lastname em formato inválido + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza parcialmente o booking com lastname em formato inválido + Então o booking deve ser atualizado com sucesso + E o lastname do booking atualizado deve ser o valor numérico enviado # Comportamento seguro, não um bug: PATCH com corpo vazio não falha nem # altera nada, documentado aqui como um no-op válido. @TC-024 - Scenario: Atualizar parcialmente o booking com corpo vazio não altera nada - Given que existe um booking criado - And que ele possui um token de autenticação válido - When ele atualiza parcialmente o booking com corpo vazio - Then o booking deve ser atualizado com sucesso - And os dados do booking não devem ter sido alterados + Cenário: Atualizar parcialmente o booking com corpo vazio não altera nada + Dado que existe um booking criado + E que o usuário possui um token de autenticação válido + Quando o usuário atualiza parcialmente o booking com corpo vazio + Então o booking deve ser atualizado com sucesso + E os dados do booking não devem ter sido alterados # Mesmo comportamento do TC-014 (DELETE em id inexistente retorna 405), # agora confirmado também em PUT e PATCH: nenhum dos três verbos retorna @@ -98,12 +99,12 @@ Feature: Atualização de booking (PUT e PATCH) # https://github.com/ThomasTDS/qa-api-python/issues/24, que cobre os # três verbos. @TC-025 - Scenario Outline: Tentar atualizar um booking inexistente - Given que ele possui um token de autenticação válido - When ele tenta "" um booking inexistente - Then a resposta deve indicar que o método não é permitido + Esquema do Cenário: Tentar atualizar um booking inexistente + Dado que o usuário possui um token de autenticação válido + Quando o usuário tenta "" um booking inexistente + Então a resposta deve indicar que o método não é permitido - Examples: + Exemplos: | verbo | | atualizar | | atualizar parcialmente | diff --git a/steps/common_steps.py b/steps/common_steps.py index 5bdf3e4..40fca32 100644 --- a/steps/common_steps.py +++ b/steps/common_steps.py @@ -7,17 +7,17 @@ from steps.booking_data import default_booking -@given("que ele não possui nenhum token de autenticação") +@given("que o usuário não possui nenhum token de autenticação") def sem_token_de_autenticacao(context: Context) -> None: context.token = None -@given("que ele possui um token de autenticação válido") +@given("que o usuário possui um token de autenticação válido") def com_token_de_autenticacao_valido(context: Context) -> None: context.token = context.auth_client.get_valid_token() -@given("que ele possui um token de autenticação inválido") +@given("que o usuário possui um token de autenticação inválido") def com_token_de_autenticacao_invalido(context: Context) -> None: context.token = "token-invalido-qualquer" diff --git a/steps/test_auth.py b/steps/test_auth.py index c5a0589..2f0ae8f 100644 --- a/steps/test_auth.py +++ b/steps/test_auth.py @@ -6,18 +6,18 @@ scenarios("../features/auth.feature") -@when("ele solicita um token com credenciais válidas") +@when("o usuário solicita um token com credenciais válidas") def solicita_um_token_com_credenciais_validas(context: Context) -> None: client = context.auth_client context.last_response = client.create_token(client.username, client.password) -@when(parsers.parse('ele solicita um token com o usuário "{username}" e a senha "{password}"')) +@when(parsers.parse('o usuário solicita um token com o login "{username}" e a senha "{password}"')) def solicita_um_token(context: Context, username: str, password: str) -> None: context.last_response = context.auth_client.create_token(username, password) -@then("ele deve receber um token de autenticação válido") +@then("o usuário deve receber um token de autenticação válido") def deve_receber_um_token_valido(context: Context) -> None: assert context.response.status_code == 200 body = AuthResponse.model_validate_json(context.response.text) diff --git a/steps/test_create_booking.py b/steps/test_create_booking.py index 71a377e..1f3fedd 100644 --- a/steps/test_create_booking.py +++ b/steps/test_create_booking.py @@ -20,7 +20,7 @@ def _create_raw(context: Context, payload: dict[str, Any]) -> None: context.booking_id = context.last_response.json().get("bookingid") -@when("ele cria um booking com dados válidos") +@when("o usuário cria um booking com dados válidos") def cria_um_booking_com_dados_validos(context: Context) -> None: context.booking_data = default_booking() context.last_response = context.booking_client.create_booking(context.booking_data) @@ -38,7 +38,7 @@ def id_do_booking_criado_deve_ser_retornado(context: Context) -> None: context.booking_id = body.bookingid -@when("ele tenta criar um booking sem informar o firstname") +@when("o usuário tenta criar um booking sem informar o firstname") def tenta_criar_booking_sem_firstname(context: Context) -> None: _create_raw(context, booking_payload(without="firstname")) @@ -48,7 +48,7 @@ def resposta_indica_erro_interno_do_servidor(context: Context) -> None: assert context.response.status_code == 500 -@when("ele cria um booking com totalprice em formato inválido") +@when("o usuário cria um booking com totalprice em formato inválido") def cria_booking_com_totalprice_invalido(context: Context) -> None: _create_raw(context, booking_payload(totalprice="nao-e-numero")) @@ -60,7 +60,7 @@ def resposta_nao_corresponde_ao_schema(context: Context) -> None: CreateBookingResponse.model_validate_json(context.response.text) -@when("ele cria um booking com depositpaid em formato inválido") +@when("o usuário cria um booking com depositpaid em formato inválido") def cria_booking_com_depositpaid_invalido(context: Context) -> None: _create_raw(context, booking_payload(depositpaid="sim")) @@ -70,7 +70,7 @@ def depositpaid_do_booking_criado_deve_ser_true(context: Context) -> None: assert context.response.json()["booking"]["depositpaid"] is True -@when("ele cria um booking com checkin em formato inválido") +@when("o usuário cria um booking com checkin em formato inválido") def cria_booking_com_checkin_invalido(context: Context) -> None: _create_raw(context, booking_payload(bookingdates={"checkin": "data-invalida", "checkout": "2026-01-05"})) @@ -82,7 +82,7 @@ def checkin_do_booking_criado_deve_estar_corrompido(context: Context) -> None: assert "NaN" in checkin -@when("ele cria um booking com totalprice negativo") +@when("o usuário cria um booking com totalprice negativo") def cria_booking_com_totalprice_negativo(context: Context) -> None: _create_raw(context, booking_payload(totalprice=-150)) diff --git a/steps/test_delete_booking.py b/steps/test_delete_booking.py index b06a01e..ce98726 100644 --- a/steps/test_delete_booking.py +++ b/steps/test_delete_booking.py @@ -5,14 +5,14 @@ scenarios("../features/delete_booking.feature") -@when("ele remove o booking") -@when("ele tenta remover o booking") +@when("o usuário remove o booking") +@when("o usuário tenta remover o booking") def remove_o_booking(context: Context) -> None: assert context.booking_id is not None context.last_response = context.booking_client.delete_booking(context.booking_id, context.token_or_empty) -@when(parsers.parse('ele tenta remover o booking pelo id "{booking_id}"')) +@when(parsers.parse('o usuário tenta remover o booking pelo id "{booking_id}"')) def tenta_remover_o_booking_pelo_id_informado(context: Context, booking_id: str) -> None: context.last_response = context.booking_client.delete_booking(int(booking_id), context.token_or_empty) diff --git a/steps/test_get_booking.py b/steps/test_get_booking.py index 3f8caa5..1ac9f65 100644 --- a/steps/test_get_booking.py +++ b/steps/test_get_booking.py @@ -6,13 +6,13 @@ scenarios("../features/get_booking.feature") -@when("ele busca o booking pelo id") +@when("o usuário busca o booking pelo id") def busca_o_booking_pelo_id(context: Context) -> None: assert context.booking_id is not None context.last_response = context.booking_client.get_booking(context.booking_id) -@when(parsers.parse('ele busca o booking pelo id "{booking_id}"')) +@when(parsers.parse('o usuário busca o booking pelo id "{booking_id}"')) def busca_o_booking_pelo_id_informado(context: Context, booking_id: str) -> None: context.last_response = context.booking_client.get_booking(int(booking_id)) @@ -29,7 +29,7 @@ def resposta_indica_booking_nao_encontrado(context: Context) -> None: assert context.response.status_code == 404 -@when("ele busca bookings filtrando pelo firstname e lastname do booking criado") +@when("o usuário busca bookings filtrando pelo firstname e lastname do booking criado") def busca_bookings_filtrando_pelo_booking_criado(context: Context) -> None: assert context.booking_data is not None context.last_response = context.booking_client.get_booking_ids( @@ -45,7 +45,7 @@ def id_do_booking_criado_esta_entre_os_resultados(context: Context) -> None: assert any(b.bookingid == context.booking_id for b in body) -@when("ele busca bookings filtrando por um firstname e lastname que não correspondem a nenhum booking") +@when("o usuário busca bookings filtrando por um firstname e lastname que não correspondem a nenhum booking") def busca_bookings_filtrando_por_dados_inexistentes(context: Context) -> None: context.last_response = context.booking_client.get_booking_ids( firstname="Inexistente-xyz-999", diff --git a/steps/test_update_booking.py b/steps/test_update_booking.py index 2a7fc24..5cbf74d 100644 --- a/steps/test_update_booking.py +++ b/steps/test_update_booking.py @@ -7,7 +7,7 @@ scenarios("../features/update_booking.feature") -@when("ele atualiza o booking com novos dados") +@when("o usuário atualiza o booking com novos dados") def atualiza_o_booking_com_novos_dados(context: Context) -> None: assert context.booking_data is not None assert context.booking_id is not None @@ -16,7 +16,7 @@ def atualiza_o_booking_com_novos_dados(context: Context) -> None: context.booking_data = updated -@when("ele tenta atualizar o booking com novos dados") +@when("o usuário tenta atualizar o booking com novos dados") def tenta_atualizar_o_booking_com_novos_dados(context: Context) -> None: assert context.booking_data is not None assert context.booking_id is not None @@ -29,8 +29,8 @@ def booking_deve_ser_atualizado_com_sucesso(context: Context) -> None: assert context.response.status_code == 200 -@when(parsers.parse('ele atualiza parcialmente o booking alterando o sobrenome para "{lastname}"')) -@when(parsers.parse('ele tenta atualizar parcialmente o booking alterando o sobrenome para "{lastname}"')) +@when(parsers.parse('o usuário atualiza parcialmente o booking alterando o sobrenome para "{lastname}"')) +@when(parsers.parse('o usuário tenta atualizar parcialmente o booking alterando o sobrenome para "{lastname}"')) def atualiza_parcialmente_o_sobrenome(context: Context, lastname: str) -> None: assert context.booking_id is not None context.last_response = context.booking_client.partial_update_booking( @@ -44,7 +44,7 @@ def sobrenome_do_booking_deve_ser(context: Context, lastname: str) -> None: assert body.lastname == lastname -@when("ele atualiza o booking com corpo vazio") +@when("o usuário atualiza o booking com corpo vazio") def atualiza_o_booking_com_corpo_vazio(context: Context) -> None: assert context.booking_id is not None context.last_response = context.booking_client.update_booking_raw(context.booking_id, {}, context.token_or_empty) @@ -55,7 +55,7 @@ def resposta_indica_requisicao_invalida(context: Context) -> None: assert context.response.status_code == 400 -@when("ele atualiza o booking com totalprice em formato inválido") +@when("o usuário atualiza o booking com totalprice em formato inválido") def atualiza_o_booking_com_totalprice_invalido(context: Context) -> None: assert context.booking_id is not None payload = booking_payload(totalprice="nao-e-numero") @@ -69,7 +69,7 @@ def totalprice_do_booking_atualizado_deve_ser_nulo(context: Context) -> None: assert context.response.json()["totalprice"] is None -@when("ele atualiza o booking sem o campo bookingdates") +@when("o usuário atualiza o booking sem o campo bookingdates") def atualiza_o_booking_sem_bookingdates(context: Context) -> None: assert context.booking_id is not None payload = booking_payload(without="bookingdates") @@ -78,7 +78,7 @@ def atualiza_o_booking_sem_bookingdates(context: Context) -> None: ) -@when("ele atualiza parcialmente o booking com lastname em formato inválido") +@when("o usuário atualiza parcialmente o booking com lastname em formato inválido") def atualiza_parcialmente_o_booking_com_lastname_invalido(context: Context) -> None: assert context.booking_id is not None context.last_response = context.booking_client.partial_update_booking( @@ -91,7 +91,7 @@ def lastname_do_booking_atualizado_deve_ser_o_valor_numerico(context: Context) - assert context.response.json()["lastname"] == 12345 -@when("ele atualiza parcialmente o booking com corpo vazio") +@when("o usuário atualiza parcialmente o booking com corpo vazio") def atualiza_parcialmente_o_booking_com_corpo_vazio(context: Context) -> None: assert context.booking_id is not None context.last_response = context.booking_client.partial_update_booking( @@ -105,7 +105,7 @@ def dados_do_booking_nao_devem_ter_sido_alterados(context: Context) -> None: assert body == context.booking_data -@when(parsers.parse('ele tenta "{verbo}" um booking inexistente')) +@when(parsers.parse('o usuário tenta "{verbo}" um booking inexistente')) def tenta_verbo_um_booking_inexistente(context: Context, verbo: str) -> None: if verbo == "atualizar": context.last_response = context.booking_client.update_booking(