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:
@@ -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"}
|
||||
Reference in New Issue
Block a user