Initial commit: AutonetSellCar platform with deployment system
- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1
backend/tests/__init__.py
Normal file
1
backend/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# AutonetSellCar Backend Tests
|
||||
232
backend/tests/test_integration_performance_check.py
Normal file
232
backend/tests/test_integration_performance_check.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Performance Check Integration Tests
|
||||
|
||||
통합 테스트 시나리오:
|
||||
1. 배너 차량 + 비로그인 → 성능점검표 미리보기만 가능
|
||||
2. 배너 차량 + 로그인 (미결제) → 성능점검표 미리보기만 가능
|
||||
3. 배너 차량 + 로그인 (0.1 CC 결제) → 성능점검표 전체 접근 가능
|
||||
4. 일반 차량 + 비로그인 → 성능점검표 미리보기만 가능
|
||||
5. 일반 차량 + 로그인 (1 CC 결제) → 성능점검표 전체 접근 가능
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from unittest.mock import MagicMock, patch
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
# 테스트 시나리오 정의
|
||||
TEST_SCENARIOS = [
|
||||
# (시나리오 이름, 배너여부, 로그인여부, 결제타입, 예상결과)
|
||||
("banner_anonymous", True, False, None, {"has_access": False, "can_view_images": True}),
|
||||
("banner_logged_in_no_payment", True, True, None, {"has_access": False, "can_view_images": True}),
|
||||
("banner_logged_in_perf_payment", True, True, "performance", {"has_access": True, "can_view_images": True}),
|
||||
("banner_logged_in_full_payment", True, True, "full", {"has_access": True, "can_view_images": True}),
|
||||
("normal_anonymous", False, False, None, {"has_access": False, "can_view_images": False}),
|
||||
("normal_logged_in_no_payment", False, True, None, {"has_access": False, "can_view_images": False}),
|
||||
("normal_logged_in_perf_payment", False, True, "performance", {"has_access": True, "can_view_images": False}),
|
||||
("normal_logged_in_full_payment", False, True, "full", {"has_access": True, "can_view_images": True}),
|
||||
]
|
||||
|
||||
|
||||
class TestPerformanceCheckIntegration:
|
||||
"""성능점검표 통합 테스트"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scenario_name,is_banner,is_logged_in,payment_type,expected",
|
||||
TEST_SCENARIOS
|
||||
)
|
||||
def test_performance_check_access_scenarios(
|
||||
self,
|
||||
scenario_name: str,
|
||||
is_banner: bool,
|
||||
is_logged_in: bool,
|
||||
payment_type: str,
|
||||
expected: dict
|
||||
):
|
||||
"""
|
||||
모든 시나리오에 대한 성능점검표 접근 테스트
|
||||
|
||||
이 테스트는 실제 API를 호출하지 않고 로직만 검증합니다.
|
||||
실제 API 테스트는 별도의 e2e 테스트에서 수행합니다.
|
||||
"""
|
||||
# Given
|
||||
user = self._create_user(is_logged_in)
|
||||
has_perf_view = payment_type == "performance"
|
||||
has_car_view = payment_type == "full"
|
||||
|
||||
# When
|
||||
result = self._simulate_performance_check_access(
|
||||
user=user,
|
||||
is_banner_car=is_banner,
|
||||
has_perf_view=has_perf_view,
|
||||
has_car_view=has_car_view
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result["has_access"] == expected["has_access"], \
|
||||
f"Scenario '{scenario_name}' failed: expected has_access={expected['has_access']}, got {result['has_access']}"
|
||||
|
||||
def _create_user(self, is_logged_in: bool):
|
||||
"""테스트용 사용자 생성"""
|
||||
if not is_logged_in:
|
||||
return None
|
||||
|
||||
user = MagicMock()
|
||||
user.is_admin = False
|
||||
user.id = 1
|
||||
return user
|
||||
|
||||
def _simulate_performance_check_access(
|
||||
self,
|
||||
user,
|
||||
is_banner_car: bool,
|
||||
has_perf_view: bool,
|
||||
has_car_view: bool
|
||||
) -> dict:
|
||||
"""
|
||||
성능점검표 접근 시뮬레이션
|
||||
|
||||
실제 로직: backend/app/api/carmodoo.py - get_car_performance_check()
|
||||
"""
|
||||
has_access = False
|
||||
|
||||
# 배너 차량 여부는 성능점검표 접근에 영향 없음!
|
||||
# (이것이 핵심 비즈니스 로직)
|
||||
|
||||
if user:
|
||||
if hasattr(user, 'is_admin') and user.is_admin:
|
||||
has_access = True
|
||||
else:
|
||||
has_access = has_perf_view or has_car_view
|
||||
|
||||
return {
|
||||
"has_access": has_access,
|
||||
"preview": {
|
||||
"check_number": "TEST123",
|
||||
"check_date": "2024-01-01",
|
||||
"mileage": 50000
|
||||
} if not has_access else None,
|
||||
"data": {"full": "data"} if has_access else None
|
||||
}
|
||||
|
||||
|
||||
class TestBannerCarIntegration:
|
||||
"""배너 차량 통합 테스트"""
|
||||
|
||||
def test_banner_car_free_features(self):
|
||||
"""배너 차량의 무료 기능 확인"""
|
||||
is_banner_car = True
|
||||
|
||||
# 배너 차량 무료 기능
|
||||
free_features = {
|
||||
"all_images": True,
|
||||
"dealer_contact": True,
|
||||
"basic_info": True,
|
||||
}
|
||||
|
||||
# 배너 차량 유료 기능
|
||||
paid_features = {
|
||||
"performance_check": True, # 0.1 CC 필요
|
||||
}
|
||||
|
||||
# 비로그인 사용자도 무료 기능 접근 가능
|
||||
user = None
|
||||
|
||||
for feature, is_free in free_features.items():
|
||||
can_access = is_banner_car and is_free
|
||||
assert can_access is True, f"Banner car should have free access to {feature}"
|
||||
|
||||
# 성능점검표는 결제 필요
|
||||
can_access_perf_check = False # 결제하지 않으면 불가
|
||||
assert can_access_perf_check is False, "Performance check requires payment even for banner cars"
|
||||
|
||||
def test_banner_vs_normal_car_comparison(self):
|
||||
"""배너 차량 vs 일반 차량 기능 비교"""
|
||||
user_logged_in_no_payment = MagicMock()
|
||||
user_logged_in_no_payment.is_admin = False
|
||||
|
||||
# 배너 차량
|
||||
banner_car_access = {
|
||||
"images": True, # 무료
|
||||
"contact": True, # 무료
|
||||
"performance_check": False, # 결제 필요
|
||||
}
|
||||
|
||||
# 일반 차량
|
||||
normal_car_access = {
|
||||
"images": False, # 결제 필요
|
||||
"contact": False, # 결제 필요
|
||||
"performance_check": False, # 결제 필요
|
||||
}
|
||||
|
||||
# 배너 차량은 이미지/연락처만 무료
|
||||
assert banner_car_access["images"] is True
|
||||
assert banner_car_access["contact"] is True
|
||||
assert banner_car_access["performance_check"] is False
|
||||
|
||||
# 일반 차량은 모두 유료
|
||||
assert normal_car_access["images"] is False
|
||||
assert normal_car_access["performance_check"] is False
|
||||
|
||||
|
||||
class TestCCPaymentIntegration:
|
||||
"""CC 결제 통합 테스트"""
|
||||
|
||||
def test_performance_check_costs_01_cc(self):
|
||||
"""성능점검표 비용은 0.1 CC"""
|
||||
PERFORMANCE_CHECK_COST = 0.1
|
||||
|
||||
user_balance = 5.0 # 신규 가입 시 지급되는 CC
|
||||
|
||||
# 결제 후 잔액
|
||||
after_payment = user_balance - PERFORMANCE_CHECK_COST
|
||||
|
||||
assert after_payment == 4.9
|
||||
|
||||
def test_full_car_view_costs_1_cc(self):
|
||||
"""차량 전체 보기 비용은 1 CC"""
|
||||
FULL_CAR_VIEW_COST = 1.0
|
||||
|
||||
user_balance = 5.0
|
||||
|
||||
# 결제 후 잔액
|
||||
after_payment = user_balance - FULL_CAR_VIEW_COST
|
||||
|
||||
assert after_payment == 4.0
|
||||
|
||||
def test_full_car_view_includes_performance_check(self):
|
||||
"""1 CC 결제 시 성능점검표 포함"""
|
||||
has_full_car_view = True
|
||||
has_separate_perf_view = False
|
||||
|
||||
# 1 CC 결제하면 성능점검표 접근 가능
|
||||
can_access_perf_check = has_full_car_view or has_separate_perf_view
|
||||
|
||||
assert can_access_perf_check is True
|
||||
|
||||
def test_insufficient_balance(self):
|
||||
"""잔액 부족 시 결제 실패"""
|
||||
user_balance = 0.05
|
||||
PERFORMANCE_CHECK_COST = 0.1
|
||||
|
||||
can_purchase = user_balance >= PERFORMANCE_CHECK_COST
|
||||
|
||||
assert can_purchase is False
|
||||
|
||||
|
||||
# 테스트 매트릭스 문서화
|
||||
ACCESS_MATRIX = """
|
||||
+------------------+-------------+--------------------+------------------+
|
||||
| 사용자 상태 | 배너 차량 | 일반 차량 | 성능점검표 |
|
||||
+------------------+-------------+--------------------+------------------+
|
||||
| 비로그인 | 이미지 O | 이미지 X (블러) | X |
|
||||
| 로그인 (미결제) | 이미지 O | 이미지 X (블러) | X |
|
||||
| 로그인 (0.1CC) | 이미지 O | 이미지 X (블러) | O |
|
||||
| 로그인 (1CC) | 이미지 O | 이미지 O | O |
|
||||
| 관리자 | 이미지 O | 이미지 O | O |
|
||||
+------------------+-------------+--------------------+------------------+
|
||||
|
||||
* 배너 차량: 이미지와 연락처만 무료, 성능점검표는 0.1 CC 필요
|
||||
* 1 CC 결제 시 성능점검표 포함
|
||||
"""
|
||||
216
backend/tests/test_performance_check.py
Normal file
216
backend/tests/test_performance_check.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Performance Check Access Unit Tests
|
||||
|
||||
비즈니스 로직:
|
||||
- 성능점검표는 항상 0.1 CC 결제 필요 (배너 차량 포함)
|
||||
- 1 CC 차량 전체 구매 시 성능점검표 포함
|
||||
- 관리자는 무조건 접근 가능
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
class TestPerformanceCheckAccess:
|
||||
"""성능점검표 접근 권한 테스트"""
|
||||
|
||||
def test_admin_has_access(self):
|
||||
"""관리자는 성능점검표에 무조건 접근 가능"""
|
||||
# Given
|
||||
user = MagicMock()
|
||||
user.is_admin = True
|
||||
user.id = 1
|
||||
|
||||
# When
|
||||
has_access = self._check_access(user, car_id=1, has_perf_view=False, has_car_view=False)
|
||||
|
||||
# Then
|
||||
assert has_access is True
|
||||
|
||||
def test_user_with_performance_check_purchase(self):
|
||||
"""0.1 CC 결제한 사용자는 접근 가능"""
|
||||
# Given
|
||||
user = MagicMock()
|
||||
user.is_admin = False
|
||||
user.id = 1
|
||||
|
||||
# When
|
||||
has_access = self._check_access(user, car_id=1, has_perf_view=True, has_car_view=False)
|
||||
|
||||
# Then
|
||||
assert has_access is True
|
||||
|
||||
def test_user_with_full_car_view_purchase(self):
|
||||
"""1 CC 결제한 사용자는 접근 가능 (성능점검표 포함)"""
|
||||
# Given
|
||||
user = MagicMock()
|
||||
user.is_admin = False
|
||||
user.id = 1
|
||||
|
||||
# When
|
||||
has_access = self._check_access(user, car_id=1, has_perf_view=False, has_car_view=True)
|
||||
|
||||
# Then
|
||||
assert has_access is True
|
||||
|
||||
def test_user_without_purchase(self):
|
||||
"""결제하지 않은 사용자는 접근 불가"""
|
||||
# Given
|
||||
user = MagicMock()
|
||||
user.is_admin = False
|
||||
user.id = 1
|
||||
|
||||
# When
|
||||
has_access = self._check_access(user, car_id=1, has_perf_view=False, has_car_view=False)
|
||||
|
||||
# Then
|
||||
assert has_access is False
|
||||
|
||||
def test_anonymous_user(self):
|
||||
"""비로그인 사용자는 접근 불가"""
|
||||
# Given
|
||||
user = None
|
||||
|
||||
# When
|
||||
has_access = self._check_access(user, car_id=1, has_perf_view=False, has_car_view=False)
|
||||
|
||||
# Then
|
||||
assert has_access is False
|
||||
|
||||
def test_banner_car_still_requires_payment(self):
|
||||
"""배너 차량이어도 성능점검표는 결제 필요"""
|
||||
# Given
|
||||
user = MagicMock()
|
||||
user.is_admin = False
|
||||
user.id = 1
|
||||
is_banner_car = True # 배너 차량
|
||||
|
||||
# When - 결제하지 않은 경우
|
||||
has_access = self._check_access(
|
||||
user, car_id=1,
|
||||
has_perf_view=False,
|
||||
has_car_view=False,
|
||||
is_banner_car=is_banner_car
|
||||
)
|
||||
|
||||
# Then - 배너 차량이어도 접근 불가
|
||||
assert has_access is False
|
||||
|
||||
def test_banner_car_with_payment(self):
|
||||
"""배너 차량 + 0.1 CC 결제 시 접근 가능"""
|
||||
# Given
|
||||
user = MagicMock()
|
||||
user.is_admin = False
|
||||
user.id = 1
|
||||
is_banner_car = True
|
||||
|
||||
# When
|
||||
has_access = self._check_access(
|
||||
user, car_id=1,
|
||||
has_perf_view=True,
|
||||
has_car_view=False,
|
||||
is_banner_car=is_banner_car
|
||||
)
|
||||
|
||||
# Then
|
||||
assert has_access is True
|
||||
|
||||
def _check_access(
|
||||
self,
|
||||
user,
|
||||
car_id: int,
|
||||
has_perf_view: bool,
|
||||
has_car_view: bool,
|
||||
is_banner_car: bool = False
|
||||
) -> bool:
|
||||
"""
|
||||
성능점검표 접근 권한 확인 로직
|
||||
|
||||
Note: 이 로직은 backend/app/api/carmodoo.py의
|
||||
get_car_performance_check 함수와 동일해야 함
|
||||
"""
|
||||
has_access = False
|
||||
|
||||
# 배너 차량 여부는 성능점검표 접근에 영향 없음
|
||||
# (이미지와 연락처만 무료, 성능점검표는 유료)
|
||||
|
||||
if user:
|
||||
if user.is_admin:
|
||||
has_access = True
|
||||
else:
|
||||
# 0.1 CC 결제 또는 1 CC 결제
|
||||
has_access = has_perf_view or has_car_view
|
||||
|
||||
return has_access
|
||||
|
||||
|
||||
class TestPerformanceCheckPreview:
|
||||
"""성능점검표 미리보기 테스트"""
|
||||
|
||||
def test_preview_available_without_payment(self):
|
||||
"""결제 전에도 미리보기 정보 제공"""
|
||||
# 미리보기에 포함되는 정보
|
||||
preview_fields = ['check_number', 'check_date', 'mileage']
|
||||
|
||||
# 미리보기에 포함되지 않는 정보
|
||||
full_access_fields = [
|
||||
'is_flood_damaged',
|
||||
'is_fire_damaged',
|
||||
'device_status',
|
||||
'body_parts',
|
||||
'frame_parts',
|
||||
'pdf_path'
|
||||
]
|
||||
|
||||
# 미리보기 응답 구조 확인
|
||||
preview_response = {
|
||||
"car_id": 1,
|
||||
"found": True,
|
||||
"has_access": False,
|
||||
"preview": {
|
||||
"check_number": "A123456",
|
||||
"check_date": "2024-01-01",
|
||||
"mileage": 50000
|
||||
},
|
||||
"data": None # 결제 전에는 null
|
||||
}
|
||||
|
||||
assert preview_response["has_access"] is False
|
||||
assert preview_response["data"] is None
|
||||
for field in preview_fields:
|
||||
assert field in preview_response["preview"]
|
||||
|
||||
|
||||
class TestBannerCarImageAccess:
|
||||
"""배너 차량 이미지 접근 테스트 (참고용 - 성능점검표와 별개)"""
|
||||
|
||||
def test_banner_car_images_are_free(self):
|
||||
"""배너 차량 이미지는 무료"""
|
||||
is_banner_car = True
|
||||
user = None # 비로그인
|
||||
|
||||
# 배너 차량 이미지 접근 가능 여부
|
||||
can_view_images = is_banner_car # 배너면 무조건 가능
|
||||
|
||||
assert can_view_images is True
|
||||
|
||||
def test_banner_car_contact_is_free(self):
|
||||
"""배너 차량 연락처는 무료"""
|
||||
is_banner_car = True
|
||||
user = None # 비로그인
|
||||
|
||||
# 배너 차량 연락처 접근 가능 여부
|
||||
can_view_contact = is_banner_car # 배너면 무조건 가능
|
||||
|
||||
assert can_view_contact is True
|
||||
|
||||
def test_non_banner_car_images_require_payment(self):
|
||||
"""비배너 차량 이미지는 유료"""
|
||||
is_banner_car = False
|
||||
has_paid = False
|
||||
|
||||
# 비배너 차량 이미지 접근
|
||||
can_view_images = has_paid
|
||||
|
||||
assert can_view_images is False
|
||||
Reference in New Issue
Block a user