"""Tests for gitea_safe_push — Branch existence checks before file operations.""" import json from unittest.mock import MagicMock, patch, call from pathlib import Path import pytest import sys sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from bin.gitea_safe_push import GiteaSafePush, GiteaAPIError class TestGiteaAPIError: def test_creation(self): e = GiteaAPIError(404, "not found", '{"message":"not found"}') assert e.status == 404 assert "404" in str(e) def test_is_exception(self): e = GiteaAPIError(500, "internal") assert isinstance(e, Exception) class TestBranchExists: def test_branch_exists(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "_api", return_value={"name": "main"}): assert push.branch_exists("owner/repo", "main") is True def test_branch_not_exists(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "_api", side_effect=GiteaAPIError(404, "not found")): assert push.branch_exists("owner/repo", "nonexistent") is False def test_api_error_propagates(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "_api", side_effect=GiteaAPIError(500, "server error")): with pytest.raises(GiteaAPIError): push.branch_exists("owner/repo", "main") class TestEnsureBranch: def test_already_exists(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "branch_exists", return_value=True): assert push.ensure_branch("owner/repo", "my-branch") is True def test_creates_branch(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "branch_exists", side_effect=[False, True]): with patch.object(push, "_api", return_value={"name": "my-branch"}): assert push.ensure_branch("owner/repo", "my-branch", base="main") is True def test_creation_fails(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "branch_exists", return_value=False): with patch.object(push, "_api", side_effect=GiteaAPIError(422, "invalid")): assert push.ensure_branch("owner/repo", "bad-branch") is False class TestPushFile: def test_rejects_missing_branch(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "branch_exists", return_value=False): result = push.push_file("owner/repo", "missing", "file.py", "content", "msg") assert result is False def test_creates_new_file(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "branch_exists", return_value=True): with patch.object(push, "_api", side_effect=[ GiteaAPIError(404, "not found"), # GET existing file {}, # POST new file ]): result = push.push_file("owner/repo", "branch", "new.py", "content", "msg") assert result is True def test_updates_existing_file(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "branch_exists", return_value=True): with patch.object(push, "_api", side_effect=[ {"sha": "abc123"}, # GET existing file {}, # PUT update ]): result = push.push_file("owner/repo", "branch", "existing.py", "new content", "msg") assert result is True def test_create_branch_when_missing(self): push = GiteaSafePush("https://forge.example.com", "token123") # Mock branch_exists: first call returns False (doesn't exist), # second call (inside ensure_branch) returns True (created externally) exists_calls = [False, True] exists_idx = [0] def mock_exists(repo, branch): idx = min(exists_idx[0], len(exists_calls) - 1) exists_idx[0] += 1 return exists_calls[idx] with patch.object(push, "branch_exists", side_effect=mock_exists): with patch.object(push, "_api") as mock_api: mock_api.side_effect = [ GiteaAPIError(404, "not found"), # GET existing file (not found) {"content": {"path": "f.py"}}, # POST new file ] result = push.push_file("owner/repo", "new-branch", "f.py", "c", "m", create_branch=True) assert result is True class TestPushFiles: def test_push_multiple_files(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "ensure_branch", return_value=True): with patch.object(push, "push_file", return_value=True): results = push.push_files("owner/repo", "branch", { "a.py": "content a", "b.py": "content b", }, "message") assert all(results.values()) assert len(results) == 2 def test_branch_creation_fails_aborts_all(self): push = GiteaSafePush("https://forge.example.com", "token123") with patch.object(push, "ensure_branch", return_value=False): results = push.push_files("owner/repo", "bad", {"a.py": "x"}, "msg") assert all(v is False for v in results.values())