# IMK 요구사항 테스트 커버리지 (2026-07-24 실행) IMK 요청 표(14~23) 항목별 **자동 테스트가 실제로 걸려 있는지**와, 이번에 돌린 실행 플로우를 정리한다. 결론부터: **자동 테스트가 있는 IMK 항목은 #15(공급사 재협상) 하나뿐**. 나머지는 UI·라벨·엑셀·스크립트 변경이라 전 스위트(회귀 안전망)만 통과할 뿐 항목 전용 테스트는 없다. ## 1. 실행한 테스트 스위트 | 스위트 | 실행 커맨드 | DB | 결과 | |---|---|---|---| | negodata 전체 | `APP_ENV=test .venv/bin/python -m pytest tests/` | 격리 test DB(negosium_test_db, 세션마다 재생성) | **83 passed / 1 failed** | | 포털(negosium) 전체 | 컨테이너 `APP_ENV=local pytest tests/` | dev DB(negosium_db), 테스트가 자기 행만 시드/정리(비파괴) | **71 passed** | | negodata #15 심사 | `pytest tests/test_renegotiation.py` | 격리 test DB | **9 passed** | | 포털 #15 요청/철회 | 컨테이너 `pytest tests/test_renegotiation.py` | dev DB(자기정리, 잔여 0 확인) | **6 passed** | - negodata 1 실패 = `test_scheduler::test_scheduler_registers_both_jobs` — 스케줄러에 `sync_lps_results` 잡이 새로 추가됐는데 단언을 안 고친 **stale 테스트**. IMK 항목과 무관. - 포털은 로컬 venv가 없어 컨테이너에 pytest 임시 설치 후 실행. dev DB지만 `PYTESTRENEGO-`/`PYTESTNEGO-` 프리픽스로 자기 행만 지운다. ## 2. IMK 표(14~23) 항목별 커버리지 | # | 요구사항 | 자동 테스트 | 근거/플로우 | |---|---|---|---| | 14 | 발주배수 필드 추가 | ❌ 없음 | companies.settings 커스텀 필드. 전용 테스트 없음 | | **15** | **공급사 재협상 요청 + 승인 화면** | ✅ **있음(15건)** | 포털 요청/철회 6 + negodata 심사 9. 아래 3절 | | 16 | 통계 인상 억제율 지표 | ❌ 없음 | 통계 파생집계, 전용 테스트 없음 | | 17 | 협력사 분류카테고리 추가 | ❌ 없음(간접만) | `test_quotation_create/anchoring`이 category 값을 쓰지만 분류 기능 자체 검증 아님 | | 18 | 배송리드타임 → 표준납기일 | ❌ 없음 | 라벨 문자열 변경, 테스트 대상 아님 | | 19 | 협력사 화면 구분 영역 삭제 | ❌ 없음 | 프론트 UI 제거, 테스트 대상 아님 | | 20 | 실적(계약) 공급사 컬럼 추가 | ❌ 없음 | 상품 컬럼 추가, 전용 테스트 없음 | | 21 | 스크립트 변수명 노출(internet_lowest_price) | ❌ 없음 | agent 스크립트 렌더 수정, 전용 테스트 없음 | | 22 | 협상카드 일괄 선택 | ❌ 없음 | 프론트 UI, 테스트 대상 아님 | | 23 | 상품 업로드 양식 수정 | ❌ 없음 | 엑셀 양식/컬럼, 전용 테스트 없음 | 정리: **10개 중 자동 테스트 보유는 1개(#15)**. 나머지 9개는 성격상(UI/라벨/엑셀/스크립트) 단위테스트 대상이 아니거나 아직 미작성 → 사람이 화면에서 확인해야 함. ## 3. #15 재협상 — 테스트별 검증 플로우 ### 포털(negosium) — 요청/철회 (`backend/tests/test_renegotiation.py`, e2e HTTP) 개찰(OPEN_*) 마감 + 본인 마지막 라운드 세션을 시드하고 실제 로그인 → 엔드포인트 호출로 검증. | 테스트 | 플로우 | 기대 | |---|---|---| | request_records_pending | 개찰건에 `POST .../renegotiation` | success + custom.renegotiation=PENDING(사유·희망가) + 담당자 알림 1건 | | request_twice_blocked | 같은 세션에 요청 2회 | 2번째 거부, PENDING 1건 유지 | | request_blocked_on_awarded | 낙찰(AWARDED)건에 요청 | 거부 + 미기록 | | request_forbidden_other_supplier | 남의 공급사 세션에 요청 | 거부 + 미기록 | | cancel_sets_canceled_and_allows_rerequest | 요청 후 `DELETE` 철회 → 재요청 | CANCELED → 재요청 시 PENDING | | cancel_requires_pending | 대기 요청 없는데 철회 | 거부 | ### negodata — 심사(승인/반려) (`negodata/backend/tests/test_renegotiation.py`, 서비스 단위) 승인이 호출하는 `regenerate_quotation`(견적 풀체인)은 스텁으로 격리하고 #15 고유 계약만 검증. | 테스트 | 플로우 | 기대 | |---|---|---| | list_scoped_to_company | 내 회사·남의 회사 요청 각 1건 → 목록 | 내 회사 건만(total=1) | | list_filters_by_status | 대기·반려 각 1건 → status=1 필터 | 대기 건만 | | approve_transitions_and_persists | 대기건 승인(재생성 성공 스텁) | APPROVED + next_quotation_id + memo 박제, 요청 공급사 포함 호출 | | approve_includes_extra_suppliers | 승인 시 추가 공급사 지정 | 재생성 공급사 = 요청자 ∪ 추가(중복 제거) | | approve_blocks_when_not_pending | 이미 승인된 건 재승인 | 거부(INVALID) + 재생성 미호출 | | approve_keeps_pending_when_regenerate_fails | 재생성 실패 스텁 | 실패 반환 + 상태 PENDING 유지 | | approve_other_company_not_found | 남의 회사 세션 승인(IDOR) | NOT_FOUND + 재생성 미호출 | | reject_transitions_and_saves_memo | 대기건 반려(사유) | REJECTED + 반려사유 저장 | | reject_blocks_when_not_pending | 이미 반려된 건 재반려 | 거부(INVALID) | ## 4. 미커버 항목에 대한 권고 - #14/#16/#17/#20/#23(데이터·집계·엑셀)은 서비스 단위 테스트를 붙일 수 있음 — 필요 시 작성. - #18/#19/#22(라벨·UI)와 #21(스크립트 렌더)은 화면·실협상으로 확인하는 게 맞음. - #15는 "승인 → 실제 다음 라운드 견적이 올바른 상품/카드로 생성되는지"는 스텁으로 끊었으므로, 실 데이터 승인 1회로 최종 확인 필요.