Skip to main content
Glama

Codebase MCP Server

by Ravenight13
test_identifier_validation.py14.4 kB
"""Security test for identifier validation (T027). Tests comprehensive validation of project identifiers including valid formats, invalid formats, edge cases, and security constraints. Test Scenario: Quickstart Scenario 9 - Security Validates: Security Scenario (Identifier Validation) Traces to: FR-005, FR-006, FR-007, FR-008 (validation rules) Constitutional Compliance: - Principle V: Production quality (comprehensive validation testing) - Principle VIII: Type safety (Pydantic model validation) - Principle VII: TDD (validation correctness) """ from __future__ import annotations import pytest from pydantic import ValidationError from src.models.project_identifier import ProjectIdentifier # ============================================================================== # Unit Tests - Valid Identifier Acceptance # ============================================================================== @pytest.mark.security @pytest.mark.parametrize( "valid_id,expected_schema_name", [ # Simple valid formats ("client-a", "project_client_a"), ("frontend", "project_frontend"), ("my-project-123", "project_my_project_123"), # Edge cases (valid) ("a", "project_a"), # Minimum length (1 char) ("z", "project_z"), # Single letter ("0", "project_0"), # Single digit ("abc123", "project_abc123"), # Alphanumeric only ("123abc", "project_123abc"), # Starts with digit ("project-2024-q1-test", "project_project_2024_q1_test"), # Complex format # Max length (50 chars) ("a" * 50, f"project_{'a' * 50}"), ("x" * 49 + "9", f"project_{'x' * 49}9"), # Multiple hyphens (non-consecutive) ("a-b-c-d-e", "project_a_b_c_d_e"), ("test-project-name-123", "project_test_project_name_123"), # Mixed formats ("client-123", "project_client_123"), ("v1-api-backend", "project_v1_api_backend"), ("prod-2024", "project_prod_2024"), ], ) @pytest.mark.unit def test_valid_identifiers(valid_id: str, expected_schema_name: str) -> None: """Test valid identifiers are accepted with correct schema conversion. Validates that ProjectIdentifier model accepts all valid identifier formats and correctly converts them to PostgreSQL schema names. Valid Format Rules (FR-005, FR-006, FR-007, FR-008): - Lowercase alphanumeric characters: a-z, 0-9 - Hyphens allowed: - (not at start/end, not consecutive) - Length: 1-50 characters - Schema name: "project_" + identifier (hyphens → underscores) Test Steps: 1. Create ProjectIdentifier with valid identifier 2. Verify no validation error raised 3. Verify identifier value preserved 4. Verify schema name correctly generated Expected Result: PASS - All valid identifiers accepted, schema names correct Traces to: - FR-005: Lowercase alphanumeric + hyphen format - FR-006: Max 50 characters - FR-007: No leading/trailing hyphens - FR-008: No consecutive hyphens """ # Create identifier (should not raise validation error) identifier = ProjectIdentifier(value=valid_id) # Verify value preserved assert identifier.value == valid_id # Verify schema name conversion schema_name = identifier.to_schema_name() assert schema_name == expected_schema_name # Verify schema name format assert schema_name.startswith("project_") assert "-" not in schema_name # All hyphens converted to underscores # Verify conversion: remove prefix, compare rest assert schema_name[8:] == valid_id.replace("-", "_") # Skip "project_" prefix # ============================================================================== # Unit Tests - Invalid Identifier Rejection # ============================================================================== @pytest.mark.security @pytest.mark.parametrize( "invalid_id,error_pattern", [ # Uppercase characters (FR-005: lowercase only) ("My_Project", "lowercase"), ("PROJECT", "lowercase"), ("Client-A", "lowercase"), ("myProject", "lowercase"), ("ABC", "lowercase"), ("Test-123", "lowercase"), # Underscores not allowed (FR-005: alphanumeric + hyphen only) ("_project", "lowercase"), ("project_name", "lowercase"), ("my_project_123", "lowercase"), # Leading hyphens (FR-007: cannot start with hyphen) ("-project", "start"), ("-client", "start"), ("-a", "start"), ("--project", "start"), # Trailing hyphens (FR-007: cannot end with hyphen) ("project-", "end"), ("client-", "end"), ("a-", "end"), ("project--", "end"), # Consecutive hyphens (FR-008: no consecutive hyphens) ("project--name", "consecutive"), ("client---a", "consecutive"), ("a--b", "consecutive"), ("test--project--name", "consecutive"), # Special characters not allowed (FR-005: alphanumeric + hyphen only) ("project name", "lowercase"), # Space ("project.name", "lowercase"), # Period ("project@123", "lowercase"), # At sign ("project#name", "lowercase"), # Hash ("project$name", "lowercase"), # Dollar ("project%name", "lowercase"), # Percent ("project&name", "lowercase"), # Ampersand ("project*name", "lowercase"), # Asterisk ("project+name", "lowercase"), # Plus ("project=name", "lowercase"), # Equals ("project/name", "lowercase"), # Slash ("project\\name", "lowercase"), # Backslash ("project|name", "lowercase"), # Pipe ("project:name", "lowercase"), # Colon ("project;name", "lowercase"), # Semicolon ("project'name", "lowercase"), # Single quote ('project"name', "lowercase"), # Double quote ("project<name", "lowercase"), # Less than ("project>name", "lowercase"), # Greater than ("project?name", "lowercase"), # Question mark ("project!name", "lowercase"), # Exclamation # Exceeds max length (FR-006: 50 characters max) ("a" * 51, "50"), ("project-" + "x" * 50, "50"), ("x" * 100, "50"), # Empty string (Pydantic min_length validation) ("", "at least 1"), # Unicode characters not allowed (FR-005: ASCII alphanumeric only) ("project-名前", "lowercase"), ("projet-café", "lowercase"), ("test-проект", "lowercase"), ("client-मेरा", "lowercase"), # SQL injection patterns (already tested in test_sql_injection.py) ("'; DROP TABLE--", "lowercase"), ("' OR '1'='1", "lowercase"), ], ) @pytest.mark.unit def test_invalid_identifiers(invalid_id: str, error_pattern: str) -> None: """Test invalid identifiers are rejected with clear error messages. Validates that ProjectIdentifier model rejects all invalid identifier formats with clear, actionable error messages. Invalid Format Examples: - Uppercase characters - Special characters (except hyphen) - Leading/trailing hyphens - Consecutive hyphens - Exceeds 50 characters - Empty string Test Steps: 1. Attempt to create ProjectIdentifier with invalid identifier 2. Verify ValidationError raised 3. Verify error message contains expected pattern 4. Verify error message is actionable (describes the problem) Expected Result: PASS - All invalid identifiers rejected with clear error messages Traces to: - FR-005: Lowercase alphanumeric + hyphen enforcement - FR-006: 50 character maximum - FR-007: No leading/trailing hyphens - FR-008: No consecutive hyphens - Constitutional Principle V: Production quality (clear error messages) """ # Attempt to create identifier (should raise validation error) with pytest.raises(ValidationError) as exc_info: ProjectIdentifier(value=invalid_id) # Verify error message contains expected pattern error_msg = str(exc_info.value).lower() assert error_pattern in error_msg, ( f"Expected error message to contain '{error_pattern}', " f"but got: {exc_info.value}" ) # Verify error message is informative (mentions validation aspect) assert any( keyword in error_msg for keyword in [ "lowercase", "alphanumeric", "hyphen", "start", "end", "consecutive", "50", "length", "identifier", "format", ] ), f"Error message should be informative, got: {exc_info.value}" # ============================================================================== # Unit Tests - Edge Cases # ============================================================================== @pytest.mark.security @pytest.mark.unit def test_edge_case_minimum_length() -> None: """Test minimum valid identifier length (1 character). Validates that single-character identifiers are accepted. Expected Result: PASS - Single character identifiers accepted """ # Single lowercase letter identifier = ProjectIdentifier(value="a") assert identifier.value == "a" assert identifier.to_schema_name() == "project_a" # Single digit identifier = ProjectIdentifier(value="0") assert identifier.value == "0" assert identifier.to_schema_name() == "project_0" @pytest.mark.security @pytest.mark.unit def test_edge_case_maximum_length() -> None: """Test maximum valid identifier length (50 characters). Validates that 50-character identifiers are accepted, but 51+ are rejected. Expected Result: PASS - 50 chars accepted, 51 chars rejected """ # Maximum length (50 chars) - should pass max_length_id = "a" * 50 identifier = ProjectIdentifier(value=max_length_id) assert identifier.value == max_length_id assert len(identifier.value) == 50 # Exceeds max length (51 chars) - should fail over_length_id = "a" * 51 with pytest.raises(ValidationError) as exc_info: ProjectIdentifier(value=over_length_id) error_msg = str(exc_info.value).lower() assert "50" in error_msg or "length" in error_msg @pytest.mark.security @pytest.mark.unit def test_edge_case_hyphen_placement() -> None: """Test hyphen placement rules (not at start/end, not consecutive). Validates that hyphens are only allowed in valid positions. Valid Positions: - Between alphanumeric characters: "a-b" - Multiple hyphens (non-consecutive): "a-b-c" Invalid Positions: - Leading hyphen: "-abc" - Trailing hyphen: "abc-" - Consecutive hyphens: "a--b" Expected Result: PASS - Valid hyphen placements accepted, invalid rejected """ # Valid: hyphen between characters identifier = ProjectIdentifier(value="a-b") assert identifier.value == "a-b" # Valid: multiple hyphens (non-consecutive) identifier = ProjectIdentifier(value="a-b-c-d") assert identifier.value == "a-b-c-d" # Invalid: leading hyphen with pytest.raises(ValidationError) as exc_info: ProjectIdentifier(value="-abc") assert "start" in str(exc_info.value).lower() # Invalid: trailing hyphen with pytest.raises(ValidationError) as exc_info: ProjectIdentifier(value="abc-") assert "end" in str(exc_info.value).lower() # Invalid: consecutive hyphens with pytest.raises(ValidationError) as exc_info: ProjectIdentifier(value="a--b") assert "consecutive" in str(exc_info.value).lower() @pytest.mark.security @pytest.mark.unit def test_schema_name_conversion_consistency() -> None: """Test that schema name conversion is consistent and reversible. Validates that: - Hyphens are consistently converted to underscores - Schema names always start with "project_" - Conversion is deterministic (same input → same output) Expected Result: PASS - Schema name conversion is consistent """ test_cases = [ ("client-a", "project_client_a"), ("my-project-name", "project_my_project_name"), ("test-123-abc", "project_test_123_abc"), ("abc", "project_abc"), # No hyphens ] for identifier_value, expected_schema in test_cases: identifier = ProjectIdentifier(value=identifier_value) schema_name = identifier.to_schema_name() # Verify expected schema name assert schema_name == expected_schema # Verify schema name format assert schema_name.startswith("project_") assert "-" not in schema_name # All hyphens converted # Verify consistency (calling again produces same result) assert identifier.to_schema_name() == schema_name @pytest.mark.security @pytest.mark.unit def test_error_message_clarity() -> None: """Test that error messages provide clear guidance for fixing identifiers. Validates that error messages: - Explain what's wrong with the identifier - Provide examples of valid formats - Are actionable (user knows how to fix the issue) Expected Result: PASS - Error messages are clear and actionable """ # Test uppercase error message with pytest.raises(ValidationError) as exc_info: ProjectIdentifier(value="MyProject") error_msg = str(exc_info.value) assert "lowercase" in error_msg.lower() assert "alphanumeric" in error_msg.lower() # Verify examples provided in error message # (from ProjectIdentifier.validate_format docstring) assert any( example in error_msg for example in ["client-a", "example"] ) or "format" in error_msg.lower() # ============================================================================== # Module Exports # ============================================================================== __all__ = [ "test_valid_identifiers", "test_invalid_identifiers", "test_edge_case_minimum_length", "test_edge_case_maximum_length", "test_edge_case_hyphen_placement", "test_schema_name_conversion_consistency", "test_error_message_clarity", ]

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