Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 95 additions & 1 deletion test/test_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down
38 changes: 36 additions & 2 deletions tim_mcp/clients/terraform_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
with retry logic, caching, and comprehensive error handling.
"""

import re
import time
from typing import Any

Expand All @@ -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>
prerelease_pattern = r"^\d+\.\d+\.\d+-"
return bool(re.match(prerelease_pattern, version))


class TerraformClient:
"""Async client for interacting with Terraform Registry API."""

Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down