"""B-4 — licensed_restricted 차단 술어 + watch 타깃 (material/jurisdiction/license) 매핑 순수 테스트. 차단 술어(_license_sql)는 retrieval 3-leg + digest 가 공유하는 단일 술어. 실제 제외 동작은 GPU 라이브(합성 restricted doc 검색 제외)로 검증 — 여기선 술어 형태 + 매핑 표 계약만. [[feedback_external_api_fixture_first]] / [[feedback_structural_integrity_over_path_discipline]] """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent / "app")) from services.search.license_filter import ( # noqa: E402 restricted_exclude_orm, restricted_exclude_sql, ) from services.search.retrieval_service import _license_sql # noqa: E402 from workers.file_watcher import _TARGET_AXIS # noqa: E402 def test_shared_predicate_single_source(): # retrieval/digest/briefing 가 같은 술어 정의를 공유 — drift 방지(단일 source 계약) assert _license_sql("d") == " AND " + restricted_exclude_sql("d") assert _license_sql("") == " AND " + restricted_exclude_sql("") assert restricted_exclude_sql("d").startswith("COALESCE(d.extract_meta") def test_restricted_exclude_orm_constructs(): # study 풀이(explanation_rag)용 ORM 표현 — 컴파일 SQL 이 raw 술어와 동일 구조인지 from sqlalchemy.dialects import postgresql clause = restricted_exclude_orm() sql = str(clause.compile(dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True})) assert "extract_meta" in sql assert "'license'" in sql and "'restricted'" in sql # JSONB 경로 키 assert "'false'" in sql and "'true'" in sql # COALESCE 기본 + 비교값 def test_license_sql_shape_with_alias(): sql = _license_sql("d") assert sql.startswith(" AND ") # 항상 ' AND ...' (WHERE 합성용) assert "COALESCE(d.extract_meta -> 'license' ->> 'restricted', 'false')" in sql assert "<> 'true'" in sql # restricted=true 만 제외 def test_license_sql_shape_no_alias(): # alias='' = 단일 FROM documents (컬럼 직접 참조) sql = _license_sql("") assert "COALESCE(extract_meta -> 'license' ->> 'restricted', 'false')" in sql assert ".extract_meta" not in sql # 점 없는 컬럼 직접 def test_axis_books_papers_are_restricted(): for folder, mt in (("Books", "book"), ("Papers_Purchased", "paper")): material, jur, lic = _TARGET_AXIS[folder] assert material == mt assert jur is None # 책/논문 = 관할 없음(A-2 paper NULL 강제와 정합) assert lic["scheme"] == "proprietary" assert lic["restricted"] is True # RAG/digest 차단 대상 assert lic["redistribute"] is False def test_axis_manuals_proprietary_but_not_restricted(): material, jur, lic = _TARGET_AXIS["Manuals"] assert material == "manual" assert lic["scheme"] == "proprietary" assert lic["restricted"] is False # 사용자 결정 2026-06-13 (검색·RAG 활용) def test_axis_kgs_law_kr_public_not_restricted(): material, jur, lic = _TARGET_AXIS["KGS_Code"] assert (material, jur) == ("law", "KR") assert lic["scheme"] == "kogl" assert lic["restricted"] is False # 법정 위임 공공 → 차단 아님 def test_axis_non_target_folder_yields_none(): # Inbox/Recordings 등 비대상 = (None, None, None) → material/license 미주입 assert _TARGET_AXIS.get("Inbox", (None, None, None)) == (None, None, None)