Pipelinen parannuksia building blockeilla
This commit is contained in:
277
zipit/todo_new/prompts/6_qa_test_main.py.md
Normal file
277
zipit/todo_new/prompts/6_qa_test_main.py.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# 6 — QA (qa) — test_main.py
|
||||
|
||||
**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
|
||||
|
||||
Write pytest tests for this project:
|
||||
|
||||
--- 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"
|
||||
|
||||
Write a complete test_main.py file with TestClient.
|
||||
```
|
||||
|
||||
## Tulos
|
||||
|
||||
```
|
||||
from fastapi.testclient import TestClient
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, func
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
engine = create_engine(DATABASE_URL)
|
||||
TestingSessionLocal = 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, server_default=func.now())
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def override_get_db():
|
||||
try:
|
||||
yield TestingSessionLocal()
|
||||
finally:
|
||||
pass
|
||||
|
||||
from main import app, get_db
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_database():
|
||||
with engine.begin() as conn:
|
||||
conn.execute("DELETE FROM tasks")
|
||||
|
||||
def test_create_task():
|
||||
response = client.post("/tasks/", json={"title": "Test Task", "description": "This is a test task"})
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["id"] > 0
|
||||
assert data["title"] == "Test Task"
|
||||
assert data["description"] == "This is a test task"
|
||||
|
||||
def test_list_tasks():
|
||||
client.post("/tasks/", json={"title": "Task 1", "description": "First task"})
|
||||
client.post("/tasks/", json={"title": "Task 2", "description": "Second task"})
|
||||
response = client.get("/tasks/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 2
|
||||
|
||||
def test_get_task():
|
||||
response = client.post("/tasks/", json={"title": "Get Task", "description": "Task to get"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
|
||||
def test_get_task_not_found():
|
||||
response = client.get("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update_task():
|
||||
response = client.post("/tasks/", json={"title": "Update Task", "description": "Task to update"})
|
||||
task_id = response.json()["id"]
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put(f"/tasks/{task_id}", json=updated_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == task_id
|
||||
assert data["title"] == "Updated Title"
|
||||
|
||||
def test_update_task_not_found():
|
||||
updated_data = {"title": "Updated Title"}
|
||||
response = client.put("/tasks/999", json=updated_data)
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task():
|
||||
response = client.post("/tasks/", json={"title": "Delete Task", "description": "Task to delete"})
|
||||
task_id = response.json()["id"]
|
||||
response = client.delete(f"/tasks/{task_id}")
|
||||
assert response.status_code == 204
|
||||
response = client.get(f"/tasks/{task_id}")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_task_not_found():
|
||||
response = client.delete("/tasks/999")
|
||||
assert response.status_code == 404
|
||||
|
||||
This `test_main.py` file includes tests for all CRUD operations using the FastAPI TestClient. Each test creates its own data and ensures that there are no dependencies between tests. The database is reset before each test to ensure isolation.
|
||||
```
|
||||
Reference in New Issue
Block a user