Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
414 changes: 414 additions & 0 deletions hack/mkdocs/__tests__/test_breakage.py

Large diffs are not rendered by default.

139 changes: 139 additions & 0 deletions hack/mkdocs/__tests__/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Copyright 2025 The Kubernetes Authors.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import shutil
import sys
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

import hack.mkdocs_linking as linking


class TestCommandLineInterface(unittest.TestCase):
docs_path: Path

def setUp(self) -> None:
"""Set up a temporary directory structure for each test."""
self.test_dir = Path("./temp_test_convert_links")
if self.test_dir.exists():
shutil.rmtree(self.test_dir)

self.docs_path = self.test_dir / "docs"
self.redirect_map_file = self.test_dir / "redirect_map.json"
self.docs_path.mkdir(parents=True)

self.linking_module = sys.modules["linking"]
self.original_globals = {
"DOCS_DIR": self.linking_module.DOCS_DIR,
"REDIRECT_MAP_FILE": self.linking_module.REDIRECT_MAP_FILE,
}
self.linking_module.DOCS_DIR = self.docs_path # type: ignore
self.linking_module.REDIRECT_MAP_FILE = self.redirect_map_file # type: ignore

def test_main_handles_prepare_docs_exceptions(self) -> None:
"""Test main() handles exceptions from prepare_docs gracefully."""
# Arrange: Mock prepare_docs to raise an exception
original_prepare_docs = linking.prepare_docs

def failing_prepare_docs(docs_dir_path=None):
raise Exception("Test exception from prepare_docs")

linking.prepare_docs = failing_prepare_docs

import sys

original_argv = sys.argv
sys.argv = ["linking.py", "--prepare"]

try:
# Act & Assert: Exception should propagate (this is expected behavior)
with self.assertRaises(Exception) as context:
linking.main()

self.assertIn("Test exception from prepare_docs", str(context.exception))

finally:
# Restore everything
linking.prepare_docs = original_prepare_docs
sys.argv = original_argv

def test_main_with_prepare_argument(self) -> None:
"""Test main() function when called with --prepare argument."""
# Arrange: Create some test files
(self.docs_path / "test.md").write_text("# Test Document")
(self.docs_path / "guide.md").write_text("# Guide Document")

# Mock sys.argv to simulate command line arguments
import sys

original_argv = sys.argv
sys.argv = ["linking.py", "--prepare", "--docs-dir", str(self.docs_path)]

try:
# Act: Call main function
linking.main()

# Assert: Verify that prepare_docs was executed
self.assertTrue(self.redirect_map_file.exists())
redirect_map = json.loads(self.redirect_map_file.read_text())
self.assertIn("test", redirect_map)
self.assertIn("guide", redirect_map)

finally:
# Restore original argv
sys.argv = original_argv

def test_prepare_docs_called_correctly(self) -> None:
"""Test that prepare_docs is called when --prepare is used."""
# Arrange: Create test files and mock prepare_docs
(self.docs_path / "sample.md").write_text("# Sample")

original_prepare_docs = linking.prepare_docs
prepare_docs_called = False

def mock_prepare_docs(docs_dir_path=None):
nonlocal prepare_docs_called
prepare_docs_called = True
# Call the original function to ensure it works
original_prepare_docs(docs_dir_path)

linking.prepare_docs = mock_prepare_docs

import sys

original_argv = sys.argv
# Pass docs_dir to ensure the redirect map is created in the temp folder
sys.argv = ["linking.py", "--prepare", "--docs-dir", str(self.docs_path)]

try:
# Act: Call main
linking.main()

# Assert: Verify prepare_docs was called
self.assertTrue(prepare_docs_called)

# Verify it actually worked
self.assertTrue(self.redirect_map_file.exists())

finally:
# Restore everything
linking.prepare_docs = original_prepare_docs
sys.argv = original_argv


if __name__ == "__main__":
unittest.main(verbosity=2)
144 changes: 144 additions & 0 deletions hack/mkdocs/__tests__/test_convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Copyright 2025 The Kubernetes Authors.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import shutil
import sys
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

import hack.mkdocs_linking as linking


class TestConvertFromRelativeLinks(unittest.TestCase):
"""Tests for the convert_internal_links function."""

test_dir: Path
docs_path: Path
redirect_map_file: Path
linking_module: object
original_globals: dict

def setUp(self) -> None:
"""Set up a temporary directory structure for each test."""
self.test_dir = Path("./temp_test_convert_links")
if self.test_dir.exists():
shutil.rmtree(self.test_dir)

self.docs_path = self.test_dir / "docs"
self.redirect_map_file = self.test_dir / "redirect_map.json"
self.docs_path.mkdir(parents=True)

self.linking_module = sys.modules["linking"]
self.original_globals = {
"DOCS_DIR": self.linking_module.DOCS_DIR,
"REDIRECT_MAP_FILE": self.linking_module.REDIRECT_MAP_FILE,
}
self.linking_module.DOCS_DIR = self.docs_path # type: ignore
self.linking_module.REDIRECT_MAP_FILE = self.redirect_map_file # type: ignore

def test_basic_link_conversion(self) -> None:
"""Test that a simple relative link is converted to a macro."""
# Arrange
(self.docs_path / "index.md").write_text("Link to [About](./about.md).")
(self.docs_path / "about.md").write_text("This is the about page.")
linking.prepare_docs(str(self.docs_path))

# Act
linking.convert_internal_links(str(self.docs_path))

# Assert
content = (self.docs_path / "index.md").read_text()
expected = '---\nid: index\n---\nLink to [About]({{ internal_link("about") }}).'
self.assertEqual(content, expected)

def test_file_with_no_links(self) -> None:
"""Test that a file with no links is not modified."""
# Arrange
original_content = "This document has no links. Just plain text."
(self.docs_path / "no-links.md").write_text(original_content)
linking.prepare_docs(str(self.docs_path))

# Act
linking.convert_internal_links(str(self.docs_path))

# Assert
final_content = (self.docs_path / "no-links.md").read_text()
expected_content = "---\nid: no-links\n---\n" + original_content
self.assertEqual(final_content, expected_content)

def test_handles_complex_relative_paths(self) -> None:
"""Test conversion of links with complex relative paths like ../.."""
# Arrange
(self.docs_path / "guides" / "advanced").mkdir(parents=True)
(self.docs_path / "api" / "v1").mkdir(parents=True)

(self.docs_path / "guides" / "advanced" / "config.md").write_text(
"See the [Auth API](../../api/v1/auth.md) for details."
)
(self.docs_path / "api" / "v1" / "auth.md").write_text("Auth API docs.")
linking.prepare_docs(str(self.docs_path))

# Act
linking.convert_internal_links(str(self.docs_path))

# Assert
content = (self.docs_path / "guides" / "advanced" / "config.md").read_text()
expected = '---\nid: guides-advanced-config\n---\nSee the [Auth API]({{ internal_link("api-v1-auth") }}) for details.'
self.assertEqual(content, expected)

def test_idempotency_does_not_reconvert_links(self) -> None:
"""Test that running the conversion twice doesn't change already converted links."""
# Arrange
(self.docs_path / "index.md").write_text("Link to [About](./about.md).")
(self.docs_path / "about.md").write_text("This is the about page.")
linking.prepare_docs(str(self.docs_path))

# Act
linking.convert_internal_links(str(self.docs_path)) # First run
content_after_first_run = (self.docs_path / "index.md").read_text()

linking.convert_internal_links(str(self.docs_path)) # Second run
content_after_second_run = (self.docs_path / "index.md").read_text()

# Assert
expected = '---\nid: index\n---\nLink to [About]({{ internal_link("about") }}).'
self.assertEqual(content_after_first_run, expected)
self.assertEqual(
content_after_second_run,
expected,
"Content should not change on the second run.",
)

def test_leaves_broken_links_unchanged(self) -> None:
"""Test that a link to a non-existent .md file is not converted."""
# Arrange
original_content = "This is a [Broken Link](./nonexistent.md)."
(self.docs_path / "index.md").write_text(original_content)
linking.prepare_docs(str(self.docs_path))

# Act
linking.convert_internal_links(str(self.docs_path))

# Assert
final_content = (self.docs_path / "index.md").read_text()
expected_content = "---\nid: index\n---\n" + original_content
self.assertEqual(
final_content, expected_content, "Broken link should not be modified."
)


if __name__ == "__main__":
unittest.main(verbosity=2)
Loading