# 5 — QA (qa) — review **Malli:** `qwen-coder` ## System Prompt ``` You are a QA engineer responsible for code review and automated testing. CODE REVIEW CHECKLIST: 1. IMPORTS: Every "from X import Y" must match an actual export in file X 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) 3. TYPES: All function parameters have type hints, return types specified 4. ERRORS: Every db query that can return None has a 404 check 5. RESOURCES: Database session uses yield+finally pattern (no leaks) 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers WHEN REVIEWING: - If all checks pass: respond "LGTM" - If issues found: list each as "ISSUE: filename.py: description" - Be specific and actionable, not vague WHEN WRITING TESTS: - ALWAYS import app from main.py: from main import app, get_db - ALWAYS import Base from models.py: from models import Base - NEVER redefine the app, models, or routes in the test file - Use file-based SQLite for test isolation: sqlite:///./test.db - Override the get_db dependency to use test database - Use TestClient from fastapi.testclient - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - Each test should create its own data, not depend on other tests ``` ## Syöte ``` You are a QA engineer responsible for code review and automated testing. CODE REVIEW CHECKLIST: 1. IMPORTS: Every "from X import Y" must match an actual export in file X 2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User) 3. TYPES: All function parameters have type hints, return types specified 4. ERRORS: Every db query that can return None has a 404 check 5. RESOURCES: Database session uses yield+finally pattern (no leaks) 6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic 7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE) 8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict() 9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers WHEN REVIEWING: - If all checks pass: respond "LGTM" - If issues found: list each as "ISSUE: filename.py: description" - Be specific and actionable, not vague WHEN WRITING TESTS: - ALWAYS import app from main.py: from main import app, get_db - ALWAYS import Base from models.py: from models import Base - NEVER redefine the app, models, or routes in the test file - Use file-based SQLite for test isolation: sqlite:///./test.db - Override the get_db dependency to use test database - Use TestClient from fastapi.testclient - Test all CRUD: create (201), list (200), get by id (200/404), update (200), delete (204) - Each test should create its own data, not depend on other tests Review this project code for issues. If everything is correct, respond with "LGTM". Otherwise list issues as "ISSUE: filename.py: description". --- models.py --- from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from datetime import datetime DATABASE_URL = "sqlite:///./todo.db" engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() class Task(Base): __tablename__ = "tasks" id = Column(Integer, primary_key=True, index=True) title = Column(String(100), nullable=False) description = Column(Text) status = Column(String(20), default="pending") created_at = Column(DateTime, default=datetime.utcnow) Base.metadata.create_all(bind=engine) --- schemas.py --- from pydantic import BaseModel class TaskCreate(BaseModel): title: str description: str | None = None status: str = "pending" class TaskResponse(TaskCreate): id: int class Config: from_attributes = True --- main.py --- from fastapi import FastAPI, Depends, HTTPException, Query from sqlalchemy.orm import Session from models import Base, engine, SessionLocal, Task from schemas import TaskCreate, TaskResponse Base.metadata.create_all(bind=engine) app = FastAPI() def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.post("/tasks/", response_model=TaskResponse, status_code=201) def create_task(task: TaskCreate, db: Session = Depends(get_db)): db_task = Task(**task.model_dump()) db.add(db_task) db.commit() db.refresh(db_task) return db_task @app.get("/tasks/", response_model=list[TaskResponse]) def list_tasks(status: str | None = Query(None), db: Session = Depends(get_db)): if status: tasks = db.query(Task).filter(Task.status == status).all() else: tasks = db.query(Task).all() return tasks @app.get("/tasks/{task_id}", response_model=TaskResponse) def get_task(task_id: int, db: Session = Depends(get_db)): task = db.query(Task).filter(Task.id == task_id).first() if not task: raise HTTPException(status_code=404, detail="Not found") return task @app.put("/tasks/{task_id}", response_model=TaskResponse) def update_task(task_id: int, task: TaskCreate, db: Session = Depends(get_db)): db_task = db.query(Task).filter(Task.id == task_id).first() if not db_task: raise HTTPException(status_code=404, detail="Not found") for key, value in task.model_dump().items(): setattr(db_task, key, value) db.commit() db.refresh(db_task) return db_task @app.delete("/tasks/{task_id}", status_code=204) def delete_task(task_id: int, db: Session = Depends(get_db)): db_task = db.query(Task).filter(Task.id == task_id).first() if not db_task: raise HTTPException(status_code=404, detail="Not found") db.delete(db_task) db.commit() --- pyproject.toml --- [project] name = "todo-app" version = "0.1.0" requires-python = ">=3.11" dependencies = [ "fastapi", "uvicorn[standard]", "sqlalchemy", ] [project.scripts] dev = "uvicorn main:app --reload" ``` ## Tulos ``` LGTM ```