## 업종 교체 (tour → clinic) PlaceCategory 코드 4번의 의미를 바꾼다. 아직 배포 전이라 데이터 마이그레이션은 없다. - category_schema: tour_activity.json → clinic.json. 체험 스키마(안전 유의사항·우천 시 운영·준비물)를 진료 스키마(진료과목·의료진·상담료·보험 적용·야간/주말진료)로 바꿨다. unit 은 프로그램 → 시술이다(마취 방식·회복 기간·권장 횟수·시술 후 주의사항). - 소개문 계열만 allow_llm 이다. 시술 효과·비용 같은 값은 LLM 이 못 쓴다 — 이 레포의 "검증 전에는 발행 금지" 규칙이 의료 문구에서 특히 중요하다. - jsonld: TouristAttraction → MedicalClinic. 프론트 AeoReadiness 의 같은 표도 맞췄다. - 색 팔레트를 병원 톤(클린 블루·세이지·누드·모노)으로, 아이콘을 Compass → Stethoscope 로. - mock_adapter 목데이터를 시술 기준으로 교체. 스키마에 없는 key 를 쓰면 수집이 죽는다. - site_payload 의 기본 섹션표를 에디터(industryData)와 같게 맞췄다 — test_site_theme 이 이 둘을 대조한다. ## 로그인 관문 되돌리기 (b94daa9·d6a6c8e revert) 두 커밋이 /builder 를 통째로 RequireAuth 뒤로 옮겨 `/` 가 곧바로 로그인 화면이 됐다. `/` 는 자기 화면 없이 /builder 로 넘기기만 하므로, 문 앞 가드는 곧 루트 가드다. 위저드를 열어 두고 에디터 진입에서 한 번 받는969fb67설계로 되돌린다.d6a6c8e가 스스로 "969fb67 과 정면으로 다른 설계"라고 적어 두었다. ## 그 밖 - test_site_theme 의 경로가 solution/front 로 남아 있었다(frontend 개명 누락). - .dockerignore: 이 머신에 buildx 가 없어 레거시 빌더가 돌고, 그러면 nginx/Dockerfile.dockerignore 가 무시된다. 루트 것 하나로 두 이미지를 다 커버한다. 검증: frontend·admin·site lint·build 0. 백엔드 534 passed / 4 failed — 그 4개(test_build_publish 3 · test_snapshot 1)는 이 변경 전부터 실패하던 것이다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xa8ME5FQJy4VA8pPokTo1a
79 lines
3.9 KiB
Python
79 lines
3.9 KiB
Python
"""업종 스키마 — 4개 업종 파일이 규약대로 로드되는지.
|
|
|
|
업종 추가 = resources/ 에 JSON 1개 + PlaceCategory 코드 1줄. 이 테스트는 그 규약이 깨지면 잡는다.
|
|
"""
|
|
import pytest
|
|
|
|
from common.category_schema import CategorySchemaError, all_schemas, get_schema, is_valid_key
|
|
from common.enums import PlaceCategory
|
|
|
|
|
|
def test_all_categories_have_schema():
|
|
"""검증: PlaceCategory 의 모든 업종에 스키마 파일이 있는지.
|
|
기대결과: 4개 업종(숙박·카페·음식점·피부과·성형외과)이 모두 로드되고 code 가 1:1로 맞는다."""
|
|
schemas = all_schemas()
|
|
assert set(schemas) == {c.value for c in PlaceCategory}
|
|
for code, schema in schemas.items():
|
|
assert schema.code == code
|
|
assert schema.category == PlaceCategory(code)
|
|
assert schema.fields, f"{schema.name}: 필드가 비었다"
|
|
|
|
|
|
def test_keys_unique_within_category():
|
|
"""검증: 업종 안에서 fact key 가 유일한지.
|
|
기대결과: 중복 없음 (facts 의 (place,unit,key) 유니크가 의미를 가지려면 필수)."""
|
|
for schema in all_schemas().values():
|
|
keys = list(schema.fields)
|
|
assert len(keys) == len(set(keys)), f"{schema.name}: key 중복"
|
|
|
|
|
|
def test_place_and_unit_scopes_exist():
|
|
"""검증: 업종마다 place 스코프 필드가 있는지.
|
|
기대결과: 전 업종에 place 필드 존재. unit 필드는 업종별로 있을 수도 없을 수도 있다."""
|
|
for schema in all_schemas().values():
|
|
assert schema.keys_by_scope("place"), f"{schema.name}: place 스코프 필드가 없다"
|
|
|
|
|
|
def test_lodging_has_claim_critical_fields():
|
|
"""검증: 숙박 업종에 예약 클레임 직결 항목이 critical 로 잡혀 있는지.
|
|
기대결과: 체크인·체크아웃·취사·반려동물·취소규정이 모두 critical=True 이고 LLM 이 못 쓴다."""
|
|
schema = get_schema(PlaceCategory.LODGING)
|
|
for key in ("check_in_time", "check_out_time", "cooking_allowed", "pet_allowed", "cancel_policy"):
|
|
spec = schema.get(key)
|
|
assert spec is not None, f"숙박 스키마에 {key} 가 없다"
|
|
assert spec.critical is True, f"{key} 는 critical 이어야 한다"
|
|
assert spec.allow_llm is False, f"{key} 는 LLM 이 값을 만들면 안 된다"
|
|
|
|
|
|
def test_llm_writable_fields_are_sentences_only():
|
|
"""검증: LLM 이 값을 만들 수 있는 필드가 '문장' 필드뿐인지 (절대규칙 7 — LLM 은 사실을 만들지 않는다).
|
|
기대결과: allow_llm=True 인 필드는 전부 type=text 이고 critical 이 아니다."""
|
|
for schema in all_schemas().values():
|
|
for key in schema.llm_writable_keys():
|
|
spec = schema.get(key)
|
|
assert spec.type == "text", f"{schema.name}.{key}: LLM 이 쓸 수 있는 필드는 text 여야 한다"
|
|
assert spec.critical is False, f"{schema.name}.{key}: critical 필드를 LLM 이 쓰면 안 된다"
|
|
|
|
|
|
def test_required_fields_are_never_llm_written():
|
|
"""검증: 발행 필수 항목을 LLM 이 채우지 못하는지.
|
|
기대결과: required=True 인 필드는 전부 allow_llm=False."""
|
|
for schema in all_schemas().values():
|
|
for key in schema.required_keys():
|
|
assert schema.get(key).allow_llm is False, f"{schema.name}.{key}: 필수 항목을 LLM 이 채우면 안 된다"
|
|
|
|
|
|
def test_is_valid_key_rejects_cross_category_key():
|
|
"""검증: 업종에 없는 key 를 걸러내는지 (facts 쓰기 전 FACT_INVALID_KEY 판정).
|
|
기대결과: 숙박의 check_in_time 은 카페 스키마에서 거부된다."""
|
|
assert is_valid_key(PlaceCategory.LODGING, "check_in_time") is True
|
|
assert is_valid_key(PlaceCategory.CAFE, "check_in_time") is False
|
|
assert is_valid_key(PlaceCategory.CAFE, "break_time") is True
|
|
|
|
|
|
def test_unknown_category_raises():
|
|
"""검증: 지원하지 않는 업종 코드 조회.
|
|
기대결과: CategorySchemaError."""
|
|
with pytest.raises(CategorySchemaError):
|
|
get_schema(99)
|