@@ -59,7 +59,7 @@ All code examples in this plan follow these guidelines and must be maintained th
59
59
# ## A. Enhance Exception Handling
60
60
61
61
1. **Create Specific Exception Types**
62
- - Create a hierarchy of exceptions with specific subtypes in `src/vcspull/exc.py`:
62
+ - ✓ Create a hierarchy of exceptions with specific subtypes in `src/vcspull/exc.py`:
63
63
```python
64
64
import enum
65
65
import typing as t
@@ -3676,3 +3676,278 @@ When implementing Pydantic models, follow these guidelines:
3676
3676
- Examples for all major features
3677
3677
3678
3678
# # 3. Additional Tests to Add
3679
+
3680
+ # ## 11. Testing Pydantic Models and Validators
3681
+
3682
+ 1. **✓ Basic Model Validation Tests**
3683
+ - ✓ Add tests for `RepositoryModel` validation:
3684
+ ```python
3685
+ import pytest
3686
+ import typing as t
3687
+
3688
+ from vcspull.schemas import RepositoryModel
3689
+
3690
+ def test_repository_model_valid():
3691
+ """Test valid repository model."""
3692
+ # Create a valid model
3693
+ repo = RepositoryModel(
3694
+ vcs="git",
3695
+ name="test-repo",
3696
+ path="/path/to/repo",
3697
+ url="https://github.com/user/repo",
3698
+ )
3699
+
3700
+ # Verify basic attributes
3701
+ assert repo.vcs == "git"
3702
+ assert repo.name == "test-repo"
3703
+ assert str(repo.path).endswith("/path/to/repo")
3704
+ assert repo.url == "https://github.com/user/repo"
3705
+
3706
+ def test_repository_model_invalid_vcs():
3707
+ """Test invalid VCS type."""
3708
+ with pytest.raises(ValueError) as excinfo:
3709
+ RepositoryModel(
3710
+ vcs="invalid",
3711
+ name="test-repo",
3712
+ path="/path/to/repo",
3713
+ url="https://github.com/user/repo",
3714
+ )
3715
+
3716
+ # Verify error message
3717
+ assert "Invalid VCS type" in str(excinfo.value)
3718
+ ```
3719
+
3720
+ 2. **Pending: Path Validation Tests**
3721
+ - Create tests for path validation and normalization:
3722
+ ```python
3723
+ import os
3724
+ import pathlib
3725
+
3726
+ def test_repository_model_path_expansion():
3727
+ """Test path expansion in repository model."""
3728
+ # Test with environment variables
3729
+ os.environ["TEST_PATH"] = "/test/path"
3730
+ repo = RepositoryModel(
3731
+ vcs="git",
3732
+ name="test-repo",
3733
+ path="${TEST_PATH}/repo",
3734
+ url="https://github.com/user/repo",
3735
+ )
3736
+
3737
+ # Verify path expansion
3738
+ assert str(repo.path) == "/test/path/repo"
3739
+
3740
+ # Test with tilde expansion
3741
+ repo = RepositoryModel(
3742
+ vcs="git",
3743
+ name="test-repo",
3744
+ path="~/repo",
3745
+ url="https://github.com/user/repo",
3746
+ )
3747
+
3748
+ # Verify tilde expansion
3749
+ assert str(repo.path) == str(pathlib.Path.home() / "repo")
3750
+ ```
3751
+
3752
+ 3. **Pending: URL Validation Tests**
3753
+ - Test different URL formats and validation:
3754
+ ```python
3755
+ def test_repository_model_url_validation():
3756
+ """Test URL validation in repository model."""
3757
+ # Test valid URLs
3758
+ valid_urls = [
3759
+ "https://github.com/user/repo",
3760
+ "git@github.com:user/repo.git",
3761
+ "file:///path/to/repo",
3762
+ ]
3763
+
3764
+ for url in valid_urls:
3765
+ repo = RepositoryModel(
3766
+ vcs =" git" ,
3767
+ name =" test-repo" ,
3768
+ path =" /path/to/repo" ,
3769
+ url =url,
3770
+ )
3771
+ assert repo.url == url
3772
+
3773
+ # Test invalid URLs
3774
+ invalid_urls = [" " , " " ]
3775
+
3776
+ for url in invalid_urls:
3777
+ with pytest.raises(ValueError) as excinfo:
3778
+ RepositoryModel(
3779
+ vcs =" git" ,
3780
+ name =" test-repo" ,
3781
+ path =" /path/to/repo" ,
3782
+ url =url,
3783
+ )
3784
+ assert "URL cannot be empty" in str(excinfo.value)
3785
+ ```
3786
+
3787
+ 4. **Pending: Configuration Dict Model Tests**
3788
+ - Test the dictionary-like behavior of config models:
3789
+ ```python
3790
+ from vcspull.schemas import ConfigSectionDictModel, RepositoryModel
3791
+
3792
+ def test_config_section_dict_model():
3793
+ """Test ConfigSectionDictModel behavior."""
3794
+ # Create repository models
3795
+ repo1 = RepositoryModel(
3796
+ vcs =" git" ,
3797
+ name =" repo1" ,
3798
+ path =" /path/to/repo1" ,
3799
+ url =" https://github.com/user/repo1" ,
3800
+ )
3801
+
3802
+ repo2 = RepositoryModel(
3803
+ vcs =" git" ,
3804
+ name =" repo2" ,
3805
+ path =" /path/to/repo2" ,
3806
+ url =" https://github.com/user/repo2" ,
3807
+ )
3808
+
3809
+ # Create section model
3810
+ section = ConfigSectionDictModel(root={"repo1": repo1, "repo2": repo2})
3811
+
3812
+ # Test dictionary-like access
3813
+ assert section["repo1"] == repo1
3814
+ assert section["repo2"] == repo2
3815
+
3816
+ # Test keys, values, items
3817
+ assert set(section.keys()) == {"repo1", "repo2"}
3818
+ assert list(section.values()) == [repo1, repo2]
3819
+ assert dict(section.items()) == {"repo1": repo1, "repo2": repo2}
3820
+ ```
3821
+
3822
+ 5. **Pending: Raw to Validated Conversion Tests**
3823
+ - Test conversion from raw to validated models:
3824
+ ```python
3825
+ from vcspull.schemas import (
3826
+ RawConfigDictModel,
3827
+ convert_raw_to_validated,
3828
+ )
3829
+
3830
+ def test_convert_raw_to_validated():
3831
+ """Test conversion from raw to validated models."""
3832
+ # Create raw config
3833
+ raw_config = RawConfigDictModel(root={
3834
+ "section1": {
3835
+ "repo1": {
3836
+ "vcs": "git",
3837
+ "name": "repo1",
3838
+ "path": "/path/to/repo1",
3839
+ "url": "https://github.com/user/repo1",
3840
+ },
3841
+ "repo2": "https://github.com/user/repo2", # Shorthand URL
3842
+ }
3843
+ })
3844
+
3845
+ # Convert to validated config
3846
+ validated = convert_raw_to_validated(raw_config)
3847
+
3848
+ # Verify structure
3849
+ assert "section1" in validated.root
3850
+ assert "repo1" in validated["section1"].root
3851
+ assert "repo2" in validated["section1"].root
3852
+
3853
+ # Verify expanded shorthand URL
3854
+ assert validated["section1"]["repo2"].url == "https://github.com/user/repo2"
3855
+ assert validated["section1"]["repo2"].name == "repo2"
3856
+ ```
3857
+
3858
+ 6. **Pending: Integration with CLI Tests**
3859
+ - Test CLI commands with Pydantic models:
3860
+ ```python
3861
+ def test_cli_with_pydantic_models(runner, tmp_path):
3862
+ """Test CLI commands with Pydantic models."""
3863
+ # Create a test config file with valid and invalid entries
3864
+ config_file = tmp_path / "config.yaml"
3865
+ config_file.write_text("""
3866
+ section1:
3867
+ repo1:
3868
+ vcs: git
3869
+ name: repo1
3870
+ path: {tmp_path}/repo1
3871
+ url: https://github.com/user/repo1
3872
+ repo2:
3873
+ vcs: invalid # Invalid VCS type
3874
+ name: repo2
3875
+ path: {tmp_path}/repo2
3876
+ url: https://github.com/user/repo2
3877
+ """.format(tmp_path=tmp_path))
3878
+
3879
+ # Run CLI command with the config file
3880
+ result = runner.invoke(cli, ["sync", "--config", str(config_file)])
3881
+
3882
+ # Verify that the valid repository is processed
3883
+ assert "Processing repository repo1" in result.output
3884
+
3885
+ # Verify that the invalid repository is reported with a Pydantic error
3886
+ assert "Invalid VCS type: invalid" in result.output
3887
+ ```
3888
+
3889
+ 7. **Pending: Error Handling in Models**
3890
+ - Test error handling and error formatting:
3891
+ ```python
3892
+ from vcspull.validator import format_pydantic_errors
3893
+ from pydantic import ValidationError
3894
+
3895
+ def test_format_pydantic_errors():
3896
+ """Test formatting of Pydantic validation errors."""
3897
+ try:
3898
+ RepositoryModel(
3899
+ vcs =" invalid" ,
3900
+ name =" " , # Empty name
3901
+ path =" " , # Empty path
3902
+ url =" " , # Empty URL
3903
+ )
3904
+ except ValidationError as e:
3905
+ # Format the error
3906
+ error_msg = format_pydantic_errors(e)
3907
+
3908
+ # Verify formatted error message
3909
+ assert "vcs: Invalid VCS type" in error_msg
3910
+ assert "name: " in error_msg
3911
+ assert "path: " in error_msg
3912
+ assert "url: URL cannot be empty" in error_msg
3913
+ ```
3914
+
3915
+ 8. **Pending: Advanced Validation Tests**
3916
+ - Create tests for more complex validation scenarios:
3917
+ ```python
3918
+ def test_repository_model_with_remotes():
3919
+ """Test repository model with Git remotes."""
3920
+ from vcspull.schemas import GitRemote
3921
+
3922
+ # Create Git remotes
3923
+ remotes = {
3924
+ "origin": GitRemote(
3925
+ name =" origin" ,
3926
+ url =" https://github.com/user/repo" ,
3927
+ fetch =" +refs/heads/*:refs/remotes/origin/*" ,
3928
+ push =" refs/heads/*:refs/heads/*" ,
3929
+ ),
3930
+ "upstream": GitRemote(
3931
+ name =" upstream" ,
3932
+ url =" https://github.com/upstream/repo" ,
3933
+ ),
3934
+ }
3935
+
3936
+ # Create repository with remotes
3937
+ repo = RepositoryModel(
3938
+ vcs =" git" ,
3939
+ name =" test-repo" ,
3940
+ path =" /path/to/repo" ,
3941
+ url =" https://github.com/user/repo" ,
3942
+ remotes =remotes,
3943
+ )
3944
+
3945
+ # Verify remotes
3946
+ assert repo.remotes is not None
3947
+ assert "origin" in repo.remotes
3948
+ assert "upstream" in repo.remotes
3949
+ assert repo.remotes["origin"].url == "https://github.com/user/repo"
3950
+ assert repo.remotes["upstream"].url == "https://github.com/upstream/repo"
3951
+ ```
3952
+
3953
+ # # 12. Performance Testing
0 commit comments