여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제 - 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다 - eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다 파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이). site·frontend·admin tsc, site vitest 105 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
"""발행 URL 의 색인 통보(IndexNow)."""
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from services import indexnow
|
|
|
|
SITEMAP = """<?xml version="1.0" encoding="UTF-8"?>
|
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
<url><loc>https://w4ai.o2o.kr/s/butter/</loc></url>
|
|
<url><loc>https://w4ai.o2o.kr/s/butter/faq</loc></url>
|
|
</urlset>
|
|
"""
|
|
|
|
|
|
def _out(tmp_path: Path, slug: str, body: str = SITEMAP) -> Path:
|
|
site = tmp_path / "s" / slug
|
|
site.mkdir(parents=True)
|
|
(site / "sitemap.xml").write_text(body)
|
|
return tmp_path
|
|
|
|
|
|
def test_키가_없으면_통보하지_않는다(monkeypatch):
|
|
monkeypatch.delenv("INDEXNOW_KEY", raising=False)
|
|
assert indexnow.is_configured() is False
|
|
|
|
|
|
def test_보낼_URL_은_사이트맵에서_읽는다(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("SITE_OUTPUT_DIR", str(_out(tmp_path, "butter")))
|
|
assert indexnow.site_urls("butter") == [
|
|
"https://w4ai.o2o.kr/s/butter/",
|
|
"https://w4ai.o2o.kr/s/butter/faq",
|
|
]
|
|
|
|
|
|
def test_사이트맵이_없으면_빈_목록(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("SITE_OUTPUT_DIR", str(tmp_path))
|
|
assert indexnow.site_urls("없는사이트") == []
|
|
|
|
|
|
def test_깨진_사이트맵은_예외를_올리지_않는다(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("SITE_OUTPUT_DIR", str(_out(tmp_path, "butter", "<urlset>깨짐")))
|
|
assert indexnow.site_urls("butter") == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_키파일_위치와_호스트를_URL에서_뽑는다(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("SITE_OUTPUT_DIR", str(_out(tmp_path, "butter")))
|
|
monkeypatch.setenv("INDEXNOW_KEY", "abc12345")
|
|
sent = {}
|
|
|
|
async def fake_post(self, url, json=None, **kwargs):
|
|
sent["url"] = url
|
|
sent["body"] = json
|
|
return httpx.Response(200, request=httpx.Request("POST", url))
|
|
|
|
monkeypatch.setattr(httpx.AsyncClient, "post", fake_post)
|
|
|
|
result = await indexnow.submit("butter")
|
|
|
|
assert sent["url"] == indexnow.ENDPOINT
|
|
assert sent["body"]["host"] == "w4ai.o2o.kr"
|
|
assert sent["body"]["key"] == "abc12345"
|
|
# 키 파일은 오리진 루트에 있다 — 사이트 경로(/s/butter) 아래가 아니다.
|
|
assert sent["body"]["keyLocation"] == "https://w4ai.o2o.kr/abc12345.txt"
|
|
assert sent["body"]["urlList"] == [
|
|
"https://w4ai.o2o.kr/s/butter/",
|
|
"https://w4ai.o2o.kr/s/butter/faq",
|
|
]
|
|
assert result == {"ok": True, "status": 200, "urls": 2}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_거절되어도_예외를_올리지_않는다(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("SITE_OUTPUT_DIR", str(_out(tmp_path, "butter")))
|
|
monkeypatch.setenv("INDEXNOW_KEY", "abc12345")
|
|
|
|
async def fake_post(self, url, json=None, **kwargs):
|
|
# 403 = 키 파일 대조 실패.
|
|
return httpx.Response(403, text="key not found", request=httpx.Request("POST", url))
|
|
|
|
monkeypatch.setattr(httpx.AsyncClient, "post", fake_post)
|
|
|
|
assert await indexnow.submit("butter") == {"ok": False, "status": 403, "urls": 2}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_네트워크_실패도_삼킨다(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("SITE_OUTPUT_DIR", str(_out(tmp_path, "butter")))
|
|
monkeypatch.setenv("INDEXNOW_KEY", "abc12345")
|
|
|
|
async def fake_post(self, url, json=None, **kwargs):
|
|
raise httpx.ConnectTimeout("timed out")
|
|
|
|
monkeypatch.setattr(httpx.AsyncClient, "post", fake_post)
|
|
|
|
result = await indexnow.submit("butter")
|
|
assert result["ok"] is False
|
|
assert "ConnectTimeout" in result["error"]
|