|
7 | 7 | import pytest |
8 | 8 |
|
9 | 9 | sys.path.insert(0, str(Path(__file__).resolve().parents[1] / ".hermes")) |
10 | | -from linkcheck import check_links |
| 10 | +from linkcheck import PageLinks, SITE_URL, check_links, document_base_url |
11 | 11 |
|
12 | 12 |
|
13 | 13 | @pytest.mark.parametrize("link", ["#missing", "target.html#missing", "target.html?q=1#missing"]) |
@@ -63,3 +63,61 @@ def test_github_compatibility_wrapper_reports_broken_links(tmp_path): |
63 | 63 | result = subprocess.run([sys.executable, str(wrapper), str(tmp_path), "--exit-code"], capture_output=True) |
64 | 64 | assert result.returncode == 1 |
65 | 65 | assert b"missing fragment" in result.stdout |
| 66 | + |
| 67 | + |
| 68 | +def test_root_base_changes_nested_stylesheet_resolution(tmp_path): |
| 69 | + blog = tmp_path / "blog" |
| 70 | + blog.mkdir() |
| 71 | + (blog / "post.html").write_text('<base href="/devforge/"><link href="blog.css" rel="stylesheet">') |
| 72 | + (blog / "blog.css").write_text("body {}") |
| 73 | + # A browser requests /devforge/blog.css, not /devforge/blog/blog.css. |
| 74 | + assert check_links(str(tmp_path)) == 1 |
| 75 | + (tmp_path / "blog.css").write_text("body {}") |
| 76 | + assert check_links(str(tmp_path)) == 0 |
| 77 | + |
| 78 | + |
| 79 | +def test_first_base_href_wins_and_is_not_a_resource_link(tmp_path): |
| 80 | + (tmp_path / "blog").mkdir() |
| 81 | + (tmp_path / "assets").mkdir() |
| 82 | + (tmp_path / "blog/post.html").write_text( |
| 83 | + '<base target="_blank"><base href="../assets/">' |
| 84 | + '<base href="/devforge/missing/"><a href="target.html#ok">target</a>' |
| 85 | + ) |
| 86 | + (tmp_path / "assets/target.html").write_text('<h1 id="ok">Heading</h1>') |
| 87 | + # There is no assets/index.html. The base href itself does not fetch one. |
| 88 | + assert check_links(str(tmp_path)) == 0 |
| 89 | + |
| 90 | + |
| 91 | +def test_fragment_uses_base_target_instead_of_source_page(tmp_path): |
| 92 | + (tmp_path / "blog").mkdir() |
| 93 | + (tmp_path / "blog/post.html").write_text( |
| 94 | + '<base href="../target.html"><h1 id="section">Source</h1><a href="#section">target</a>' |
| 95 | + ) |
| 96 | + (tmp_path / "target.html").write_text("No target anchor") |
| 97 | + assert check_links(str(tmp_path)) == 1 |
| 98 | + (tmp_path / "target.html").write_text('<h1 id="section">Target</h1>') |
| 99 | + assert check_links(str(tmp_path)) == 0 |
| 100 | + |
| 101 | + |
| 102 | +def test_external_base_makes_relative_urls_external(tmp_path): |
| 103 | + (tmp_path / "index.html").write_text( |
| 104 | + '<base href="https://example.com/assets/"><a href="missing.html">external</a>' |
| 105 | + '<a href="https://coding-dev-tools.github.io/devforge/target.html#ok">local</a>' |
| 106 | + ) |
| 107 | + (tmp_path / "target.html").write_text('<h1 id="ok">Target</h1>') |
| 108 | + assert check_links(str(tmp_path)) == 0 |
| 109 | + |
| 110 | + |
| 111 | +@pytest.mark.parametrize("href", ["", "data:text/html,example", "javascript:alert(1)", "http://[invalid"]) |
| 112 | +def test_empty_or_disallowed_first_base_falls_back_without_using_second_base(href): |
| 113 | + parser = PageLinks() |
| 114 | + parser.feed(f'<base href="{href}"><base href="/devforge/ignored/">') |
| 115 | + document_url = SITE_URL + "blog/post.html" |
| 116 | + assert document_base_url(parser, document_url) == document_url |
| 117 | + |
| 118 | + |
| 119 | +def test_escaped_base_example_does_not_change_document_base(): |
| 120 | + parser = PageLinks() |
| 121 | + parser.feed('<code><base href="/devforge/"></code>') |
| 122 | + document_url = SITE_URL + "blog/post.html" |
| 123 | + assert document_base_url(parser, document_url) == document_url |
0 commit comments