"""견적 생성 — item×supplier 조합마다 세션이 생기고, 목표가가 산정되는지 검증. 기존 test_features.test_quotation_create 는 item/supplier 없이 '세션 0건' 경로만 본다. 여기선 상품(인터넷최저가)을 시드해 세션 생성 + 목표가 계산(신규=인터넷최저가×(1−수수료))까지 본다. 서비스(create_quotation)를 직접 호출한다 — HTTP/auth 경로(현재 /v1/auth/create 미존재)를 안 타고 생성 로직만 격리. """ import uuid from datetime import datetime from sqlalchemy import text from common.enums import QuotationType from crud.quotation_crud import QuotationCRUD from router.v1.quotation.protocol import Req_CreateQuotation from services.quotation_service import QuotationService FUTURE = datetime(2999, 1, 1) # 마감시각 미래 — 생성 직후 크론에 안 잡히게 async def test_create_builds_sessions_with_target_price(db_engine, company_id): """검증: 신규견적을 상품2×공급사2로 생성. 기대결과: success=True, 세션 4개, 각 목표가 = int(인터넷최저가 × (1−0.078)).""" item1 = await _seed_item(db_engine, company_id, internet_lowest=100_000) item2 = await _seed_item(db_engine, company_id, internet_lowest=50_000) suppliers = [uuid.uuid4(), uuid.uuid4()] req = Req_CreateQuotation( qt_setting_id=uuid.uuid4(), # FK 미설정 — 세팅 없으면 율 0(신규는 인터넷최저가만 쓰므로 무관) name="신규견적A", type=QuotationType.NEW_QUOTE.value, end_time=FUTURE, item_ids=[item1, item2], supplier_ids=suppliers, ) res = await _service().create_quotation(str(uuid.uuid4()), req) assert res.result.success is True assert res.session_count == 4 # 상품 2 × 공급사 2 fee = QuotationService.INTERNET_AVERAGE_FEE expected = {item1: int(100_000 * (1 - fee)), item2: int(50_000 * (1 - fee))} rows = await _session_target_prices(db_engine, res.qt_id) assert len(rows) == 4 for item_id, target_price in rows: assert target_price == expected[item_id] # 상품별 목표가가 공급사 수만큼 동일 async def test_create_without_price_fails(db_engine, company_id): """검증: 가격 후보(인터넷최저가·md 등)가 전무한 상품으로 견적 생성. 기대결과: 목표가 산정 불가로 success=False, 세션 0건(미생성).""" item = await _seed_item(db_engine, company_id, internet_lowest=None) req = Req_CreateQuotation( qt_setting_id=uuid.uuid4(), name="가격없음", type=QuotationType.NEW_QUOTE.value, end_time=FUTURE, item_ids=[item], supplier_ids=[uuid.uuid4()], ) res = await _service().create_quotation(str(uuid.uuid4()), req) assert res.result.success is False # QUOTATION_TARGET_PRICE_UNAVAILABLE rows = await _session_target_prices(db_engine, res.qt_id) if res.qt_id else [] assert rows == [] # ===== 헬퍼 (위 테스트들이 쓰는 도우미) ===== def _service(): return QuotationService(QuotationCRUD()) async def _seed_item(engine, company_id, *, internet_lowest): """상품 1건 시드(인터넷최저가만). category_type·internet_lowest_price_yn 은 NOT NULL — ORM default 는 raw INSERT 에 안 먹으므로 명시한다(conftest companies.status 와 같은 이유).""" item_id = uuid.uuid4() async with engine.begin() as conn: await conn.execute( text( "INSERT INTO items " "(item_id, company_id, user_id, name, category_type, " " internet_lowest_price_yn, internet_lowest_price) VALUES " "(:item_id, :company_id, :user_id, '상품', 1, false, :ilp)" ), {"item_id": item_id, "company_id": uuid.UUID(company_id), "user_id": uuid.uuid4(), "ilp": internet_lowest}, ) return item_id async def _session_target_prices(engine, qt_id): """생성된 견적의 (item_id -> target_price) 매핑.""" async with engine.begin() as conn: rows = (await conn.execute( text("SELECT item_id, target_price FROM sessions WHERE quotation_id = :qt"), {"qt": qt_id}, )).all() return rows