Skip to content

Commit 6706d7f

Browse files
committed
notes(test-audit): Update test plan and TODO to reflect Pydantic implementation progress
1 parent 7ae7582 commit 6706d7f

File tree

2 files changed

+341
-58
lines changed

2 files changed

+341
-58
lines changed

notes/2025-03-08 - test-audit - test plan.md

Lines changed: 276 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ All code examples in this plan follow these guidelines and must be maintained th
5959
### A. Enhance Exception Handling
6060

6161
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`:
6363
```python
6464
import enum
6565
import typing as t
@@ -3676,3 +3676,278 @@ When implementing Pydantic models, follow these guidelines:
36763676
- Examples for all major features
36773677

36783678
## 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

Comments
 (0)