AIAI University/ Stadika Смотреть курсы
AI University / Промпты / Программисту
Программисту

Промпт: интеграционные тесты для API

Тест-кейсы и код интеграционных тестов эндпоинта с реальной тестовой базой и поиском багов. Готовый промт на русском с ответами нейросетей: посмотрите, что выдают GigaChat, ChatGPT, Claude и Gemini, и откройте текст промпта, чтобы запускать его со своими данными.

Проверено на: GigaChatChatGPTClaude · Обновлено 01.10.2026

Текст промпта

Текст промпта открывается с доступом к библиотеке

Ответы нейросетей ниже получены этим промптом на примере из таблицы. Чтобы скопировать промпт и запускать его со своими данными, откройте доступ ко всем промптам или полный доступ к AI University.

Разовый платёж, без подписки. Новые промпты входят в доступ.

Что подставить в промт

ПолеПример
{stack}Python 3.12, FastAPI, SQLAlchemy 2 (async), pytest, pytest-asyncio, httpx.AsyncClient, PostgreSQL в Docker через testcontainers
{code}@router.post("/promo/apply")
async def apply_promo(body: ApplyPromo, user=Depends(current_user), db=Depends(get_db)):
promo = await db.scalar(select(Promo).where(Promo.code == body.code.upper()))
if not promo or promo.expires_at < datetime.utcnow():
raise HTTPException(404, "Промокод не найден")
if promo.used_count >= promo.max_uses:
raise HTTPException(409, "Промокод закончился")
cart = await get_cart(db, user.id)
cart.discount = min(cart.total * promo.percent / 100, promo.max_discount)
promo.used_count += 1
await db.commit()
return {"discount": cart.discount}
{rules}один промокод на корзину; повторное применение того же кода не должно увеличивать used_count; скидка не больше max_discount; применять могут только авторизованные пользователи; expires_at хранится как timestamptz

Ответы нейросетей на этот промпт

Один и тот же промпт с примером выше, ответы без правок: выберите нейросеть, которой пользуетесь, и посмотрите, что она выдаёт.

GigaChat 2 Max06.10.2026
1. Таблица тест-кейсов
СценарийВходные данныеОжидаемый результат
Корректный промокодПромокод существуетУспех
Просроченный промокодPromo.expires_at < current_timeHTTP-ошибка 404
Истощённый промокодИспользован до предела max_usesHTTP-ошибка 409
Некорректная скидкаСкидка превышает лимитОграничение скидки
Повторное использованиеТо же самое тело дважды подрядОдин раз используется
Незарегистрированный юзерНет токенаHTTP-ошибка 401
Конкурентные запросыНесколько запросов одновременноСогласованность изменений
Неверный промо-кодКод отличается от существующихHTTP-ошибка 404
Недопустимые символыСпецсимволы в теле запросаВалидатор отклоняет
Пустой промокодEmpty stringВалидатор отклоняет
Устаревшая версия протоколаHTTP версии ниже 1.1Версия должна поддерживаться
Параллельное изменение БДОбновление другой записи параллельноБезопасность транзакций

2. Пример реализации тестов
Фикстуры и фабрики данных (tests/conftest.py)
import asyncio
from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from fastapi.testclient import TestClient
from testcontainers.postgres import PostgresContainer
from app.main import app
from app.models import Base
from app.db import get_db


@pytest.fixture(scope="session")
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()


@pytest.fixture(scope="session")
async def postgres_container() -> AsyncGenerator[str, None]:
    with PostgresContainer("postgres:latest") as pg:
        uri = f"postgresql+asyncpg://{pg.get_username()}:{pg.get_password()}@{pg.get_hostname()}:{pg.get_port()}/{pg.get_db_name()}"
        engine = create_async_engine(uri)
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)
        yield uri
        await engine.dispose()


@pytest.fixture(scope="function")
async def session(postgres_container):
    engine = create_async_engine(postgres_container)
    SessionLocal = async_sessionmaker(bind=engine, expire_on_commit=False)
    async with SessionLocal() as session:
        yield session
    await engine.dispose()


@pytest.fixture(scope="module")
def client(session):
    # Замещаем зависимость get_db для тестирования
    def override_get_db():
        return session

    app.dependency_overrides[get_db] = override_get_db
    return TestClient(app)
Примеры тестов (tests/test_endpoints.py)
import pytest
from datetime import timedelta, datetime
from httpx import AsyncClient
from app.schemas.promo import ApplyPromo
from app.models import User, Cart, Promo
from tests.factories import PromoFactory, UserFactory, CartFactory


# Вводим дополнительную фикстуру для работы с асинхронностью
@pytest.mark.anyio
class TestApplyPromoEndpoint:
    @pytest.fixture(autouse=True)
    async def setup(self, session):
        self.user = await UserFactory.create()
        self.cart = await CartFactory.create(user=self.user)
        self.promo = await PromoFactory.create(max_uses=5, percent=10, expires_at=datetime.utcnow() + timedelta(days=7))

    async def test_valid_promocode(self, client):
        response = await client.post(
            "/promo/apply",
            json={"code": self.promo.code},
            headers={"Authorization": f"Bearer {self.user.token}"},  # Добавляем заголовки аутентификации
        )
        assert response.status_code == 200
        data = response.json()
        assert "discount" in data and isinstance(data["discount"], float)
    
    async def test_expired_promocode(self, client):
        expired_promo = await PromoFactory.create(expires_at=datetime.utcnow() - timedelta(hours=1))
        
        response = await client.post(
            "/promo/apply",
            json={"code": expired_promo.code},
            headers={"Authorization": f"Bearer {self.user.token}"},
        )
        assert response.status_code == 404
        assert response.json()["detail"] == "Промокод не найден"

    async def test_used_up_promocode(self, client):
        fully_used_promo = await PromoFactory.create(max_uses=0)
        
        response = await client.post(
            "/promo/apply",
            json={"code": fully_used_promo.code},
            headers={"Authorization": f"Bearer {self.user.token}"},
        )
        assert response.status_code == 409
        assert response.json()["detail"] == "Промокод закончился"

    async def test_duplicate_application(self, client):
        first_response = await client.post(
            "/promo/apply",
            json={"code": self.promo.code},
            headers={"Authorization": f"Bearer {self.user.token}"},
        )
        second_response = await client.post(
            "/promo/apply",
            json={"code": self.promo.code},
            headers={"Authorization": f"Bearer {self.user.token}"},
        )
        assert first_response.status_code == 200
        assert second_response.status_code == 200  # Используется повторно, but count должен остаться прежним
        applied_promo = await session.get(Promo, self.promo.id)
        assert applied_promo.used_count == 1

    async def test_unauthorized_access(self, client):
        response = await client.post(
            "/promo/apply",
            json={"code": self.promo.code},
        )  # Без Authorization header
        assert response.status_code == 401
Фабрики данных (tests/factories.py)
from factory.alchemy import AsyncSQLAlchemyModelFactory
from app.models import User, Cart, Promo
from tests.conftest import session


class UserFactory(AsyncSQLAlchemyModelFactory):
    class Meta:
        model = User
        sqlalchemy_session = session
        sqlalchemy_session_persistence = 'commit'

    id = factory.Sequence(lambda n: n)
    email = factory.Faker('email')
    token = factory.Faker('uuid4')  # Простая заглушка для токенов


class CartFactory(AsyncSQLAlchemyModelFactory):
    class Meta:
        model = Cart
        sqlalchemy_session = session
        sqlalchemy_session_persistence = 'commit'

    id = factory.Sequence(lambda n: n)
    total = 100.00
    discount = 0.00
    user_id = factory.SelfAttribute('..id')


class PromoFactory(AsyncSQLAlchemyModelFactory):
    class Meta:
        model = Promo
        sqlalchemy_session = session
        sqlalchemy_session_persistence = 'commit'

    code = factory.Sequence(lambda n: f"PROMO{n}")
    max_uses = 5
    percent = 10
    max_discount = 100.00
    expires_at = factory.LazyFunction(lambda: datetime.utcnow() + timedelta(days=7))

3. Запуск тестов в CI и изоляция данных

Чтобы запустить эти тесты в CI-системе, мы используем testcontainers для запуска контейнера базы данных PostgreSQL, а также создаём отдельную схему БД для каждого теста, чтобы обеспечить полную изоляцию данных между тестовыми кейсами.

Пример конфигурационного файла .github/workflows/test.yml, запускающий тесты в GitHub Actions:

name: Run Integration Tests

on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  integration_tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python 3.12
        uses: actions/setup-python@v4
        with:
          python-version: 3.12
          
      - name: Install dependencies
        run: pip install -r requirements.txt
      
      - name: Run tests
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: pytest --asyncio-mode=auto tests/

Изоляция данных:
Каждый тест получает свою собственную сессию баз данных благодаря использованию отдельного механизма сессии в фикстурах. Это гарантирует, что каждый тест работает в чистой среде без взаимозависимости друг от друга.

Таким образом, после выполнения всех шагов, база данных очищается автоматически, и последующие тесты выполняются независимо.

Советы

Как пользоваться
  1. Откройте доступ и скопируйте промпт кнопкой выше.
  2. Замените поля в фигурных скобках своими данными.
  3. Отправьте в нейросеть и сравните ответ с примером на этой странице.

Подробнее о структуре хорошего запроса: гид AI University.

Похожие промпты

ПрограммистуБесплатно Найти причину ошибки по стектрейсу и логамВероятная причина плавающей ошибки, гипотезы с проверками, исправление и защита от повторения. 4 ответаОткрытьПрограммистуБесплатно Ускорить медленный SQL-запрос по EXPLAINРазбор плана выполнения, индексы и переписанный запрос, безопасное создание индекса на проде. 4 ответаОткрытьПрограммистуБесплатно Ревью pull request по diffРевью изменений с приоритетами: блокирующие ошибки, безопасность, тесты, готовые комментарии. 4 ответаОткрыть

Все 435 промптов и 6 наборов

172 промптов открыты бесплатно. Остальные и наборы-цепочки открывает доступ к библиотеке за 1 490 ₽. Полный доступ за 4 900 ₽: все курсы AI University на русском и библиотека промптов. Разовый платёж, новые промпты входят.