test: implement unit testing suite with pytest and add pre-push verification hook
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# This file marks the tests/unit directory as a Python package.
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Unit tests for MCP content block helper functions."""
|
||||
|
||||
import json
|
||||
|
||||
from mcp.types import TextContent, EmbeddedResource
|
||||
|
||||
# Import helpers directly from a tool module to test them
|
||||
from strava_mcp_server.tools.activities import _resource, _user_text
|
||||
|
||||
|
||||
class TestUserTextHelper:
|
||||
def test_returns_text_content(self):
|
||||
result = _user_text("Hello World")
|
||||
assert isinstance(result, TextContent)
|
||||
|
||||
def test_type_is_text(self):
|
||||
result = _user_text("Hello")
|
||||
assert result.type == "text"
|
||||
|
||||
def test_text_value(self):
|
||||
result = _user_text("Test message")
|
||||
assert result.text == "Test message"
|
||||
|
||||
def test_audience_is_user(self):
|
||||
result = _user_text("Hello")
|
||||
assert result.annotations is not None
|
||||
assert result.annotations.audience == ["user"]
|
||||
|
||||
|
||||
class TestResourceHelper:
|
||||
def test_returns_embedded_resource(self):
|
||||
result = _resource("internal://test/data", {"key": "value"})
|
||||
assert isinstance(result, EmbeddedResource)
|
||||
|
||||
def test_type_is_resource(self):
|
||||
result = _resource("internal://test/data", {"key": "value"})
|
||||
assert result.type == "resource"
|
||||
|
||||
def test_mime_type_is_json(self):
|
||||
result = _resource("internal://test/data", {"key": "value"})
|
||||
assert result.resource.mimeType == "application/json"
|
||||
|
||||
def test_uri_is_set(self):
|
||||
result = _resource("internal://athlete/profile", {"id": 1})
|
||||
assert str(result.resource.uri) == "internal://athlete/profile"
|
||||
|
||||
def test_text_is_valid_json(self):
|
||||
data = {"id": 42, "name": "Test Athlete", "active": True}
|
||||
result = _resource("internal://test", data)
|
||||
parsed = json.loads(result.resource.text)
|
||||
assert parsed == data
|
||||
|
||||
def test_audience_is_assistant(self):
|
||||
result = _resource("internal://test", {})
|
||||
assert result.annotations is not None
|
||||
assert result.annotations.audience == ["assistant"]
|
||||
|
||||
def test_list_data(self):
|
||||
data = [{"id": 1}, {"id": 2}]
|
||||
result = _resource("internal://activities/list", data)
|
||||
parsed = json.loads(result.resource.text)
|
||||
assert len(parsed) == 2
|
||||
assert parsed[0]["id"] == 1
|
||||
@@ -0,0 +1,98 @@
|
||||
"""Unit tests for strava_mcp_server.utils — pure functions, no external dependencies."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from strava_mcp_server.utils import (
|
||||
parse_iso_to_unix,
|
||||
format_date_iso,
|
||||
format_date_human,
|
||||
)
|
||||
|
||||
|
||||
class TestParseIsoToUnix:
|
||||
def test_full_iso_with_z(self):
|
||||
result = parse_iso_to_unix("2024-01-15T12:00:00Z")
|
||||
assert result == int(
|
||||
datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc).timestamp()
|
||||
)
|
||||
|
||||
def test_full_iso_with_offset(self):
|
||||
result = parse_iso_to_unix("2024-01-15T13:00:00+01:00")
|
||||
assert result == int(
|
||||
datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc).timestamp()
|
||||
)
|
||||
|
||||
def test_date_only(self):
|
||||
result = parse_iso_to_unix("2024-06-01")
|
||||
assert result == int(
|
||||
datetime(2024, 6, 1, 0, 0, 0, tzinfo=timezone.utc).timestamp()
|
||||
)
|
||||
|
||||
def test_none_returns_none(self):
|
||||
assert parse_iso_to_unix(None) is None
|
||||
|
||||
def test_empty_string_returns_none(self):
|
||||
assert parse_iso_to_unix("") is None
|
||||
|
||||
def test_invalid_string_returns_none(self):
|
||||
assert parse_iso_to_unix("not-a-date") is None
|
||||
|
||||
def test_returns_int(self):
|
||||
result = parse_iso_to_unix("2024-01-01T00:00:00Z")
|
||||
assert isinstance(result, int)
|
||||
|
||||
|
||||
class TestFormatDateIso:
|
||||
def test_z_suffix_normalized(self):
|
||||
result = format_date_iso("2024-01-15T12:00:00Z")
|
||||
assert result == "2024-01-15T12:00:00Z"
|
||||
|
||||
def test_offset_preserved(self):
|
||||
# format_date_iso preserves timezone offset info; normalization to UTC
|
||||
# is the caller's responsibility if needed.
|
||||
result = format_date_iso("2024-01-15T13:00:00+01:00")
|
||||
assert result == "2024-01-15T13:00:00+01:00"
|
||||
|
||||
def test_datetime_object(self):
|
||||
dt = datetime(2024, 3, 10, 8, 30, 0, tzinfo=timezone.utc)
|
||||
result = format_date_iso(dt)
|
||||
assert result == "2024-03-10T08:30:00Z"
|
||||
|
||||
def test_none_returns_na(self):
|
||||
assert format_date_iso(None) == "N/A"
|
||||
|
||||
def test_empty_string_returns_na(self):
|
||||
assert format_date_iso("") == "N/A"
|
||||
|
||||
def test_invalid_string_returned_as_is(self):
|
||||
result = format_date_iso("not-a-date")
|
||||
assert result == "not-a-date"
|
||||
|
||||
|
||||
class TestFormatDateHuman:
|
||||
def test_iso_with_z(self):
|
||||
result = format_date_human("2024-01-15T08:30:00Z")
|
||||
assert result == "15.01.2024 08:30"
|
||||
|
||||
def test_iso_with_offset(self):
|
||||
# +01:00 → displayed in local time of the datetime (13:00 in +01 = 13:00 displayed)
|
||||
result = format_date_human("2024-01-15T13:45:00+01:00")
|
||||
assert result == "15.01.2024 13:45"
|
||||
|
||||
def test_datetime_object(self):
|
||||
dt = datetime(2024, 12, 31, 23, 59, tzinfo=timezone.utc)
|
||||
result = format_date_human(dt)
|
||||
assert result == "31.12.2024 23:59"
|
||||
|
||||
def test_none_returns_na(self):
|
||||
assert format_date_human(None) == "N/A"
|
||||
|
||||
def test_empty_string_returns_na(self):
|
||||
assert format_date_human("") == "N/A"
|
||||
|
||||
def test_format_pattern(self):
|
||||
result = format_date_human("2024-06-01T00:00:00Z")
|
||||
# Ensure it matches DD.MM.YYYY HH:MM
|
||||
import re
|
||||
|
||||
assert re.match(r"\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}", result)
|
||||
Reference in New Issue
Block a user