mirror of
https://github.com/rendercv/rendercv.git
synced 2026-04-21 07:20:10 -04:00
Increase test coverage
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import typer
|
||||
@@ -11,6 +12,7 @@ from rendercv.cli.render_command.run_rendercv import (
|
||||
run_rendercv,
|
||||
timed_step,
|
||||
)
|
||||
from rendercv.exception import RenderCVUserError
|
||||
|
||||
|
||||
class TestTimedStep:
|
||||
@@ -162,6 +164,24 @@ design:
|
||||
# Restore permissions for cleanup
|
||||
yaml_file.chmod(original_mode)
|
||||
|
||||
def test_user_error_during_rendering(self, tmp_path):
|
||||
yaml_file = tmp_path / "test.yaml"
|
||||
yaml_file.write_text("cv:\n name: John Doe\n", encoding="utf-8")
|
||||
progress = ProgressPanel(quiet=True)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"rendercv.cli.render_command.run_rendercv"
|
||||
".build_rendercv_dictionary_and_model",
|
||||
side_effect=RenderCVUserError(message="test error"),
|
||||
),
|
||||
pytest.raises(typer.Exit) as exc_info,
|
||||
progress,
|
||||
):
|
||||
run_rendercv(yaml_file, progress)
|
||||
|
||||
assert exc_info.value.exit_code == 1
|
||||
|
||||
|
||||
class TestCollectInputFilePaths:
|
||||
def test_returns_only_input_file_by_default(self, tmp_path):
|
||||
@@ -212,6 +232,33 @@ class TestCollectInputFilePaths:
|
||||
|
||||
assert result == {"input": yaml_file}
|
||||
|
||||
def test_includes_cli_provided_locale_file(self, tmp_path):
|
||||
yaml_file = tmp_path / "cv.yaml"
|
||||
yaml_file.write_text("cv:\n name: John Doe\n", encoding="utf-8")
|
||||
locale_file = tmp_path / "locale.yaml"
|
||||
locale_file.touch()
|
||||
|
||||
result = collect_input_file_paths(yaml_file, locale=locale_file)
|
||||
|
||||
assert result["input"] == yaml_file
|
||||
assert result["locale"] == locale_file
|
||||
|
||||
def test_includes_yaml_referenced_locale_file(self, tmp_path):
|
||||
locale_file = tmp_path / "my_locale.yaml"
|
||||
locale_file.touch()
|
||||
|
||||
yaml_file = tmp_path / "cv.yaml"
|
||||
yaml_file.write_text(
|
||||
"cv:\n name: John Doe\n"
|
||||
"settings:\n render_command:\n locale: my_locale.yaml\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = collect_input_file_paths(yaml_file)
|
||||
|
||||
assert result["input"] == yaml_file
|
||||
assert result["locale"] == locale_file.resolve()
|
||||
|
||||
def test_cli_flags_take_precedence_over_yaml_references(self, tmp_path):
|
||||
yaml_ref_design = tmp_path / "yaml_design.yaml"
|
||||
yaml_ref_design.touch()
|
||||
|
||||
@@ -3,8 +3,10 @@ import time
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import typer
|
||||
import watchdog.events
|
||||
|
||||
from rendercv.cli.render_command import watcher
|
||||
from rendercv.cli.render_command.watcher import EventHandler
|
||||
|
||||
|
||||
class TestRunFunctionIfFilesChange:
|
||||
@@ -108,3 +110,32 @@ class TestRunFunctionIfFilesChange:
|
||||
time.sleep(0.2)
|
||||
|
||||
assert call_count > count_after_exit
|
||||
|
||||
|
||||
class TestEventHandler:
|
||||
def test_ignores_events_for_unwatched_files(self):
|
||||
mock_fn = MagicMock()
|
||||
handler = EventHandler(mock_fn, watched_files={"/watched/file.yaml"})
|
||||
|
||||
event = watchdog.events.FileModifiedEvent("/other/file.yaml")
|
||||
handler.on_modified(event)
|
||||
|
||||
mock_fn.assert_not_called()
|
||||
|
||||
def test_calls_function_for_watched_file(self):
|
||||
mock_fn = MagicMock()
|
||||
handler = EventHandler(mock_fn, watched_files={"/watched/file.yaml"})
|
||||
|
||||
event = watchdog.events.FileModifiedEvent("/watched/file.yaml")
|
||||
handler.on_modified(event)
|
||||
|
||||
mock_fn.assert_called_once()
|
||||
|
||||
def test_suppresses_typer_exit(self):
|
||||
mock_fn = MagicMock(side_effect=typer.Exit(code=1))
|
||||
handler = EventHandler(mock_fn, watched_files={"/watched/file.yaml"})
|
||||
|
||||
event = watchdog.events.FileModifiedEvent("/watched/file.yaml")
|
||||
handler.on_modified(event)
|
||||
|
||||
mock_fn.assert_called_once()
|
||||
|
||||
@@ -2,7 +2,7 @@ import json
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from typer.testing import CliRunner
|
||||
@@ -11,7 +11,10 @@ from rendercv import __version__
|
||||
from rendercv.cli.app import (
|
||||
VERSION_CHECK_TTL_SECONDS,
|
||||
app,
|
||||
fetch_and_cache_latest_version,
|
||||
fetch_latest_version_from_pypi,
|
||||
get_cache_dir,
|
||||
get_version_cache_file,
|
||||
read_version_cache,
|
||||
warn_if_new_version_is_available,
|
||||
write_version_cache,
|
||||
@@ -75,6 +78,13 @@ class TestGetCacheDir:
|
||||
assert get_cache_dir() == tmp_path / "rendercv"
|
||||
|
||||
|
||||
def test_get_version_cache_file():
|
||||
result = get_version_cache_file()
|
||||
|
||||
assert result.name == "version_check.json"
|
||||
assert result.parent == get_cache_dir()
|
||||
|
||||
|
||||
class TestReadVersionCache:
|
||||
def test_returns_none_for_missing_file(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
@@ -133,6 +143,60 @@ class TestWriteVersionCache:
|
||||
assert data["latest_version"] == "2.0.0"
|
||||
assert "last_check" in data
|
||||
|
||||
def test_silently_ignores_os_error(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"rendercv.cli.app.get_version_cache_file",
|
||||
lambda: pathlib.Path("/nonexistent/readonly/path/version_check.json"),
|
||||
)
|
||||
|
||||
write_version_cache("2.0.0")
|
||||
|
||||
|
||||
class TestFetchLatestVersionFromPypi:
|
||||
def test_returns_version_on_success(self, monkeypatch):
|
||||
response_data = json.dumps({"info": {"version": "3.0.0"}}).encode()
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = response_data
|
||||
mock_response.info.return_value.get_content_charset.return_value = "utf-8"
|
||||
mock_response.__enter__ = lambda s: s
|
||||
mock_response.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"rendercv.cli.app.urllib.request.urlopen",
|
||||
lambda *a, **kw: mock_response,
|
||||
)
|
||||
|
||||
result = fetch_latest_version_from_pypi()
|
||||
|
||||
assert result == "3.0.0"
|
||||
|
||||
@patch(
|
||||
"rendercv.cli.app.urllib.request.urlopen",
|
||||
side_effect=ConnectionError("fail"),
|
||||
)
|
||||
def test_returns_none_on_failure(self, mock_urlopen): # NOQA: ARG002
|
||||
result = fetch_latest_version_from_pypi()
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestFetchAndCacheLatestVersion:
|
||||
@patch("rendercv.cli.app.write_version_cache")
|
||||
@patch("rendercv.cli.app.fetch_latest_version_from_pypi", return_value="3.0.0")
|
||||
def test_fetches_and_writes_cache(self, mock_fetch, mock_write):
|
||||
fetch_and_cache_latest_version()
|
||||
|
||||
mock_fetch.assert_called_once()
|
||||
mock_write.assert_called_once_with("3.0.0")
|
||||
|
||||
@patch("rendercv.cli.app.write_version_cache")
|
||||
@patch("rendercv.cli.app.fetch_latest_version_from_pypi", return_value=None)
|
||||
def test_does_not_write_cache_on_fetch_failure(self, mock_fetch, mock_write):
|
||||
fetch_and_cache_latest_version()
|
||||
|
||||
mock_fetch.assert_called_once()
|
||||
mock_write.assert_not_called()
|
||||
|
||||
|
||||
def write_cache(tmp_path, version, age_seconds=0):
|
||||
"""Helper to write a version cache file for testing."""
|
||||
@@ -240,3 +304,15 @@ class TestWarnIfNewVersionIsAvailable:
|
||||
|
||||
data = json.loads(cache_file.read_text(encoding="utf-8"))
|
||||
assert data["latest_version"] == "99.0.0"
|
||||
|
||||
def test_handles_invalid_version_in_cache(self, tmp_path, capsys, monkeypatch):
|
||||
write_cache(tmp_path, "not.a.version", age_seconds=0)
|
||||
monkeypatch.setattr(
|
||||
"rendercv.cli.app.get_version_cache_file",
|
||||
lambda: tmp_path / "version_check.json",
|
||||
)
|
||||
|
||||
warn_if_new_version_is_available()
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "new version" not in captured.out.lower()
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Literal, get_args
|
||||
import pydantic
|
||||
import pytest
|
||||
|
||||
from rendercv.exception import RenderCVInternalError
|
||||
from rendercv.renderer.templater.connections import (
|
||||
compute_connections,
|
||||
compute_connections_for_markdown,
|
||||
@@ -417,3 +418,22 @@ class TestIconMapping:
|
||||
assert conn_type in fontawesome_icons, (
|
||||
f"Missing icon for connection type: {conn_type}"
|
||||
)
|
||||
|
||||
|
||||
class TestParseConnectionsInternalErrors:
|
||||
"""Test defensive guards when _key_order contains a key but the field is None."""
|
||||
|
||||
def _make_model_with_none_field(self, key: str) -> RenderCVModel:
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
cv._key_order = [key]
|
||||
return create_rendercv_model(cv)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"key",
|
||||
["phone", "website", "social_networks", "custom_connections"],
|
||||
)
|
||||
def test_raises_for_none_field_in_key_order(self, key):
|
||||
model = self._make_model_with_none_field(key)
|
||||
|
||||
with pytest.raises(RenderCVInternalError):
|
||||
parse_connections(model)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from datetime import date as Date
|
||||
from unittest.mock import patch
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
@@ -489,6 +490,95 @@ def test_remove_not_provided_placeholders(entry_templates, entry_fields, expecte
|
||||
assert result == expected
|
||||
|
||||
|
||||
class TestRenderEntryTemplatesInternalErrors:
|
||||
"""Test defensive guards when model_dump includes a key but the attribute is None."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entry_type", "field_name"),
|
||||
[
|
||||
("education", "highlights"),
|
||||
("publication", "authors"),
|
||||
],
|
||||
)
|
||||
def test_raises_when_field_in_dump_but_attribute_is_none(
|
||||
self, entry_type, field_name
|
||||
):
|
||||
if entry_type == "education":
|
||||
entry = EducationEntry.model_validate(
|
||||
{
|
||||
"institution": "MIT",
|
||||
"area": "CS",
|
||||
"highlights": ["A"],
|
||||
"start_date": "2020-01",
|
||||
"end_date": "2021-01",
|
||||
}
|
||||
)
|
||||
else:
|
||||
entry = PublicationEntry.model_validate(
|
||||
{
|
||||
"title": "Paper",
|
||||
"authors": ["John"],
|
||||
"date": "2024-01",
|
||||
}
|
||||
)
|
||||
|
||||
# Set the attribute to None while model_dump still includes it
|
||||
original_model_dump = entry.model_dump
|
||||
|
||||
def patched_model_dump(**kwargs):
|
||||
result = original_model_dump(**kwargs)
|
||||
result[field_name] = "placeholder"
|
||||
return result
|
||||
|
||||
entry.model_dump = patched_model_dump # ty: ignore[invalid-assignment]
|
||||
setattr(entry, field_name, None)
|
||||
|
||||
with pytest.raises(RenderCVInternalError):
|
||||
render_entry_templates(
|
||||
entry,
|
||||
templates=Templates(),
|
||||
locale=EnglishLocale(),
|
||||
show_time_span=False,
|
||||
current_date=Date(2024, 1, 1),
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("field_name", ["start_date", "end_date"])
|
||||
def test_raises_when_date_field_in_dump_but_attribute_is_none(self, field_name):
|
||||
entry = EducationEntry.model_validate(
|
||||
{
|
||||
"institution": "MIT",
|
||||
"area": "CS",
|
||||
"start_date": "2020-01",
|
||||
"end_date": "2021-01",
|
||||
}
|
||||
)
|
||||
|
||||
original_model_dump = entry.model_dump
|
||||
|
||||
def patched_model_dump(**kwargs):
|
||||
result = original_model_dump(**kwargs)
|
||||
result[field_name] = "placeholder"
|
||||
return result
|
||||
|
||||
entry.model_dump = patched_model_dump # ty: ignore[invalid-assignment]
|
||||
setattr(entry, field_name, None)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"rendercv.renderer.templater.entry_templates_from_input.process_date",
|
||||
return_value="Jan 2020 – Jan 2021",
|
||||
),
|
||||
pytest.raises(RenderCVInternalError),
|
||||
):
|
||||
render_entry_templates(
|
||||
entry,
|
||||
templates=Templates(),
|
||||
locale=EnglishLocale(),
|
||||
show_time_span=False,
|
||||
current_date=Date(2024, 1, 1),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("input_text", "expected"),
|
||||
[
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
from datetime import date as Date
|
||||
from unittest.mock import patch
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
|
||||
from rendercv.renderer.templater.model_processor import process_fields, process_model
|
||||
from rendercv.exception import RenderCVUserError
|
||||
from rendercv.renderer.templater.model_processor import (
|
||||
download_photo_from_url,
|
||||
process_fields,
|
||||
process_model,
|
||||
)
|
||||
from rendercv.schema.models.cv.cv import Cv
|
||||
from rendercv.schema.models.cv.entries.normal import NormalEntry
|
||||
from rendercv.schema.models.rendercv_model import RenderCVModel
|
||||
@@ -193,3 +199,85 @@ class TestProcessModel:
|
||||
result = process_model(rendercv_model, "typst")
|
||||
|
||||
assert result.settings.pdf_title == "John Doe - Resume 2024"
|
||||
|
||||
|
||||
class TestDownloadPhotoFromUrl:
|
||||
def test_skips_when_photo_is_none(self):
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
model = RenderCVModel(cv=cv)
|
||||
|
||||
download_photo_from_url(model)
|
||||
|
||||
assert model.cv.photo is None
|
||||
|
||||
def test_skips_when_photo_is_local_path(self, tmp_path):
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
model = RenderCVModel(cv=cv)
|
||||
model.cv.photo = tmp_path / "photo.jpg"
|
||||
|
||||
download_photo_from_url(model)
|
||||
|
||||
assert model.cv.photo == tmp_path / "photo.jpg"
|
||||
|
||||
def test_downloads_photo_from_url(self, tmp_path):
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
model = RenderCVModel(cv=cv)
|
||||
model.cv.photo = pydantic.HttpUrl("https://example.com/photo.jpg")
|
||||
model.settings.render_command.output_folder = tmp_path / "output"
|
||||
|
||||
with patch(
|
||||
"rendercv.renderer.templater.model_processor.urllib.request.urlretrieve"
|
||||
) as mock_retrieve:
|
||||
download_photo_from_url(model)
|
||||
|
||||
mock_retrieve.assert_called_once()
|
||||
assert model.cv.photo == tmp_path / "output" / "photo.jpg"
|
||||
|
||||
def test_uses_photo_jpg_fallback_when_no_filename_in_url(self, tmp_path):
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
model = RenderCVModel(cv=cv)
|
||||
model.cv.photo = pydantic.HttpUrl("https://example.com/")
|
||||
model.settings.render_command.output_folder = tmp_path / "output"
|
||||
|
||||
with patch(
|
||||
"rendercv.renderer.templater.model_processor.urllib.request.urlretrieve"
|
||||
):
|
||||
download_photo_from_url(model)
|
||||
|
||||
assert model.cv.photo == tmp_path / "output" / "photo.jpg"
|
||||
|
||||
def test_skips_download_when_file_already_exists(self, tmp_path):
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
model = RenderCVModel(cv=cv)
|
||||
model.cv.photo = pydantic.HttpUrl("https://example.com/photo.jpg")
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
(output_dir / "photo.jpg").write_bytes(b"existing")
|
||||
model.settings.render_command.output_folder = output_dir
|
||||
|
||||
with patch(
|
||||
"rendercv.renderer.templater.model_processor.urllib.request.urlretrieve"
|
||||
) as mock_retrieve:
|
||||
download_photo_from_url(model)
|
||||
|
||||
mock_retrieve.assert_not_called()
|
||||
assert model.cv.photo == output_dir / "photo.jpg"
|
||||
|
||||
def test_raises_user_error_on_download_failure(self, tmp_path):
|
||||
cv = Cv.model_validate({"name": "John Doe"})
|
||||
model = RenderCVModel(cv=cv)
|
||||
model.cv.photo = pydantic.HttpUrl("https://example.com/photo.jpg")
|
||||
model.settings.render_command.output_folder = tmp_path / "output"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"rendercv.renderer.templater.model_processor"
|
||||
".urllib.request.urlretrieve",
|
||||
side_effect=ConnectionError("network error"),
|
||||
),
|
||||
pytest.raises(RenderCVUserError) as exc_info,
|
||||
):
|
||||
download_photo_from_url(model)
|
||||
|
||||
assert exc_info.value.message is not None
|
||||
assert "Failed to download photo" in exc_info.value.message
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import pathlib
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from rendercv.exception import RenderCVInternalError
|
||||
from rendercv.renderer.pdf_png import generate_pdf, generate_png
|
||||
from rendercv.renderer.typst import generate_typst
|
||||
from rendercv.schema.models.design.built_in_design import available_themes
|
||||
@@ -135,3 +137,32 @@ class TestGeneratePngCleansUpOldFiles:
|
||||
|
||||
assert result is not None
|
||||
assert len(result) >= 1
|
||||
|
||||
|
||||
def test_raises_error_when_typst_returns_none_bytes(
|
||||
tmp_path: pathlib.Path,
|
||||
minimal_rendercv_model: RenderCVModel,
|
||||
):
|
||||
model = RenderCVModel(
|
||||
cv=minimal_rendercv_model.cv,
|
||||
design={"theme": "classic"},
|
||||
locale=minimal_rendercv_model.locale,
|
||||
settings=minimal_rendercv_model.settings,
|
||||
)
|
||||
output_dir = tmp_path / "output"
|
||||
output_dir.mkdir()
|
||||
|
||||
model.settings.render_command.typst_path = output_dir / "John_Doe_CV.typ"
|
||||
typst_path = generate_typst(model)
|
||||
model.settings.render_command.png_path = output_dir / "John_Doe_CV.png"
|
||||
|
||||
mock_compiler = MagicMock()
|
||||
mock_compiler.compile.return_value = [None]
|
||||
|
||||
with (
|
||||
patch(
|
||||
"rendercv.renderer.pdf_png.get_typst_compiler", return_value=mock_compiler
|
||||
),
|
||||
pytest.raises(RenderCVInternalError, match="Typst compiler returned None"),
|
||||
):
|
||||
generate_png(model, typst_path)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
|
||||
from rendercv.exception import RenderCVInternalError
|
||||
from rendercv.schema.models.cv.cv import Cv
|
||||
from rendercv.schema.models.cv.section import available_entry_type_names
|
||||
|
||||
@@ -87,3 +89,10 @@ class TestCv:
|
||||
|
||||
assert "tel:" not in serialized["phone"]
|
||||
assert serialized["phone"] == "+90-541-999-99-99"
|
||||
|
||||
def test_raises_internal_error_when_field_name_is_none(self):
|
||||
mock_info = MagicMock(spec=pydantic.ValidationInfo)
|
||||
mock_info.field_name = None
|
||||
|
||||
with pytest.raises(RenderCVInternalError, match="field_name is None"):
|
||||
Cv.validate_list_or_scalar_fields("test@example.com", mock_info)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import os
|
||||
import pathlib
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
|
||||
from rendercv.exception import RenderCVInternalError
|
||||
from rendercv.schema.models.design.design import Design
|
||||
from rendercv.schema.models.validation_context import ValidationContext
|
||||
|
||||
@@ -140,3 +142,56 @@ class SomeOtherClass(BaseModel):
|
||||
design_adapter.validate_python(
|
||||
{"theme": "classic", "colors": "invalid_value_not_a_dict"}
|
||||
)
|
||||
|
||||
def test_raises_internal_error_when_spec_is_none(self, design_adapter, tmp_path):
|
||||
custom_theme_path = tmp_path / "mytheme"
|
||||
custom_theme_path.mkdir()
|
||||
(custom_theme_path / "EducationEntry.j2.typ").touch()
|
||||
(custom_theme_path / "__init__.py").write_text("# empty", encoding="utf-8")
|
||||
|
||||
with (
|
||||
patch(
|
||||
"rendercv.schema.models.design.design.importlib.util"
|
||||
".spec_from_file_location",
|
||||
return_value=None,
|
||||
),
|
||||
pytest.raises(RenderCVInternalError, match="Failed to load spec"),
|
||||
):
|
||||
design_adapter.validate_python(
|
||||
{"theme": "mytheme"},
|
||||
context={
|
||||
"context": ValidationContext(
|
||||
input_file_path=tmp_path / "input.yaml"
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
def test_raises_internal_error_when_spec_loader_is_none(
|
||||
self, design_adapter, tmp_path
|
||||
):
|
||||
custom_theme_path = tmp_path / "mytheme"
|
||||
custom_theme_path.mkdir()
|
||||
(custom_theme_path / "EducationEntry.j2.typ").touch()
|
||||
(custom_theme_path / "__init__.py").write_text("# empty", encoding="utf-8")
|
||||
|
||||
mock_spec = MagicMock()
|
||||
mock_spec.name = "theme"
|
||||
mock_spec.loader = None
|
||||
mock_spec.submodule_search_locations = None
|
||||
|
||||
with (
|
||||
patch(
|
||||
"rendercv.schema.models.design.design.importlib.util"
|
||||
".spec_from_file_location",
|
||||
return_value=mock_spec,
|
||||
),
|
||||
pytest.raises(RenderCVInternalError, match=r"spec\.loader is None"),
|
||||
):
|
||||
design_adapter.validate_python(
|
||||
{"theme": "mytheme"},
|
||||
context={
|
||||
"context": ValidationContext(
|
||||
input_file_path=tmp_path / "input.yaml"
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from dataclasses import asdict
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pydantic
|
||||
import pytest
|
||||
|
||||
from rendercv.exception import RenderCVInternalError
|
||||
from rendercv.schema.models.custom_error_types import CustomPydanticErrorTypes
|
||||
from rendercv.schema.models.rendercv_model import RenderCVModel
|
||||
from rendercv.schema.models.validation_context import ValidationContext
|
||||
from rendercv.schema.pydantic_error_handling import (
|
||||
@@ -184,6 +186,26 @@ class TestParseValidationErrorsWithOverlaySources:
|
||||
assert err.yaml_source == "design_yaml_file"
|
||||
|
||||
|
||||
class TestParseValidationErrorsInternalErrors:
|
||||
def test_raises_for_entry_validation_error_missing_ctx(self):
|
||||
mock_exception = MagicMock(spec=pydantic.ValidationError)
|
||||
mock_exception.errors.return_value = [
|
||||
{
|
||||
"type": CustomPydanticErrorTypes.entry_validation.value,
|
||||
"loc": ("cv",),
|
||||
"msg": "entry validation failed",
|
||||
"input": {},
|
||||
}
|
||||
]
|
||||
|
||||
input_dict = read_yaml("cv:\n name: John Doe")
|
||||
|
||||
with pytest.raises(
|
||||
RenderCVInternalError, match="entry_validation error missing"
|
||||
):
|
||||
parse_validation_errors(mock_exception, input_dict)
|
||||
|
||||
|
||||
class TestGetInnerYamlObjectFromItsKey:
|
||||
def test_returns_object_and_coordinates_for_valid_key(self):
|
||||
yaml_content = "name: John\nage: 30"
|
||||
|
||||
@@ -11,6 +11,7 @@ from rendercv.schema.rendercv_model_builder import (
|
||||
build_rendercv_dictionary,
|
||||
build_rendercv_dictionary_and_model,
|
||||
build_rendercv_model_from_commented_map,
|
||||
get_yaml_error_location,
|
||||
)
|
||||
from rendercv.schema.sample_generator import dictionary_to_yaml
|
||||
|
||||
@@ -712,3 +713,12 @@ class TestBuildRendercvModel:
|
||||
_, model = build_rendercv_dictionary_and_model(main_yaml, **kwargs) # ty: ignore[invalid-argument-type]
|
||||
|
||||
assert check(model)
|
||||
|
||||
|
||||
class TestGetYamlErrorLocation:
|
||||
def test_returns_none_when_no_marks(self):
|
||||
error = ruamel.yaml.YAMLError()
|
||||
|
||||
result = get_yaml_error_location(error)
|
||||
|
||||
assert result is None
|
||||
|
||||
@@ -36,3 +36,9 @@ class TestReadYaml:
|
||||
|
||||
with pytest.raises(RenderCVUserError, match="empty"):
|
||||
read_yaml(empty_file_path)
|
||||
|
||||
def test_treats_asterisk_as_plain_text(self):
|
||||
result = read_yaml("key: *not_an_alias")
|
||||
|
||||
assert isinstance(result, CommentedMap)
|
||||
assert result["key"] == "*not_an_alias"
|
||||
|
||||
Reference in New Issue
Block a user