Files
ev-charging-backend/test_flow.py
2026-04-18 05:59:31 +09:00

163 lines
5.6 KiB
Python

"""전체 충전 흐름 테스트 스크립트
API 서버가 실행 중인 상태에서 실행.
실제 토스 결제 없이 전체 흐름을 시뮬레이션.
사용법:
pip3 install httpx
python3 test_flow.py
"""
import asyncio
import httpx
import json
import sys
BASE_URL = "http://localhost:8000/api/v1"
G = "\033[92m"
Y = "\033[93m"
C = "\033[96m"
R = "\033[91m"
E = "\033[0m"
def log(step, msg, data=None):
print(f"\n{C}{''*50}{E}")
print(f"{Y}[{step}]{E} {msg}")
if data:
print(f"{G}{json.dumps(data, indent=2, ensure_ascii=False, default=str)}{E}")
def check(resp, step):
"""응답 확인 - 실패 시 내용 출력 후 중단"""
if resp.status_code >= 400:
print(f"\n{R}{''*50}")
print(f" ERROR at [{step}] - HTTP {resp.status_code}")
print(f" {resp.json()}")
print(f"{''*50}{E}\n")
sys.exit(1)
return resp.json()
async def main():
async with httpx.AsyncClient(base_url=BASE_URL, timeout=10.0) as c:
# 0. 헬스체크
resp = await c.get("http://localhost:8000/health")
log("0. 헬스체크", f"HTTP {resp.status_code}", resp.json())
# 1. 충전기 등록
log("1. 충전기 등록", "CHARGER_001")
resp = await c.post("/chargers/", json={
"charge_box_id": "CHARGER_001",
"name": "A동 주차장 1번",
"location": "수원시 영통구 테스트 아파트 지하1층",
"power_kw": 7.0,
})
if resp.status_code == 409:
log("1. 충전기 등록", "이미 등록됨 - 건너뜀")
else:
log("1. 충전기 등록", f"결과: {resp.status_code}", resp.json())
# 1-1. 이전 테스트 잔여 세션 정리
log("1-1. 세션 정리", "미완료 세션 취소")
resp = await c.post("/sessions/reset/CHARGER_001")
log("1-1. 세션 정리", "결과", resp.json())
# 2. 충전기 상태 -> Available
log("2. 상태 업데이트", "충전기 Available 설정")
resp = await c.post("/ocpp/status", json={
"charge_box_id": "CHARGER_001",
"connector_id": 1,
"status": "Available",
})
log("2. 상태 업데이트", "결과", check(resp, "2"))
# 3. 세션 생성 (QR 스캔)
log("3. 세션 생성", "QR 스캔 -> 세션 생성")
resp = await c.post("/sessions/", json={
"charge_box_id": "CHARGER_001",
"connector_id": 1,
})
session = check(resp, "3")
session_uid = session["session_uid"]
id_tag = session["id_tag"]
log("3. 세션 생성", f"세션: {session_uid} / tag: {id_tag}", session)
# 4. 결제 준비
log("4. 결제 준비", "orderId 발급")
resp = await c.post("/payments/prepare", json={
"session_uid": session_uid,
"amount": 10000,
})
payment = check(resp, "4")
log("4. 결제 준비", f"주문: {payment['order_id']}", payment)
# 5. 결제 우회 (테스트용)
log("5. 결제 우회", "토스 결제 없이 AUTHORIZED 강제 설정")
print(f" {Y}(실제로는 토스페이먼츠 결제 UI -> POST /payments/confirm){E}")
resp = await c.post(f"/sessions/{session_uid}/force-authorize")
log("5. 결제 우회", "결과", check(resp, "5"))
# 6. StartTransaction (충전기 -> 서버)
log("6. StartTransaction", "충전기가 충전 시작 보고")
resp = await c.post("/ocpp/start-transaction", json={
"charge_box_id": "CHARGER_001",
"connector_id": 1,
"id_tag": id_tag,
"meter_start": 100000,
"transaction_id": 1001,
})
result = check(resp, "6")
log("6. StartTransaction", f"결과: {result.get('status')}", result)
# 7. MeterValues (충전 중 실시간 전력량)
for i, wh in enumerate([105000, 110000, 115000, 120000]):
kwh = (wh - 100000) / 1000
log(f"7-{i+1}. MeterValues", f"{wh}Wh (누적 {kwh}kWh 충전)")
await c.post("/ocpp/meter-values", json={
"charge_box_id": "CHARGER_001",
"connector_id": 1,
"transaction_id": 1001,
"value": wh,
})
# 8. 충전 중 세션 상태 확인
resp = await c.get(f"/sessions/{session_uid}")
log("8. 세션 상태", "충전 중", check(resp, "8"))
# 9. StopTransaction (충전 종료)
log("9. StopTransaction", "충전 종료 - 30kWh 충전 완료")
resp = await c.post("/ocpp/stop-transaction", json={
"charge_box_id": "CHARGER_001",
"transaction_id": 1001,
"meter_stop": 130000,
"reason": "Local",
})
result = check(resp, "9")
log("9. StopTransaction", "정산 결과", result)
# 10. 최종 정산
resp = await c.get(f"/sessions/{session_uid}/billing")
billing = check(resp, "10")
log("10. 최종 정산", "요금 내역", billing)
# 11. 대시보드
resp = await c.get("/dashboard/summary")
log("11. 대시보드", "전체 요약", check(resp, "11"))
# 완료
saved = billing.get("saved_vs_cpo", 0)
total = billing.get("total_bill", 0)
kwh = billing.get("charged_kwh", 0)
print(f"\n{G}{''*50}")
print(f" 충전 완료: {kwh}kWh")
print(f" 요금: {total:,}원 (170원/kWh)")
print(f" CPO 대비 절감: {saved:,}")
print(f"{''*50}{E}\n")
if __name__ == "__main__":
asyncio.run(main())