|
| 1 | +"""Test cases for null handling fixes in migration system.""" |
| 2 | + |
| 3 | +import tempfile |
| 4 | +from pathlib import Path |
| 5 | + |
| 6 | +import pytest |
| 7 | + |
| 8 | +from sqlspec.migrations.fix import MigrationFixer |
| 9 | +from sqlspec.migrations.validation import detect_out_of_order_migrations |
| 10 | +from sqlspec.utils.version import is_sequential_version, is_timestamp_version, parse_version |
| 11 | + |
| 12 | + |
| 13 | +class TestNullHandlingFixes: |
| 14 | + """Test fixes for None value handling in migrations.""" |
| 15 | + |
| 16 | + def test_parse_version_with_none(self): |
| 17 | + """Test parse_version handles None gracefully.""" |
| 18 | + with pytest.raises(ValueError, match="Invalid migration version: version string is None or empty"): |
| 19 | + parse_version(None) |
| 20 | + |
| 21 | + def test_parse_version_with_empty_string(self): |
| 22 | + """Test parse_version handles empty string gracefully.""" |
| 23 | + with pytest.raises(ValueError, match="Invalid migration version: version string is None or empty"): |
| 24 | + parse_version("") |
| 25 | + |
| 26 | + def test_parse_version_with_whitespace_only(self): |
| 27 | + """Test parse_version handles whitespace-only strings.""" |
| 28 | + with pytest.raises(ValueError, match="Invalid migration version: version string is None or empty"): |
| 29 | + parse_version(" ") |
| 30 | + |
| 31 | + def test_parse_version_valid_formats_still_work(self): |
| 32 | + """Test that valid version formats still work after fixes.""" |
| 33 | + # Sequential versions |
| 34 | + result = parse_version("0001") |
| 35 | + assert result.type.value == "sequential" |
| 36 | + assert result.sequence == 1 |
| 37 | + |
| 38 | + result = parse_version("9999") |
| 39 | + assert result.type.value == "sequential" |
| 40 | + assert result.sequence == 9999 |
| 41 | + |
| 42 | + # Timestamp versions |
| 43 | + result = parse_version("20251011120000") |
| 44 | + assert result.type.value == "timestamp" |
| 45 | + assert result.timestamp is not None |
| 46 | + |
| 47 | + # Extension versions |
| 48 | + result = parse_version("ext_litestar_0001") |
| 49 | + assert result.type.value == "sequential" # Base is sequential |
| 50 | + assert result.extension == "litestar" |
| 51 | + |
| 52 | + def test_migration_fixer_handles_none_gracefully(self): |
| 53 | + """Test MigrationFixer.update_file_content handles None values.""" |
| 54 | + with tempfile.TemporaryDirectory() as temp_dir: |
| 55 | + migrations_path = Path(temp_dir) |
| 56 | + fixer = MigrationFixer(migrations_path) |
| 57 | + |
| 58 | + test_file = migrations_path / "test.sql" |
| 59 | + test_file.write_text("-- Test content") |
| 60 | + |
| 61 | + # Should not crash with None values |
| 62 | + fixer.update_file_content(test_file, None, "0001") |
| 63 | + fixer.update_file_content(test_file, "0001", None) |
| 64 | + fixer.update_file_content(test_file, None, None) |
| 65 | + |
| 66 | + # File should remain unchanged |
| 67 | + content = test_file.read_text() |
| 68 | + assert content == "-- Test content" |
| 69 | + |
| 70 | + def test_validation_filters_none_values(self): |
| 71 | + """Test migration validation filters None values properly.""" |
| 72 | + # Should not crash with None values in lists |
| 73 | + gaps = detect_out_of_order_migrations( |
| 74 | + pending_versions=["0001", None, "0003", ""], applied_versions=[None, "0002", " ", "0004"] |
| 75 | + ) |
| 76 | + |
| 77 | + # Should only process valid versions |
| 78 | + assert len(gaps) >= 0 # Should not crash |
| 79 | + |
| 80 | + def test_sequential_pattern_edge_cases(self): |
| 81 | + """Test sequential pattern handles edge cases.""" |
| 82 | + assert is_sequential_version("0001") |
| 83 | + assert is_sequential_version("9999") |
| 84 | + assert is_sequential_version("10000") |
| 85 | + assert not is_sequential_version("20251011120000") # Timestamp |
| 86 | + assert not is_sequential_version("abc") |
| 87 | + assert not is_sequential_version("") |
| 88 | + assert not is_sequential_version(None) |
| 89 | + |
| 90 | + def test_timestamp_pattern_edge_cases(self): |
| 91 | + """Test timestamp pattern handles edge cases.""" |
| 92 | + assert is_timestamp_version("20251011120000") |
| 93 | + assert is_timestamp_version("20250101000000") |
| 94 | + assert is_timestamp_version("20251231235959") |
| 95 | + assert not is_timestamp_version("0001") # Sequential |
| 96 | + assert not is_timestamp_version("2025101112000") # Too short |
| 97 | + assert not is_timestamp_version("202510111200000") # Too long |
| 98 | + assert not is_timestamp_version("") |
| 99 | + assert not is_timestamp_version(None) |
| 100 | + |
| 101 | + def test_error_messages_are_descriptive(self): |
| 102 | + """Test that error messages are helpful for debugging.""" |
| 103 | + try: |
| 104 | + parse_version(None) |
| 105 | + except ValueError as e: |
| 106 | + assert "version string is None or empty" in str(e) |
| 107 | + |
| 108 | + try: |
| 109 | + parse_version("") |
| 110 | + except ValueError as e: |
| 111 | + assert "version string is None or empty" in str(e) |
0 commit comments