""" Unit tests for Phase 6: API Layer components. Tests cover: - GraphQL space schema types - CLI space commands """ import pytest import tempfile import json from pathlib import Path from unittest.mock import Mock, MagicMock, patch from click.testing import CliRunner from markitect.graphql.space_schema import ( InformationSpaceType, SpaceDocumentType, SpaceVariableType, SpaceConfigType, SpaceMetadataType, RenderResultType, SyncResultType, SpaceQuery, SpaceMutation, CreateSpaceInput, UpdateSpaceInput, AddDocumentInput, CreateSpacePayload, UpdateSpacePayload, DeleteSpacePayload, AddDocumentPayload, SpaceStatusEnum, SyncDirectionEnum, ConflictResolutionEnum, ) from markitect.spaces.cli import ( space, create_space, list_spaces, show_space, delete_space, add_document, list_documents, set_variable, list_variables, export_space, sync_space, ) from markitect.spaces.models import ( InformationSpace, SpaceDocument, SpaceVariable, SpaceConfig, SpaceMetadata, SpaceStatus, ) class TestSpaceStatusEnum: """Tests for SpaceStatusEnum.""" def test_status_values(self): """Test all status values are defined.""" assert SpaceStatusEnum.DRAFT assert SpaceStatusEnum.ACTIVE assert SpaceStatusEnum.ARCHIVED assert SpaceStatusEnum.DELETED class TestSyncDirectionEnum: """Tests for SyncDirectionEnum.""" def test_direction_values(self): """Test all direction values are defined.""" assert SyncDirectionEnum.SPACE_TO_DIRECTORY assert SyncDirectionEnum.DIRECTORY_TO_SPACE assert SyncDirectionEnum.BIDIRECTIONAL class TestConflictResolutionEnum: """Tests for ConflictResolutionEnum.""" def test_resolution_values(self): """Test all resolution values are defined.""" assert ConflictResolutionEnum.SPACE_WINS assert ConflictResolutionEnum.DIRECTORY_WINS assert ConflictResolutionEnum.NEWER_WINS assert ConflictResolutionEnum.SKIP class TestInformationSpaceType: """Tests for InformationSpaceType GraphQL type.""" def test_type_has_required_fields(self): """Test that type has required fields.""" # Check field definitions exist on the type assert hasattr(InformationSpaceType, 'id') assert hasattr(InformationSpaceType, 'name') assert hasattr(InformationSpaceType, 'description') assert hasattr(InformationSpaceType, 'status') assert hasattr(InformationSpaceType, 'config') assert hasattr(InformationSpaceType, 'metadata') class TestSpaceDocumentType: """Tests for SpaceDocumentType GraphQL type.""" def test_type_has_required_fields(self): """Test that type has required fields.""" assert hasattr(SpaceDocumentType, 'id') assert hasattr(SpaceDocumentType, 'space_id') assert hasattr(SpaceDocumentType, 'document_id') assert hasattr(SpaceDocumentType, 'space_path') class TestSpaceVariableType: """Tests for SpaceVariableType GraphQL type.""" def test_type_has_required_fields(self): """Test that type has required fields.""" assert hasattr(SpaceVariableType, 'space_id') assert hasattr(SpaceVariableType, 'name') assert hasattr(SpaceVariableType, 'value') assert hasattr(SpaceVariableType, 'scope') class TestSpaceConfigType: """Tests for SpaceConfigType GraphQL type.""" def test_type_has_required_fields(self): """Test that type has required fields.""" assert hasattr(SpaceConfigType, 'default_variant') assert hasattr(SpaceConfigType, 'enable_caching') assert hasattr(SpaceConfigType, 'theme') class TestRenderResultType: """Tests for RenderResultType GraphQL type.""" def test_type_has_required_fields(self): """Test that type has required fields.""" assert hasattr(RenderResultType, 'content') assert hasattr(RenderResultType, 'format') assert hasattr(RenderResultType, 'content_hash') class TestSyncResultType: """Tests for SyncResultType GraphQL type.""" def test_type_has_required_fields(self): """Test that type has required fields.""" assert hasattr(SyncResultType, 'success') assert hasattr(SyncResultType, 'created_count') assert hasattr(SyncResultType, 'updated_count') assert hasattr(SyncResultType, 'conflict_count') class TestSpaceQuery: """Tests for SpaceQuery GraphQL type.""" def test_query_has_space_field(self): """Test that query has space field.""" assert hasattr(SpaceQuery, 'space') def test_query_has_spaces_field(self): """Test that query has spaces field.""" assert hasattr(SpaceQuery, 'spaces') def test_query_has_document_fields(self): """Test that query has document fields.""" assert hasattr(SpaceQuery, 'space_documents') assert hasattr(SpaceQuery, 'space_document') def test_query_has_variable_fields(self): """Test that query has variable fields.""" assert hasattr(SpaceQuery, 'space_variables') assert hasattr(SpaceQuery, 'space_variable') class TestSpaceMutation: """Tests for SpaceMutation GraphQL type.""" def test_mutation_has_space_lifecycle(self): """Test mutation has space lifecycle operations.""" assert hasattr(SpaceMutation, 'create_space') assert hasattr(SpaceMutation, 'update_space') assert hasattr(SpaceMutation, 'delete_space') assert hasattr(SpaceMutation, 'activate_space') assert hasattr(SpaceMutation, 'archive_space') def test_mutation_has_document_operations(self): """Test mutation has document operations.""" assert hasattr(SpaceMutation, 'add_document') assert hasattr(SpaceMutation, 'remove_document') def test_mutation_has_variable_operations(self): """Test mutation has variable operations.""" assert hasattr(SpaceMutation, 'set_variable') assert hasattr(SpaceMutation, 'delete_variable') def test_mutation_has_render_operations(self): """Test mutation has render operations.""" assert hasattr(SpaceMutation, 'render_document') assert hasattr(SpaceMutation, 'render_space') def test_mutation_has_sync_operations(self): """Test mutation has sync operations.""" assert hasattr(SpaceMutation, 'sync_space') assert hasattr(SpaceMutation, 'export_space') assert hasattr(SpaceMutation, 'import_directory') class TestPayloadTypes: """Tests for payload types.""" def test_create_space_payload(self): """Test CreateSpacePayload type.""" assert hasattr(CreateSpacePayload, 'space') assert hasattr(CreateSpacePayload, 'success') assert hasattr(CreateSpacePayload, 'errors') def test_update_space_payload(self): """Test UpdateSpacePayload type.""" assert hasattr(UpdateSpacePayload, 'space') assert hasattr(UpdateSpacePayload, 'success') assert hasattr(UpdateSpacePayload, 'errors') def test_delete_space_payload(self): """Test DeleteSpacePayload type.""" assert hasattr(DeleteSpacePayload, 'success') assert hasattr(DeleteSpacePayload, 'deleted_id') assert hasattr(DeleteSpacePayload, 'errors') def test_add_document_payload(self): """Test AddDocumentPayload type.""" assert hasattr(AddDocumentPayload, 'document') assert hasattr(AddDocumentPayload, 'success') assert hasattr(AddDocumentPayload, 'errors') # CLI Tests class TestCLISpaceGroup: """Tests for CLI space command group.""" def test_space_group_exists(self): """Test space command group exists.""" assert space is not None assert hasattr(space, 'commands') def test_space_commands_registered(self): """Test that expected commands are registered.""" commands = space.commands expected = [ 'create', 'list', 'show', 'delete', 'activate', 'archive', 'add-doc', 'remove-doc', 'list-docs', 'set-var', 'get-var', 'list-vars', 'render', 'export', 'import', 'sync', 'invalidate-cache', ] for cmd in expected: assert cmd in commands, f"Command '{cmd}' not found in space group" class TestCLICreateSpace: """Tests for CLI create space command.""" @patch('markitect.spaces.cli.get_space_service') def test_create_space_basic(self, mock_get_service): """Test basic space creation.""" mock_service = Mock() mock_space = InformationSpace( id="test-id", name="Test Space", status=SpaceStatus.DRAFT, ) mock_service.create_space.return_value = mock_space mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['create', 'Test Space']) assert result.exit_code == 0 assert 'Created space' in result.output @patch('markitect.spaces.cli.get_space_service') def test_create_space_with_options(self, mock_get_service): """Test space creation with options.""" mock_service = Mock() mock_space = InformationSpace( id="test-id", name="Test Space", description="A test", status=SpaceStatus.DRAFT, ) mock_service.create_space.return_value = mock_space mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, [ 'create', 'Test Space', '--description', 'A test', '--theme', 'github', ]) assert result.exit_code == 0 mock_service.create_space.assert_called_once() @patch('markitect.spaces.cli.get_space_service') def test_create_space_json_output(self, mock_get_service): """Test space creation with JSON output.""" from datetime import datetime mock_service = Mock() mock_space = InformationSpace( id="test-id", name="Test Space", status=SpaceStatus.DRAFT, created_at=datetime.now(), ) mock_service.create_space.return_value = mock_space mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['create', 'Test Space', '-j']) assert result.exit_code == 0 data = json.loads(result.output) assert data['id'] == 'test-id' assert data['name'] == 'Test Space' class TestCLIListSpaces: """Tests for CLI list spaces command.""" @patch('markitect.spaces.cli.get_space_service') def test_list_spaces_empty(self, mock_get_service): """Test listing empty spaces.""" mock_service = Mock() mock_service.list_spaces.return_value = [] mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['list']) assert result.exit_code == 0 assert 'No spaces found' in result.output @patch('markitect.spaces.cli.get_space_service') def test_list_spaces_with_results(self, mock_get_service): """Test listing spaces with results.""" mock_service = Mock() mock_service.list_spaces.return_value = [ InformationSpace(id="id-1", name="Space 1", status=SpaceStatus.ACTIVE), InformationSpace(id="id-2", name="Space 2", status=SpaceStatus.DRAFT), ] mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['list']) assert result.exit_code == 0 assert 'Space 1' in result.output assert 'Space 2' in result.output class TestCLIShowSpace: """Tests for CLI show space command.""" @patch('markitect.spaces.cli.get_space_service') def test_show_space_not_found(self, mock_get_service): """Test showing non-existent space.""" mock_service = Mock() mock_service.get_space.return_value = None mock_service.get_space_by_name.return_value = None mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['show', 'nonexistent']) assert result.exit_code == 1 assert 'not found' in result.output @patch('markitect.spaces.cli.get_space_service') def test_show_space_found(self, mock_get_service): """Test showing existing space.""" mock_service = Mock() mock_space = InformationSpace( id="test-id", name="Test Space", description="A test space", status=SpaceStatus.ACTIVE, config=SpaceConfig(), ) mock_service.get_space.return_value = mock_space mock_service.list_documents.return_value = [] mock_service.list_variables.return_value = [] mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['show', 'test-id']) assert result.exit_code == 0 assert 'Test Space' in result.output assert 'active' in result.output class TestCLIDeleteSpace: """Tests for CLI delete space command.""" @patch('markitect.spaces.cli.get_space_service') def test_delete_space_with_force(self, mock_get_service): """Test deleting space with force flag.""" mock_service = Mock() mock_service.get_space.return_value = InformationSpace( id="test-id", name="Test Space", status=SpaceStatus.ACTIVE, ) mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['delete', 'test-id', '-f']) assert result.exit_code == 0 assert 'Deleted space' in result.output mock_service.delete_space.assert_called_once_with('test-id') class TestCLIDocumentCommands: """Tests for CLI document commands.""" @patch('markitect.spaces.cli.get_space_service') def test_add_document(self, mock_get_service): """Test adding a document.""" mock_service = Mock() mock_doc = SpaceDocument( id="doc-id", space_id="space-id", document_id="doc-id", space_path="/test.md", ) mock_service.add_document.return_value = mock_doc mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, [ 'add-doc', 'space-id', '--path', '/test.md', '--document-id', 'doc-id', ]) assert result.exit_code == 0 assert 'Added document' in result.output @patch('markitect.spaces.cli.get_space_service') def test_list_documents_empty(self, mock_get_service): """Test listing empty documents.""" mock_service = Mock() mock_service.list_documents.return_value = [] mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['list-docs', 'space-id']) assert result.exit_code == 0 assert 'No documents' in result.output class TestCLIVariableCommands: """Tests for CLI variable commands.""" @patch('markitect.spaces.cli.get_space_service') def test_set_variable(self, mock_get_service): """Test setting a variable.""" mock_service = Mock() mock_var = SpaceVariable( space_id="space-id", name="test_var", value="test_value", scope="space", ) mock_service.set_variable.return_value = mock_var mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, [ 'set-var', 'space-id', 'test_var', 'test_value', ]) assert result.exit_code == 0 assert 'Set variable' in result.output @patch('markitect.spaces.cli.get_space_service') def test_list_variables_empty(self, mock_get_service): """Test listing empty variables.""" mock_service = Mock() mock_service.list_variables.return_value = [] mock_get_service.return_value = mock_service runner = CliRunner() result = runner.invoke(space, ['list-vars', 'space-id']) assert result.exit_code == 0 assert 'No variables' in result.output class TestCLISyncCommands: """Tests for CLI sync commands.""" @patch('markitect.spaces.cli.get_space_service') def test_export_space_not_found(self, mock_get_service): """Test exporting non-existent space.""" mock_service = Mock() mock_service.get_space.return_value = None mock_get_service.return_value = mock_service runner = CliRunner() with tempfile.TemporaryDirectory() as tmpdir: result = runner.invoke(space, ['export', 'nonexistent', tmpdir]) assert result.exit_code == 1 assert 'not found' in result.output @patch('markitect.spaces.cli.get_space_service') def test_sync_space_not_found(self, mock_get_service): """Test syncing non-existent space.""" mock_service = Mock() mock_service.get_space.return_value = None mock_get_service.return_value = mock_service runner = CliRunner() with tempfile.TemporaryDirectory() as tmpdir: result = runner.invoke(space, ['sync', 'nonexistent', tmpdir]) assert result.exit_code == 1 assert 'not found' in result.output