mirror of
https://github.com/rendercv/rendercv.git
synced 2025-12-23 21:47:55 -05:00
* add grade field to education entry * remove optional and use "| None" * remove optional and use "| None" * update schema
1163 lines
33 KiB
Python
1163 lines
33 KiB
Python
import io
|
||
import json
|
||
import os
|
||
import shutil
|
||
from datetime import date as Date
|
||
|
||
import pydantic
|
||
import pytest
|
||
import ruamel.yaml
|
||
|
||
from rendercv import data
|
||
from rendercv.data import generator
|
||
from rendercv.data.models import (
|
||
computers,
|
||
curriculum_vitae,
|
||
entry_types,
|
||
locale,
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("date", "expected_date_object", "expected_error"),
|
||
[
|
||
("2020-01-01", Date(2020, 1, 1), None),
|
||
("2020-01", Date(2020, 1, 1), None),
|
||
("2020", Date(2020, 1, 1), None),
|
||
(2020, Date(2020, 1, 1), None),
|
||
("present", Date.today(), None),
|
||
("invalid", None, ValueError),
|
||
("20222", None, ValueError),
|
||
("202222-20200", None, ValueError),
|
||
("202222-12-20", None, ValueError),
|
||
("2022-20-20", None, ValueError),
|
||
],
|
||
)
|
||
def test_get_date_object(date, expected_date_object, expected_error):
|
||
if expected_error:
|
||
with pytest.raises(expected_error):
|
||
computers.get_date_object(date)
|
||
else:
|
||
assert computers.get_date_object(date) == expected_date_object
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("date", "expected_date_string"),
|
||
[
|
||
(Date(2020, 1, 1), "Jan 2020"),
|
||
(Date(2020, 2, 1), "Feb 2020"),
|
||
(Date(2020, 3, 1), "Mar 2020"),
|
||
(Date(2020, 4, 1), "Apr 2020"),
|
||
(Date(2020, 5, 1), "May 2020"),
|
||
(Date(2020, 6, 1), "June 2020"),
|
||
(Date(2020, 7, 1), "July 2020"),
|
||
(Date(2020, 8, 1), "Aug 2020"),
|
||
(Date(2020, 9, 1), "Sept 2020"),
|
||
(Date(2020, 10, 1), "Oct 2020"),
|
||
(Date(2020, 11, 1), "Nov 2020"),
|
||
(Date(2020, 12, 1), "Dec 2020"),
|
||
],
|
||
)
|
||
def test_format_date(date, expected_date_string):
|
||
assert data.format_date(date) == expected_date_string
|
||
|
||
|
||
def test_read_input_file(input_file_path):
|
||
data_model = data.read_input_file(input_file_path)
|
||
|
||
assert isinstance(data_model, data.RenderCVDataModel)
|
||
|
||
|
||
def test_read_input_file_directly_with_contents():
|
||
input_dictionary = {
|
||
"cv": {
|
||
"name": "John Doe",
|
||
},
|
||
"design": {
|
||
"theme": "classic",
|
||
},
|
||
}
|
||
|
||
# dump the dictionary to a yaml file
|
||
yaml_object = ruamel.yaml.YAML()
|
||
yaml_object.width = 60
|
||
yaml_object.indent(mapping=2, sequence=4, offset=2)
|
||
with io.StringIO() as string_stream:
|
||
yaml_object.dump(input_dictionary, string_stream)
|
||
yaml_string = string_stream.getvalue()
|
||
|
||
data_model = data.read_input_file(yaml_string)
|
||
|
||
assert isinstance(data_model, data.RenderCVDataModel)
|
||
|
||
|
||
def test_read_input_file_invalid_file(tmp_path):
|
||
invalid_file_path = tmp_path / "invalid.extension"
|
||
invalid_file_path.write_text("dummy content", encoding="utf-8")
|
||
with pytest.raises(ValueError): # NOQA: PT011
|
||
data.read_input_file(invalid_file_path)
|
||
|
||
|
||
def test_read_input_file_that_doesnt_exist(tmp_path):
|
||
non_existent_file_path = tmp_path / "non_existent_file.yaml"
|
||
with pytest.raises(FileNotFoundError):
|
||
data.read_input_file(non_existent_file_path)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"theme",
|
||
data.available_themes,
|
||
)
|
||
def test_create_a_sample_data_model(theme):
|
||
data_model = data.create_a_sample_data_model("John Doe", theme)
|
||
assert isinstance(data_model, data.RenderCVDataModel)
|
||
|
||
|
||
def test_create_a_sample_data_model_invalid_theme():
|
||
with pytest.raises(ValueError): # NOQA: PT011
|
||
data.create_a_sample_data_model("John Doe", "invalid")
|
||
|
||
|
||
def test_generate_json_schema():
|
||
schema = data.generate_json_schema()
|
||
assert isinstance(schema, dict)
|
||
|
||
|
||
def test_generate_json_schema_file(tmp_path):
|
||
schema_file_path = tmp_path / "schema.json"
|
||
data.generate_json_schema_file(schema_file_path)
|
||
|
||
assert schema_file_path.exists()
|
||
|
||
schema_text = schema_file_path.read_text(encoding="utf-8")
|
||
schema = json.loads(schema_text)
|
||
|
||
assert isinstance(schema, dict)
|
||
|
||
|
||
@pytest.mark.skip(
|
||
reason=(
|
||
"This test doesn't work currently, due to the `rendercv_settings.date` field."
|
||
)
|
||
)
|
||
def test_if_the_schema_is_the_latest(root_directory_path):
|
||
original_schema_file_path = root_directory_path / "schema.json"
|
||
original_schema_text = original_schema_file_path.read_text()
|
||
original_schema = json.loads(original_schema_text)
|
||
new_schema = data.generate_json_schema()
|
||
|
||
assert original_schema == new_schema
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
(
|
||
"start_date",
|
||
"end_date",
|
||
"date",
|
||
"expected_date_string",
|
||
"expected_date_string_only_years",
|
||
"expected_time_span",
|
||
),
|
||
[
|
||
(
|
||
"2020-01-01",
|
||
"2021-01-01",
|
||
None,
|
||
"Jan 2020 – Jan 2021", # NOQA: RUF001
|
||
"2020 – 2021", # NOQA: RUF001
|
||
"1 year 1 month",
|
||
),
|
||
(
|
||
"2020-01-01",
|
||
"2022-01-01",
|
||
None,
|
||
"Jan 2020 – Jan 2022", # NOQA: RUF001
|
||
"2020 – 2022", # NOQA: RUF001
|
||
"2 years 1 month",
|
||
),
|
||
(
|
||
"2020-01-01",
|
||
"2021-12-10",
|
||
None,
|
||
"Jan 2020 – Dec 2021", # NOQA: RUF001
|
||
"2020 – 2021", # NOQA: RUF001
|
||
"2 years",
|
||
),
|
||
(
|
||
Date(2020, 1, 1),
|
||
Date(2021, 1, 1),
|
||
None,
|
||
"Jan 2020 – Jan 2021", # NOQA: RUF001
|
||
"2020 – 2021", # NOQA: RUF001
|
||
"1 year 1 month",
|
||
),
|
||
(
|
||
"2020-01",
|
||
"2021-01",
|
||
None,
|
||
"Jan 2020 – Jan 2021", # NOQA: RUF001
|
||
"2020 – 2021", # NOQA: RUF001
|
||
"1 year 1 month",
|
||
),
|
||
(
|
||
"2020-01",
|
||
"2021-02-01",
|
||
None,
|
||
"Jan 2020 – Feb 2021", # NOQA: RUF001
|
||
"2020 – 2021", # NOQA: RUF001
|
||
"1 year 2 months",
|
||
),
|
||
(
|
||
"2020-01-01",
|
||
"2021-01",
|
||
None,
|
||
"Jan 2020 – Jan 2021", # NOQA: RUF001
|
||
"2020 – 2021", # NOQA: RUF001
|
||
"1 year 1 month",
|
||
),
|
||
(
|
||
"2020-01-01",
|
||
None,
|
||
None,
|
||
"Jan 2020 – present", # NOQA: RUF001
|
||
"2020 – present", # NOQA: RUF001
|
||
"4 years 1 month",
|
||
),
|
||
(
|
||
"2020-02-01",
|
||
"present",
|
||
None,
|
||
"Feb 2020 – present", # NOQA: RUF001
|
||
"2020 – present", # NOQA: RUF001
|
||
"4 years",
|
||
),
|
||
("2020-01-01", "2021-01-01", "2023-02-01", "Feb 2023", "2023", ""),
|
||
("2020", "2021", None, "2020 – 2021", "2020 – 2021", "1 year"), # NOQA: RUF001
|
||
(
|
||
"2020",
|
||
None,
|
||
None,
|
||
"2020 – present", # NOQA: RUF001
|
||
"2020 – present", # NOQA: RUF001
|
||
"4 years",
|
||
),
|
||
(
|
||
"2020-10-10",
|
||
"2022",
|
||
None,
|
||
"Oct 2020 – 2022", # NOQA: RUF001
|
||
"2020 – 2022", # NOQA: RUF001
|
||
"2 years",
|
||
),
|
||
(
|
||
"2020-10-10",
|
||
"2020-11-05",
|
||
None,
|
||
"Oct 2020 – Nov 2020", # NOQA: RUF001
|
||
"2020 – 2020", # NOQA: RUF001
|
||
"1 month",
|
||
),
|
||
(
|
||
"2022",
|
||
"2023-10-10",
|
||
None,
|
||
"2022 – Oct 2023", # NOQA: RUF001
|
||
"2022 – 2023", # NOQA: RUF001
|
||
"1 year",
|
||
),
|
||
(
|
||
"2020-01-01",
|
||
"present",
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"",
|
||
),
|
||
(
|
||
"2020-01-01",
|
||
None,
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"",
|
||
),
|
||
(
|
||
None,
|
||
None,
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"",
|
||
),
|
||
(
|
||
None,
|
||
"2020-01-01",
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"My Custom Date",
|
||
"",
|
||
),
|
||
(None, None, "2020-01-01", "Jan 2020", "2020", ""),
|
||
(None, None, "2020-09", "Sept 2020", "2020", ""),
|
||
(None, None, Date(2020, 1, 1), "Jan 2020", "2020", ""),
|
||
(None, None, None, "", "", ""),
|
||
(None, "2020-01-01", None, "Jan 2020", "2020", ""),
|
||
(None, "present", None, "Jan 2024", "2024", ""),
|
||
("2002", "2020", "2024", "2024", "2024", ""),
|
||
],
|
||
)
|
||
def test_dates(
|
||
start_date,
|
||
end_date,
|
||
date,
|
||
expected_date_string,
|
||
expected_date_string_only_years,
|
||
expected_time_span,
|
||
):
|
||
data.RenderCVSettings(date="2024-01-01") # type: ignore
|
||
entry_base = entry_types.EntryBase(
|
||
start_date=start_date, end_date=end_date, date=date
|
||
)
|
||
|
||
assert entry_base.date_string == expected_date_string
|
||
assert entry_base.date_string_only_years == expected_date_string_only_years
|
||
assert entry_base.time_span_string == expected_time_span
|
||
|
||
|
||
def test_dates_style():
|
||
assert data.format_date(Date(2020, 1, 1), "TEST") == "TEST"
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("date", "expected_date_string"),
|
||
[
|
||
("2020-01-01", "Jan 2020"),
|
||
("2020-01", "Jan 2020"),
|
||
("2020", "2020"),
|
||
],
|
||
)
|
||
def test_publication_dates(publication_entry, date, expected_date_string):
|
||
publication_entry["date"] = date
|
||
publication_entry = data.PublicationEntry(**publication_entry)
|
||
assert publication_entry.date_string == expected_date_string
|
||
|
||
|
||
@pytest.mark.parametrize("date", ["2025-23-23"])
|
||
def test_invalid_publication_dates(publication_entry, date):
|
||
publication_entry["date"] = date
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.PublicationEntry(**publication_entry)
|
||
|
||
|
||
def test_education_entry_grade_field(education_entry):
|
||
education_entry["grade"] = "GPA: 3.00/4.00"
|
||
entry = data.EducationEntry(**education_entry)
|
||
assert entry.grade == "GPA: 3.00/4.00"
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("start_date", "end_date", "date"),
|
||
[
|
||
("aaa", "2021-01-01", None),
|
||
("2020-01-01", "aaa", None),
|
||
("2023-01-01", "2021-01-01", None),
|
||
("2022", "2021", None),
|
||
("2025", "2021", None),
|
||
("2020-01-01", "invalid_end_date", None),
|
||
("invalid_start_date", "2021-01-01", None),
|
||
("2020-99-99", "2021-01-01", None),
|
||
("2020-10-12", "2020-99-99", None),
|
||
(None, None, "2020-20-20"),
|
||
],
|
||
)
|
||
def test_invalid_dates(start_date, end_date, date):
|
||
with pytest.raises(pydantic.ValidationError):
|
||
entry_types.EntryBase(start_date=start_date, end_date=end_date, date=date)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("doi", "expected_doi_url"),
|
||
[
|
||
("10.1109/TASC.2023.3340648", "https://doi.org/10.1109/TASC.2023.3340648"),
|
||
],
|
||
)
|
||
def test_doi_url(publication_entry, doi, expected_doi_url):
|
||
publication_entry["doi"] = doi
|
||
publication_entry = data.PublicationEntry(**publication_entry)
|
||
assert publication_entry.doi_url == expected_doi_url
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("network", "username"),
|
||
[
|
||
("Mastodon", "invalidmastodon"),
|
||
("Mastodon", "@inva@l@id"),
|
||
("Mastodon", "@invalid@ne<>twork.com"),
|
||
("StackOverflow", "invalidusername"),
|
||
("StackOverflow", "invalidusername//"),
|
||
("StackOverflow", "invalidusername/invalid"),
|
||
("YouTube", "@invalidusername"),
|
||
("NONAME", "@invalidusername"),
|
||
],
|
||
)
|
||
def test_invalid_social_networks(network, username):
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.SocialNetwork(network=network, username=username)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("network", "username", "expected_url"),
|
||
[
|
||
("LinkedIn", "myusername", "https://linkedin.com/in/myusername"),
|
||
("GitHub", "myusername", "https://github.com/myusername"),
|
||
("IMDB", "nm0000001", "https://imdb.com/name/nm0000001"),
|
||
("Instagram", "myusername", "https://instagram.com/myusername"),
|
||
("ORCID", "0000-0000-0000-0000", "https://orcid.org/0000-0000-0000-0000"),
|
||
("Mastodon", "@myusername@test.org", "https://test.org/@myusername"),
|
||
(
|
||
"StackOverflow",
|
||
"4567/myusername",
|
||
"https://stackoverflow.com/users/4567/myusername",
|
||
),
|
||
(
|
||
"GitLab",
|
||
"myusername",
|
||
"https://gitlab.com/myusername",
|
||
),
|
||
(
|
||
"ResearchGate",
|
||
"myusername",
|
||
"https://researchgate.net/profile/myusername",
|
||
),
|
||
(
|
||
"YouTube",
|
||
"myusername",
|
||
"https://youtube.com/@myusername",
|
||
),
|
||
(
|
||
"Google Scholar",
|
||
"myusername",
|
||
"https://scholar.google.com/citations?user=myusername",
|
||
),
|
||
(
|
||
"Telegram",
|
||
"myusername",
|
||
"https://t.me/myusername",
|
||
),
|
||
(
|
||
"X",
|
||
"myusername",
|
||
"https://x.com/myusername",
|
||
),
|
||
],
|
||
)
|
||
def test_social_network_url(network, username, expected_url):
|
||
social_network = data.SocialNetwork(network=network, username=username)
|
||
assert str(social_network.url) == expected_url
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("entry", "expected_entry_type", "expected_section_type"),
|
||
[
|
||
(
|
||
"publication_entry",
|
||
"PublicationEntry",
|
||
"SectionWithPublicationEntries",
|
||
),
|
||
(
|
||
"experience_entry",
|
||
"ExperienceEntry",
|
||
"SectionWithExperienceEntries",
|
||
),
|
||
(
|
||
"education_entry",
|
||
"EducationEntry",
|
||
"SectionWithEducationEntries",
|
||
),
|
||
(
|
||
"normal_entry",
|
||
"NormalEntry",
|
||
"SectionWithNormalEntries",
|
||
),
|
||
("one_line_entry", "OneLineEntry", "SectionWithOneLineEntries"),
|
||
("text_entry", "TextEntry", "SectionWithTextEntries"),
|
||
("bullet_entry", "BulletEntry", "SectionWithBulletEntries"),
|
||
],
|
||
)
|
||
def test_get_entry_type_name_and_section_validator(
|
||
entry, expected_entry_type, expected_section_type, request: pytest.FixtureRequest
|
||
):
|
||
entry = request.getfixturevalue(entry)
|
||
entry_type, section_type = (
|
||
curriculum_vitae.get_entry_type_name_and_section_validator(
|
||
entry, entry_types.available_entry_models
|
||
)
|
||
)
|
||
assert entry_type == expected_entry_type
|
||
assert section_type.__name__ == expected_section_type
|
||
|
||
# initialize the entry with the entry type
|
||
if entry_type != "TextEntry":
|
||
entry = eval(f"data.{entry_type}(**entry)")
|
||
entry_type, section_type = (
|
||
curriculum_vitae.get_entry_type_name_and_section_validator(
|
||
entry, entry_types.available_entry_models
|
||
)
|
||
)
|
||
assert entry_type == expected_entry_type
|
||
assert section_type.__name__ == expected_section_type
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"EntryType",
|
||
data.available_entry_models,
|
||
)
|
||
def test_entries_with_extra_attributes(EntryType, request: pytest.FixtureRequest):
|
||
# Get the name of the class:
|
||
entry_type_name: str = EntryType.__name__
|
||
|
||
# Convert from camel case to snake case
|
||
entry_type_name = "".join(
|
||
["_" + c.lower() if c.isupper() else c for c in entry_type_name]
|
||
).lstrip("_")
|
||
|
||
# Get entry contents from fixture:
|
||
entry_contents = request.getfixturevalue(entry_type_name)
|
||
|
||
entry_contents["extra_attribute"] = "extra value"
|
||
|
||
entry = EntryType(**entry_contents)
|
||
|
||
assert entry.extra_attribute == "extra value"
|
||
|
||
|
||
def test_sections(
|
||
education_entry,
|
||
experience_entry,
|
||
publication_entry,
|
||
normal_entry,
|
||
one_line_entry,
|
||
text_entry,
|
||
):
|
||
input = {
|
||
"name": "John Doe",
|
||
"sections": {
|
||
"arbitrary_title": [
|
||
education_entry,
|
||
education_entry,
|
||
],
|
||
"arbitrary_title_2": [
|
||
experience_entry,
|
||
experience_entry,
|
||
],
|
||
"arbitrary_title_3": [
|
||
publication_entry,
|
||
publication_entry,
|
||
],
|
||
"arbitrary_title_4": [
|
||
normal_entry,
|
||
normal_entry,
|
||
],
|
||
"arbitrary_title_5": [
|
||
one_line_entry,
|
||
one_line_entry,
|
||
],
|
||
"arbitrary_title_6": [
|
||
text_entry,
|
||
text_entry,
|
||
],
|
||
},
|
||
}
|
||
|
||
cv = data.CurriculumVitae(**input)
|
||
assert len(cv.sections) == 6
|
||
for section in cv.sections:
|
||
assert len(section.entries) == 2
|
||
|
||
|
||
def test_section_with_different_entry_types(
|
||
education_entry,
|
||
experience_entry,
|
||
):
|
||
input = {
|
||
"name": "John Doe",
|
||
"sections": {
|
||
"arbitrary_title": [
|
||
education_entry,
|
||
experience_entry,
|
||
],
|
||
},
|
||
}
|
||
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.CurriculumVitae(**input)
|
||
|
||
|
||
def test_sections_with_invalid_entries():
|
||
input = {"name": "John Doe", "sections": {}}
|
||
input["sections"]["section_title"] = [
|
||
{
|
||
"this": "is",
|
||
"an": "invalid",
|
||
"entry": 10,
|
||
}
|
||
]
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.CurriculumVitae(**input)
|
||
|
||
|
||
def test_sections_without_list():
|
||
input = {"name": "John Doe", "sections": {}}
|
||
input["sections"]["section_title"] = {
|
||
"this section": "does not have a list of entries but a single entry."
|
||
}
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.CurriculumVitae(**input)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"invalid_custom_theme_name",
|
||
[
|
||
"pathdoesntexist",
|
||
"invalid_theme_name",
|
||
],
|
||
)
|
||
def test_invalid_custom_theme(invalid_custom_theme_name):
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.RenderCVDataModel(
|
||
cv={"name": "John Doe"}, # type: ignore
|
||
design={"theme": invalid_custom_theme_name},
|
||
)
|
||
|
||
|
||
def test_custom_theme_with_missing_files(tmp_path):
|
||
custom_theme_path = tmp_path / "customtheme"
|
||
custom_theme_path.mkdir()
|
||
os.chdir(tmp_path)
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.RenderCVDataModel(
|
||
cv={"name": "John Doe"}, # type: ignore
|
||
design={"theme": "customtheme"},
|
||
)
|
||
|
||
|
||
def test_custom_theme(testdata_directory_path):
|
||
os.chdir(
|
||
testdata_directory_path
|
||
/ "test_copy_theme_files_to_output_directory_custom_theme"
|
||
)
|
||
data_model = data.RenderCVDataModel(
|
||
cv={"name": "John Doe"}, # type: ignore
|
||
design={"theme": "dummytheme"},
|
||
)
|
||
|
||
assert data_model.design.theme == "dummytheme"
|
||
|
||
|
||
def test_custom_theme_without_init_file(tmp_path, testdata_directory_path):
|
||
reference_custom_theme_path = (
|
||
testdata_directory_path
|
||
/ "test_copy_theme_files_to_output_directory_custom_theme"
|
||
/ "dummytheme"
|
||
)
|
||
|
||
# copy the directory to tmp_path:
|
||
custom_theme_path = tmp_path / "dummytheme"
|
||
shutil.copytree(reference_custom_theme_path, custom_theme_path, dirs_exist_ok=True)
|
||
|
||
# remove the __init__.py file:
|
||
init_file = custom_theme_path / "__init__.py"
|
||
init_file.unlink()
|
||
|
||
os.chdir(tmp_path)
|
||
data_model = data.RenderCVDataModel(
|
||
cv={"name": "John Doe"}, # type: ignore
|
||
design={"theme": "dummytheme"},
|
||
)
|
||
|
||
assert data_model.design.theme == "dummytheme"
|
||
|
||
|
||
def test_custom_theme_with_broken_init_file(tmp_path, testdata_directory_path):
|
||
reference_custom_theme_path = (
|
||
testdata_directory_path
|
||
/ "test_copy_theme_files_to_output_directory_custom_theme"
|
||
/ "dummytheme"
|
||
)
|
||
|
||
# copy the directory to tmp_path:
|
||
custom_theme_path = tmp_path / "dummytheme"
|
||
shutil.copytree(reference_custom_theme_path, custom_theme_path, dirs_exist_ok=True)
|
||
|
||
# overwrite the __init__.py file (syntax error)
|
||
init_file = custom_theme_path / "__init__.py"
|
||
init_file.write_text("invalid python code", encoding="utf-8")
|
||
|
||
os.chdir(tmp_path)
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.RenderCVDataModel(
|
||
cv={"name": "John Doe"}, # type: ignore
|
||
design={"theme": "dummytheme"},
|
||
)
|
||
|
||
# overwrite the __init__.py file (import error)
|
||
init_file = custom_theme_path / "__init__.py"
|
||
init_file.write_text("from ... import test", encoding="utf-8")
|
||
|
||
os.chdir(tmp_path)
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.RenderCVDataModel(
|
||
cv={"name": "John Doe"}, # type: ignore
|
||
design={"theme": "dummytheme"},
|
||
)
|
||
|
||
|
||
def test_locale():
|
||
data_model = data.create_a_sample_data_model("John Doe")
|
||
data_model.locale = data.Locale(
|
||
month="a",
|
||
months="b",
|
||
year="c",
|
||
years="d",
|
||
present="e",
|
||
to="f",
|
||
abbreviations_for_months=[
|
||
"1",
|
||
"2",
|
||
"3",
|
||
"4",
|
||
"5",
|
||
"6",
|
||
"7",
|
||
"8",
|
||
"9",
|
||
"10",
|
||
"11",
|
||
"12",
|
||
],
|
||
full_names_of_months=[
|
||
"1",
|
||
"2",
|
||
"3",
|
||
"4",
|
||
"5",
|
||
"6",
|
||
"7",
|
||
"8",
|
||
"9",
|
||
"10",
|
||
"11",
|
||
"12",
|
||
],
|
||
phone_number_format="international",
|
||
)
|
||
|
||
locale_as_dict = data_model.locale.model_dump()
|
||
del locale_as_dict["page_numbering_template"]
|
||
del locale_as_dict["last_updated_date_template"]
|
||
del locale_as_dict["language"]
|
||
|
||
assert locale_as_dict == locale.locale
|
||
|
||
|
||
def test_if_local_catalog_resets():
|
||
data_model = data.create_a_sample_data_model("John Doe")
|
||
|
||
data_model.locale = data.Locale(
|
||
month="a",
|
||
)
|
||
|
||
assert locale.locale["month"] == "a"
|
||
|
||
data_model = data.create_a_sample_data_model("John Doe")
|
||
|
||
assert locale.locale["month"] == "month"
|
||
|
||
|
||
def test_curriculum_vitae():
|
||
data.CurriculumVitae(name="Test Doe")
|
||
|
||
assert curriculum_vitae.curriculum_vitae == {"name": "Test Doe"}
|
||
|
||
|
||
def test_if_curriculum_vitae_resets():
|
||
data.CurriculumVitae(name="Test Doe")
|
||
|
||
assert curriculum_vitae.curriculum_vitae["name"] == "Test Doe"
|
||
|
||
data.create_a_sample_data_model("John Doe")
|
||
|
||
assert curriculum_vitae.curriculum_vitae["name"] == "John Doe"
|
||
|
||
|
||
def test_dictionary_to_yaml():
|
||
input_dictionary = {
|
||
"test_list": [
|
||
"a",
|
||
"b",
|
||
"c",
|
||
],
|
||
"test_dict": {
|
||
"a": 1,
|
||
"b": 2,
|
||
},
|
||
}
|
||
yaml_string = generator.dictionary_to_yaml(input_dictionary)
|
||
|
||
# load the yaml string
|
||
yaml_object = ruamel.yaml.YAML()
|
||
output_dictionary = yaml_object.load(yaml_string)
|
||
|
||
assert input_dictionary == output_dictionary
|
||
|
||
|
||
def test_create_a_sample_yaml_input_file(tmp_path):
|
||
input_file_path = tmp_path / "input.yaml"
|
||
yaml_contents = data.create_a_sample_yaml_input_file(input_file_path)
|
||
|
||
assert input_file_path.exists()
|
||
assert yaml_contents == input_file_path.read_text(encoding="utf-8")
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("key", "expected_section_title"),
|
||
[
|
||
("this_is_a_test", "This Is a Test"),
|
||
("welcome_to_RenderCV!", "Welcome to RenderCV!"),
|
||
("\\faGraduationCap_education", "\\faGraduationCap Education"),
|
||
("Hello_World", "Hello World"),
|
||
("Hello World", "Hello World"),
|
||
],
|
||
)
|
||
def test_dictionary_key_to_proper_section_title(key, expected_section_title):
|
||
assert (
|
||
computers.dictionary_key_to_proper_section_title(key) == expected_section_title
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("url", "expected_clean_url"),
|
||
[
|
||
("https://example.com", "example.com"),
|
||
("https://example.com/", "example.com"),
|
||
("https://example.com/test", "example.com/test"),
|
||
("https://example.com/test/", "example.com/test"),
|
||
("https://www.example.com/test/", "www.example.com/test"),
|
||
],
|
||
)
|
||
def test_make_a_url_clean(url, expected_clean_url):
|
||
assert computers.make_a_url_clean(url) == expected_clean_url
|
||
assert (
|
||
data.PublicationEntry(title="Test", authors=["test"], url=url).clean_url
|
||
== expected_clean_url
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("path_name", "expected_value"),
|
||
[
|
||
("NAME_IN_SNAKE_CASE", "John_Doe"),
|
||
("NAME_IN_LOWER_SNAKE_CASE", "john_doe"),
|
||
("NAME_IN_UPPER_SNAKE_CASE", "JOHN_DOE"),
|
||
("NAME_IN_KEBAB_CASE", "John-Doe"),
|
||
("NAME_IN_LOWER_KEBAB_CASE", "john-doe"),
|
||
("NAME_IN_UPPER_KEBAB_CASE", "JOHN-DOE"),
|
||
("NAME", "John Doe"),
|
||
("FULL_MONTH_NAME", "January"),
|
||
("MONTH_ABBREVIATION", "Jan"),
|
||
("MONTH", "1"),
|
||
("MONTH_IN_TWO_DIGITS", "01"),
|
||
("YEAR", "2024"),
|
||
("YEAR_IN_TWO_DIGITS", "24"),
|
||
],
|
||
)
|
||
def test_render_command_settings_placeholders(path_name, expected_value):
|
||
data.RenderCVSettings(date="2024-01-01") # type: ignore
|
||
|
||
data.CurriculumVitae(name="John Doe")
|
||
|
||
render_command_settings = data.RenderCommandSettings(
|
||
pdf_path=path_name,
|
||
typst_path=path_name,
|
||
html_path=path_name,
|
||
markdown_path=path_name,
|
||
output_folder_name=path_name,
|
||
)
|
||
|
||
assert render_command_settings.pdf_path.name == expected_value # type: ignore
|
||
assert render_command_settings.typst_path.name == expected_value # type: ignore
|
||
assert render_command_settings.html_path.name == expected_value # type: ignore
|
||
assert render_command_settings.markdown_path.name == expected_value # type: ignore
|
||
assert render_command_settings.output_folder_name == expected_value
|
||
|
||
|
||
def test_make_keywords_bold_in_a_string():
|
||
assert (
|
||
data.make_keywords_bold_in_a_string(
|
||
"This is a test string with some keywords.",
|
||
["test", "keywords"],
|
||
)
|
||
== "This is a **test** string with some **keywords**."
|
||
)
|
||
|
||
|
||
def test_bold_keywords():
|
||
data_model_as_dict = {
|
||
"cv": {
|
||
"sections": {
|
||
"test": ["test_keyword_1"],
|
||
"test2": [
|
||
{
|
||
"institution": "Test Institution",
|
||
"area": "Test Area",
|
||
"degree": None,
|
||
"date": None,
|
||
"start_date": None,
|
||
"end_date": None,
|
||
"location": None,
|
||
"summary": "test_keyword_3 test_keyword_4",
|
||
"highlights": ["test_keyword_2"],
|
||
}
|
||
],
|
||
"test3": [
|
||
{
|
||
"company": "Test Company",
|
||
"position": "Test Position",
|
||
"date": None,
|
||
"start_date": None,
|
||
"end_date": None,
|
||
"location": None,
|
||
"summary": "test_keyword_6 test_keyword_7",
|
||
"highlights": ["test_keyword_5", "test_keyword_6"],
|
||
}
|
||
],
|
||
"test4": [
|
||
{
|
||
"name": "Test",
|
||
"date": None,
|
||
"start_date": None,
|
||
"end_date": None,
|
||
"location": None,
|
||
"summary": "test_keyword_3 test_keyword_4",
|
||
"highlights": ["test_keyword_2"],
|
||
}
|
||
],
|
||
"test6": [{"bullet": "test_keyword_3 test_keyword_4"}],
|
||
"test7": [
|
||
{
|
||
"label": "Test Institution",
|
||
"details": "test_keyword_3 test_keyword_4",
|
||
}
|
||
],
|
||
},
|
||
},
|
||
"rendercv_settings": {
|
||
"bold_keywords": [
|
||
"test_keyword_1",
|
||
"test_keyword_2",
|
||
"test_keyword_3",
|
||
"test_keyword_4",
|
||
"test_keyword_5",
|
||
"test_keyword_6",
|
||
"test_keyword_7",
|
||
],
|
||
},
|
||
}
|
||
|
||
data_model = data.validate_input_dictionary_and_return_the_data_model(
|
||
data_model_as_dict
|
||
)
|
||
|
||
for section in data_model.cv.sections:
|
||
for entry in section.entries:
|
||
if section.title == "Test":
|
||
assert "**test_keyword_1**" in entry
|
||
elif section.title == "Test2":
|
||
assert "**test_keyword_2**" in entry.highlights[0]
|
||
assert "**test_keyword_3**" in entry.summary
|
||
assert "**test_keyword_4**" in entry.summary
|
||
elif section.title == "Test3":
|
||
assert "**test_keyword_5**" in entry.highlights[0]
|
||
assert "**test_keyword_6**" in entry.highlights[1]
|
||
assert "**test_keyword_6**" in entry.summary
|
||
assert "**test_keyword_7**" in entry.summary
|
||
elif section.title == "Test4":
|
||
assert "**test_keyword_2**" in entry.highlights[0]
|
||
assert "**test_keyword_3**" in entry.summary
|
||
assert "**test_keyword_4**" in entry.summary
|
||
elif section.title == "Test6":
|
||
assert "**test_keyword_3**" in entry.bullet
|
||
assert "**test_keyword_4**" in entry.bullet
|
||
elif section.title == "Test7":
|
||
assert "**test_keyword_3**" in entry.details
|
||
assert "**test_keyword_4**" in entry.details
|
||
|
||
|
||
def test_none_entries():
|
||
with pytest.raises(pydantic.ValidationError):
|
||
data.RenderCVDataModel(
|
||
cv=data.CurriculumVitae(
|
||
name="John Doe",
|
||
sections={
|
||
"test": [
|
||
None,
|
||
],
|
||
},
|
||
)
|
||
)
|
||
|
||
|
||
def _create_sorting_data_model(order: str) -> data.RenderCVDataModel:
|
||
entries = [
|
||
{
|
||
"company": "A",
|
||
"position": "P",
|
||
"start_date": "2020-01-01",
|
||
},
|
||
{
|
||
"company": "B",
|
||
"position": "P",
|
||
"start_date": "2022-01-01",
|
||
},
|
||
{
|
||
"company": "C",
|
||
"position": "P",
|
||
"date": "2021-05-01",
|
||
},
|
||
{
|
||
"company": "D",
|
||
"position": "P",
|
||
"date": "2022-01-01",
|
||
},
|
||
]
|
||
|
||
cv = data.CurriculumVitae(
|
||
name="John Doe",
|
||
sections={"exp": entries},
|
||
)
|
||
|
||
settings = data.RenderCVSettings(date="2024-01-01", sort_entries=order)
|
||
|
||
return data.RenderCVDataModel(cv=cv, rendercv_settings=settings)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("order", "expected"),
|
||
[
|
||
(
|
||
"reverse-chronological",
|
||
["B", "A", "D", "C"],
|
||
),
|
||
(
|
||
"chronological",
|
||
["C", "D", "A", "B"],
|
||
),
|
||
(
|
||
"none",
|
||
["A", "B", "C", "D"],
|
||
),
|
||
],
|
||
)
|
||
def test_sort_entries(order, expected):
|
||
data_model = _create_sorting_data_model(order)
|
||
entries = data_model.cv.sections[0].entries
|
||
companies = [e.company for e in entries]
|
||
assert companies == expected
|
||
|
||
|
||
def _create_sorting_data_model_with_ranges(order: str) -> data.RenderCVDataModel:
|
||
entries = [
|
||
{
|
||
"company": "A",
|
||
"position": "P",
|
||
"start_date": "2020-01-01",
|
||
"end_date": "2021-06-01",
|
||
},
|
||
{
|
||
"company": "B",
|
||
"position": "P",
|
||
"start_date": "2019-01-01",
|
||
"end_date": "2022-06-01",
|
||
},
|
||
{
|
||
"company": "C",
|
||
"position": "P",
|
||
"start_date": "2021-01-01",
|
||
"end_date": "2022-06-01",
|
||
},
|
||
{
|
||
"company": "D",
|
||
"position": "P",
|
||
"date": "2020-05-01",
|
||
},
|
||
]
|
||
|
||
cv = data.CurriculumVitae(
|
||
name="John Doe",
|
||
sections={"exp": entries},
|
||
)
|
||
|
||
settings = data.RenderCVSettings(date="2024-01-01", sort_entries=order)
|
||
|
||
return data.RenderCVDataModel(cv=cv, rendercv_settings=settings)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("order", "expected"),
|
||
[
|
||
(
|
||
"reverse-chronological",
|
||
["C", "B", "A", "D"],
|
||
),
|
||
(
|
||
"chronological",
|
||
["D", "A", "B", "C"],
|
||
),
|
||
],
|
||
)
|
||
def test_sort_entries_with_ranges(order, expected):
|
||
data_model = _create_sorting_data_model_with_ranges(order)
|
||
entries = data_model.cv.sections[0].entries
|
||
companies = [e.company for e in entries]
|
||
assert companies == expected
|
||
|
||
|
||
def _create_sorting_data_model_with_ties(order: str) -> data.RenderCVDataModel:
|
||
entries = [
|
||
{
|
||
"company": "A",
|
||
"position": "P",
|
||
"date": "2020-01-01",
|
||
},
|
||
{
|
||
"company": "B",
|
||
"position": "P",
|
||
"date": "2020-01-01",
|
||
},
|
||
{
|
||
"company": "C",
|
||
"position": "P",
|
||
"date": "2020-01-02",
|
||
},
|
||
]
|
||
|
||
cv = data.CurriculumVitae(
|
||
name="John Doe",
|
||
sections={"exp": entries},
|
||
)
|
||
|
||
settings = data.RenderCVSettings(date="2024-01-01", sort_entries=order)
|
||
|
||
return data.RenderCVDataModel(cv=cv, rendercv_settings=settings)
|
||
|
||
|
||
@pytest.mark.parametrize("order", ["reverse-chronological", "chronological"])
|
||
def test_sort_entries_tie_keeps_order(order):
|
||
data_model = _create_sorting_data_model_with_ties(order)
|
||
entries = data_model.cv.sections[0].entries
|
||
companies = [e.company for e in entries]
|
||
if order == "reverse-chronological":
|
||
assert companies == ["C", "A", "B"]
|
||
else:
|
||
assert companies == ["A", "B", "C"]
|