Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/booking_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def get_booking(self, booking_id: int) -> requests.Response:
def create_booking(self, booking: Booking) -> requests.Response:
return self.session.post(f"{self.base_url}/booking", json=booking.model_dump(exclude_none=True))

# Recebe um dict cru (sem passar pela validação do Pydantic) para testes
# negativos que precisam mandar um payload propositalmente inválido.
def create_booking_raw(self, payload: dict[str, Any]) -> requests.Response:
return self.session.post(f"{self.base_url}/booking", json=payload)

def update_booking(self, booking_id: int, booking: Booking, token: str) -> requests.Response:
return self.session.put(
f"{self.base_url}/booking/{booking_id}",
Expand Down
1 change: 1 addition & 0 deletions docs/test-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Legenda:
| 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) |

`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.

Expand Down
11 changes: 11 additions & 0 deletions features/create_booking.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,14 @@ Feature: Criação de booking
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

# Bug real da API, documentado em
# https://github.com/ThomasTDS/qa-api-python/issues/8: em vez de rejeitar
# com 400, a API derruba com 500 quando falta um campo obrigatório. Esse
# cenário trava o comportamento atual; se um dia passar a devolver 400,
# é 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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ markers = [
"TC-012: id do test case, ver docs/test-cases.md",
"TC-013: id do test case, ver docs/test-cases.md",
"TC-014: id do test case, ver docs/test-cases.md",
"TC-015: id do test case, ver docs/test-cases.md",
]

[tool.ruff]
Expand Down
17 changes: 17 additions & 0 deletions steps/test_booking_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ 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")
def tenta_criar_booking_sem_firstname(context: Context) -> None:
payload = {
"lastname": "Ciclano",
"totalprice": 150,
"depositpaid": True,
"bookingdates": {"checkin": "2026-01-01", "checkout": "2026-01-05"},
}
context.last_response = context.booking_client.create_booking_raw(payload)


@then("a resposta deve indicar um erro interno do servidor")
def resposta_indica_erro_interno_do_servidor(context: Context) -> None:
assert context.last_response is not None
assert context.last_response.status_code == 500


# CONSULTA
@when("ele busca o booking pelo id")
def busca_o_booking_pelo_id(context: Context) -> None:
Expand Down
Loading