trello-tools: плагин и маркетплейс gp-tools для Claude Code

Сервер запускается через uv run --script, ключи запрашиваются
при установке плагина (userConfig) вместо .env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 14:14:29 +00:00
commit b4cabbf146
14 changed files with 2100 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
import json
import sys
from pathlib import Path
import httpx
import pytest
import respx
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import qa_client # noqa: E402
@pytest.fixture(autouse=True)
def qa_env(monkeypatch):
monkeypatch.setenv("QA_SERVICE_URL", "https://qa.example.com")
monkeypatch.setenv("QA_SERVICE_API_KEY", "svc-key-789")
@pytest.mark.asyncio
async def test_publish_doc_posts_to_docs():
with respx.mock:
route = respx.post("https://qa.example.com/docs").mock(
return_value=httpx.Response(200, json={"slug": "abc", "url": "https://qa.example.com/docs/abc"})
)
result = await qa_client.publish_doc("Заголовок", "текст", "https://trello.com/c/x")
assert route.called
request = route.calls.last.request
assert request.headers["X-API-Key"] == "svc-key-789"
assert result == {"slug": "abc", "url": "https://qa.example.com/docs/abc"}
@pytest.mark.asyncio
async def test_update_doc_puts_to_docs_slug():
with respx.mock:
route = respx.put("https://qa.example.com/docs/abc").mock(
return_value=httpx.Response(200, json={"slug": "abc", "url": "https://qa.example.com/docs/abc"})
)
result = await qa_client.update_doc(
"abc", "Новый заголовок", "новый текст", "https://trello.com/c/x"
)
assert route.called
request = route.calls.last.request
assert request.headers["X-API-Key"] == "svc-key-789"
assert result == {"slug": "abc", "url": "https://qa.example.com/docs/abc"}
@pytest.mark.asyncio
async def test_update_doc_missing_slug_raises():
with respx.mock:
respx.put("https://qa.example.com/docs/does-not-exist").mock(
return_value=httpx.Response(404, json={"detail": "Doc not found"})
)
with pytest.raises(httpx.HTTPStatusError):
await qa_client.update_doc("does-not-exist", "T", "текст")
def test_update_doc_requires_config(monkeypatch):
monkeypatch.delenv("QA_SERVICE_URL", raising=False)
monkeypatch.delenv("QA_SERVICE_API_KEY", raising=False)
with pytest.raises(qa_client.QaServiceConfigError):
qa_client._config()
@pytest.mark.asyncio
async def test_get_doc_fetches_raw_by_slug_or_url():
doc = {"slug": "abc", "markdown": "текст", "scenarios": []}
with respx.mock:
route = respx.get("https://qa.example.com/docs/abc/raw").mock(
return_value=httpx.Response(200, json=doc)
)
by_slug = await qa_client.get_doc("abc")
by_url = await qa_client.get_doc("https://qa.example.com/docs/abc/")
assert route.call_count == 2
assert route.calls.last.request.headers["X-API-Key"] == "svc-key-789"
assert by_slug == by_url == doc
@pytest.mark.asyncio
async def test_publish_doc_sends_problem():
with respx.mock:
route = respx.post("https://qa.example.com/docs").mock(
return_value=httpx.Response(200, json={"slug": "abc", "url": "u"})
)
await qa_client.publish_doc("T", "текст", problem="Падает экспорт")
body = json.loads(route.calls.last.request.content)
assert body["problem"] == "Падает экспорт"
assert body["card_url"] is None
@pytest.mark.asyncio
async def test_publish_task_uses_bearer_api_key():
with respx.mock:
route = respx.post("https://qa.example.com/tasks").mock(
return_value=httpx.Response(200, json={"slug": "t1"})
)
await qa_client.publish_task("front", "Кнопка", "текст", "https://trello.com/c/x")
request = route.calls.last.request
assert request.headers["Authorization"] == "Bearer svc-key-789"
assert json.loads(request.content) == {
"title": "Кнопка",
"markdown": "текст",
"target": "front",
"card_url": "https://trello.com/c/x",
}
@pytest.mark.asyncio
async def test_tasks_work_with_tasks_token_only(monkeypatch):
# Claude на фронте: основного ключа нет, только токен заданий.
monkeypatch.delenv("QA_SERVICE_API_KEY")
monkeypatch.setenv("QA_TASKS_TOKEN", "tasks-tok")
with respx.mock:
route = respx.get("https://qa.example.com/tasks/t1/raw").mock(
return_value=httpx.Response(200, json={"slug": "t1"})
)
await qa_client.get_task("https://qa.example.com/tasks/t1")
assert route.calls.last.request.headers["Authorization"] == "Bearer tasks-tok"
with pytest.raises(qa_client.QaServiceConfigError):
qa_client._config()
@pytest.mark.asyncio
async def test_update_and_list_tasks():
with respx.mock:
put = respx.put("https://qa.example.com/tasks/t1").mock(
return_value=httpx.Response(200, json={"slug": "t1"})
)
listing = respx.get("https://qa.example.com/tasks").mock(
return_value=httpx.Response(200, json=[])
)
await qa_client.update_task("t1", "back", "T", "v2")
await qa_client.list_tasks(card_url="https://trello.com/c/abc", target=None)
assert json.loads(put.calls.last.request.content)["target"] == "back"
assert dict(listing.calls.last.request.url.params) == {"card": "https://trello.com/c/abc"}