163 lines
5.6 KiB
Python
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())
|