# app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import List, Optional
from decimal import Decimal, ROUND_HALF_UP
app = FastAPI()
class Item(BaseModel):
name: str
price: Decimal
is_offer: bool = False
@property
def rounded_price(self) -> Decimal:
return self.price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
class Cart(BaseModel):
items: List[Item] = Field(default_factory=list)
def total(self) -> Decimal:
return sum(item.rounded_price for item in self.items)
def add_item(self, item: Item) -> None:
self.items.append(item)
class TaxCalculator:
TAX_RATE = Decimal("0.10") # 10% 세금
@staticmethod
def calculate_tax(amount: Decimal) -> Decimal:
tax = amount * TaxCalculator.TAX_RATE
return tax.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
class CartWithTax(BaseModel):
items: List[Item]
total: Decimal
tax: Decimal
total_with_tax: Decimal
@app.post("/cart", response_model=Cart)
async def create_cart(items: List[Item]) -> Cart:
cart = Cart(items=items)
return cart
@app.put("/cart/add", response_model=Cart)
async def add_item_to_cart(cart: Cart, item: Item) -> Cart:
cart.add_item(item)
return cart
@app.get("/cart/tax", response_model=CartWithTax)
async def get_cart_with_tax(cart: Cart) -> CartWithTax:
total = cart.total()
tax = TaxCalculator.calculate_tax(total)
total_with_tax = total + tax
return CartWithTax(items=cart.items, total=total, tax=tax, total_with_tax=total_with_tax)
Python
복사
# tests/test_cart.py
import pytest
from fastapi.testclient import TestClient
from decimal import Decimal
from app.main import app, Cart, Item, TaxCalculator
client = TestClient(app)
@pytest.fixture
def sample_cart():
return Cart(items=[
Item(name="item1", price=Decimal("100.0"), is_offer=False),
Item(name="item2", price=Decimal("200.0"), is_offer=True)
])
@pytest.fixture
def new_item():
return Item(name="item3", price=Decimal("150.0"), is_offer=False)
def test_create_cart():
response = client.post("/cart", json=[
{"name": "item1", "price": "100.0", "is_offer": False},
{"name": "item2", "price": "200.0", "is_offer": True}
])
assert response.status_code == 200
assert len(response.json()["items"]) == 2
assert response.json()["items"][0]["name"] == "item1"
def test_add_item_to_cart(sample_cart, new_item):
response = client.put("/cart/add", json={"items": sample_cart.items, "total": sample_cart.total()}, params={"item": new_item})
assert response.status_code == 200
assert len(response.json()["items"]) == 3
def test_calculate_tax():
amount = Decimal("100.0")
tax = TaxCalculator.calculate_tax(amount)
assert tax == Decimal("10.00")
def test_get_cart_with_tax(sample_cart):
response = client.get("/cart/tax", json={"items": sample_cart.items})
assert response.status_code == 200
assert response.json()["total"] == "300.00"
assert response.json()["tax"] == "30.00"
assert response.json()["total_with_tax"] == "330.00"
Python
복사
1.
도메인 모델 정의
•
Item: 개별 상품을 나타냅니다.
•
Cart: 장바구니로 add_item 메서드를 통해 아이템을 추가할 수 있습니다.
•
CartWithTax: 세금 정보를 포함한 장바구니 모델입니다.
2.
도메인 서비스
•
TaxCalculator: 세금 계산을 처리하는 도메인 서비스입니다.
3.
API 엔드포인트
•
/cart: 새 장바구니 생성.
•
/cart/add: 기존 장바구니에 아이템 추가.
•
/cart/tax: 세금 계산 후 전체 금액 반환.
4.
테스트 코드
•
test_create_cart: 장바구니를 생성하는 엔드 투 엔드 테스트.
•
test_add_item_to_cart: 기존 장바구니에 아이템을 추가하는 엣지 투 엣지 테스트.
•
test_calculate_tax: 세금 계산 로직을 테스트하는 유닛 테스트.
•
test_get_cart_with_tax: 세금이 포함된 장바구니 반환을 테스트하는 엔드 투 엔드 테스트.