diff --git a/app/routers/deals.py b/app/routers/deals.py index 0f3c170..2f4cc67 100644 --- a/app/routers/deals.py +++ b/app/routers/deals.py @@ -15,7 +15,8 @@ ) async def list_deals(db: Session = Depends(get_db)): # TODO - return [] + ''' deal을 list 형태로 반환''' + return db.query(Deal).all() # db.query()를 통해 Deal에 있는 모든 데이터들을 가져온다. @router.post( "/", @@ -25,7 +26,15 @@ async def list_deals(db: Session = Depends(get_db)): ) async def create_deal(deal: DealCreate, db: Session = Depends(get_db)): # TODO - raise NotImplementedError("TODO") + ''' + deal을 생성. + ''' + new_deal = Deal(**deal.dict()) # deal를 처리할 수 있는 형태로. + db.add(new_deal) # DB 세션에 추가. + db.commit() # 변경사항 저장. + db.refresh(new_deal) # 갱신 + + return new_deal @router.get( "/{deal_id}", @@ -34,7 +43,16 @@ async def create_deal(deal: DealCreate, db: Session = Depends(get_db)): ) async def get_deal(deal_id: int, db: Session = Depends(get_db)): # TODO - raise NotImplementedError("TODO") + ''' + 해당되는 deal을 반환. + ''' + deal = db.query(Deal).filter(Deal.id == deal_id).first() # deal_id인 멤버를 DB에서 찾기. + + if deal is None: + raise HTTPException(status_code=404, detail="Deal not found") + + return deal + @router.delete( "/{deal_id}", @@ -43,7 +61,16 @@ async def get_deal(deal_id: int, db: Session = Depends(get_db)): ) async def delete_deal(deal_id: int, db: Session = Depends(get_db)): # TODO - raise NotImplementedError("TODO") + ''' + deal을 삭제. + ''' + deal = db.query(Deal).filter(Deal.id == deal_id).first() + + if deal is None: + raise HTTPException(status_code=404, detail="Deal not found") + + db.delete(deal) + db.commit() # Special endpoint: Recommend Deal class DealRecommendInput(BaseModel): diff --git a/app/routers/docents.py b/app/routers/docents.py index 382db19..5d81049 100644 --- a/app/routers/docents.py +++ b/app/routers/docents.py @@ -14,7 +14,11 @@ ) async def list_docents(db: Session = Depends(get_db)): # TODO - return [] + ''' + docents를 리스트 형태로 반환. + ''' + + return db.query(Docent).all() @router.post( "/", @@ -24,7 +28,17 @@ async def list_docents(db: Session = Depends(get_db)): ) async def create_docent(docent: DocentCreate, db: Session = Depends(get_db)): # TODO - raise NotImplementedError("TODO") + + ''' + docent 생성. + ''' + + new_docent = Docent(**docent.dict()) + db.add(new_docent) + db.commit() + db.refresh(new_docent) + + return new_docent @router.get( "/{docent_id}", @@ -33,7 +47,14 @@ async def create_docent(docent: DocentCreate, db: Session = Depends(get_db)): ) async def get_docent(docent_id: int, db: Session = Depends(get_db)): # TODO - raise NotImplementedError("TODO") + '''해당되는 docent를 반환''' + + docent = db.query(Docent).filter(Docent.id == docent_id).first() + + if docent is None: + raise HTTPException(status_code=404, detail="Docent Not Found") + + return docent @router.delete( "/{docent_id}", @@ -42,7 +63,16 @@ async def get_docent(docent_id: int, db: Session = Depends(get_db)): ) async def delete_docent(docent_id: int, db: Session = Depends(get_db)): # TODO - raise NotImplementedError("TODO") + ''' + 해당되는 docent를 삭제. + ''' + docent = db.query(Docent).filter(Docent.id == docent_id).first() + + if docent is None: + raise HTTPException(status_code=404,detail="Docent Not Found") + + db.delete(docent) + db.commit() # Special endpoint: Generate Docent Content class DocentGenerateInput(BaseModel): diff --git a/main.py b/main.py index eb1f59f..370a220 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,10 @@ -def main(): - print("Hello from fastapi-community!") +from fastapi import FastAPI +app = FastAPI() -if __name__ == "__main__": - main() +@app.get("/itmes/") +async def read_items(q: str | None = None): + results = {"itmes":[{{"item_id":"Foo"},{"item_id":"Bar"}}]} + if q: + results.update({"q":q}) + return results \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e67a454..243dd8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,28 +1,8 @@ -annotated-types==0.7.0 -anyio==3.7.1 -certifi==2026.1.4 -click==8.3.1 -fastapi==0.104.1 greenlet==3.3.0 -h11==0.16.0 -httpcore==1.0.9 -httptools==0.7.1 -httpx==0.25.2 -idna==3.11 -iniconfig==2.3.0 -packaging==25.0 -pluggy==1.6.0 -psycopg2-binary==2.9.9 -pydantic==2.5.0 -pydantic-core==2.14.1 -pytest==7.4.3 -python-dotenv==1.0.0 -pyyaml==6.0.3 -sniffio==1.3.1 -sqlalchemy==2.0.23 -starlette==0.27.0 +sqlalchemy==2.0.45 typing-extensions==4.15.0 -uvicorn==0.24.0 -uvloop==0.22.1 -watchfiles==1.1.1 -websockets==16.0 +fastapi>=0.110.0 +python-dotenv>=1.0.0 +uvicorn>=0.20.0 +httpx>=0.27.0 +pytest>=8.0.0 diff --git a/tests/conftest.py b/tests/conftest.py index 9d338eb..03c2e2e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,32 +1,47 @@ import pytest import os +import sys -# app.db import 전에 환경변수 설정 (ImportError 방지) -# CI 환경에서 app/db.py가 DATABASE_URL을 요구하므로 dummy 값 설정 +# 1. 환경변수 설정 (app.db import 전) +# 실제 DB 연결을 시도하지 않도록 Dummy URL 설정 os.environ.setdefault("DATABASE_URL", "sqlite:///./test_dummy.db") from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker + +# 2. Base 및 모델 Import from app.db import Base, get_db +# ★중요★: 테이블을 생성하려면 Base가 모델들을 알고 있어야 합니다. +# 만약 에러가 계속된다면 아래처럼 모델들을 여기서 import 해주세요. +# from app.models.deals import Deal +# from app.models.docents import Docent -# Mock creation of tables in production DB (since we don't have ODBC driver in test env) -from unittest.mock import MagicMock -Base.metadata.create_all = MagicMock() +# ------------------------------------------------------------------ +# [수정됨] MagicMock 제거 +# Base.metadata.create_all 메서드를 Mocking 해버리면, +# 아래에서 create_all(bind=engine)을 호출해도 아무 일도 일어나지 않습니다. +# ------------------------------------------------------------------ from app.main import app -# 테스트 DB (in-memory SQLite, 또는 별도 테스트 Azure SQL) -# 로컬 개발 환경에서 빠르게 돌리기 위해 SQLite 사용 +# 3. 테스트 DB 설정 (SQLite 사용) SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test.db" +# 메모리 DB를 원하면 "sqlite:///:memory:" 사용 가능 + engine = create_engine( - SQLALCHEMY_TEST_DATABASE_URL, connect_args={"check_same_thread": False} + SQLALCHEMY_TEST_DATABASE_URL, + connect_args={"check_same_thread": False} ) + TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# 4. 테스트 시작 전 테이블 생성 +# MagicMock을 지웠으므로 이제 이 코드가 정상 작동하여 'deals', 'docents' 테이블을 만듭니다. Base.metadata.create_all(bind=engine) def override_get_db(): + db = TestingSessionLocal() try: - db = TestingSessionLocal() yield db finally: db.close() @@ -36,4 +51,9 @@ def override_get_db(): @pytest.fixture def client(): from fastapi.testclient import TestClient - return TestClient(app) + + # (선택 사항) 테스트 할 때마다 DB를 깨끗하게 비우고 싶다면 + # Base.metadata.drop_all(bind=engine) + # Base.metadata.create_all(bind=engine) + + return TestClient(app) \ No newline at end of file diff --git a/tests/test_deals.py b/tests/test_deals.py new file mode 100644 index 0000000..3f2e8d1 --- /dev/null +++ b/tests/test_deals.py @@ -0,0 +1,43 @@ +from datetime import datetime, timedelta + +def test_deal_lifecycle(client): + # 1. Create a fresh deal (POST) + payload = { + "event_id": 999, + "discount_rate": 25, + "starts_at": datetime.utcnow().isoformat(), + "ends_at": (datetime.utcnow() + timedelta(days=7)).isoformat() + } + + response = client.post("/deals/", json=payload) + assert response.status_code == 201 + data = response.json() + + deal_id = data["id"] + assert data["discount_rate"] == 25 + assert data["event_id"] == 999 + + # 2. List all deals (GET) + response = client.get("/deals/") + assert response.status_code == 200 + deals = response.json() + + found = False + for d in deals: + if d["id"] == deal_id: + found = True + break + assert found is True + + # 3. Get specific deal (GET /{id}) + response = client.get(f"/deals/{deal_id}") + assert response.status_code == 200 + assert response.json()["id"] == deal_id + + # 4. Delete the deal (DELETE) + response = client.delete(f"/deals/{deal_id}") + assert response.status_code == 204 + + # 5. Verify it's gone (GET /{id} -> 404) + response = client.get(f"/deals/{deal_id}") + assert response.status_code == 404 diff --git a/tests/test_docents.py b/tests/test_docents.py new file mode 100644 index 0000000..38895e2 --- /dev/null +++ b/tests/test_docents.py @@ -0,0 +1,51 @@ +def test_docent_lifecycle(client): + # 1. Create a fresh docent (POST) + payload = { + "place_id": 101, + "tone": "serene", + "content": "이곳은 아주 조용하고 평화로운 공간입니다." + } + + response = client.post("/docents/", json=payload) + assert response.status_code == 201 + data = response.json() + + docent_id = data["id"] + assert data["tone"] == "serene" + assert data["place_id"] == 101 + + # 2. List all docents (GET) + response = client.get("/docents/") + assert response.status_code == 200 + docents = response.json() + + found = False + for d in docents: + if d["id"] == docent_id: + found = True + break + assert found is True + + # 3. Get specific docent (GET /{id}) + response = client.get(f"/docents/{docent_id}") + assert response.status_code == 200 + assert response.json()["id"] == docent_id + + # 4. Delete the docent (DELETE) + response = client.delete(f"/docents/{docent_id}") + assert response.status_code == 204 + + # 5. Verify it's gone (GET /{id} -> 404) + response = client.get(f"/docents/{docent_id}") + assert response.status_code == 404 + +def test_generate_docent(client): + # Special endpoint test + payload = { + "place_id": 500, + "tone": "energetic" + } + response = client.post("/docents/generate", json=payload) + assert response.status_code == 200 + data = response.json() + assert "energetic" in data["content"]