diff --git a/.gitignore b/.gitignore index e69de29..61f2dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +**/__pycache__/ diff --git a/README.md b/README.md index e9e08fa..441f0ee 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,15 @@ contribution. These files are created from the database using the script `script The Processing application's contribution manager reads in a `contribs.txt` file. This file is created from the database using the script `scripts/to_contribs_txt.py`. +These artifacts are created and released via Github Actions. + +## Contributing + +Thanks for your interest in contributing to this repository. +The scripts are currently written in Python. + +Because much of this repository is workflows and automation, we recommend to run the workflows on your fork before +creating pull requests. ## Contributors diff --git a/requirements.txt b/requirements.txt index 337ae4e..b797d83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ ruamel.yaml tenacity~=9.0.0 requests~=2.32.3 -pydantic~=2.9.2 +pydantic~=2.11 javaproperties~=0.8.2 +pytest diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/parse_and_validate_properties_txt.py b/scripts/parse_and_validate_properties_txt.py index 78b4b3c..9cab83c 100644 --- a/scripts/parse_and_validate_properties_txt.py +++ b/scripts/parse_and_validate_properties_txt.py @@ -2,8 +2,6 @@ Reads a properties txt file from a library's release artifacts, and validates the contents. If valid, it returns the contents as an object. - -TODO: write tests for validation """ import json import argparse @@ -13,38 +11,32 @@ import re import os from typing import Optional, Union -from pydantic import BaseModel, Field, ConfigDict, field_validator +from pydantic import BaseModel, Field, ConfigDict, field_validator, AliasChoices import javaproperties as jp + class PropertiesBase(BaseModel): name: str - authors: str + authors: str = Field(validation_alias=AliasChoices('authors','authorList')) url: str - categories: Optional[str] = Field(None) + categories: Optional[str] = Field(None, validation_alias=AliasChoices('categories','category')) sentence: str paragraph: Optional[str] = None version: int prettyVersion: str minRevision: int = Field(0) maxRevision: int = Field(0) - modes: Optional[str] = Field(None, alias='compatibleModesList') + modes: Optional[str] = Field(None, validation_alias=AliasChoices('modes','compatibleModesList')) model_config = ConfigDict( extra='allow', ) class PropertiesExisting(PropertiesBase): - authors: str = Field(alias='authorList') - categories: Optional[str] = Field(None, alias='category') version: Union[int, str] prettyVersion: Optional[str] = None - model_config = ConfigDict( - extra='allow', - populate_by_name=True, - ) - @field_validator('minRevision', 'maxRevision', mode='before') def default_on_error(cls, v): if v.isdigit(): @@ -54,7 +46,7 @@ def default_on_error(cls, v): class LibraryPropertiesNew(PropertiesBase): - categories: str + categories: str = Field(validation_alias=AliasChoices('categories','category')) @retry(stop=stop_after_attempt(3), diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..fe27af9 --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,132 @@ +import pytest + + +# Test Fixtures +@pytest.fixture +def valid_properties_data(): + """Complete valid data for PropertiesExisting""" + return { + "name": "Test Library", + "authors": "John Doe, Jane Smith", + "url": "https://example.com/library", + "categories": "utility,helper", + "sentence": "A helpful test library for demonstrations", + "paragraph": "This is a more detailed description of the test library.", + "version": "1", + "prettyVersion": "1.0.0", + "minRevision": "5", + "maxRevision": "10", + "modes": "standard,debug" + } + + +@pytest.fixture +def valid_properties_data_aliases(): + """Complete valid data for PropertiesExisting""" + return { + "name": "Test Library", + "authorList": "John Doe, Jane Smith", + "url": "https://example.com/library", + "category": "utility,helper", + "sentence": "A helpful test library for demonstrations", + "paragraph": "This is a more detailed description of the test library.", + "version": "1", + "prettyVersion": "1.0.0", + "minRevision": "5", + "maxRevision": "10", + "compatibleModesList": "standard,debug" + } + + +@pytest.fixture +def minimal_properties_base_data(): + """Minimal required data for PropertiesExisting""" + return { + "name": "Minimal Library", + "authors": "Test Author", + "url": "https://minimal.com", + "sentence": "A minimal test case", + "version": "1", + "prettyVersion": "1.0" + } + + +@pytest.fixture +def minimal_properties_base_data_aliases(): + """Minimal required data for PropertiesExisting""" + return { + "name": "Minimal Library", + "authorList": "Test Author", + "url": "https://minimal.com", + "sentence": "A minimal test case", + "version": "1", + "prettyVersion": "1.0" + } + + +@pytest.fixture +def minimal_properties_existing_data(): + """Minimal required data for PropertiesExisting""" + return { + "name": "Minimal Library", + "authors": "Test Author", + "url": "https://minimal.com", + "sentence": "A minimal test case", + "version": "1", + } + + +@pytest.fixture +def minimal_properties_existing_data_aliases(): + """Minimal required data for PropertiesExisting""" + return { + "name": "Minimal Library", + "authorList": "Test Author", + "url": "https://minimal.com", + "sentence": "A minimal test case", + "version": "1", + } + + +@pytest.fixture +def minimal_properties_library_data(): + """Minimal required data for PropertiesExisting""" + return { + "name": "Minimal Library", + "authors": "Test Author", + "url": "https://minimal.com", + "categories": "minimal,library", + "sentence": "A minimal test case", + "version": "1", + "prettyVersion": "1.0" + } + + +@pytest.fixture +def minimal_properties_library_data_aliases(): + """Minimal required data for PropertiesExisting""" + return { + "name": "Minimal Library", + "authorList": "Test Author", + "url": "https://minimal.com", + "category": "minimal,library", + "sentence": "A minimal test case", + "version": "1", + "prettyVersion": "1.0" + } + + +@pytest.fixture +def properties_with_extra_fields(): + """Data with extra fields (should be allowed due to extra='allow')""" + return { + "name": "Extra Fields Library", + "authors": "Extra Author", + "url": "https://extra.com", + "categories": "extra,fields", + "sentence": "Library with extra fields", + "version": "2", + "prettyVersion": "2.0", + "customField": "custom value", + "anotherExtra": "42" + } diff --git a/tests/unit/test_library_properties_new.py b/tests/unit/test_library_properties_new.py new file mode 100644 index 0000000..0483321 --- /dev/null +++ b/tests/unit/test_library_properties_new.py @@ -0,0 +1,191 @@ +import pytest +from pydantic import ValidationError +from scripts.parse_and_validate_properties_txt import LibraryPropertiesNew + + +# Test Cases +class TestLibraryPropertiesNew: + + def test_valid_complete_data(self, valid_properties_data): + """Test creation with all valid fields""" + props = LibraryPropertiesNew(**valid_properties_data) + + assert props.name == valid_properties_data['name'] + assert props.authors == valid_properties_data['authors'] + assert props.url == valid_properties_data['url'] + assert props.categories == valid_properties_data['categories'] + assert props.sentence == valid_properties_data['sentence'] + assert props.paragraph == valid_properties_data['paragraph'] + assert props.version == int(valid_properties_data['version']) + assert props.prettyVersion == valid_properties_data['prettyVersion'] + assert props.minRevision == int(valid_properties_data['minRevision']) + assert props.maxRevision == int(valid_properties_data['maxRevision']) + assert props.modes == valid_properties_data['modes'] + + + def test_valid_complete_data_aliases(self, valid_properties_data_aliases): + """Test creation with all valid fields""" + props = LibraryPropertiesNew(**valid_properties_data_aliases) + + assert props.name == valid_properties_data_aliases['name'] + assert props.authors == valid_properties_data_aliases['authorList'] + assert props.url == valid_properties_data_aliases['url'] + assert props.categories == valid_properties_data_aliases['category'] + assert props.sentence == valid_properties_data_aliases['sentence'] + assert props.paragraph == valid_properties_data_aliases['paragraph'] + assert props.version == int(valid_properties_data_aliases['version']) + assert props.prettyVersion == valid_properties_data_aliases['prettyVersion'] + assert props.minRevision == int(valid_properties_data_aliases['minRevision']) + assert props.maxRevision == int(valid_properties_data_aliases['maxRevision']) + assert props.modes == valid_properties_data_aliases['compatibleModesList'] + + + def test_minimal_required_data(self, minimal_properties_library_data): + """Test creation with only required fields""" + props = LibraryPropertiesNew(**minimal_properties_library_data) + + assert props.name == minimal_properties_library_data['name'] + assert props.authors == minimal_properties_library_data['authors'] + assert props.url == minimal_properties_library_data['url'] + assert props.categories == minimal_properties_library_data['categories'] + assert props.sentence == minimal_properties_library_data['sentence'] + assert props.paragraph is None + assert props.version == int(minimal_properties_library_data['version']) + assert props.prettyVersion == minimal_properties_library_data['prettyVersion'] + assert props.minRevision == 0 # Default value + assert props.maxRevision == 0 # Default value + assert props.modes is None + + + def test_minimal_required_data_aliases(self, minimal_properties_library_data_aliases): + """Test creation with only required fields""" + props = LibraryPropertiesNew(**minimal_properties_library_data_aliases) + + assert props.name == minimal_properties_library_data_aliases['name'] + assert props.authors == minimal_properties_library_data_aliases['authorList'] + assert props.url == minimal_properties_library_data_aliases['url'] + assert props.categories == minimal_properties_library_data_aliases['category'] + assert props.sentence == minimal_properties_library_data_aliases['sentence'] + assert props.paragraph is None + assert props.version == int(minimal_properties_library_data_aliases['version']) + assert props.prettyVersion == minimal_properties_library_data_aliases['prettyVersion'] + assert props.minRevision == 0 # Default value + assert props.maxRevision == 0 # Default value + assert props.modes is None + + + def test_extra_fields_allowed(self, properties_with_extra_fields): + """Test that extra fields are allowed due to extra='allow'""" + props = LibraryPropertiesNew(**properties_with_extra_fields) + + assert props.name == properties_with_extra_fields['name'] + assert props.customField == properties_with_extra_fields['customField'] + assert props.anotherExtra == properties_with_extra_fields['anotherExtra'] + + + def test_missing_required_field_name(self, minimal_properties_library_data): + """Test validation error when required field 'name' is missing""" + minimal_properties_library_data.pop("name") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "name" in str(exc_info.value) + assert "Field required" in str(exc_info.value) + + + def test_missing_required_field_authors(self, minimal_properties_library_data): + """Test validation error when required field 'authors' is missing""" + minimal_properties_library_data.pop("authors") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "authors" in str(exc_info.value) + + + def test_missing_required_field_url(self, minimal_properties_library_data): + """Test validation error when required field 'url' is missing""" + minimal_properties_library_data.pop("url") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "url" in str(exc_info.value) + + + def test_missing_required_field_categories(self, minimal_properties_library_data): + """Test validation error when required field 'categories' is missing""" + minimal_properties_library_data.pop("categories") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "categories" in str(exc_info.value) + + + def test_missing_required_field_sentence(self, minimal_properties_library_data): + """Test validation error when required field 'sentence' is missing""" + minimal_properties_library_data.pop("sentence") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "sentence" in str(exc_info.value) + + + def test_missing_required_field_version(self, minimal_properties_library_data): + """Test validation error when required field 'version' is missing""" + minimal_properties_library_data.pop("version") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "version" in str(exc_info.value) + + + def test_missing_required_field_pretty_version(self, minimal_properties_library_data): + """Test validation error when required field 'prettyVersion' is missing""" + minimal_properties_library_data.pop("prettyVersion") + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "prettyVersion" in str(exc_info.value) + + + def test_invalid_type_version(self, minimal_properties_library_data): + """Test validation error when 'version' has wrong type""" + minimal_properties_library_data["version"] = "not_an_int" + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "version" in str(exc_info.value) + assert "Input should be a valid integer" in str(exc_info.value) + + + def test_invalid_type_min_revision(self, minimal_properties_library_data): + """Test validation error when 'minRevision' has wrong type""" + minimal_properties_library_data["minRevision"] = "not_an_int" + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "minRevision" in str(exc_info.value) + assert "Input should be a valid integer" in str(exc_info.value) + + + def test_invalid_type_max_revision(self, minimal_properties_library_data): + """Test validation error when 'maxRevision' has wrong type""" + minimal_properties_library_data["maxRevision"] = "not_an_int" + with pytest.raises(ValidationError) as exc_info: + LibraryPropertiesNew(**minimal_properties_library_data) + + assert "maxRevision" in str(exc_info.value) + assert "Input should be a valid integer" in str(exc_info.value) + + + def test_model_dump(self, valid_properties_data): + """Test that model serialization works correctly""" + props = LibraryPropertiesNew(**valid_properties_data) + dumped = props.model_dump() + + assert dumped["name"] == "Test Library" + assert dumped["modes"] == "standard,debug" + assert "compatibleModesList" not in dumped # Should use field name, not alias + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/unit/test_properties_base.py b/tests/unit/test_properties_base.py new file mode 100644 index 0000000..8e8495a --- /dev/null +++ b/tests/unit/test_properties_base.py @@ -0,0 +1,188 @@ +import pytest +from pydantic import ValidationError +from scripts.parse_and_validate_properties_txt import PropertiesBase + + +# Test Cases +class TestPropertiesBase: + + def test_valid_complete_data(self, valid_properties_data): + """Test creation with all valid fields""" + props = PropertiesBase(**valid_properties_data) + + assert props.name == valid_properties_data['name'] + assert props.authors == valid_properties_data['authors'] + assert props.url == valid_properties_data['url'] + assert props.categories == valid_properties_data['categories'] + assert props.sentence == valid_properties_data['sentence'] + assert props.paragraph == valid_properties_data['paragraph'] + assert props.version == int(valid_properties_data['version']) + assert props.prettyVersion == valid_properties_data['prettyVersion'] + assert props.minRevision == int(valid_properties_data['minRevision']) + assert props.maxRevision == int(valid_properties_data['maxRevision']) + assert props.modes == valid_properties_data['modes'] + + + def test_valid_complete_data_aliases(self, valid_properties_data_aliases): + """Test creation with all valid fields""" + props = PropertiesBase(**valid_properties_data_aliases) + + assert props.name == valid_properties_data_aliases['name'] + assert props.authors == valid_properties_data_aliases['authorList'] + assert props.url == valid_properties_data_aliases['url'] + assert props.categories == valid_properties_data_aliases['category'] + assert props.sentence == valid_properties_data_aliases['sentence'] + assert props.paragraph == valid_properties_data_aliases['paragraph'] + assert props.version == int(valid_properties_data_aliases['version']) + assert props.prettyVersion == valid_properties_data_aliases['prettyVersion'] + assert props.minRevision == int(valid_properties_data_aliases['minRevision']) + assert props.maxRevision == int(valid_properties_data_aliases['maxRevision']) + assert props.modes == valid_properties_data_aliases['compatibleModesList'] + + + def test_minimal_required_data(self, minimal_properties_base_data): + """Test creation with only required fields""" + props = PropertiesBase(**minimal_properties_base_data) + + assert props.name == minimal_properties_base_data['name'] + assert props.authors == minimal_properties_base_data['authors'] + assert props.url == minimal_properties_base_data['url'] + assert props.categories is None + assert props.sentence == minimal_properties_base_data['sentence'] + assert props.paragraph is None + assert props.version == int(minimal_properties_base_data['version']) + assert props.prettyVersion == minimal_properties_base_data['prettyVersion'] + assert props.minRevision == 0 # Default value + assert props.maxRevision == 0 # Default value + assert props.modes is None + + + def test_minimal_required_data_aliases(self, minimal_properties_base_data_aliases): + """Test creation with only required fields""" + props = PropertiesBase(**minimal_properties_base_data_aliases) + + assert props.name == minimal_properties_base_data_aliases['name'] + assert props.authors == minimal_properties_base_data_aliases['authorList'] + assert props.url == minimal_properties_base_data_aliases['url'] + assert props.categories is None + assert props.sentence == minimal_properties_base_data_aliases['sentence'] + assert props.paragraph is None + assert props.version == int(minimal_properties_base_data_aliases['version']) + assert props.prettyVersion == minimal_properties_base_data_aliases['prettyVersion'] + assert props.minRevision == 0 # Default value + assert props.maxRevision == 0 # Default value + assert props.modes is None + + + def test_extra_fields_allowed(self, properties_with_extra_fields): + """Test that extra fields are allowed due to extra='allow'""" + props = PropertiesBase(**properties_with_extra_fields) + + assert props.name == properties_with_extra_fields['name'] + assert props.customField == properties_with_extra_fields['customField'] + assert props.anotherExtra == properties_with_extra_fields['anotherExtra'] + + + def test_missing_required_field_name(self, minimal_properties_base_data): + """Test validation error when required field 'name' is missing""" + minimal_properties_base_data.pop("name") + + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "name" in str(exc_info.value) + assert "Field required" in str(exc_info.value) + + + def test_missing_required_field_authors(self, minimal_properties_base_data): + """Test validation error when required field 'authors' is missing""" + minimal_properties_base_data.pop("authors") + + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "authors" in str(exc_info.value) + + + def test_missing_required_field_url(self, minimal_properties_base_data): + """Test validation error when required field 'url' is missing""" + minimal_properties_base_data.pop("url") + + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "url" in str(exc_info.value) + + + def test_missing_required_field_sentence(self, minimal_properties_base_data): + """Test validation error when required field 'sentence' is missing""" + minimal_properties_base_data.pop("sentence") + + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "sentence" in str(exc_info.value) + + + def test_missing_required_field_version(self, minimal_properties_base_data): + """Test validation error when required field 'version' is missing""" + minimal_properties_base_data.pop("version") + + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "version" in str(exc_info.value) + + + def test_missing_required_field_pretty_version(self, minimal_properties_base_data): + """Test validation error when required field 'prettyVersion' is missing""" + minimal_properties_base_data.pop("prettyVersion") + + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "prettyVersion" in str(exc_info.value) + + + def test_invalid_type_version(self, minimal_properties_base_data): + """Test validation error when 'version' has wrong type""" + minimal_properties_base_data["version"] = "not_an_int" + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "version" in str(exc_info.value) + assert "Input should be a valid integer" in str(exc_info.value) + + + def test_invalid_type_min_revision(self, minimal_properties_base_data): + """Test validation error when 'minRevision' has wrong type""" + minimal_properties_base_data["minRevision"] = "not_an_int" + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "minRevision" in str(exc_info.value) + assert "Input should be a valid integer" in str(exc_info.value) + + + def test_invalid_type_max_revision(self, minimal_properties_base_data): + """Test validation error when 'maxRevision' has wrong type""" + minimal_properties_base_data["maxRevision"] = "not_an_int" + with pytest.raises(ValidationError) as exc_info: + PropertiesBase(**minimal_properties_base_data) + + assert "maxRevision" in str(exc_info.value) + assert "Input should be a valid integer" in str(exc_info.value) + + + def test_model_dump(self, valid_properties_data): + """Test that model serialization works correctly""" + props = PropertiesBase(**valid_properties_data) + dumped = props.model_dump() + + assert dumped["name"] == "Test Library" + assert dumped["modes"] == "standard,debug" + assert "compatibleModesList" not in dumped # Should use field name, not alias + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/unit/test_properties_existing.py b/tests/unit/test_properties_existing.py new file mode 100644 index 0000000..1c05bf0 --- /dev/null +++ b/tests/unit/test_properties_existing.py @@ -0,0 +1,169 @@ +import pytest +from pydantic import ValidationError +from scripts.parse_and_validate_properties_txt import PropertiesExisting + + +# Test Cases +class TestPropertiesExisting: + def test_valid_complete_data(self, valid_properties_data): + """Test creation with all valid fields""" + props = PropertiesExisting(**valid_properties_data) + + assert props.name == valid_properties_data['name'] + assert props.authors == valid_properties_data['authors'] + assert props.url == valid_properties_data['url'] + assert props.categories == valid_properties_data['categories'] + assert props.sentence == valid_properties_data['sentence'] + assert props.paragraph == valid_properties_data['paragraph'] + assert props.version == valid_properties_data['version'] + assert props.prettyVersion == valid_properties_data['prettyVersion'] + assert props.minRevision == int(valid_properties_data['minRevision']) + assert props.maxRevision == int(valid_properties_data['maxRevision']) + assert props.modes == valid_properties_data['modes'] + + + def test_valid_complete_data_aliases(self, valid_properties_data_aliases): + """Test creation with all valid fields""" + props = PropertiesExisting(**valid_properties_data_aliases) + + assert props.name == valid_properties_data_aliases['name'] + assert props.authors == valid_properties_data_aliases['authorList'] + assert props.url == valid_properties_data_aliases['url'] + assert props.categories == valid_properties_data_aliases['category'] + assert props.sentence == valid_properties_data_aliases['sentence'] + assert props.paragraph == valid_properties_data_aliases['paragraph'] + assert props.version == valid_properties_data_aliases['version'] + assert props.prettyVersion == valid_properties_data_aliases['prettyVersion'] + assert props.minRevision == int(valid_properties_data_aliases['minRevision']) + assert props.maxRevision == int(valid_properties_data_aliases['maxRevision']) + assert props.modes == valid_properties_data_aliases['compatibleModesList'] + + + def test_minimal_required_data(self, minimal_properties_existing_data): + """Test creation with only required fields""" + props = PropertiesExisting(**minimal_properties_existing_data) + + assert props.name == minimal_properties_existing_data['name'] + assert props.authors == minimal_properties_existing_data['authors'] + assert props.url == minimal_properties_existing_data['url'] + assert props.categories is None + assert props.sentence == minimal_properties_existing_data['sentence'] + assert props.paragraph is None + assert props.version == minimal_properties_existing_data['version'] + assert props.prettyVersion is None + assert props.minRevision == 0 # Default value + assert props.maxRevision == 0 # Default value + assert props.modes is None + + + def test_minimal_required_data_alias(self, minimal_properties_existing_data_aliases): + """Test creation with only required fields""" + props = PropertiesExisting(**minimal_properties_existing_data_aliases) + + assert props.name == minimal_properties_existing_data_aliases['name'] + assert props.authors == minimal_properties_existing_data_aliases['authorList'] + assert props.url == minimal_properties_existing_data_aliases['url'] + assert props.categories is None + assert props.sentence == minimal_properties_existing_data_aliases['sentence'] + assert props.paragraph is None + assert props.version == minimal_properties_existing_data_aliases['version'] + assert props.prettyVersion is None + assert props.minRevision == 0 # Default value + assert props.maxRevision == 0 # Default value + assert props.modes is None + + + def test_extra_fields_allowed(self, properties_with_extra_fields): + """Test that extra fields are allowed due to extra='allow'""" + props = PropertiesExisting(**properties_with_extra_fields) + + assert props.name == properties_with_extra_fields['name'] + assert props.customField == properties_with_extra_fields['customField'] + assert props.anotherExtra == properties_with_extra_fields['anotherExtra'] + + + def test_missing_required_field_name(self, minimal_properties_existing_data): + """Test validation error when required field 'name' is missing""" + minimal_properties_existing_data.pop("name") + + with pytest.raises(ValidationError) as exc_info: + PropertiesExisting(**minimal_properties_existing_data) + + assert "name" in str(exc_info.value) + assert "Field required" in str(exc_info.value) + + + def test_missing_required_field_authors(self, minimal_properties_existing_data): + """Test validation error when required field 'authors' is missing""" + minimal_properties_existing_data.pop("authors") + + with pytest.raises(ValidationError) as exc_info: + PropertiesExisting(**minimal_properties_existing_data) + + assert "authors" in str(exc_info.value) + + + def test_missing_required_field_url(self, minimal_properties_existing_data): + """Test validation error when required field 'url' is missing""" + minimal_properties_existing_data.pop("url") + + with pytest.raises(ValidationError) as exc_info: + PropertiesExisting(**minimal_properties_existing_data) + + assert "url" in str(exc_info.value) + + + def test_missing_required_field_sentence(self, minimal_properties_existing_data): + """Test validation error when required field 'sentence' is missing""" + minimal_properties_existing_data.pop("sentence") + + with pytest.raises(ValidationError) as exc_info: + PropertiesExisting(**minimal_properties_existing_data) + + assert "sentence" in str(exc_info.value) + + + def test_missing_required_field_version(self, minimal_properties_existing_data): + """Test validation error when required field 'version' is missing""" + minimal_properties_existing_data.pop("version") + + with pytest.raises(ValidationError) as exc_info: + PropertiesExisting(**minimal_properties_existing_data) + + assert "version" in str(exc_info.value) + + + def test_string_type_version(self, minimal_properties_existing_data): + """Test validation when 'version' is string""" + minimal_properties_existing_data["version"] = "not_an_int" + props = PropertiesExisting(**minimal_properties_existing_data) + assert props.version == minimal_properties_existing_data['version'] + + + def test_string_type_min_revision(self, minimal_properties_existing_data): + """Test when 'minRevision' string defaults to 0""" + minimal_properties_existing_data["minRevision"] = "not_an_int" + props =PropertiesExisting(**minimal_properties_existing_data) + assert props.minRevision == 0 + + + def test_string_type_max_revision(self, minimal_properties_existing_data): + """Test when 'maxRevision' string defaults to 0""" + minimal_properties_existing_data["maxRevision"] = "not_an_int" + props =PropertiesExisting(**minimal_properties_existing_data) + assert props.maxRevision == 0 + + + def test_model_dump(self, valid_properties_data): + """Test that model serialization works correctly""" + props = PropertiesExisting(**valid_properties_data) + dumped = props.model_dump() + + assert dumped["name"] == "Test Library" + assert dumped["modes"] == "standard,debug" + assert "compatibleModesList" not in dumped # Should use field name, not alias + + + +if __name__ == "__main__": + pytest.main([__file__, "-v"])