CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl-labs/pytest-api-testing

Pytest patterns for Python APIs -- httpx AsyncClient, conftest fixtures, database isolation, parametrize edge cases, error response testing, auth flows, factory fixtures

99

1.23x
Quality

99%

Does it follow best practices?

Impact

100%

1.23x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

task.mdevals/scenario-5/

Test Authentication and External Service Resilience for a Notification API

Problem/Feature Description

A communications platform exposes a FastAPI service that lets authenticated users send notifications via an external email provider. The service issues JWT tokens on login and uses them to gate access to its endpoints. A recent incident revealed that when the email provider's SMTP server went down, the API returned a 500 error instead of a graceful degraded response — and the tests didn't catch it because they had never simulated a provider failure.

You have been asked to write a test suite that covers the full authentication lifecycle and the external provider failure scenario. The tests should verify that unauthenticated requests are rejected, that invalid tokens are rejected, that valid tokens grant access, and that an SMTP outage causes the API to return a 503 (not a 500).

Output Specification

Produce the following files:

  • tests/conftest.py — client fixture, any helper fixtures for auth
  • tests/test_notifications.py — the test suite
  • pyproject.toml — pytest configuration

Input Files

The following files are provided as inputs. Extract them before beginning.

=============== FILE: app/main.py =============== from fastapi import FastAPI, HTTPException, Depends, Header from pydantic import BaseModel from typing import Optional import jwt import time

app = FastAPI()

SECRET_KEY = "test-secret-key" _users = {} _next_id = 1

class RegisterRequest(BaseModel): email: str password: str

class LoginRequest(BaseModel): email: str password: str

class NotificationRequest(BaseModel): recipient: str message: str

def get_current_user(authorization: Optional[str] = Header(None)): if not authorization or not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="Missing or invalid token") token = authorization.split(" ", 1)[1] try: payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) return payload["sub"] except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/api/auth/register", status_code=201) async def register(body: RegisterRequest): global _next_id if body.email in _users: raise HTTPException(status_code=409, detail="Email already registered") _users[body.email] = {"id": _next_id, "email": body.email, "password": body.password} _next_id += 1 return {"data": {"id": _users[body.email]["id"], "email": body.email}}

@app.post("/api/auth/login") async def login(body: LoginRequest): user = _users.get(body.email) if not user or user["password"] != body.password: raise HTTPException(status_code=401, detail="Invalid credentials") token = jwt.encode( {"sub": body.email, "exp": int(time.time()) + 3600}, SECRET_KEY, algorithm="HS256", ) return {"access_token": token}

@app.post("/api/notifications", status_code=202) async def send_notification(body: NotificationRequest, user: str = Depends(get_current_user)): from app import email_service try: email_service.send_email(to=body.recipient, message=body.message) except ConnectionError: raise HTTPException(status_code=503, detail="Email service unavailable") return {"data": {"status": "queued", "recipient": body.recipient}}

=============== FILE: app/email_service.py =============== def send_email(to: str, message: str) -> None: """Send an email via SMTP. Raises ConnectionError if SMTP is unavailable.""" # Real implementation would connect to SMTP server pass

evals

tile.json