Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions app/routers/deals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"/",
Expand All @@ -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}",
Expand All @@ -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}",
Expand All @@ -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):
Expand Down
38 changes: 34 additions & 4 deletions app/routers/docents.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
)
async def list_docents(db: Session = Depends(get_db)):
# TODO
return []
'''
docents를 리스트 형태로 반환.
'''

return db.query(Docent).all()

@router.post(
"/",
Expand All @@ -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}",
Expand All @@ -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}",
Expand All @@ -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):
Expand Down
12 changes: 8 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
@@ -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
32 changes: 6 additions & 26 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
40 changes: 30 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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)
43 changes: 43 additions & 0 deletions tests/test_deals.py
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions tests/test_docents.py
Original file line number Diff line number Diff line change
@@ -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"]