Files
OpenShell/tasks/scripts/check_docs_nav_test.py
Johnny Greco 87929ad130 docs: keep page URLs aligned with file paths (#3713)
* docs: align page file names and nav labels with published URLs

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

* docs: redirect moved dev pages and fix agent guide redirects

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

* ci(docs): check that page URLs match their file paths

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

* fix(docs): align navigation checks with repo conventions

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

* docs: omit historical redirect aliases

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

* fix(docs): preserve published overview and TypeScript URLs

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

* fix(docs): redirect unversioned overview and TypeScript URLs

Signed-off-by: Johnny Greco <jogreco@nvidia.com>

---------

Signed-off-by: Johnny Greco <jogreco@nvidia.com>
2026-09-28 02:08:54 +00:00

243 lines
6.3 KiB
Python

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import textwrap
from typing import TYPE_CHECKING
import check_docs_nav
import pytest
if TYPE_CHECKING:
from pathlib import Path
FERN_CONFIG = """\
instances:
- url: example.docs.buildwithfern.com/openshell
versions:
- display-name: Dev
path: ../docs/index.yml
slug: dev
"""
def page(title: str, **extra: str) -> str:
lines = (
["---", f'title: "{title}"']
+ [f'{k}: "{v}"' for k, v in extra.items()]
+ ["---", ""]
)
return "\n".join(lines)
def build(tmp_path: Path, nav: str, pages: dict[str, str], redirects: str = "") -> Path:
docs = tmp_path / "docs"
for rel, content in pages.items():
(docs / rel).parent.mkdir(parents=True, exist_ok=True)
(docs / rel).write_text(content)
(docs / "index.yml").write_text(textwrap.dedent(nav))
(tmp_path / "fern").mkdir()
(tmp_path / "fern" / "docs.yml").write_text(
FERN_CONFIG + textwrap.dedent(redirects)
)
return tmp_path
def test_fern_slug_splits_camel_case() -> None:
assert check_docs_nav.fern_slug("TypeScript") == "type-script"
assert check_docs_nav.fern_slug("Why OpenShell") == "why-open-shell"
assert check_docs_nav.fern_slug("API Errors") == "api-errors"
assert check_docs_nav.fern_slug("0.1.0") == "0-1-0"
def test_matching_nav_passes(tmp_path: Path) -> None:
root = build(
tmp_path,
"""
navigation:
- section: "Guides"
slug: guides
path: guides/index.mdx
contents:
- page: "Setup"
path: guides/setup.mdx
- page: "Policy Prover"
path: guides/prover.mdx
slug: prover
""",
{
"guides/index.mdx": page("Guides"),
"guides/setup.mdx": page("Set Up", **{"sidebar-title": "Setup"}),
"guides/prover.mdx": page("Policy Prover"),
},
)
assert check_docs_nav.run(root) == []
def test_label_url_that_differs_from_file_path_fails(tmp_path: Path) -> None:
root = build(
tmp_path,
"""
navigation:
- section: "SDK"
slug: sdk
contents:
- page: "TypeScript"
path: sdk/typescript.mdx
""",
{"sdk/typescript.mdx": page("TypeScript")},
)
issues = check_docs_nav.run(root)
assert len(issues) == 1
assert "URL /sdk/type-script" in issues[0]
assert "relative slug in docs/index.yml" in issues[0]
def test_frontmatter_slug_can_pin_the_url_to_the_file_path(tmp_path: Path) -> None:
root = build(
tmp_path,
"""
navigation:
- section: "SDK"
slug: sdk
contents:
- page: "TypeScript"
path: sdk/typescript.mdx
""",
{"sdk/typescript.mdx": page("TypeScript", slug="sdk/typescript")},
)
assert check_docs_nav.run(root) == []
def test_sidebar_name_must_match_nav_label(tmp_path: Path) -> None:
root = build(
tmp_path,
"""
navigation:
- section: "About"
slug: about
contents:
- page: "Overview"
path: about/overview.mdx
""",
{"about/overview.mdx": page("Overview of the Product")},
)
issues = check_docs_nav.run(root)
assert issues == [
"about/overview.mdx: title 'Overview of the Product' does not match nav label 'Overview'"
]
@pytest.mark.parametrize(
("nav", "rel"),
[
(
"""
navigation:
- section: Guides
slug: ignored
skip-slug: true
contents:
- page: Setup
path: setup.mdx
""",
"setup.mdx",
),
(
"""
navigation:
- section: Guides
contents:
- page: Setup
slug: ignored
skip-slug: true
path: guides/index.mdx
""",
"guides/index.mdx",
),
(
"""
navigation:
- section: Guides
contents:
- folder: guides
slug: ignored
skip-slug: true
""",
"guides/setup.mdx",
),
],
)
def test_skip_slug_omits_navigation_level(tmp_path: Path, nav: str, rel: str) -> None:
root = build(tmp_path, nav, {rel: page("Setup")})
assert check_docs_nav.run(root) == []
def test_skip_slug_detects_url_that_differs_from_file_path(tmp_path: Path) -> None:
root = build(
tmp_path,
"""
navigation:
- section: Guides
skip-slug: true
contents:
- page: Setup
path: guides/setup.mdx
""",
{"guides/setup.mdx": page("Setup")},
)
issues = check_docs_nav.run(root)
assert len(issues) == 1
assert "URL /setup" in issues[0]
assert "does not match its file path /guides/setup" in issues[0]
def test_folder_pages_use_file_names_and_orphans_fail(tmp_path: Path) -> None:
root = build(
tmp_path,
"""
navigation:
- folder: kubernetes
title: "Kubernetes"
""",
{
"kubernetes/openshift.mdx": page("OpenShift"),
"stray.mdx": page("Stray"),
},
)
assert check_docs_nav.run(root) == ["stray.mdx: not reachable from docs/index.yml"]
@pytest.mark.parametrize(
("redirect", "expected"),
[
(
' - source: "/openshell/dev/old"\n destination: "/openshell/dev/missing"\n',
"destination does not exist",
),
(
' - source: "/openshell/dev/guides/setup"\n destination: "/openshell/dev/guides/setup"\n',
"is still a live page",
),
],
)
def test_dev_redirects_must_point_to_live_pages(
tmp_path: Path, redirect: str, expected: str
) -> None:
root = build(
tmp_path,
"""
navigation:
- section: "Guides"
slug: guides
contents:
- page: "Setup"
path: guides/setup.mdx
""",
{"guides/setup.mdx": page("Setup")},
"redirects:\n" + redirect,
)
issues = check_docs_nav.run(root)
assert len(issues) == 1 and expected in issues[0]