# #region Test.GitSchemas [C:2] [TYPE Module] [SEMANTICS test,git,schemas,pydantic] # @BRIEF Pydantic model validation tests for all git_schemas.py models. # @RELATION BINDS_TO -> [GitSchemas] # @TEST_CONTRACT: GitServerConfigBase -> validates all required fields # @TEST_CONTRACT: GitServerConfigCreate -> extends base with optional config_id # @TEST_CONTRACT: GitServerConfigUpdate -> all fields optional for partial update # @TEST_CONTRACT: GitServerConfigSchema -> includes id, status, last_validated # @TEST_CONTRACT: GitRepositorySchema -> tracks repository binding metadata # @TEST_CONTRACT: BranchSchema -> validates name, commit_hash, is_remote # @TEST_CONTRACT: CommitSchema -> validates full commit metadata # @TEST_CONTRACT: BranchCreate -> name + from_branch required # @TEST_CONTRACT: ConflictResolution -> validates resolution pattern ^(mine|theirs|manual)$ # @TEST_CONTRACT: MergeStatusSchema -> validates merge state defaults # @TEST_CONTRACT: MergeConflictFileSchema -> file_path required, mine/theirs optional # @TEST_CONTRACT: MergeResolveRequest -> wraps ConflictResolution list # @TEST_CONTRACT: MergeContinueRequest -> optional message # @TEST_CONTRACT: DeployRequest -> environment_id required # @TEST_CONTRACT: RepoInitRequest -> config_id + remote_url required # @TEST_CONTRACT: RepositoryBindingSchema -> all fields required # @TEST_CONTRACT: RepoStatusBatchRequest -> optional dashboard_ids list # @TEST_CONTRACT: RepoStatusBatchResponse -> statuses dict[str, dict] # @TEST_CONTRACT: PromoteRequest -> validates mode pattern ^(mr|direct)$ # @TEST_CONTRACT: PromoteResponse -> all fields with defaults # @TEST_CONTRACT: GiteaRepoSchema, RemoteRepoSchema -> remote repo descriptors # @TEST_CONTRACT: GiteaRepoCreateRequest, RemoteRepoCreateRequest -> create payloads # @TEST_EDGE: missing_required_field -> pydantic.ValidationError # @TEST_EDGE: invalid_resolution_pattern -> pattern mismatch # @TEST_EDGE: invalid_mode_pattern -> pattern mismatch # @TEST_EDGE: empty_name -> max_length=255 but min_length=1 enforced from datetime import datetime, timezone from enum import Enum import pytest from pydantic import ValidationError class TestGitServerConfigBase: """GitServerConfigBase — base configuration schema.""" def test_minimal_valid(self): from src.api.routes.git_schemas import GitServerConfigBase from src.models.git import GitProvider obj = GitServerConfigBase( name="My Git Server", provider=GitProvider.GITHUB, url="https://github.com", pat="ghp_test123", ) assert obj.name == "My Git Server" assert obj.provider == GitProvider.GITHUB assert obj.url == "https://github.com" assert obj.pat == "ghp_test123" assert obj.default_repository is None assert obj.default_branch == "prod" def test_missing_required_fields(self): from src.api.routes.git_schemas import GitServerConfigBase with pytest.raises(ValidationError): GitServerConfigBase() def test_missing_pat(self): from src.api.routes.git_schemas import GitServerConfigBase from src.models.git import GitProvider with pytest.raises(ValidationError): GitServerConfigBase( name="Test", provider=GitProvider.GITHUB, url="https://github.com", ) def test_with_defaults(self): from src.api.routes.git_schemas import GitServerConfigBase from src.models.git import GitProvider obj = GitServerConfigBase( name="Test", provider=GitProvider.GITLAB, url="https://gitlab.com", pat="glpat-abc", default_repository="org/repo", default_branch="develop", ) assert obj.default_repository == "org/repo" assert obj.default_branch == "develop" def test_duplicate_pat_field(self): """The source has `pat: str` declared twice (lines 23-24). Verify it works.""" from src.api.routes.git_schemas import GitServerConfigBase from src.models.git import GitProvider obj = GitServerConfigBase( name="Dup", provider=GitProvider.GITEA, url="https://gitea.dev", pat="token123" ) assert obj.pat == "token123" class TestGitServerConfigCreate: """GitServerConfigCreate — creation schema extends base.""" def test_valid(self): from src.api.routes.git_schemas import GitServerConfigCreate from src.models.git import GitProvider obj = GitServerConfigCreate( name="New Server", provider=GitProvider.GITHUB, url="https://github.com", pat="ghp_new", config_id="cfg-123", ) assert obj.config_id == "cfg-123" def test_without_config_id(self): from src.api.routes.git_schemas import GitServerConfigCreate from src.models.git import GitProvider obj = GitServerConfigCreate( name="New Server", provider=GitProvider.GITHUB, url="https://github.com", pat="ghp_new", ) assert obj.config_id is None class TestGitServerConfigUpdate: """GitServerConfigUpdate — all fields optional for partial update.""" def test_empty_update(self): from src.api.routes.git_schemas import GitServerConfigUpdate obj = GitServerConfigUpdate() assert obj.name is None assert obj.provider is None assert obj.url is None assert obj.pat is None assert obj.default_repository is None assert obj.default_branch is None def test_partial_update(self): from src.api.routes.git_schemas import GitServerConfigUpdate from src.models.git import GitProvider obj = GitServerConfigUpdate(name="Updated Name", provider=GitProvider.GITLAB) assert obj.name == "Updated Name" assert obj.provider == GitProvider.GITLAB assert obj.url is None class TestGitServerConfigSchema: """GitServerConfigSchema — includes id, status, last_validated.""" def test_valid(self): from src.api.routes.git_schemas import GitServerConfigSchema from src.models.git import GitProvider, GitStatus now = datetime.now(timezone.utc) obj = GitServerConfigSchema( id="cfg-1", name="Server", provider=GitProvider.GITHUB, url="https://github.com", pat="ghp_xxx", status=GitStatus.CONNECTED, last_validated=now, ) assert obj.id == "cfg-1" assert obj.status == GitStatus.CONNECTED assert obj.last_validated == now assert obj.model_config.get("from_attributes") is True class TestGitRepositorySchema: """GitRepositorySchema — repository binding.""" def test_valid(self): from src.api.routes.git_schemas import GitRepositorySchema from src.models.git import SyncStatus obj = GitRepositorySchema( id="repo-1", dashboard_id=42, config_id="cfg-1", remote_url="https://github.com/org/repo.git", local_path="/data/repos/dash-42", current_branch="main", sync_status=SyncStatus.CLEAN, ) assert obj.dashboard_id == 42 assert obj.sync_status == SyncStatus.CLEAN assert obj.model_config.get("from_attributes") is True class TestBranchSchema: """BranchSchema — branch metadata.""" def test_valid(self): from src.api.routes.git_schemas import BranchSchema now = datetime.now(timezone.utc) obj = BranchSchema( name="feature/test", commit_hash="abc123def456", is_remote=False, last_updated=now, ) assert obj.name == "feature/test" assert obj.commit_hash == "abc123def456" assert obj.is_remote is False class TestCommitSchema: """CommitSchema — commit details.""" def test_valid(self): from src.api.routes.git_schemas import CommitSchema now = datetime.now(timezone.utc) obj = CommitSchema( hash="a1b2c3d4", author="Test User", email="test@example.com", timestamp=now, message="Fix dashboard layout", files_changed=["dashboard.json", "chart.yaml"], ) assert obj.hash == "a1b2c3d4" assert len(obj.files_changed) == 2 def test_empty_files(self): from src.api.routes.git_schemas import CommitSchema now = datetime.now(timezone.utc) obj = CommitSchema( hash="abc", author="A", email="a@b.com", timestamp=now, message="x", files_changed=[], ) assert obj.files_changed == [] class TestBranchCreate: """BranchCreate — branch creation request.""" def test_valid(self): from src.api.routes.git_schemas import BranchCreate obj = BranchCreate(name="feature/new", from_branch="main") assert obj.name == "feature/new" assert obj.from_branch == "main" def test_missing_name(self): from src.api.routes.git_schemas import BranchCreate with pytest.raises(ValidationError): BranchCreate(from_branch="main") # name missing class TestBranchCheckout: """BranchCheckout — checkout request.""" def test_valid(self): from src.api.routes.git_schemas import BranchCheckout obj = BranchCheckout(name="feature/test") assert obj.name == "feature/test" class TestCommitCreate: """CommitCreate — staging and committing.""" def test_valid(self): from src.api.routes.git_schemas import CommitCreate obj = CommitCreate(message="Updated dashboard", files=["dash.json"]) assert obj.message == "Updated dashboard" assert obj.files == ["dash.json"] class TestConflictResolution: """ConflictResolution — pattern validation for resolution field.""" def test_valid_resolutions(self): from src.api.routes.git_schemas import ConflictResolution for res in ("mine", "theirs", "manual"): obj = ConflictResolution(file_path="dash.json", resolution=res) assert obj.resolution == res def test_invalid_resolution(self): from src.api.routes.git_schemas import ConflictResolution with pytest.raises(ValidationError): ConflictResolution(file_path="dash.json", resolution="auto") def test_empty_resolution(self): from src.api.routes.git_schemas import ConflictResolution with pytest.raises(ValidationError): ConflictResolution(file_path="dash.json", resolution="") def test_with_content(self): from src.api.routes.git_schemas import ConflictResolution obj = ConflictResolution( file_path="dash.json", resolution="manual", content='{"fixed": true}' ) assert obj.content == '{"fixed": true}' class TestMergeStatusSchema: """MergeStatusSchema — merge state.""" def test_minimal(self): from src.api.routes.git_schemas import MergeStatusSchema obj = MergeStatusSchema( has_unfinished_merge=False, repository_path="/data/repos/dash-42", git_dir="/data/repos/dash-42/.git", current_branch="main", ) assert obj.has_unfinished_merge is False assert obj.conflicts_count == 0 assert obj.merge_head is None def test_with_conflicts(self): from src.api.routes.git_schemas import MergeStatusSchema obj = MergeStatusSchema( has_unfinished_merge=True, repository_path="/data/repos/dash-42", git_dir="/data/repos/dash-42/.git", current_branch="feature/x", merge_head="abc123", merge_message_preview="Merge branch 'feature/y'", conflicts_count=3, ) assert obj.has_unfinished_merge is True assert obj.merge_head == "abc123" assert obj.conflicts_count == 3 class TestMergeConflictFileSchema: """MergeConflictFileSchema — conflicted file with optional snapshots.""" def test_minimal(self): from src.api.routes.git_schemas import MergeConflictFileSchema obj = MergeConflictFileSchema(file_path="dash.json") assert obj.file_path == "dash.json" assert obj.mine is None assert obj.theirs is None def test_with_snapshots(self): from src.api.routes.git_schemas import MergeConflictFileSchema obj = MergeConflictFileSchema( file_path="chart.yaml", mine='{"v": 1}', theirs='{"v": 2}' ) assert obj.mine == '{"v": 1}' assert obj.theirs == '{"v": 2}' class TestMergeResolveRequest: """MergeResolveRequest — wraps conflict resolutions.""" def test_empty(self): from src.api.routes.git_schemas import MergeResolveRequest obj = MergeResolveRequest() assert obj.resolutions == [] def test_with_resolutions(self): from src.api.routes.git_schemas import MergeResolveRequest, ConflictResolution obj = MergeResolveRequest( resolutions=[ ConflictResolution(file_path="a.json", resolution="mine"), ConflictResolution(file_path="b.json", resolution="theirs"), ] ) assert len(obj.resolutions) == 2 class TestMergeContinueRequest: """MergeContinueRequest — optional commit message.""" def test_without_message(self): from src.api.routes.git_schemas import MergeContinueRequest obj = MergeContinueRequest() assert obj.message is None def test_with_message(self): from src.api.routes.git_schemas import MergeContinueRequest obj = MergeContinueRequest(message="Finish merge") assert obj.message == "Finish merge" class TestDeploymentEnvironmentSchema: """DeploymentEnvironmentSchema — deployment target.""" def test_valid(self): from src.api.routes.git_schemas import DeploymentEnvironmentSchema obj = DeploymentEnvironmentSchema( id="env-prod", name="Production", superset_url="https://superset.example.com", is_active=True, ) assert obj.id == "env-prod" assert obj.is_active is True assert obj.model_config.get("from_attributes") is True class TestDeployRequest: """DeployRequest — deployment request.""" def test_valid(self): from src.api.routes.git_schemas import DeployRequest obj = DeployRequest(environment_id="env-prod") assert obj.environment_id == "env-prod" def test_missing_environment_id(self): from src.api.routes.git_schemas import DeployRequest with pytest.raises(ValidationError): DeployRequest() class TestRepoInitRequest: """RepoInitRequest — repository initialization.""" def test_valid(self): from src.api.routes.git_schemas import RepoInitRequest obj = RepoInitRequest( config_id="cfg-1", remote_url="https://github.com/org/repo.git" ) assert obj.config_id == "cfg-1" assert obj.remote_url == "https://github.com/org/repo.git" class TestRepositoryBindingSchema: """RepositoryBindingSchema — binding descriptor.""" def test_valid(self): from src.api.routes.git_schemas import RepositoryBindingSchema from src.models.git import GitProvider obj = RepositoryBindingSchema( dashboard_id=42, config_id="cfg-1", provider=GitProvider.GITHUB, remote_url="https://github.com/org/repo.git", local_path="/data/repos/dash-42", ) assert obj.dashboard_id == 42 assert obj.provider == GitProvider.GITHUB class TestRepoStatusBatchRequest: """RepoStatusBatchRequest — batch status request.""" def test_default_empty(self): from src.api.routes.git_schemas import RepoStatusBatchRequest obj = RepoStatusBatchRequest() assert obj.dashboard_ids == [] def test_with_ids(self): from src.api.routes.git_schemas import RepoStatusBatchRequest obj = RepoStatusBatchRequest(dashboard_ids=[1, 2, 3]) assert obj.dashboard_ids == [1, 2, 3] class TestRepoStatusBatchResponse: """RepoStatusBatchResponse — batch status response.""" def test_valid(self): from src.api.routes.git_schemas import RepoStatusBatchResponse obj = RepoStatusBatchResponse( statuses={"42": {"branch": "main", "sync": "synced"}} ) assert obj.statuses["42"]["branch"] == "main" class TestPromoteRequest: """PromoteRequest — branch promotion with pattern validation.""" def test_valid_mr_mode(self): from src.api.routes.git_schemas import PromoteRequest obj = PromoteRequest(from_branch="feature/x", to_branch="main", mode="mr") assert obj.mode == "mr" assert obj.draft is False assert obj.remove_source_branch is False def test_valid_direct_mode(self): from src.api.routes.git_schemas import PromoteRequest obj = PromoteRequest(from_branch="hotfix", to_branch="prod", mode="direct") assert obj.mode == "direct" def test_invalid_mode(self): from src.api.routes.git_schemas import PromoteRequest with pytest.raises(ValidationError): PromoteRequest(from_branch="a", to_branch="b", mode="merge") def test_with_all_options(self): from src.api.routes.git_schemas import PromoteRequest obj = PromoteRequest( from_branch="feature/new", to_branch="staging", mode="mr", title="Promote new feature", description="Full description", reason="Business need", draft=True, remove_source_branch=True, ) assert obj.title == "Promote new feature" assert obj.draft is True assert obj.remove_source_branch is True def test_empty_from_branch(self): from src.api.routes.git_schemas import PromoteRequest with pytest.raises(ValidationError): PromoteRequest(from_branch="", to_branch="main", mode="mr") class TestPromoteResponse: """PromoteResponse — promotion result.""" def test_minimal(self): from src.api.routes.git_schemas import PromoteResponse obj = PromoteResponse( mode="mr", from_branch="feature/x", to_branch="main", status="merged", ) assert obj.url is None assert obj.reference_id is None assert obj.policy_violation is False def test_full(self): from src.api.routes.git_schemas import PromoteResponse obj = PromoteResponse( mode="direct", from_branch="hotfix", to_branch="prod", status="deployed", url="https://git.example.com/merge/42", reference_id="42", policy_violation=True, ) assert obj.url == "https://git.example.com/merge/42" assert obj.policy_violation is True class TestGiteaRepoSchema: """GiteaRepoSchema — Gitea repository descriptor.""" def test_minimal(self): from src.api.routes.git_schemas import GiteaRepoSchema obj = GiteaRepoSchema(name="my-repo", full_name="org/my-repo") assert obj.private is False assert obj.clone_url is None def test_full(self): from src.api.routes.git_schemas import GiteaRepoSchema obj = GiteaRepoSchema( name="my-repo", full_name="org/my-repo", private=True, clone_url="https://gitea.dev/org/my-repo.git", html_url="https://gitea.dev/org/my-repo", ssh_url="git@gitea.dev:org/my-repo.git", default_branch="main", ) assert obj.private is True assert obj.ssh_url == "git@gitea.dev:org/my-repo.git" class TestGiteaRepoCreateRequest: """GiteaRepoCreateRequest — create payload with validation.""" def test_minimal(self): from src.api.routes.git_schemas import GiteaRepoCreateRequest obj = GiteaRepoCreateRequest(name="new-repo") assert obj.private is True assert obj.auto_init is True assert obj.default_branch == "prod" def test_empty_name(self): from src.api.routes.git_schemas import GiteaRepoCreateRequest with pytest.raises(ValidationError): GiteaRepoCreateRequest(name="") def test_public_repo(self): from src.api.routes.git_schemas import GiteaRepoCreateRequest obj = GiteaRepoCreateRequest(name="public-repo", private=False) assert obj.private is False class TestRemoteRepoSchema: """RemoteRepoSchema — provider-agnostic remote repo.""" def test_minimal(self): from src.api.routes.git_schemas import RemoteRepoSchema from src.models.git import GitProvider obj = RemoteRepoSchema( provider=GitProvider.GITHUB, name="repo", full_name="org/repo" ) assert obj.provider == GitProvider.GITHUB def test_full(self): from src.api.routes.git_schemas import RemoteRepoSchema from src.models.git import GitProvider obj = RemoteRepoSchema( provider=GitProvider.GITLAB, name="repo", full_name="org/repo", private=True, clone_url="https://gitlab.com/org/repo.git", html_url="https://gitlab.com/org/repo", ssh_url="git@gitlab.com:org/repo.git", default_branch="main", ) assert obj.clone_url == "https://gitlab.com/org/repo.git" class TestRemoteRepoCreateRequest: """RemoteRepoCreateRequest — provider-agnostic create.""" def test_minimal(self): from src.api.routes.git_schemas import RemoteRepoCreateRequest obj = RemoteRepoCreateRequest(name="new-repo") assert obj.private is True assert obj.default_branch == "prod" def test_empty_name(self): from src.api.routes.git_schemas import RemoteRepoCreateRequest with pytest.raises(ValidationError): RemoteRepoCreateRequest(name="") def test_custom_branch(self): from src.api.routes.git_schemas import RemoteRepoCreateRequest obj = RemoteRepoCreateRequest(name="repo", default_branch="develop") assert obj.default_branch == "develop" # #endregion Test.GitSchemas