diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 395c2e1..60f45bd 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -87,7 +87,10 @@ jobs: # Body = this version's section from CHANGELOG.md (fallback to a stub). # Commitizen writes headings as "## vX.Y.Z (date)", so match that form # (not the Keep-a-Changelog "## [X.Y.Z]" brackets) and stop at the next. - body=$(awk "/^## v$v/{f=1;next} /^## v/{f=0} f" CHANGELOG.md) + # Escape dots and anchor on the trailing space so the start pattern is an + # exact version match ("## v0.3.0 " won't re-arm on "## v0.3.01 ..."). + ve=$(printf '%s' "$v" | sed 's/\./\\./g') + body=$(awk "/^## v$ve /{f=1;next} /^## v/{f=0} f" CHANGELOG.md) [ -z "$body" ] && body="Release v$v" jq -n --arg tag "v$v" --arg name "v$v" --arg body "$body" \ '{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:false}' \ diff --git a/tests/test_tool_auth.py b/tests/test_tool_auth.py index 37a8e21..a572e12 100644 --- a/tests/test_tool_auth.py +++ b/tests/test_tool_auth.py @@ -11,16 +11,28 @@ import pytest from intervals_mcp_server import credentials from intervals_mcp_server.credentials import CredentialError -from intervals_mcp_server.tools import activities, custom_items, events, gear, power_curves, wellness +from intervals_mcp_server.tools import ( + activities, + athlete, + custom_items, + events, + gear, + power_curves, + wellness, + workouts, +) # (tool callable, minimal required positional args) -TOOL_CALLS = [ +TOOL_CALLS: list[tuple] = [ (activities.get_activities, ()), (activities.get_activity_details, ("1",)), (activities.get_activity_intervals, ("1",)), (activities.get_activity_streams, ("1",)), (activities.get_activity_messages, ("1",)), (activities.add_activity_message, ("1", "hi")), + (activities.search_activities, ("ride",)), + (activities.get_activity_best_efforts, ("1",)), + (activities.get_activity_interval_stats, ("1", 0, 100)), (events.get_events, ()), (events.get_event_by_id, ("e1",)), (events.delete_event, ("e1",)), @@ -28,6 +40,15 @@ TOOL_CALLS = [ (events.add_or_update_event, ("Ride", "Name")), (events.add_or_update_note, ("Name", "desc")), (wellness.get_wellness_data, ()), + (wellness.update_wellness, ()), + (wellness.update_wellness_bulk, ([],)), + (wellness.get_training_readiness, ()), + (athlete.get_athlete_profile, ()), + (athlete.get_sport_settings, ()), + (athlete.get_athlete_summary, ()), + (athlete.update_sport_settings, (1,)), + (workouts.get_workouts, ()), + (workouts.get_workout, (1,)), (power_curves.get_athlete_power_curves, ()), (gear.get_gear_list, ()), (custom_items.get_custom_items, ()), @@ -50,6 +71,17 @@ def test_tool_returns_message_when_unauthorized(monkeypatch, func, args): assert result == "ACCOUNT NOT APPROVED" -def test_all_20_tools_covered(): - """Guard: if a tool is added, add it here so its auth gate is tested.""" - assert len(TOOL_CALLS) == 20 +def test_all_tools_covered(): + """Guard: every registered MCP tool must appear in TOOL_CALLS. + + Compares against the live tool registry instead of a hand-maintained count, + so adding a tool without adding its auth-gate test fails loudly here. + """ + from intervals_mcp_server.mcp_instance import mcp + + registered = {t.name for t in asyncio.run(mcp.list_tools())} + covered = {f.__name__ for f, _ in TOOL_CALLS} + assert covered == registered, ( + f"auth-gate matrix out of sync: missing={sorted(registered - covered)} " + f"extra={sorted(covered - registered)}" + )