blob: fa2889f9f2d3f868364804dba64002540b796cb7 [file]
import json
import os
import pathlib
import re
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
DATA = ROOT / "data" / "downloads" / "asf.json"
PARTIAL = ROOT / "layouts" / "_partials" / "asf-downloads.html"
PAGES = (
ROOT / "content" / "en" / "docs" / "download" / "download.md",
ROOT / "content" / "cn" / "docs" / "download" / "download.md",
)
I18N = (ROOT / "i18n" / "en.yaml", ROOT / "i18n" / "zh-CN.yaml")
PUBLIC_DIR = pathlib.Path(os.environ["DOWNLOAD_PUBLIC_DIR"]) if os.environ.get("DOWNLOAD_PUBLIC_DIR") else None
# Exact dist state, verified against
# https://downloads.apache.org/hugegraph/<version>/ on 2026-09-13. The data
# file must derive exactly these artifacts; a new release edits both the data
# file and this expectation with a fresh dist listing.
EXPECTED_ARTIFACTS = {
"1.7.0": {
"apache-hugegraph-incubating-1.7.0.tar.gz",
"apache-hugegraph-toolchain-incubating-1.7.0.tar.gz",
"apache-hugegraph-incubating-1.7.0-src.tar.gz",
"apache-hugegraph-toolchain-incubating-1.7.0-src.tar.gz",
"apache-hugegraph-ai-incubating-1.7.0-src.tar.gz",
"apache-hugegraph-computer-incubating-1.7.0-src.tar.gz",
},
"1.5.0": {
"apache-hugegraph-incubating-1.5.0.tar.gz",
"apache-hugegraph-toolchain-incubating-1.5.0.tar.gz",
"apache-hugegraph-incubating-1.5.0-src.tar.gz",
"apache-hugegraph-toolchain-incubating-1.5.0-src.tar.gz",
"apache-hugegraph-ai-incubating-1.5.0-src.tar.gz",
"apache-hugegraph-computer-incubating-1.5.0-src.tar.gz",
},
"1.3.0": {
"apache-hugegraph-incubating-1.3.0.tar.gz",
"apache-hugegraph-toolchain-incubating-1.3.0.tar.gz",
"apache-hugegraph-incubating-1.3.0-src.tar.gz",
"apache-hugegraph-toolchain-incubating-1.3.0-src.tar.gz",
"apache-hugegraph-ai-incubating-1.3.0-src.tar.gz",
"apache-hugegraph-commons-incubating-1.3.0-src.tar.gz",
},
"1.2.0": {
"apache-hugegraph-incubating-1.2.0.tar.gz",
"apache-hugegraph-toolchain-incubating-1.2.0.tar.gz",
"apache-hugegraph-incubating-1.2.0-src.tar.gz",
"apache-hugegraph-toolchain-incubating-1.2.0-src.tar.gz",
"apache-hugegraph-computer-incubating-1.2.0-src.tar.gz",
"apache-hugegraph-commons-incubating-1.2.0-src.tar.gz",
},
"1.0.0": {
"apache-hugegraph-incubating-1.0.0.tar.gz",
"apache-hugegraph-toolchain-incubating-1.0.0.tar.gz",
"apache-hugegraph-computer-incubating-1.0.0.tar.gz",
"apache-hugegraph-incubating-1.0.0-src.tar.gz",
"apache-hugegraph-toolchain-incubating-1.0.0-src.tar.gz",
"apache-hugegraph-computer-incubating-1.0.0-src.tar.gz",
"apache-hugegraph-commons-incubating-1.0.0-src.tar.gz",
},
}
# Site-owned labels only; ui_assets_download is inherited from OINK and checked
# in the rendered output below.
FIXED_I18N_KEYS = (
"download_release_version",
"download_release_date",
"download_release_notes",
"download_table_component",
"download_table_type",
"download_table_mirror",
"download_type_binary",
"download_type_source",
"download_asf_note",
)
def load_data() -> dict:
with DATA.open(encoding="utf-8") as handle:
return json.load(handle)
def derive_files(release: dict, components: dict) -> set[str]:
files = set()
infix = "-incubating" if release["incubating"] else ""
for kind, suffix in (("binary", ""), ("source", "-src")):
for component_id in release.get(kind, []):
prefix = components[component_id]["prefix"]
files.add(f"{prefix}{infix}-{release['version']}{suffix}.tar.gz")
return files
def i18n_keys(path: pathlib.Path) -> set[str]:
keys = set()
for line in path.read_text(encoding="utf-8").splitlines():
if line and not line.startswith(("#", " ")) and ":" in line:
keys.add(line.split(":", 1)[0].strip())
return keys
class DownloadDataTest(unittest.TestCase):
def setUp(self) -> None:
self.data = load_data()
def test_schema_and_release_ordering(self) -> None:
data = self.data
self.assertEqual(
set(data), {"$comment", "dist_path", "components", "releases"}
)
self.assertEqual(data["dist_path"], "hugegraph")
label_keys = [c["label_key"] for c in data["components"].values()]
self.assertEqual(len(label_keys), len(set(label_keys)))
for component_id, component in data["components"].items():
self.assertRegex(component_id, r"^[a-z][a-z0-9]*\Z")
self.assertRegex(component["prefix"], r"^apache-hugegraph(-[a-z]+)?\Z")
self.assertRegex(component["label_key"], r"^download_component_[a-z]+\Z")
releases = data["releases"]
versions = [release["version"] for release in releases]
self.assertEqual(versions, sorted(versions, key=lambda v: tuple(map(int, v.split("."))), reverse=True))
self.assertEqual(len(versions), len(set(versions)))
self.assertEqual(sum(1 for release in releases if release.get("latest")), 1)
self.assertTrue(releases[0].get("latest"), "the newest release must be the latest")
for release in releases:
self.assertRegex(release["version"], r"^\d+\.\d+\.\d+\Z")
self.assertRegex(release["date"], r"^\d{4}-\d{2}-\d{2}\Z")
self.assertIsInstance(release["incubating"], bool)
for kind in ("binary", "source"):
ids = release.get(kind, [])
self.assertTrue(ids, f"{release['version']} has no {kind} artifacts")
self.assertEqual(len(ids), len(set(ids)))
for component_id in ids:
self.assertIn(component_id, data["components"])
def test_derived_artifacts_match_the_verified_dist_listing(self) -> None:
data = self.data
derived = {
release["version"]: derive_files(release, data["components"])
for release in data["releases"]
}
self.assertEqual(derived, EXPECTED_ARTIFACTS)
total = sum(len(files) for files in derived.values())
self.assertEqual(total, 31)
def test_derived_urls_are_well_formed(self) -> None:
data = self.data
pattern = re.compile(r"^https://[a-z.]+/[A-Za-z0-9./?=_-]+$")
for release in data["releases"]:
for file in derive_files(release, data["components"]):
urls = (
f"https://www.apache.org/dyn/closer.lua/{data['dist_path']}/"
f"{release['version']}/{file}?action=download",
f"https://downloads.apache.org/{data['dist_path']}/"
f"{release['version']}/{file}.asc",
f"https://downloads.apache.org/{data['dist_path']}/"
f"{release['version']}/{file}.sha512",
)
for url in urls:
self.assertRegex(url, pattern)
def test_pages_render_from_data_not_hardcoded_tables(self) -> None:
for page in PAGES:
text = page.read_text(encoding="utf-8")
self.assertIn("{{< asf-downloads latest >}}", text, page)
self.assertIn("{{< asf-downloads archived >}}", text, page)
self.assertNotIn("closer.lua", text, page)
self.assertNotIn(".tar.gz", text, page)
artifact_links = re.findall(
r"downloads\.apache\.org/hugegraph/\d", text
)
self.assertEqual(artifact_links, [], page)
def test_partial_derives_urls_with_the_same_tokens(self) -> None:
# The partial re-derives the same filenames and URLs in Go templates;
# lock its literal format tokens to this module's derivation so the
# two cannot drift apart silently.
template = PARTIAL.read_text(encoding="utf-8")
for token in (
'"-incubating" ""',
'"-src" ""',
'"%s%s-%s%s.tar.gz" $component.prefix $infix $version $suffix',
'https://www.apache.org/dyn/closer.lua/%s/%s/%s?action=download',
'https://downloads.apache.org/%s/%s/%s.asc',
'https://downloads.apache.org/%s/%s/%s.sha512',
):
self.assertIn(token, template)
def test_i18n_catalogues_carry_every_label(self) -> None:
data = self.data
required = set(FIXED_I18N_KEYS)
for component in data["components"].values():
required.add(component["label_key"])
for catalogue in I18N:
missing = required - i18n_keys(catalogue)
self.assertEqual(missing, set(), catalogue)
def test_rendered_download_pages_have_verified_rows(self) -> None:
if PUBLIC_DIR is None:
self.skipTest("set DOWNLOAD_PUBLIC_DIR after an aggregate build")
for relative in ("docs/download/download/index.html", "cn/docs/download/download/index.html"):
page = PUBLIC_DIR / relative
self.assertTrue(page.is_file(), page)
text = page.read_text(encoding="utf-8")
self.assertIn("hg-asf-release", text, page)
labels = re.findall(r'aria-label="([^"<>]+): apache-hugegraph[^"<>]*"', text)
self.assertTrue(labels, page)
self.assertTrue(all(label.strip() and label != "ui_assets_download" for label in labels), page)
self.assertIn("1.7.0", text, page)
for version, expected_files in EXPECTED_ARTIFACTS.items():
self.assertIn(f"hugegraph-{version}-release-notes", text, page)
for filename in expected_files:
self.assertIn(filename, text, page)
self.assertIn(f"/dyn/closer.lua/hugegraph/{version}/{filename}?action=download", text, page)
self.assertIn(f"downloads.apache.org/hugegraph/{version}/{filename}.asc", text, page)
self.assertIn(f"downloads.apache.org/hugegraph/{version}/{filename}.sha512", text, page)
if __name__ == "__main__":
unittest.main()