Skip to main content
Glama

Codebase MCP Server

by Ravenight13
test_invalid_identifier.py11.3 kB
"""Integration test for invalid project identifier rejection (T022). Tests validation of project_id parameter to prevent invalid identifiers, SQL injection, and malformed input from reaching database operations. Test Scenario: Quickstart Scenario 6 - Invalid Project Identifier Validates: Edge Case (Invalid Characters), Acceptance Scenario 6 Traces to: FR-004, FR-005, FR-006, FR-007, FR-008 (validation rules), FR-016 (security) Constitutional Compliance: - Principle VII: TDD (validates input validation correctness) - Principle V: Production quality (comprehensive security testing) - Principle VIII: Type safety (Pydantic validation integration) """ from __future__ import annotations from pathlib import Path import pytest import pytest_asyncio from pydantic import ValidationError from src.mcp.tools.indexing import index_repository from src.mcp.tools.search import search_code # ============================================================================== # Test Fixtures # ============================================================================== @pytest.fixture def repo_validation(tmp_path: Path) -> str: """Fixture: Test repository for validation scenarios. Creates a minimal Python repository for testing project_id validation. Args: tmp_path: Pytest fixture providing temporary directory Returns: Absolute path to repository directory """ repo_dir = tmp_path / "repo-validation-test" repo_dir.mkdir() # Create Python file for testing (repo_dir / "app.py").write_text( """ def main() -> None: '''Application entry point''' print('Hello, World!') """ ) return str(repo_dir) # ============================================================================== # Integration Tests - Invalid Identifier Rejection # ============================================================================== @pytest.mark.parametrize( "invalid_id,expected_error_keyword", [ # Uppercase characters (FR-004: lowercase alphanumeric only) ("My_Project", "lowercase"), ("PROJECT-A", "lowercase"), ("Client_123", "lowercase"), # Invalid start/end characters (FR-006: cannot start/end with hyphen) ("-project", "start"), ("project-", "end"), ("-invalid-", "start"), # Consecutive hyphens (FR-007: no consecutive hyphens) ("project--name", "consecutive"), ("client---a", "consecutive"), # SQL injection attempts (FR-016: security validation) ("project'; DROP TABLE code_chunks--", "lowercase"), ("project/**/OR/**/1=1--", "lowercase"), ('project"; DELETE FROM repositories WHERE 1=1--', "lowercase"), ("project' UNION SELECT * FROM pg_shadow--", "lowercase"), # Exceeds max length (FR-008: 50 characters max) ("a" * 51, "50 character"), ("project-" + "x" * 50, "50 character"), # Special characters not allowed (FR-005: alphanumeric + hyphen only) ("project_name", "alphanumeric"), ("project.name", "alphanumeric"), ("project@123", "alphanumeric"), ("project#name", "alphanumeric"), ("project name", "alphanumeric"), # Space ("project/name", "alphanumeric"), ("project\\name", "alphanumeric"), # Unicode characters (FR-005: ASCII alphanumeric only) ("project-名前", "alphanumeric"), ("projet-café", "alphanumeric"), # Empty components (FR-006: cannot have empty components) ("a--b", "consecutive"), ], ) @pytest.mark.integration @pytest.mark.asyncio async def test_invalid_project_identifier_index( repo_validation: str, invalid_id: str, expected_error_keyword: str, ) -> None: """Test invalid identifiers rejected during indexing with clear errors. Validates that invalid project_id values are rejected by Pydantic validation BEFORE any database operations occur, with clear and actionable error messages. Test Steps: 1. Attempt to index repository with invalid project_id 2. Verify ValidationError or ValueError raised 3. Verify error message contains expected keyword 4. Verify no database operations occurred (no schema created) Expected Result: PASS - all invalid identifiers rejected with clear error messages Traces to: - FR-004: Lowercase alphanumeric characters only - FR-005: Alphanumeric + hyphen format enforcement - FR-006: No leading/trailing hyphens, no empty components - FR-007: No consecutive hyphens - FR-008: 50 character maximum length - FR-016: SQL injection prevention - Acceptance Scenario 6: Invalid Identifier """ # Attempt to index with invalid identifier with pytest.raises((ValidationError, ValueError)) as exc_info: await index_repository.fn( repo_path=repo_validation, project_id=invalid_id, ) # Verify error message contains expected keyword error_message = str(exc_info.value).lower() assert expected_error_keyword in error_message, ( f"Expected error message to contain '{expected_error_keyword}', " f"but got: {exc_info.value}" ) # Verify field-level error (project_id mentioned) assert "project" in error_message or "identifier" in error_message # Verify invalid value shown in error (helps debugging) # Note: SQL injection strings may be sanitized in error output if len(invalid_id) <= 50 and not any( char in invalid_id for char in ["'", '"', ";", "--", "/*"] ): assert invalid_id.lower() in error_message or "value" in error_message @pytest.mark.parametrize( "invalid_id,expected_error_keyword", [ ("My_Project", "lowercase"), ("-project", "start"), ("project'; DROP TABLE--", "lowercase"), ("a" * 51, "50 character"), ("project_name", "alphanumeric"), ], ) @pytest.mark.integration @pytest.mark.asyncio async def test_invalid_project_identifier_search( invalid_id: str, expected_error_keyword: str, ) -> None: """Test invalid identifiers rejected during search with clear errors. Validates that invalid project_id values are rejected during search operations BEFORE any database queries occur. Test Steps: 1. Attempt to search with invalid project_id 2. Verify ValidationError or ValueError raised 3. Verify error message contains expected keyword 4. Verify no database queries executed Expected Result: PASS - all invalid identifiers rejected during search Traces to: - FR-004, FR-005, FR-006, FR-007, FR-008: Validation rules - FR-016: SQL injection prevention """ # Attempt to search with invalid identifier with pytest.raises((ValidationError, ValueError)) as exc_info: await search_code.fn( query="test query", project_id=invalid_id, ) # Verify error message contains expected keyword error_message = str(exc_info.value).lower() assert expected_error_keyword in error_message, ( f"Expected error message to contain '{expected_error_keyword}', " f"but got: {exc_info.value}" ) @pytest.mark.integration @pytest.mark.asyncio async def test_valid_project_identifiers_accepted(repo_validation: str) -> None: """Test that valid project identifiers are accepted correctly. Validates that well-formed project_id values pass validation and operations succeed. Valid Formats (FR-004, FR-005, FR-006, FR-007, FR-008): - Lowercase letters: a-z - Digits: 0-9 - Hyphens: - (not at start/end, not consecutive) - Max length: 50 characters Test Steps: 1. Test various valid project_id formats 2. Verify validation passes 3. Verify operations succeed Expected Result: PASS - all valid identifiers accepted Traces to: - FR-004, FR-005, FR-006, FR-007, FR-008: Validation rules """ valid_identifiers = [ "a", # Single character "project-a", # Single hyphen "client-123", # With digits "my-project-name", # Multiple hyphens "abc123", # Alphanumeric only "project-2024-q1", # Complex format "x" * 50, # Max length (50 chars) "test-project-123-abc", # Mix of letters, digits, hyphens ] for valid_id in valid_identifiers: try: # Test with search (lighter operation) result = await search_code.fn( query="test", project_id=valid_id, ) # Verify operation succeeded assert result["project_id"] == valid_id assert "schema_name" in result assert result["schema_name"] == f"project_{valid_id.replace('-', '_')}" except (ValidationError, ValueError) as e: pytest.fail( f"Valid identifier '{valid_id}' was rejected with error: {e}" ) except Exception as e: # Infrastructure issues are acceptable (not validation failures) error_msg = str(e).lower() if any( keyword in error_msg for keyword in ["project_id", "column", "embedding", "changeevent"] ): pytest.skip( f"Test skipped due to infrastructure issue for '{valid_id}': {e}" ) else: raise @pytest.mark.integration @pytest.mark.asyncio async def test_empty_project_id_uses_default(repo_validation: str) -> None: """Test that None project_id uses default workspace (backward compatibility). Validates that when project_id is None (omitted), the system uses the default workspace without errors. Test Steps: 1. Search with project_id=None (explicitly) 2. Verify uses default workspace (schema_name="project_default") 3. Verify operation succeeds Expected Result: PASS - None project_id accepted and uses default workspace Traces to: - FR-018: Backward compatibility - FR-003: Default workspace support """ try: # Search with None project_id (backward compatibility) result = await search_code.fn( query="test", project_id=None, # Explicitly None ) # Verify uses default workspace assert result["project_id"] is None assert result["schema_name"] == "project_default" except Exception as e: error_msg = str(e).lower() if any( keyword in error_msg for keyword in ["project_id", "column", "embedding", "changeevent"] ): pytest.skip(f"Test blocked by infrastructure issue: {e}") else: raise # ============================================================================== # Module Exports # ============================================================================== __all__ = [ "test_invalid_project_identifier_index", "test_invalid_project_identifier_search", "test_valid_project_identifiers_accepted", "test_empty_project_id_uses_default", ]

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ravenight13/codebase-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server