From 1c5b771feb55ef2a5eb67690e7c40c7e83be6006 Mon Sep 17 00:00:00 2001 From: Abhijeet More Date: Mon, 4 Aug 2025 21:49:55 +0530 Subject: [PATCH 01/13] fix(build): raise clear error if mkdocs.yml config file is missing --- readthedocs/doc_builder/backends/mkdocs.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 36f2e1e5d6f..2a7212ca71c 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -14,6 +14,8 @@ from readthedocs.doc_builder.base import BaseBuilder from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML +from readthedocs.doc_builder.exceptions import BuildUserError + log = structlog.get_logger(__name__) @@ -99,6 +101,13 @@ def build(self): "--config-file", os.path.relpath(self.yaml_file, self.project_path), ] + + if not os.path.exists(self.yaml_file): + raise BuildUserError( + f"MkDocs configuration file is missing — we could not find a 'mkdocs.yml' file at {self.yaml_file}. " + "Please make sure your repository includes one and try again." + ) + if self.config.mkdocs.fail_on_warning: build_command.append("--strict") cmd_ret = self.run( From 364220e18877fd21b0a5555d759d3ffdea9026eb Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:19:29 +0530 Subject: [PATCH 02/13] fix import order to satisfy Ruff --- readthedocs/doc_builder/backends/mkdocs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 2a7212ca71c..52183e40c63 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -5,16 +5,15 @@ """ import os - import structlog -import yaml from django.conf import settings from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder +from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML -from readthedocs.doc_builder.exceptions import BuildUserError + From 16fea11f18193d9a61099c58bae00978d16ae077 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:47:30 +0530 Subject: [PATCH 03/13] Update mkdocs.py Sorry for being so late. --- readthedocs/doc_builder/backends/mkdocs.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 52183e40c63..d90e1441163 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -13,7 +13,7 @@ from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML - +from readthedocs.doc_builder.exceptions import ProjectConfigurationError @@ -102,10 +102,7 @@ def build(self): ] if not os.path.exists(self.yaml_file): - raise BuildUserError( - f"MkDocs configuration file is missing — we could not find a 'mkdocs.yml' file at {self.yaml_file}. " - "Please make sure your repository includes one and try again." - ) + raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) if self.config.mkdocs.fail_on_warning: build_command.append("--strict") From 354cff285222d3905b5421e0bf8ce10b724ae855 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:04:25 +0530 Subject: [PATCH 04/13] Update exceptions.py added MKDOCS_NOT_FOUND constant to ProjectConfigurationError --- readthedocs/projects/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 466d09a174d..93c8b89e697 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -9,6 +9,8 @@ class ProjectConfigurationError(BuildUserError): NOT_FOUND = "project:sphinx:conf-py-not-found" MULTIPLE_CONF_FILES = "project:sphinx:multiple-conf-py-files-found" + MKDOCS_NOT_FOUND = "project:configuration:mkdocs-not-found" + class UserFileNotFound(BuildUserError): From 032a0bb1db0cc94a27fc64773e577dc82fb9ae83 Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:06:29 +0530 Subject: [PATCH 05/13] Update notifications.py added MkDocs notification for missing mkdocs.yml --- readthedocs/projects/notifications.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 9b9e5d43e7a..03aced968a6 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -125,6 +125,20 @@ ), type=ERROR, ), + Message( + id=ProjectConfigurationError.MKDOCS_NOT_FOUND, + header=_("MkDocs configuration file is missing"), + body=_( + textwrap.dedent( + """ + A configuration file was not found. + Make sure you have a mkdocs.yml file in your repository. + """ + ).strip(), + ), + type=ERROR, + ), + Message( id=ProjectConfigurationError.MULTIPLE_CONF_FILES, header=_("Multiple Sphinx configuration files found"), From b0837a93f39ebcf82f56ef515d100a14f236dd7e Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:08:14 +0530 Subject: [PATCH 06/13] Update mkdocs.py raise MKDOCS_NOT_FOUND when mkdocs.yml is missing --- readthedocs/doc_builder/backends/mkdocs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index d90e1441163..ef7969f305f 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -46,6 +46,8 @@ def __init__(self, *args, **kwargs): # This is the *MkDocs* yaml file self.yaml_file = self.get_yaml_config() + if not os.path.exists(self.yaml_file): + raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_NOT_FOUND) def get_final_doctype(self): """ From ded0110dab1edf8d8782eba3e3814dd3bff34d1b Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:14:06 +0530 Subject: [PATCH 07/13] Fixing minor mistake in import of mkdocs.py --- readthedocs/doc_builder/backends/mkdocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index ef7969f305f..f68efecb223 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -13,7 +13,7 @@ from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML -from readthedocs.doc_builder.exceptions import ProjectConfigurationError +from readthedocs.projects.exceptions import ProjectConfigurationError From f5900ce5f9925776a8b9266f6e866450e43905af Mon Sep 17 00:00:00 2001 From: RabbitAlbatross <143070183+RabbitAlbatross@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:19:02 +0530 Subject: [PATCH 08/13] yaml import problem fixed --- readthedocs/doc_builder/backends/mkdocs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index f68efecb223..e9236c3d9ed 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -6,6 +6,7 @@ import os import structlog +import yaml from django.conf import settings from readthedocs.core.utils.filesystem import safe_open From fbe174540e3739de49795d498a04fa809283bd02 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 13 Oct 2025 12:20:12 +0200 Subject: [PATCH 09/13] Updates to match Sphinx builder --- readthedocs/doc_builder/backends/mkdocs.py | 43 +++++++++++----------- readthedocs/projects/exceptions.py | 3 +- readthedocs/projects/notifications.py | 5 +-- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index e9236c3d9ed..0a750705ace 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -5,17 +5,17 @@ """ import os + import structlog import yaml from django.conf import settings from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.doc_builder.exceptions import BuildUserError from readthedocs.projects.constants import MKDOCS from readthedocs.projects.constants import MKDOCS_HTML from readthedocs.projects.exceptions import ProjectConfigurationError - +from readthedocs.projects.exceptions import UserFileNotFound log = structlog.get_logger(__name__) @@ -46,9 +46,12 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.yaml_file = self.get_yaml_config() - if not os.path.exists(self.yaml_file): - raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_NOT_FOUND) + self.config_file = None + if self.config.mkdocs.configuration: + self.config_file = os.path.join( + self.project_path, + self.config.mkdocs.configuration, + ) def get_final_doctype(self): """ @@ -63,7 +66,7 @@ def get_final_doctype(self): # Allow symlinks, but only the ones that resolve inside the base directory. with safe_open( - self.yaml_file, + self.config_file, "r", allow_symlinks=True, base_path=self.project_path, @@ -72,22 +75,23 @@ def get_final_doctype(self): use_directory_urls = config.get("use_directory_urls", True) return MKDOCS if use_directory_urls else MKDOCS_HTML - def get_yaml_config(self): - """Find the ``mkdocs.yml`` file in the project root.""" - mkdocs_path = self.config.mkdocs.configuration - if not mkdocs_path: - mkdocs_path = "mkdocs.yml" - return os.path.join( - self.project_path, - mkdocs_path, - ) - def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" + if self.config_file is None: + raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND) + + if not os.path.exists(self.config_file): + raise UserFileNotFound( + message_id=UserFileNotFound.FILE_NOT_FOUND, + format_values={ + "filename": os.path.relpath(self.config_file, self.project_path), + }, + ) + # Write the mkdocs.yml to the build logs self.run( "cat", - os.path.relpath(self.yaml_file, self.project_path), + os.path.relpath(self.config_file, self.project_path), cwd=self.project_path, ) @@ -101,12 +105,9 @@ def build(self): "--site-dir", os.path.join("$READTHEDOCS_OUTPUT", "html"), "--config-file", - os.path.relpath(self.yaml_file, self.project_path), + os.path.relpath(self.config_file, self.project_path), ] - if not os.path.exists(self.yaml_file): - raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND) - if self.config.mkdocs.fail_on_warning: build_command.append("--strict") cmd_ret = self.run( diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 93c8b89e697..5b9d9ce7e52 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -9,8 +9,7 @@ class ProjectConfigurationError(BuildUserError): NOT_FOUND = "project:sphinx:conf-py-not-found" MULTIPLE_CONF_FILES = "project:sphinx:multiple-conf-py-files-found" - MKDOCS_NOT_FOUND = "project:configuration:mkdocs-not-found" - + MKDOCS_YAML_NOT_FOUND = "project:mkdocs:yaml-not-found" class UserFileNotFound(BuildUserError): diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 03aced968a6..53648da7171 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -101,7 +101,7 @@ ), Message( id=RepositoryError.UNSUPPORTED_VCS, - header=_("Repository type not suported"), + header=_("Repository type not supported"), body=_( textwrap.dedent( """ @@ -126,7 +126,7 @@ type=ERROR, ), Message( - id=ProjectConfigurationError.MKDOCS_NOT_FOUND, + id=ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND, header=_("MkDocs configuration file is missing"), body=_( textwrap.dedent( @@ -138,7 +138,6 @@ ), type=ERROR, ), - Message( id=ProjectConfigurationError.MULTIPLE_CONF_FILES, header=_("Multiple Sphinx configuration files found"), From 92288157a2349026325f86d8ec8439181a92aaa6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 28 Oct 2025 12:18:56 +0100 Subject: [PATCH 10/13] Update tests and code to make them pass --- readthedocs/doc_builder/backends/mkdocs.py | 25 ------------------- .../projects/tests/test_build_tasks.py | 20 ++++++++++++++- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 0a750705ace..19f71a199d4 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -10,10 +10,7 @@ import yaml from django.conf import settings -from readthedocs.core.utils.filesystem import safe_open from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.projects.constants import MKDOCS -from readthedocs.projects.constants import MKDOCS_HTML from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.exceptions import UserFileNotFound @@ -53,28 +50,6 @@ def __init__(self, *args, **kwargs): self.config.mkdocs.configuration, ) - def get_final_doctype(self): - """ - Select a doctype based on the ``use_directory_urls`` setting. - - https://www.mkdocs.org/user-guide/configuration/#use_directory_urls - """ - - # TODO: we should eventually remove this method completely and stop - # relying on "loading the `mkdocs.yml` file in a safe way just to know - # if it's a MKDOCS or MKDOCS_HTML documentation type". - - # Allow symlinks, but only the ones that resolve inside the base directory. - with safe_open( - self.config_file, - "r", - allow_symlinks=True, - base_path=self.project_path, - ) as fh: - config = yaml_load_safely(fh) - use_directory_urls = config.get("use_directory_urls", True) - return MKDOCS if use_directory_urls else MKDOCS_HTML - def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" if self.config_file is None: diff --git a/readthedocs/projects/tests/test_build_tasks.py b/readthedocs/projects/tests/test_build_tasks.py index 51177f88868..89bab7cf4df 100644 --- a/readthedocs/projects/tests/test_build_tasks.py +++ b/readthedocs/projects/tests/test_build_tasks.py @@ -302,6 +302,14 @@ def test_build_updates_documentation_type(self, load_yaml_config): ) ).touch() + # Create "mkdocs.yml" for the "cat" command to find it + pathlib.Path( + os.path.join( + self.project.checkout_path(self.version.slug), + "mkdocs.yml", + ) + ).touch() + self._trigger_update_docs_task() # Update version state @@ -1413,7 +1421,7 @@ def test_build_commands_executed_with_clone_token( os.makedirs(self.project.artifact_path(version=self.version.slug, type_="epub")) os.makedirs(self.project.artifact_path(version=self.version.slug, type_="pdf")) - get_clone_token.return_value = "toke:1234" + get_clone_token.return_value = "token:1234" github_app_installation = get( GitHubAppInstallation, installation_id=1234, @@ -2626,6 +2634,16 @@ def test_mkdocs_fail_on_warning(self, load_yaml_config): validate=True, ) + # Create "mkdocs.yaml" for the "cat" command to find it + os.makedirs(os.path.join(self.project.checkout_path(version=self.version.slug), "docs")) + pathlib.Path( + os.path.join( + self.project.checkout_path(self.version.slug), + "docs", + "mkdocs.yaml", + ) + ).touch() + self._trigger_update_docs_task() self.mocker.mocks["environment.run"].assert_has_calls( From 34ee160d399eb1717700d36784ac49133108e982 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 5 Nov 2025 16:38:33 +0100 Subject: [PATCH 11/13] Feedback from review --- readthedocs/doc_builder/backends/mkdocs.py | 14 ++++---------- readthedocs/projects/exceptions.py | 1 - readthedocs/projects/notifications.py | 13 ------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 19f71a199d4..04cdc6a8d71 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -11,7 +11,6 @@ from django.conf import settings from readthedocs.doc_builder.base import BaseBuilder -from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.exceptions import UserFileNotFound @@ -43,18 +42,13 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.config_file = None - if self.config.mkdocs.configuration: - self.config_file = os.path.join( - self.project_path, - self.config.mkdocs.configuration, - ) + self.config_file = self.config_file = os.path.join( + self.project_path, + self.config.mkdocs.configuration, + ) def show_conf(self): """Show the current ``mkdocs.yaml`` being used.""" - if self.config_file is None: - raise ProjectConfigurationError(ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND) - if not os.path.exists(self.config_file): raise UserFileNotFound( message_id=UserFileNotFound.FILE_NOT_FOUND, diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py index 5b9d9ce7e52..466d09a174d 100644 --- a/readthedocs/projects/exceptions.py +++ b/readthedocs/projects/exceptions.py @@ -9,7 +9,6 @@ class ProjectConfigurationError(BuildUserError): NOT_FOUND = "project:sphinx:conf-py-not-found" MULTIPLE_CONF_FILES = "project:sphinx:multiple-conf-py-files-found" - MKDOCS_YAML_NOT_FOUND = "project:mkdocs:yaml-not-found" class UserFileNotFound(BuildUserError): diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 53648da7171..2e4fc616ac8 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -125,19 +125,6 @@ ), type=ERROR, ), - Message( - id=ProjectConfigurationError.MKDOCS_YAML_NOT_FOUND, - header=_("MkDocs configuration file is missing"), - body=_( - textwrap.dedent( - """ - A configuration file was not found. - Make sure you have a mkdocs.yml file in your repository. - """ - ).strip(), - ), - type=ERROR, - ), Message( id=ProjectConfigurationError.MULTIPLE_CONF_FILES, header=_("Multiple Sphinx configuration files found"), From 2f31863040f419128573c0d46976f1fe004234d6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 5 Nov 2025 17:20:54 +0100 Subject: [PATCH 12/13] Update readthedocs/doc_builder/backends/mkdocs.py Co-authored-by: Santos Gallegos --- readthedocs/doc_builder/backends/mkdocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 04cdc6a8d71..52bc208c2c5 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -42,7 +42,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This is the *MkDocs* yaml file - self.config_file = self.config_file = os.path.join( + self.config_file = os.path.join( self.project_path, self.config.mkdocs.configuration, ) From 4273d8c2a18366809c7804c0fc2874288fae2845 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:54:13 +0100 Subject: [PATCH 13/13] Raise clear error when `mkdocs.yml` config file is missing (#12607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MkDocs builds failed with unclear errors when the configuration file was missing. This aligns MkDocs error handling with the existing Sphinx implementation. ### Changes - **Error messaging**: When `mkdocs.configuration` is declared but the file doesn't exist, raises `UserFileNotFound` with the path - **Test coverage**: Added `MkDocsBuilderTest.test_project_without_mkdocs_yml` to verify the error behavior ### Error Examples Missing `mkdocs.configuration` key: ![MkDocs configuration file is missing](https://github.com/user-attachments/assets/e6409f0f-d23a-413f-90ae-80e672994a98) Declared file doesn't exist: ![Expected file not found](https://github.com/user-attachments/assets/41244494-e276-4a39-ad9c-7753207a7622) Closes #11937 Closes #12378 --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: humitos <244656+humitos@users.noreply.github.com> --- .../rtd_tests/tests/test_doc_builder.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py index e510de3a45a..605d8c66d71 100644 --- a/readthedocs/rtd_tests/tests/test_doc_builder.py +++ b/readthedocs/rtd_tests/tests/test_doc_builder.py @@ -8,9 +8,11 @@ from django.test.utils import override_settings from readthedocs.config.tests.test_config import get_build_config +from readthedocs.doc_builder.backends.mkdocs import BaseMkdocs from readthedocs.doc_builder.backends.sphinx import BaseSphinx from readthedocs.doc_builder.python_environments import Virtualenv from readthedocs.projects.exceptions import ProjectConfigurationError +from readthedocs.projects.exceptions import UserFileNotFound from readthedocs.projects.models import Project @@ -112,3 +114,62 @@ def test_multiple_conf_py( with pytest.raises(ProjectConfigurationError): with override_settings(DOCROOT=tmp_docs_dir): base_sphinx.show_conf() + + +@override_settings(PRODUCTION_DOMAIN="readthedocs.org") +class MkDocsBuilderTest(TestCase): + fixtures = ["test_data", "eric"] + + def setUp(self): + self.project = Project.objects.get(slug="pip") + self.version = self.project.versions.first() + + self.build_env = mock.MagicMock() + self.build_env.project = self.project + self.build_env.version = self.version + self.build_env.build = { + "id": 123, + } + self.build_env.api_client = mock.MagicMock() + + @patch("readthedocs.doc_builder.backends.mkdocs.BaseMkdocs.run") + @patch("readthedocs.projects.models.Project.checkout_path") + @patch("readthedocs.doc_builder.python_environments.load_yaml_config") + def test_project_without_mkdocs_yml( + self, + load_yaml_config, + checkout_path, + _, + ): + """ + Test for a project with a missing ``mkdocs.yml`` file. + + When ``mkdocs.configuration`` points to a file that doesn't exist, + a ``UserFileNotFound`` error should be raised. + """ + tmp_dir = tempfile.mkdtemp() + checkout_path.return_value = tmp_dir + python_env = Virtualenv( + version=self.version, + build_env=self.build_env, + config=get_build_config( + {"mkdocs": {"configuration": "mkdocs.yml"}}, + validate=True, + source_file=f"{tmp_dir}/readthedocs.yml", + ), + ) + base_mkdocs = BaseMkdocs( + build_env=self.build_env, + python_env=python_env, + ) + with self.assertRaises(UserFileNotFound) as e: + base_mkdocs.show_conf() + + self.assertEqual( + e.exception.message_id, + UserFileNotFound.FILE_NOT_FOUND, + ) + self.assertEqual( + e.exception.format_values.get("filename"), + "mkdocs.yml", + )