Skip to content

Commit

Permalink
Update test
Browse files Browse the repository at this point in the history
  • Loading branch information
jahwag committed Aug 21, 2024
1 parent 9c69c6a commit 8b8fdfd
Showing 1 changed file with 273 additions and 66 deletions.
339 changes: 273 additions & 66 deletions tests/cli/test_project.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,296 @@
import unittest
from unittest.mock import patch, MagicMock, ANY
import pytest
from unittest.mock import patch, MagicMock, call
from click.testing import CliRunner
from claudesync.cli.main import cli
from claudesync.cli.project import sync
from claudesync.exceptions import ProviderError


class TestProjectCLI(unittest.TestCase):
@pytest.fixture
def mock_config():
config = MagicMock()
config.get.side_effect = lambda key, default=None: {
"active_organization_id": "org123",
"active_project_id": "proj456",
"active_project_name": "MainProject",
"local_path": "/path/to/project",
"submodule_detect_filenames": ["pom.xml", "build.gradle"],
}.get(key, default)
return config

def setUp(self):
self.runner = CliRunner()

@pytest.fixture
def mock_provider():
return MagicMock()


@pytest.fixture
def mock_sync_manager():
return MagicMock()


@pytest.fixture
def mock_get_local_files():
with patch("claudesync.cli.project.get_local_files") as mock:
yield mock


@pytest.fixture
def mock_detect_submodules():
with patch("claudesync.cli.project.detect_submodules") as mock:
yield mock


class TestProjectCLI:
@patch("claudesync.cli.project.validate_and_get_provider")
@patch("claudesync.cli.project.SyncManager")
@patch("claudesync.cli.project.get_local_files")
@patch("claudesync.cli.project.detect_submodules")
@patch("os.path.abspath")
@patch("os.path.join")
@patch("os.makedirs")
def test_project_sync(
self,
mock_detect_submodules,
mock_get_local_files,
mock_makedirs,
mock_path_join,
mock_path_abspath,
mock_sync_manager_class,
mock_validate_provider,
mock_config,
mock_provider,
mock_sync_manager,
mock_validate_and_get_provider,
mock_get_local_files,
mock_detect_submodules,
):
# Mock the provider and its methods
mock_provider = MagicMock()
mock_validate_and_get_provider.return_value = mock_provider
# Setup
runner = CliRunner()
mock_validate_provider.return_value = mock_provider
mock_sync_manager_class.return_value = mock_sync_manager

mock_provider.get_projects.return_value = [
{"id": "main_project_id", "name": "Main Project"},
{"id": "submodule_id", "name": "Main Project-SubModule-submodule1"},
{"id": "proj456", "name": "MainProject"},
{"id": "sub789", "name": "MainProject-SubModule-SubA"},
]
mock_provider.list_files.side_effect = [
[{"file_name": "main_file.txt"}], # Main project files
[{"file_name": "submodule_file.txt"}], # Submodule files
[
{
"uuid": "file1",
"file_name": "main.py",
"content": "print('main')",
"created_at": "2023-01-01T00:00:00Z",
}
],
[
{
"uuid": "file2",
"file_name": "sub.py",
"content": "print('sub')",
"created_at": "2023-01-01T00:00:00Z",
}
],
]

# Mock the configuration
mock_config = MagicMock()
mock_config.get.side_effect = lambda key, default=None: {
"active_organization_id": "org_id",
"active_project_id": "main_project_id",
"active_project_name": "Main Project",
"local_path": "/path/to/project",
"submodule_detect_filenames": ["pom.xml"],
}.get(key, default)

# Mock submodule detection
mock_detect_submodules.return_value = [("submodule1", "pom.xml")]

# Mock local file retrieval
mock_get_local_files.side_effect = [
{"main_file.txt": "hash1"}, # Main project files
{"submodule_file.txt": "hash2"}, # Submodule files
]
mock_get_local_files.side_effect = [{"main.py": "hash1"}, {"sub.py": "hash2"}]

mock_detect_submodules.return_value = [("SubA", "pom.xml")]

# Mock SyncManager
mock_sync_manager_instance = MagicMock()
mock_sync_manager.return_value = mock_sync_manager_instance

# Run the command
result = self.runner.invoke(cli, ["project", "sync"], obj=mock_config)

# Assertions
self.assertEqual(result.exit_code, 0)
self.assertIn("Main project 'Main Project' synced successfully.", result.output)
self.assertIn("Syncing submodule 'submodule1'...", result.output)
self.assertIn("Submodule 'submodule1' synced successfully.", result.output)
self.assertIn(
"Project sync completed successfully, including available submodules.",
result.output,
mock_path_abspath.side_effect = lambda x: x
mock_path_join.side_effect = lambda *args: "/".join(args)

# Execute
result = runner.invoke(sync, obj=mock_config)

# Assert
assert (
result.exit_code == 0
), f"Exit code was {result.exit_code}, expected 0. Exception: {result.exception}"
assert "Main project 'MainProject' synced successfully." in result.output
assert "Syncing submodule 'SubA'..." in result.output
assert "Submodule 'SubA' synced successfully." in result.output
assert (
"Project sync completed successfully, including available submodules."
in result.output
)

# Verify method calls
mock_validate_and_get_provider.assert_called_once_with(
ANY, require_project=True
mock_validate_provider.assert_called_once_with(
mock_config, require_project=True
)
mock_provider.get_projects.assert_called_once_with(
"org_id", include_archived=False
"org123", include_archived=False
)
mock_detect_submodules.assert_called_once_with(
"/path/to/project", ["pom.xml", "build.gradle"]
)

assert mock_provider.list_files.call_count == 2
mock_provider.list_files.assert_has_calls(
[call("org123", "proj456"), call("org123", "sub789")]
)

assert mock_get_local_files.call_count == 2
mock_get_local_files.assert_has_calls(
[call("/path/to/project", None), call("/path/to/project/SubA", None)]
)

assert mock_sync_manager.sync.call_count == 2
mock_sync_manager.sync.assert_has_calls(
[
call(
{"main.py": "hash1"},
[
{
"uuid": "file1",
"file_name": "main.py",
"content": "print('main')",
"created_at": "2023-01-01T00:00:00Z",
}
],
),
call(
{"sub.py": "hash2"},
[
{
"uuid": "file2",
"file_name": "sub.py",
"content": "print('sub')",
"created_at": "2023-01-01T00:00:00Z",
}
],
),
]
)

@patch("claudesync.cli.project.validate_and_get_provider")
def test_project_sync_no_local_path(self, mock_validate_provider, mock_config):
runner = CliRunner()
mock_config.get.side_effect = lambda key, default=None: (
None if key == "local_path" else default
)
mock_validate_provider.return_value = MagicMock()

result = runner.invoke(sync, obj=mock_config)

assert result.exit_code == 0
assert (
"No local path set. Please select or create a project first."
in result.output
)

@patch("claudesync.cli.project.validate_and_get_provider")
def test_project_sync_provider_error(self, mock_validate_provider, mock_config):
runner = CliRunner()
mock_validate_provider.side_effect = ProviderError("API Error")

result = runner.invoke(sync, obj=mock_config)

assert result.exit_code == 0
assert "Error: API Error" in result.output

@patch("claudesync.cli.project.validate_and_get_provider")
@patch("claudesync.cli.project.SyncManager")
def test_project_sync_no_submodules(
self,
mock_sync_manager_class,
mock_validate_provider,
mock_config,
mock_provider,
mock_sync_manager,
mock_get_local_files,
mock_detect_submodules,
):
runner = CliRunner()
mock_validate_provider.return_value = mock_provider
mock_sync_manager_class.return_value = mock_sync_manager

mock_provider.get_projects.return_value = [
{"id": "proj456", "name": "MainProject"}
]
mock_provider.list_files.return_value = [
{
"uuid": "file1",
"file_name": "main.py",
"content": "print('main')",
"created_at": "2023-01-01T00:00:00Z",
}
]
mock_get_local_files.return_value = {"main.py": "hash1"}
mock_detect_submodules.return_value = []

result = runner.invoke(sync, obj=mock_config)

assert result.exit_code == 0
assert "Main project 'MainProject' synced successfully." in result.output
assert (
"Project sync completed successfully, including available submodules."
in result.output
)
assert "Syncing submodule" not in result.output

mock_sync_manager.sync.assert_called_once()

@patch("claudesync.cli.project.validate_and_get_provider")
@patch("claudesync.cli.project.SyncManager")
def test_project_sync_with_category(
self,
mock_sync_manager_class,
mock_validate_provider,
mock_config,
mock_provider,
mock_sync_manager,
mock_get_local_files,
mock_detect_submodules,
):
runner = CliRunner()
mock_validate_provider.return_value = mock_provider
mock_sync_manager_class.return_value = mock_sync_manager

mock_provider.get_projects.return_value = [
{"id": "proj456", "name": "MainProject"}
]
mock_provider.list_files.return_value = [
{
"uuid": "file1",
"file_name": "main.py",
"content": "print('main')",
"created_at": "2023-01-01T00:00:00Z",
}
]
mock_get_local_files.return_value = {"main.py": "hash1"}
mock_detect_submodules.return_value = []

result = runner.invoke(sync, ["--category", "production_code"], obj=mock_config)

assert result.exit_code == 0
assert "Main project 'MainProject' synced successfully." in result.output

mock_get_local_files.assert_called_once_with(
"/path/to/project", "production_code"
)
mock_sync_manager.sync.assert_called_once()

@patch("claudesync.cli.project.validate_and_get_provider")
@patch("claudesync.cli.project.SyncManager")
def test_project_sync_with_invalid_category(
self,
mock_sync_manager_class,
mock_validate_provider,
mock_config,
mock_provider,
mock_sync_manager,
mock_get_local_files,
mock_detect_submodules,
):
runner = CliRunner()
mock_validate_provider.return_value = mock_provider
mock_sync_manager_class.return_value = mock_sync_manager

mock_get_local_files.side_effect = ValueError(
"Invalid category: invalid_category"
)

result = runner.invoke(
sync, ["--category", "invalid_category"], obj=mock_config
)
mock_provider.list_files.assert_any_call("org_id", "main_project_id")
mock_provider.list_files.assert_any_call("org_id", "submodule_id")
mock_get_local_files.assert_any_call("/path/to/project", None)
mock_get_local_files.assert_any_call("/path/to/project/submodule1", None)
mock_sync_manager_instance.sync.assert_called()
self.assertEqual(
mock_sync_manager_instance.sync.call_count, 2
) # Once for main project, once for submodule


if __name__ == "__main__":
unittest.main()

assert result.exit_code == 1
assert "Invalid category: invalid_category" in result.exception.args[0]

mock_sync_manager.sync.assert_not_called()

0 comments on commit 8b8fdfd

Please sign in to comment.