""" Tests for Repo Service. """ import base64 import pytest import time from pathlib import Path from app.models.repo import Repo from app.models.server import Server from app.services.repo_service import RepoService from app.services.server_service import ServerService from app.services.ssh_key_service import SshKeyService # Valid test SSH key VALID_SSH_KEY = """-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACB/pDNwjNcznNaRlLNF5G9hCQNjbqNZ7QeKyLIy/nvHAAAAJi/vqmQv6pk AAAAAtzc2gtZWQyNTUxOQAAACB/pDNwjNcznNaRlLNF5G9hCQNjbqNZ7QeKyLIy/nvHAA AAAEBD0cWNQnpLDUYEGNMSgVIApVJfCFuRfGG3uxJZRKLvqH+kM3CM1zOc1pGUssXkb2E JA2uuo1ntB4rIsjL+e8cAAAADm1lc3NlbmdlckBrZW50cm9zBAgMEBQ= -----END OPENSSH PRIVATE KEY----- """ def create_test_server_with_repo(db_session, server_name="test-server"): """Helper to create a test server.""" ssh_service = SshKeyService(db_session) ssh_key = ssh_service.create_ssh_key(name="test-ssh-key", private_key=VALID_SSH_KEY) server_service = ServerService(db_session) return server_service.create_server( name=server_name, url="https://gitea.example.com", api_token="test-api-token", ssh_key_id=ssh_key.id, sync_enabled=False, schedule_cron=None ) class TestCreateRepo: """Tests for create_repo method.""" def test_create_repo_success(self, db_session, test_env_vars): """Test successful repository creation.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo = service.create_repo( server_id=server.id, name="test-repo", full_name="owner/test-repo", clone_url="git@gitea.example.com:owner/test-repo.git", local_path="/tmp/test-repo" ) assert repo.id is not None assert repo.server_id == server.id assert repo.name == "test-repo" assert repo.full_name == "owner/test-repo" assert repo.clone_url == "git@gitea.example.com:owner/test-repo.git" assert repo.local_path == "/tmp/test-repo" assert repo.status == "pending" assert repo.last_sync_at is None assert repo.created_at is not None def test_create_repo_with_invalid_server_id(self, db_session, test_env_vars): """Test that invalid server_id is rejected.""" service = RepoService(db_session) with pytest.raises(ValueError, match="Server not found"): service.create_repo( server_id=99999, name="test-repo", full_name="owner/test-repo", clone_url="git@gitea.example.com:owner/test-repo.git", local_path="/tmp/test-repo" ) def test_create_repo_generates_local_path(self, db_session, test_env_vars): """Test that local_path can be generated correctly.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) # Test with None local_path to let it generate repo = service.create_repo( server_id=server.id, name="auto-path-repo", full_name="owner/auto-path-repo", clone_url="git@gitea.example.com:owner/auto-path-repo.git", local_path=None ) # Should generate path based on server and repo name assert repo.local_path is not None assert "auto-path-repo" in repo.local_path def test_create_repo_duplicate_name_same_server(self, db_session, test_env_vars): """Test that duplicate repo names on same server are allowed (different from servers).""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) # Create first repo service.create_repo( server_id=server.id, name="duplicate-repo", full_name="owner1/duplicate-repo", clone_url="git@gitea.example.com:owner1/duplicate-repo.git", local_path="/tmp/duplicate-repo-1" ) # Create second repo with same name but different full_name repo2 = service.create_repo( server_id=server.id, name="duplicate-repo", full_name="owner2/duplicate-repo", clone_url="git@gitea.example.com:owner2/duplicate-repo.git", local_path="/tmp/duplicate-repo-2" ) assert repo2 is not None assert repo2.full_name == "owner2/duplicate-repo" def test_create_repo_default_status(self, db_session, test_env_vars): """Test that new repos have default 'pending' status.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo = service.create_repo( server_id=server.id, name="status-test-repo", full_name="owner/status-test-repo", clone_url="git@gitea.example.com:owner/status-test-repo.git", local_path="/tmp/status-test-repo" ) assert repo.status == "pending" class TestListRepos: """Tests for list_repos method.""" def test_list_repos_empty(self, db_session, test_env_vars): """Test listing repos when none exist.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repos = service.list_repos(server.id) assert repos == [] def test_list_repos_multiple(self, db_session, test_env_vars): """Test listing multiple repos for a server.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) service.create_repo( server_id=server.id, name="repo-1", full_name="owner/repo-1", clone_url="git@gitea.example.com:owner/repo-1.git", local_path="/tmp/repo-1" ) service.create_repo( server_id=server.id, name="repo-2", full_name="owner/repo-2", clone_url="git@gitea.example.com:owner/repo-2.git", local_path="/tmp/repo-2" ) repos = service.list_repos(server.id) assert len(repos) == 2 assert any(r.name == "repo-1" for r in repos) assert any(r.name == "repo-2" for r in repos) def test_list_repos_filters_by_server(self, db_session, test_env_vars): """Test that list_repos only returns repos for specified server.""" server1 = create_test_server_with_repo(db_session, "server-1") server2 = create_test_server_with_repo(db_session, "server-2") service = RepoService(db_session) # Create repo for server1 service.create_repo( server_id=server1.id, name="server1-repo", full_name="owner/server1-repo", clone_url="git@gitea.example.com:owner/server1-repo.git", local_path="/tmp/server1-repo" ) # Create repo for server2 service.create_repo( server_id=server2.id, name="server2-repo", full_name="owner/server2-repo", clone_url="git@gitea.example.com:owner/server2-repo.git", local_path="/tmp/server2-repo" ) # List repos for server1 should only return server1's repo server1_repos = service.list_repos(server1.id) assert len(server1_repos) == 1 assert server1_repos[0].name == "server1-repo" # List repos for server2 should only return server2's repo server2_repos = service.list_repos(server2.id) assert len(server2_repos) == 1 assert server2_repos[0].name == "server2-repo" def test_list_repos_ordered_by_creation(self, db_session, test_env_vars): """Test that repos are listed in creation order.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo1 = service.create_repo( server_id=server.id, name="first-repo", full_name="owner/first-repo", clone_url="git@gitea.example.com:owner/first-repo.git", local_path="/tmp/first-repo" ) time.sleep(0.1) # Small delay to ensure timestamp difference repo2 = service.create_repo( server_id=server.id, name="second-repo", full_name="owner/second-repo", clone_url="git@gitea.example.com:owner/second-repo.git", local_path="/tmp/second-repo" ) repos = service.list_repos(server.id) assert repos[0].id == repo1.id assert repos[1].id == repo2.id class TestGetRepo: """Tests for get_repo method.""" def test_get_repo_by_id(self, db_session, test_env_vars): """Test getting a repo by ID.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) created_repo = service.create_repo( server_id=server.id, name="get-test-repo", full_name="owner/get-test-repo", clone_url="git@gitea.example.com:owner/get-test-repo.git", local_path="/tmp/get-test-repo" ) retrieved_repo = service.get_repo(created_repo.id) assert retrieved_repo is not None assert retrieved_repo.id == created_repo.id assert retrieved_repo.name == "get-test-repo" def test_get_repo_not_found(self, db_session, test_env_vars): """Test getting a non-existent repo.""" service = RepoService(db_session) repo = service.get_repo(99999) assert repo is None class TestUpdateRepoStatus: """Tests for update_repo_status method.""" def test_update_repo_status_to_syncing(self, db_session, test_env_vars): """Test updating repo status to syncing.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo = service.create_repo( server_id=server.id, name="status-repo", full_name="owner/status-repo", clone_url="git@gitea.example.com:owner/status-repo.git", local_path="/tmp/status-repo" ) updated_repo = service.update_repo_status(repo.id, "syncing") assert updated_repo.status == "syncing" assert updated_repo.id == repo.id def test_update_repo_status_to_success(self, db_session, test_env_vars): """Test updating repo status to success.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo = service.create_repo( server_id=server.id, name="status-repo", full_name="owner/status-repo", clone_url="git@gitea.example.com:owner/status-repo.git", local_path="/tmp/status-repo" ) updated_repo = service.update_repo_status(repo.id, "success") assert updated_repo.status == "success" def test_update_repo_status_to_failed(self, db_session, test_env_vars): """Test updating repo status to failed.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo = service.create_repo( server_id=server.id, name="status-repo", full_name="owner/status-repo", clone_url="git@gitea.example.com:owner/status-repo.git", local_path="/tmp/status-repo" ) updated_repo = service.update_repo_status(repo.id, "failed") assert updated_repo.status == "failed" def test_update_repo_status_not_found(self, db_session, test_env_vars): """Test updating status for non-existent repo.""" service = RepoService(db_session) with pytest.raises(ValueError, match="Repo not found"): service.update_repo_status(99999, "syncing") class TestDeleteRepo: """Tests for delete_repo method.""" def test_delete_repo_success(self, db_session, test_env_vars): """Test successful repo deletion.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) repo = service.create_repo( server_id=server.id, name="delete-test-repo", full_name="owner/delete-test-repo", clone_url="git@gitea.example.com:owner/delete-test-repo.git", local_path="/tmp/delete-test-repo" ) result = service.delete_repo(repo.id) assert result is True # Verify the repo is deleted retrieved_repo = service.get_repo(repo.id) assert retrieved_repo is None def test_delete_repo_not_found(self, db_session, test_env_vars): """Test deleting a non-existent repo.""" service = RepoService(db_session) result = service.delete_repo(99999) assert result is False class TestIntegration: """Integration tests for Repo service.""" def test_repo_lifecycle(self, db_session, test_env_vars): """Test complete lifecycle of a repo.""" server = create_test_server_with_repo(db_session) service = RepoService(db_session) # Create repo repo = service.create_repo( server_id=server.id, name="lifecycle-repo", full_name="owner/lifecycle-repo", clone_url="git@gitea.example.com:owner/lifecycle-repo.git", local_path="/tmp/lifecycle-repo" ) assert repo.status == "pending" # Update status to syncing repo = service.update_repo_status(repo.id, "syncing") assert repo.status == "syncing" # Update status to success repo = service.update_repo_status(repo.id, "success") assert repo.status == "success" # Verify we can retrieve it retrieved = service.get_repo(repo.id) assert retrieved is not None assert retrieved.status == "success" # List repos for server repos = service.list_repos(server.id) assert len(repos) == 1 assert repos[0].id == repo.id # Delete repo result = service.delete_repo(repo.id) assert result is True # Verify it's gone repos = service.list_repos(server.id) assert len(repos) == 0