[docs]  1# @Test suite for the ref_url_field configuration, TEST_NEED_ID_REFS_CONFIG_1, test, [IMPL_LNK_1]
  2"""``ref_url_field``: ubCode's per-project key, accepted by both readers, and its gate."""
  3
  4from collections.abc import Callable
  5from pathlib import Path
  6
  7import pytest
  8from sphinx.testing.util import SphinxTestApp
  9
 10from sphinx_codelinks.config import (
 11    CodeLinksConfig,
 12    check_configuration,
 13    generate_project_configs,
 14    need_id_refs_field,
 15)
 16from sphinx_needs_testkit import assert_no_warnings
 17
 18_TOML_HEAD = "[codelinks]\nset_remote_url = true\n\n"
 19_PROJECT = (
 20    "[codelinks.projects.p]\n"
 21    'remote_url_pattern = "https://example.com/{commit}/{path}#L{line}"\n'
 22    "EXTRA"
 23    "[codelinks.projects.p.source_discover]\n"
 24    'src_dir = "./src"\n'
 25)
 26
 27
 28def _project(root: Path, *, toml: str | None = None, conf_extra: str = "") -> Path:
 29    (root / "conf.py").write_text(
 30        "extensions = ['sphinx_needs', 'sphinx_codelinks']\n"
 31        "exclude_patterns = ['_build']\n" + conf_extra,
 32        encoding="utf-8",
 33    )
 34    (root / "index.rst").write_text("T\n=\n", encoding="utf-8")
 35    (root / "src").mkdir()
 36    if toml is not None:
 37        (root / "ubproject.toml").write_text(toml, encoding="utf-8")
 38    return root
 39
 40
 41def test_toml_accepts_ref_url_field(
 42    tmp_path: Path, make_app: Callable[..., SphinxTestApp]
 43) -> None:
 44    """ubCode's ``ref_url_field`` in a shared ``ubproject.toml`` is no longer a hard
 45    error (``Additional properties are not allowed ('ref_url_field' was unexpected)``)."""
 46    _project(
 47        tmp_path,
 48        toml=_TOML_HEAD + _PROJECT.replace("EXTRA", 'ref_url_field = "impl_url"\n'),
 49    )
 50    app = make_app(srcdir=tmp_path, freshenv=True)
 51    app.build()
 52
 53    assert_no_warnings(app)
 54    assert app.config.src_trace_projects["p"]["ref_url_field"] == "impl_url"
 55    assert "impl_url" in app.config.needs_string_links
 56
 57
 58def test_conf_py_accepts_ref_url_field(
 59    tmp_path: Path, make_app: Callable[..., SphinxTestApp]
 60) -> None:
 61    _project(
 62        tmp_path,
 63        conf_extra=(
 64            "src_trace_config_from_toml = None\n"
 65            "src_trace_set_remote_url = True\n"
 66            "src_trace_projects = {'p': {\n"
 67            "    'remote_url_pattern': 'https://example.com/{commit}/{path}#L{line}',\n"
 68            "    'ref_url_field': 'impl_url',\n"
 69            "    'source_discover': {'src_dir': './src'},\n"
 70            "}}\n"
 71        ),
 72    )
 73    app = make_app(srcdir=tmp_path, freshenv=True)
 74    app.build()
 75
 76    assert_no_warnings(app)
 77    assert "impl_url" in app.config.needs_string_links
 78
 79
 80@pytest.mark.parametrize(
 81    ("value", "error"),
 82    [
 83        pytest.param(5, "ref_url_field must be a string", id="not-a-string"),
 84        pytest.param(
 85            "remote-url",
 86            "ref_url_field 'remote-url' must differ from local_url_field and "
 87            "remote_url_field",
 88            id="the-remote-url-field",
 89        ),
 90    ],
 91)
 92def test_invalid_ref_url_field_is_a_configuration_error(
 93    value: object, error: str
 94) -> None:
 95    projects = {
 96        "p": {
 97            "remote_url_pattern": "https://example.com/{path}",
 98            "ref_url_field": value,
 99            "source_discover": {"src_dir": "./src"},
100        }
101    }
102    generate_project_configs(projects)  # ty: ignore[invalid-argument-type]
103    config = CodeLinksConfig(set_remote_url=True, projects=projects)  # ty: ignore[invalid-argument-type]
104
105    errors = check_configuration(config)
106    assert errors == ["Project 'p' has the following errors:", error]
107
108
109def _gate(
110    *,
111    set_remote_url: bool = True,
112    set_local_url: bool = False,
113    project: dict[str, object] | None = None,
114) -> str | None:
115    project_config: dict[str, object] = {
116        "remote_url_pattern": "https://example.com/{path}",
117        "source_discover": {"src_dir": "./src"},
118        **(project or {}),
119    }
120    projects = {"p": project_config}
121    generate_project_configs(projects)  # ty: ignore[invalid-argument-type]
122    config = CodeLinksConfig(
123        set_remote_url=set_remote_url,
124        set_local_url=set_local_url,
125        projects=projects,  # ty: ignore[invalid-argument-type]
126    )
127    return need_id_refs_field(config, config.projects["p"])
128
129
130@pytest.mark.parametrize(
131    ("kwargs", "expected"),
132    [
133        pytest.param({}, "code_url", id="default-field"),
134        pytest.param(
135            {"set_remote_url": False, "set_local_url": True},
136            "code_url",
137            id="local-urls-only",
138        ),
139        pytest.param({"set_remote_url": False}, None, id="no-urls"),
140        pytest.param({"project": {"ref_url_field": ""}}, None, id="empty-field"),
141        pytest.param(
142            {"project": {"ref_url_field": "test_url"}}, "test_url", id="named-field"
143        ),
144        pytest.param(
145            {"project": {"analyse": {"get_need_id_refs": False}}},
146            None,
147            id="refs-not-extracted",
148        ),
149        pytest.param(
150            {"project": {"analyse": {"need_id_refs": {"markers": []}}}},
151            None,
152            id="no-markers",
153        ),
154    ],
155)
156def test_attach_gate(kwargs: dict[str, object], expected: str | None) -> None:
157    """ubCode's gate: URLs on, references extracted, a marker, and a non-empty field."""
158    assert _gate(**kwargs) == expected  # ty: ignore[invalid-argument-type]