From afaf26242d346c70962ff759fa4539203ca1fe5a Mon Sep 17 00:00:00 2001 From: Jordan-Williams2 Date: Thu, 23 Oct 2025 13:44:27 +0100 Subject: [PATCH] fix: filter releases --- test/test_clients.py | 96 ++++++++++++++++++++++++++++- tim_mcp/clients/terraform_client.py | 38 +++++++++++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/test/test_clients.py b/test/test_clients.py index 0bb76c1..17fd702 100644 --- a/test/test_clients.py +++ b/test/test_clients.py @@ -8,7 +8,7 @@ import pytest from tim_mcp.clients.github_client import GitHubClient -from tim_mcp.clients.terraform_client import TerraformClient +from tim_mcp.clients.terraform_client import TerraformClient, is_prerelease_version from tim_mcp.exceptions import ModuleNotFoundError @@ -31,6 +31,41 @@ def mock_cache(): class TestTerraformClient: """Tests for the TerraformClient class.""" + def test_is_prerelease_version_with_beta(self): + """Test identifying beta versions as pre-release.""" + assert is_prerelease_version("1.0.0-beta") is True + assert is_prerelease_version("2.1.0-beta.1") is True + + def test_is_prerelease_version_with_alpha(self): + """Test identifying alpha versions as pre-release.""" + assert is_prerelease_version("1.0.0-alpha") is True + assert is_prerelease_version("3.2.1-alpha.2") is True + + def test_is_prerelease_version_with_rc(self): + """Test identifying release candidate versions as pre-release.""" + assert is_prerelease_version("1.0.0-rc") is True + assert is_prerelease_version("2.0.0-rc.1") is True + + def test_is_prerelease_version_with_draft(self): + """Test identifying draft versions as pre-release.""" + assert is_prerelease_version("2.0.1-draft") is True + assert is_prerelease_version("2.0.1-draft-addons") is True # Real example from issue #20 + + def test_is_prerelease_version_stable(self): + """Test that stable versions are not identified as pre-release.""" + assert is_prerelease_version("1.0.0") is False + assert is_prerelease_version("2.5.3") is False + assert is_prerelease_version("10.20.30") is False + + def test_is_prerelease_version_edge_cases(self): + """Test edge cases for version identification.""" + # Version with metadata but no pre-release identifier + assert is_prerelease_version("1.0.0+build.123") is False + # Empty string + assert is_prerelease_version("") is False + # Invalid format + assert is_prerelease_version("invalid") is False + @pytest.fixture def mock_session(self): """Create a mock requests session.""" @@ -125,6 +160,65 @@ async def test_get_module_versions(self, terraform_client, mock_cache): "/modules/hashicorp/consul/aws/versions" ) + @pytest.mark.asyncio + async def test_get_module_versions_filters_prerelease(self, terraform_client, mock_cache): + """Test that pre-release versions are filtered out.""" + # Setup - mix of stable and pre-release versions + mock_response = MagicMock() + mock_response.json.return_value = { + "modules": [ + {"version": "1.0.0"}, + {"version": "1.1.0-beta"}, + {"version": "1.2.0"}, + {"version": "2.0.0-draft"}, + {"version": "2.0.1-draft-addons"}, # Real example from issue #20 + {"version": "2.1.0-rc.1"}, + {"version": "2.2.0"}, + {"version": "3.0.0-alpha"}, + ] + } + mock_response.raise_for_status = MagicMock() + mock_response.status_code = 200 + terraform_client.client.get = AsyncMock(return_value=mock_response) + + # Execute + result = await terraform_client.get_module_versions( + "terraform-ibm-modules", "db2-cloud", "ibm" + ) + + # Verify - only stable versions returned + assert result == ["1.0.0", "1.2.0", "2.2.0"] + terraform_client.client.get.assert_called_once_with( + "/modules/terraform-ibm-modules/db2-cloud/ibm/versions" + ) + + @pytest.mark.asyncio + async def test_get_module_versions_all_prerelease(self, terraform_client, mock_cache): + """Test behavior when all versions are pre-release.""" + # Setup - only pre-release versions + mock_response = MagicMock() + mock_response.json.return_value = { + "modules": [ + {"version": "1.0.0-beta"}, + {"version": "1.1.0-alpha"}, + {"version": "2.0.0-rc.1"}, + ] + } + mock_response.raise_for_status = MagicMock() + mock_response.status_code = 200 + terraform_client.client.get = AsyncMock(return_value=mock_response) + + # Execute + result = await terraform_client.get_module_versions( + "terraform-ibm-modules", "test-module", "ibm" + ) + + # Verify - empty list when all are pre-release + assert result == [] + terraform_client.client.get.assert_called_once_with( + "/modules/terraform-ibm-modules/test-module/ibm/versions" + ) + class TestGitHubClient: """Tests for the GitHubClient class.""" diff --git a/tim_mcp/clients/terraform_client.py b/tim_mcp/clients/terraform_client.py index 42e80ed..3d1c9dc 100644 --- a/tim_mcp/clients/terraform_client.py +++ b/tim_mcp/clients/terraform_client.py @@ -5,6 +5,7 @@ with retry logic, caching, and comprehensive error handling. """ +import re import time from typing import Any @@ -22,6 +23,34 @@ from ..utils.cache import Cache +def is_prerelease_version(version: str) -> bool: + """ + Check if a version string is a pre-release version. + + Pre-release versions contain identifiers like: + - beta, alpha, rc (release candidate) + - draft, dev, pre + - Any version with a hyphen followed by additional identifiers + + Examples: + - "2.0.1-beta" -> True + - "2.0.1-draft-addons" -> True + - "1.0.0-rc.1" -> True + - "2.0.1" -> False + - "1.2.3" -> False + + Args: + version: Version string to check + + Returns: + True if the version is a pre-release, False otherwise + """ + # Pattern matches semantic versions with pre-release identifiers + # Format: X.Y.Z- + prerelease_pattern = r"^\d+\.\d+\.\d+-" + return bool(re.match(prerelease_pattern, version)) + + class TerraformClient: """Async client for interacting with Terraform Registry API.""" @@ -425,10 +454,13 @@ async def get_module_versions( response.raise_for_status() data = response.json() - # Extract versions - versions = [ + # Extract versions and filter out pre-release versions + all_versions = [ v.get("version") for v in data.get("modules", []) if v.get("version") ] + + # Filter out pre-release versions (beta, alpha, rc, draft, etc.) + versions = [v for v in all_versions if not is_prerelease_version(v)] # Log successful request log_api_request( @@ -439,6 +471,8 @@ async def get_module_versions( duration_ms, module_id=f"{namespace}/{name}/{provider}", version_count=len(versions), + total_versions=len(all_versions), + filtered_count=len(all_versions) - len(versions), ) # Cache the result