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:
AutonetSellCar Deploy
2025-12-30 13:24:39 +09:00
commit 1f0dcb1ddb
224 changed files with 55119 additions and 0 deletions

View File

@@ -0,0 +1 @@
# AutonetSellCar Backend Tests

View 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 결제 시 성능점검표 포함
"""

View 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