diff --git a/kipina-codebench/golden-examples/combined-readme.md b/kipina-codebench/golden-examples/combined-readme.md new file mode 100644 index 0000000..fb01fa7 --- /dev/null +++ b/kipina-codebench/golden-examples/combined-readme.md @@ -0,0 +1,204 @@ +# Example 1: Todo App (single entity) + +## models.py + +```python +"""Tietokantamallit — SQLAlchemy 2.0, Mapped-tyypitys, SQLite.""" +from datetime import date +from sqlalchemy import String, Text, Date, create_engine +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker + +DATABASE_URL = "sqlite:///./app.db" +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +class Base(DeclarativeBase): + pass + +class Todo(Base): + __tablename__ = "todos" + id: Mapped[int] = mapped_column(primary_key=True, index=True) + title: Mapped[str] = mapped_column(String(255)) + description: Mapped[str | None] = mapped_column(Text, default=None) + due_date: Mapped[date | None] = mapped_column(Date, default=None) + priority: Mapped[int] = mapped_column(default=1) + status: Mapped[str] = mapped_column(String(20), default="pending") + +Base.metadata.create_all(bind=engine) +``` + +## schemas.py + +```python +from datetime import date +from pydantic import BaseModel, ConfigDict + +class TodoCreate(BaseModel): + title: str + description: str | None = None + due_date: date | None = None + priority: int = 1 + status: str = "pending" + +class TodoResponse(TodoCreate): + id: int + model_config = ConfigDict(from_attributes=True) +``` + +## test_main.py — exactly 6 tests per entity + +```python +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from main import app, get_db +from models import Base + +test_engine = create_engine("sqlite:///./test.db", connect_args={"check_same_thread": False}) +TestSession = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) +Base.metadata.create_all(bind=test_engine) + +def override_get_db(): + db = TestSession() + try: yield db + finally: db.close() + +app.dependency_overrides[get_db] = override_get_db +client = TestClient(app) + +def test_create_todo(): + response = client.post("/todos/", json={"title": "Osta maitoa", "priority": 2}) + assert response.status_code == 201 + assert "id" in response.json() + +def test_list_todos(): + client.post("/todos/", json={"title": "Listattava"}) + response = client.get("/todos/") + assert response.status_code == 200 + assert len(response.json()) >= 1 + +def test_get_todo_by_id(): + created = client.post("/todos/", json={"title": "Haettava"}).json() + response = client.get(f"/todos/{created['id']}") + assert response.status_code == 200 + +def test_get_todo_not_found(): + response = client.get("/todos/99999") + assert response.status_code == 404 + +def test_update_todo(): + created = client.post("/todos/", json={"title": "Vanha"}).json() + response = client.put(f"/todos/{created['id']}", json={"title": "Uusi"}) + assert response.status_code == 200 + +def test_delete_todo(): + created = client.post("/todos/", json={"title": "Poistettava"}).json() + response = client.delete(f"/todos/{created['id']}") + assert response.status_code == 204 +``` + +# Example 2: Blog (two entities with ForeignKey) + +NOTE: ForeignKey is imported from sqlalchemy, NOT from sqlalchemy.orm! + +## models.py + +```python +from datetime import datetime +from sqlalchemy import String, Text, DateTime, ForeignKey, create_engine +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, sessionmaker + +DATABASE_URL = "sqlite:///./app.db" +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +class Base(DeclarativeBase): + pass + +class Author(Base): + __tablename__ = "authors" + id: Mapped[int] = mapped_column(primary_key=True, index=True) + name: Mapped[str] = mapped_column(String(255)) + email: Mapped[str] = mapped_column(String(255), unique=True) + bio: Mapped[str | None] = mapped_column(Text, default=None) + posts: Mapped[list["Post"]] = relationship(back_populates="author") + +class Post(Base): + __tablename__ = "posts" + id: Mapped[int] = mapped_column(primary_key=True, index=True) + title: Mapped[str] = mapped_column(String(255)) + content: Mapped[str] = mapped_column(Text) + author_id: Mapped[int] = mapped_column(ForeignKey("authors.id")) + published_at: Mapped[datetime | None] = mapped_column(DateTime, default=None) + status: Mapped[str] = mapped_column(String(20), default="draft") + author: Mapped["Author"] = relationship(back_populates="posts") + +Base.metadata.create_all(bind=engine) +``` + +## schemas.py + +```python +from datetime import datetime +from pydantic import BaseModel, ConfigDict + +class AuthorCreate(BaseModel): + name: str + email: str + bio: str | None = None + +class AuthorResponse(AuthorCreate): + id: int + model_config = ConfigDict(from_attributes=True) + +class PostCreate(BaseModel): + title: str + content: str + author_id: int + published_at: datetime | None = None + status: str = "draft" + +class PostResponse(PostCreate): + id: int + model_config = ConfigDict(from_attributes=True) +``` + +## test_main.py — 6 tests per entity, create parent FIRST for child tests + +```python +client = TestClient(app) # same setup as above + +def _create_author(name="Kirjailija", email=None): + if email is None: + email = f"{name.lower().replace(' ', '.')}@example.com" + return client.post("/authors/", json={"name": name, "email": email}).json() + +def test_create_author(): + response = client.post("/authors/", json={"name": "Aleksis Kivi", "email": "aleksis@example.com"}) + assert response.status_code == 201 + +def test_list_authors(): + _create_author("Minna Canth", "minna@example.com") + response = client.get("/authors/") + assert response.status_code == 200 + assert len(response.json()) >= 1 + +# ... (same pattern: get_by_id, not_found, update, delete) + +def test_create_post(): + author = _create_author("Tove Jansson", "tove@example.com") + response = client.post("/posts/", json={"title": "Artikkeli", "content": "Sisältö", "author_id": author["id"]}) + assert response.status_code == 201 + +def test_update_post(): + author = _create_author("Joel Lehtonen", "joel@example.com") + created = client.post("/posts/", json={"title": "Vanha", "content": "Teksti", "author_id": author["id"]}).json() + response = client.put(f"/posts/{created['id']}", json={"title": "Uusi", "content": "Muokattu", "author_id": author["id"]}) + assert response.status_code == 200 + +def test_delete_post(): + author = _create_author("Aino Kallas", "aino@example.com") + created = client.post("/posts/", json={"title": "Poistettava", "content": "Poistetaan", "author_id": author["id"]}).json() + response = client.delete(f"/posts/{created['id']}") + assert response.status_code == 204 +``` diff --git a/kipina-codebench/profiles.json b/kipina-codebench/profiles.json index 167a429..d6ebf28 100644 --- a/kipina-codebench/profiles.json +++ b/kipina-codebench/profiles.json @@ -12,7 +12,7 @@ "profile": "small", "role": "primary", "prompt": "code-small", - "golden": "todo-readme.md", + "golden": "combined-readme.md", "vram": "8GB", "notes": "Kevyt pääkooderi. Todo/users 100p, blog heikko. README-muoto golden examplelle." }, diff --git a/kipina-codebench/results/2026-04-14T11-06.html b/kipina-codebench/results/2026-04-14T11-06.html new file mode 100644 index 0000000..5f4b752 --- /dev/null +++ b/kipina-codebench/results/2026-04-14T11-06.html @@ -0,0 +1,183 @@ + + +
+ + +