test: guard v1.3.27 website launch (#3404)

This commit is contained in:
zhifu gao
2026-07-24 02:27:39 +08:00
committed by GitHub
parent 3a5fa06f0e
commit 0a0963647b
2 changed files with 140 additions and 4 deletions
+81 -4
View File
@@ -21,6 +21,12 @@ class PageContract:
visible_required: tuple[str, ...] = ()
@dataclass(frozen=True)
class StaticAssetContract:
signature: bytes
min_bytes: int = 128
PAGE_CONTRACTS: dict[str, PageContract] = {
f"{BASE_URL}/": PageContract(
required=(
@@ -172,6 +178,48 @@ PAGE_CONTRACTS: dict[str, PageContract] = {
),
forbidden=("funasr==1.3.10", "runtime-llamacpp-v0.1.1"),
),
f"{BASE_URL}/blog/funasr-v1-3-27-language-metadata-vllm-fallback.html": PageContract(
required=(
"funasr==1.3.27",
"/v1/audio/transcriptions",
"verbose_json.language",
'language":"en',
"AutoModel",
"runtime-llamacpp-v0.1.9",
"https://github.com/modelscope/FunASR/releases/tag/v1.3.27",
"https://github.com/modelscope/FunASR",
"https://github.com/FunAudioLLM/Fun-ASR",
"https://github.com/FunAudioLLM/SenseVoice",
"https://github.com/modelscope/FunClip",
"/donors.html",
),
forbidden=("funasr==1.3.26", "runtime-llamacpp-v0.1.1"),
),
f"{BASE_URL}/en/blog/funasr-v1-3-27-language-metadata-vllm-fallback.html": PageContract(
required=(
"funasr==1.3.27",
"/v1/audio/transcriptions",
"verbose_json.language",
'language":"en',
"AutoModel",
"runtime-llamacpp-v0.1.9",
"https://github.com/modelscope/FunASR/releases/tag/v1.3.27",
"https://github.com/modelscope/FunASR",
"https://github.com/FunAudioLLM/Fun-ASR",
"https://github.com/FunAudioLLM/SenseVoice",
"https://github.com/modelscope/FunClip",
"/en/donors.html",
),
forbidden=("funasr==1.3.26", "runtime-llamacpp-v0.1.1"),
),
}
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
STATIC_ASSET_CONTRACTS: dict[str, StaticAssetContract] = {
f"{BASE_URL}/img/banner.4f436d19.png": StaticAssetContract(PNG_SIGNATURE),
f"{BASE_URL}/logo.png": StaticAssetContract(PNG_SIGNATURE),
}
@@ -208,13 +256,23 @@ def validate_pages(pages: dict[str, str]) -> list[str]:
return failures
def _fetch_url(url: str, timeout: float, retries: int) -> str:
def validate_assets(assets: dict[str, bytes]) -> list[str]:
failures: list[str] = []
for url, contract in STATIC_ASSET_CONTRACTS.items():
body = assets.get(url)
if body is None:
failures.append(f"{url}: asset was not fetched")
elif not body.startswith(contract.signature) or len(body) < contract.min_bytes:
failures.append(f"{url}: response is not a valid PNG")
return failures
def _fetch_bytes(url: str, timeout: float, retries: int) -> bytes:
last_error: Exception | None = None
for attempt in range(retries + 1):
try:
with urllib.request.urlopen(url, timeout=timeout) as response:
body = response.read()
return body.decode("utf-8", errors="replace")
return response.read()
except Exception as exc:
last_error = exc
if attempt == retries:
@@ -223,6 +281,12 @@ def _fetch_url(url: str, timeout: float, retries: int) -> str:
raise last_error
def _fetch_url(url: str, timeout: float, retries: int) -> str:
return _fetch_bytes(url, timeout=timeout, retries=retries).decode(
"utf-8", errors="replace"
)
def fetch_pages(timeout: float, retries: int = 3) -> dict[str, str]:
pages: dict[str, str] = {}
for url in PAGE_CONTRACTS:
@@ -230,6 +294,13 @@ def fetch_pages(timeout: float, retries: int = 3) -> dict[str, str]:
return pages
def fetch_assets(timeout: float, retries: int = 3) -> dict[str, bytes]:
assets: dict[str, bytes] = {}
for url in STATIC_ASSET_CONTRACTS:
assets[url] = _fetch_bytes(url, timeout=timeout, retries=retries)
return assets
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--timeout", type=float, default=20.0)
@@ -237,13 +308,19 @@ def main(argv: list[str] | None = None) -> int:
args = parser.parse_args(argv)
failures = validate_pages(fetch_pages(timeout=args.timeout, retries=args.retries))
failures.extend(
validate_assets(fetch_assets(timeout=args.timeout, retries=args.retries))
)
if failures:
print("funasr.com static page contract failed:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
return 1
print(f"funasr.com static page contract passed for {len(PAGE_CONTRACTS)} pages")
print(
"funasr.com static contract passed for "
f"{len(PAGE_CONTRACTS)} pages and {len(STATIC_ASSET_CONTRACTS)} assets"
)
return 0
+59
View File
@@ -119,6 +119,34 @@ def test_website_contract_accepts_current_public_copy():
https://github.com/modelscope/FunClip
<a href="/en/donors.html">Thanks</a>
""",
"https://www.funasr.com/blog/funasr-v1-3-27-language-metadata-vllm-fallback.html": """
funasr==1.3.27
/v1/audio/transcriptions
verbose_json.language
language":"en
AutoModel
runtime-llamacpp-v0.1.9
https://github.com/modelscope/FunASR/releases/tag/v1.3.27
https://github.com/modelscope/FunASR
https://github.com/FunAudioLLM/Fun-ASR
https://github.com/FunAudioLLM/SenseVoice
https://github.com/modelscope/FunClip
<a href="/donors.html">功德榜</a>
""",
"https://www.funasr.com/en/blog/funasr-v1-3-27-language-metadata-vllm-fallback.html": """
funasr==1.3.27
/v1/audio/transcriptions
verbose_json.language
language":"en
AutoModel
runtime-llamacpp-v0.1.9
https://github.com/modelscope/FunASR/releases/tag/v1.3.27
https://github.com/modelscope/FunASR
https://github.com/FunAudioLLM/Fun-ASR
https://github.com/FunAudioLLM/SenseVoice
https://github.com/modelscope/FunClip
<a href="/en/donors.html">Thanks</a>
""",
}
assert checker.validate_pages(pages) == []
@@ -146,6 +174,37 @@ def test_website_contract_includes_v1326_launch_articles():
)
def test_website_contract_includes_v1327_launch_articles():
checker = _load_module()
assert (
"https://www.funasr.com/blog/funasr-v1-3-27-language-metadata-vllm-fallback.html"
in checker.PAGE_CONTRACTS
)
assert (
"https://www.funasr.com/en/blog/funasr-v1-3-27-language-metadata-vllm-fallback.html"
in checker.PAGE_CONTRACTS
)
def test_static_asset_contract_rejects_non_png_payloads():
checker = _load_module()
banner_url = "https://www.funasr.com/img/banner.4f436d19.png"
logo_url = "https://www.funasr.com/logo.png"
assert banner_url in checker.STATIC_ASSET_CONTRACTS
assert logo_url in checker.STATIC_ASSET_CONTRACTS
failures = checker.validate_assets(
{
banner_url: b"<html>not an image</html>",
logo_url: b"\x89PNG\r\n\x1a\n" + b"x" * 200,
}
)
assert failures == [f"{banner_url}: response is not a valid PNG"]
def test_website_contract_reports_stale_runtime_and_star_copy():
checker = _load_module()