feat(hub): CLI, README, описание архитектуры
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
parent
f3ac1eb5c2
commit
d7f82cb611
@ -1,15 +1,39 @@
|
||||
# Архитектура
|
||||
|
||||
{{Карта модулей на одну страницу: что где лежит, границы, потоки данных. Читается агентом перед architectural brainstorming. Обновляется вместе со спеками.}}
|
||||
Хаб моста Jira: SQLite-зеркало задач AURORA/AWPCA, служебные заметки, очередь команд, HTTP API и MCP. Агент (`jira-agent`, виртуалка) опрашивает хаб, пишет в Jira и возвращает состояние; хаб Jira не вызывает.
|
||||
|
||||
## Модули
|
||||
|
||||
| Модуль | Ответственность | Зависит от |
|
||||
|---|---|---|
|
||||
| | | |
|
||||
| `paths` | каталог данных (`JIRA_HUB_HOME`), пути базы и `backups/` | stdlib |
|
||||
| `db` | схема SQLite, миграции, `connect` | stdlib |
|
||||
| `settings` | настройки (`setting`), токены, темп, перенос из `config.example.json` | `db` |
|
||||
| `mirror` | зеркало задач и связей, `apply_state`, заметки, фильтры | `db` |
|
||||
| `commands` | контракт типов команд, валидация, временные ключи, оценка, подтверждение по состоянию | `mirror` |
|
||||
| `queue` | пакеты, выдача (`claim`), отчёты, отмена, повтор, автообновление | `commands`, `mirror`, `settings`, `db` |
|
||||
| `backup` | копии файла базы, ротация | stdlib |
|
||||
| `api` | HTTP-сервер, маршруты, авторизация по токенам, фоновый `tick` | `queue`, `mirror`, `settings`, `backup` |
|
||||
| `client` | HTTP-клиент хаба | `db`, `paths`, `settings` (только для `from_home`) |
|
||||
| `mcp_server` | инструменты MCP по stdio | `client` |
|
||||
| `importer` | разовый импорт CSV-пакета в заметки, сверка с зеркалом | `mirror`, `settings` |
|
||||
| `cli` | `init`, `serve`, `import`, `mcp`, `tokens` | `api`, `mcp_server`, `importer`, `settings`, `db`, `paths` |
|
||||
|
||||
Цепочки: `api → queue → commands/mirror/settings → db`; `mcp_server → client → HTTP API`; `importer → mirror/settings`.
|
||||
|
||||
## Потоки данных
|
||||
- Запись в Jira: клиент (`mcp_server`/интерфейс) → `POST /batches` → `queue` → агент `claim` → Jira → `POST /agent/commands/{id}/result`.
|
||||
- Чтение Jira: команда `refresh` → агент → `POST /agent/state` → `mirror.apply_state`.
|
||||
- Токены: агентский — только `/agent/*`, клиентский — остальное; MCP берёт клиентский токен из базы через `HubClient.from_home`.
|
||||
|
||||
## Границы
|
||||
- Что не должно импортировать что.
|
||||
- Зеркало пишет только `mirror.apply_state` по запросу агента (и заметки через `PUT /issues/{key}/note`); из очереди и API задач напрямую не правят.
|
||||
- Хаб не знает пароль Jira и не обращается к Jira; `init` не переносит `auth`, `base_url`, `verify_ssl`, `ca_bundle`.
|
||||
- `queue` не повторяет команды: нет автоматических ретраев, повтор — новый пакет.
|
||||
- `mcp_server` не импортирует `queue`/`mirror`/`db` напрямую и ходит только через `client`.
|
||||
- `commands`, `mirror`, `queue` не знают про HTTP; HTTP живёт только в `api` и `client`.
|
||||
|
||||
## Открытые вопросы
|
||||
-
|
||||
- Просмотрщик на живых данных и правки из UI — отдельные change'и (`viewer-live`, `viewer-edit`).
|
||||
- Автоподстройка темпа по фактам 429 не реализована: хаб только показывает их.
|
||||
- Незакрытые SQLite-соединения и сокеты в ряде тестов (ResourceWarning при `-W error::ResourceWarning`).
|
||||
|
||||
29
CLAUDE.md
29
CLAUDE.md
@ -1,28 +1,35 @@
|
||||
# jira-hub
|
||||
|
||||
## Что это
|
||||
{{ОДНИМ АБЗАЦЕМ: что делает система, для кого, главный сценарий.}}
|
||||
Хаб моста Jira для АРМ КА: единственный источник истины по задачам проекта AURORA (метка AWPCA) на Mac mini (`192.168.0.106`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спека: `openspec/changes/bridge-core/`.
|
||||
|
||||
## Стек и ограничения
|
||||
- {{Язык, сборка, тесты — например: Python 3.12, uv, pytest.}}
|
||||
- {{Жёсткие ограничения среды исполнения — без чего код не должен собираться.}}
|
||||
- Python 3.13, uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10).
|
||||
- Хаб не знает пароль Jira и не ходит в Jira: учётные данные и сеть до Jira только у агента.
|
||||
- Зеркало задач пишет только `mirror.apply_state` по загрузке состояния от агента (`POST /agent/state`); правки идут командами через очередь.
|
||||
- Хаб не повторяет команды сам: повтор — новая команда по запросу человека/интерфейса.
|
||||
- Данные: `~/.local/share/jira-hub/` (`JIRA_HUB_HOME`): `hub.sqlite`, `backups/`. Порт по умолчанию 8790.
|
||||
- Два статических токена (в таблице `setting`): агентский открывает только `/agent/*`, клиентский — остальное.
|
||||
|
||||
## Архитектура
|
||||
- `src/core/` (или {{ваш путь}}) — чистая логика без зависимостей от фреймворка/платформы. Покрывается тестами полностью.
|
||||
- `src/{{adapter}}/` — тонкая обёртка над платформой. Тестируется руками или интеграционно.
|
||||
- {{Ключевые интерфейсы/порты между ними, если есть.}}
|
||||
- `src/jira_hub/` — весь код пакета, без разделения на ядро и адаптеры; чистая логика (`commands`, `mirror`, `queue`, `settings`) принимает соединение SQLite параметром.
|
||||
- `db` (схема, миграции) ← `settings`, `mirror`, `commands` ← `queue` ← `api` (HTTP, маршруты в `ROUTES`, фоновый `tick`) ← `cli`.
|
||||
- `client` (HTTP-клиент хаба) ← `mcp_server` (stdio, инструменты MCP); `importer` → `mirror`/`settings`; `backup` вызывается из `api`.
|
||||
- Подробная карта модулей и границ — `ARCHITECTURE.md`.
|
||||
|
||||
## Команды
|
||||
- `{{npm test / pytest}}` — весь набор тестов. Зелёным считается только весь набор.
|
||||
- `{{npm run build / ...}}` — сборка.
|
||||
- `{{npm run dev / ...}}` — локальный запуск.
|
||||
- `uv run pytest` — весь набор тестов. Зелёным считается только весь набор.
|
||||
- `uv sync` — установка окружения.
|
||||
- `uv run jira-hub init --config PATH [--force]` — настройки проекта из `config.example.json`, создание токенов.
|
||||
- `uv run jira-hub serve [--host] [--port]` — локальный запуск хаба.
|
||||
- `uv run jira-hub import FOLDER` — импорт CSV-пакета `admin/jira-sync`; `uv run jira-hub mcp` — MCP по stdio; `uv run jira-hub tokens` — показать токены.
|
||||
|
||||
## Тесты
|
||||
- Golden-тесты: `tests/fixtures/<name>.*` + эталон `<name>.expected.*`. Внешние сервисы в тестах мокаются.
|
||||
- {{Где лежит тестовое окружение (vault, БД, песочница).}}
|
||||
- SQLite в памяти или во временном `JIRA_HUB_HOME` (`tmp_path`); API поднимается на порту 0 в отдельном потоке. Jira и агент в тестах не участвуют.
|
||||
|
||||
## Первая подсистема
|
||||
{{Что делается первым и почему. Тестовая инфраструктура — Task 1 первого плана.}}
|
||||
Очередь команд (`queue.py`, Task 5): пакеты, порядок `(batch.created_at, seq)`, одна команда в полёте, временные ключи `new:<n>`, подтверждение по состоянию зеркала. Остальное (API, MCP, импорт) надстраивается над ней.
|
||||
|
||||
## OpenSpec и superpowers
|
||||
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
|
||||
|
||||
174
README.md
Normal file
174
README.md
Normal file
@ -0,0 +1,174 @@
|
||||
# jira-hub
|
||||
|
||||
Хаб моста Jira для АРМ КА: зеркало задач проекта AURORA (метка AWPCA) в SQLite, служебные заметки, очередь команд,
|
||||
HTTP API в локальной сети и MCP-сервер для Claude. Хаб работает на Mac mini (`192.168.0.106`); в Jira ходит только
|
||||
агент на виртуалке (репозиторий `jira-agent`), пароль Jira хабу неизвестен.
|
||||
Спека: `openspec/changes/bridge-core/` (proposal, design, specs).
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
Данные лежат в `~/.local/share/jira-hub/` (каталог переопределяется переменной `JIRA_HUB_HOME`):
|
||||
`hub.sqlite` и `backups/`. Пакет `mcp` закреплён версией `<2` (рулинг R-10): MCP SDK 2.x ломает API.
|
||||
|
||||
## Первый запуск
|
||||
|
||||
```bash
|
||||
uv run jira-hub init --config ../admin/jira-sync/config.example.json
|
||||
uv run jira-hub serve # 0.0.0.0:8790
|
||||
```
|
||||
|
||||
`init` переносит в настройки хаба всё, что относится к проекту Jira (поля, типы, карту переходов, типы связей, доску,
|
||||
приоритеты), и создаёт два токена: агентский и клиентский. Адрес и учётные данные Jira (`base_url`, `auth`, `verify_ssl`,
|
||||
`ca_bundle`) остаются у агента и в хаб не попадают. Повторный `init` отказывает ("уже инициализирован"); с `--force`
|
||||
настройки переписываются. Токены можно показать позже: `uv run jira-hub tokens`.
|
||||
|
||||
Передать агенту нужно адрес хаба и агентский токен: через общую папку виртуалки положить в `agent.json`
|
||||
`"hub_url": "http://192.168.0.106:8790"` и `"hub_token": "<агентский токен>"`. Клиентский токен остаётся на этой машине:
|
||||
его использует MCP (читает из базы сам).
|
||||
|
||||
Порт меняется ключом `--port` или настройкой `port`; адрес прослушивания — `--host`.
|
||||
|
||||
## Импорт пакета admin/jira-sync
|
||||
|
||||
```bash
|
||||
uv run jira-hub import ../admin/jira-sync
|
||||
```
|
||||
|
||||
Переносит из CSV заметки (статус по факту, вид работы и т. п.) и выводит отчёт JSON:
|
||||
`notes` (сколько заметок записано), `expected_links`, `missing_keys` (ключи из CSV, которых нет в зеркале) и
|
||||
`missing_links` (ожидаемые связи, которых нет). Запускать после первого `refresh` с `full: true`: пока зеркало пусто,
|
||||
все ключи будут в `missing_keys`. Заметки к ещё не загруженным задачам сохраняются.
|
||||
|
||||
## MCP в Claude
|
||||
|
||||
`jira-hub mcp` запускает MCP-сервер по stdio; он ходит в API хаба клиентским токеном. Если хаб не запущен, инструменты
|
||||
отвечают «хаб не запущен: jira-hub serve». Регистрация вручную (файл `~/.claude.json` скрипты хаба не правят),
|
||||
блок для проекта `awp-ca`:
|
||||
|
||||
```json
|
||||
"jira-hub": {"type": "stdio", "command": "uv", "args": ["run", "--project", "/Users/andrey.epifancev/projects/awp-ca/jira-hub", "jira-hub", "mcp"]}
|
||||
```
|
||||
|
||||
Либо командой: `claude mcp add jira-hub -- uv run --project /Users/andrey.epifancev/projects/awp-ca/jira-hub jira-hub mcp`.
|
||||
|
||||
| Инструмент | Параметры | Результат |
|
||||
|---|---|---|
|
||||
| `issues` | фильтры как у `GET /issues`, `limit` | таблица задач |
|
||||
| `issue` | `key` | поля, связи, заметки, последние команды |
|
||||
| `notes_set` | `key`, поля заметок | обновлённые заметки |
|
||||
| `batch_create` | `title`, `commands` | `id`, оценка длительности, число команд |
|
||||
| `batch_status` | `id` | статусы команд, ошибки, оценка остатка |
|
||||
| `batch_wait` | `id`, `timeout_minutes` (до 60) | как `batch_status`, по завершении или таймауту |
|
||||
| `batches` | `limit` | последние пакеты |
|
||||
| `command_retry`, `batch_cancel` | `id` | новый пакет / результат отмены |
|
||||
| `refresh` | `keys` или `full`, `wait` | `sync_run` или id пакета |
|
||||
| `agent_status` | нет | онлайн/офлайн, версия, текущая команда, 429 за сутки |
|
||||
| `sync_log` | `limit` | последние загрузки состояния |
|
||||
| `settings_get`, `settings_set` | ключ, значение | значение / новая `settings_version` |
|
||||
|
||||
Автор команд из MCP: `claude`; из API без автора: `api`.
|
||||
|
||||
## HTTP API
|
||||
|
||||
Все маршруты требуют `Authorization: Bearer <токен>`; ответы JSON, ошибки `{error, detail}` (400/401/404/409/422).
|
||||
|
||||
| Маршрут | Токен | Назначение |
|
||||
|---|---|---|
|
||||
| `POST /agent/claim` | агент | взять следующую команду (долгий запрос до 30 с) |
|
||||
| `GET /agent/settings` | агент | настройки для агента (без токенов) |
|
||||
| `GET /agent/commands/{id}` | агент | команда по id |
|
||||
| `POST /agent/commands/{id}/result` | агент | отчёт о выполнении (идемпотентно) |
|
||||
| `POST /agent/state` | агент | загрузка состояния задач пачками |
|
||||
| `GET /agent/known-keys` | агент | ключи зеркала |
|
||||
| `GET /issues` | клиент | фильтры `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`, `epic`, `neighbor`, `q`, `limit`, `offset` |
|
||||
| `GET /issues/{key}` | клиент | задача целиком |
|
||||
| `PUT /issues/{key}/note` | клиент | заметки (частично) |
|
||||
| `POST /batches`, `GET /batches`, `GET /batches/{id}` | клиент | пакеты команд |
|
||||
| `POST /batches/{id}/cancel` | клиент | отмена пакета |
|
||||
| `POST /commands/{id}/retry`, `POST /commands/{id}/cancel` | клиент | повтор (новый пакет) / отмена (`force` для взятой) |
|
||||
| `POST /refresh` | клиент | `{keys}`, `{jql}` или `{full: true}` |
|
||||
| `GET /agent`, `GET /sync-runs` | клиент | состояние агента, журнал загрузок |
|
||||
| `GET /settings`, `PUT /settings` | клиент | настройки (PUT частично, поднимает `settings_version`) |
|
||||
|
||||
```bash
|
||||
TOKEN=$(uv run jira-hub tokens | sed -n 's/^клиентский токен: //p')
|
||||
curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8790/issues?sprint=Спринт%202&limit=20"
|
||||
```
|
||||
|
||||
## Контракт команд
|
||||
|
||||
Каждая команда описывает желаемое состояние одной задачи; агент читает, сравнивает и пишет только разницу, повтор с тем же
|
||||
результатом завершается `done` без записи.
|
||||
|
||||
| Тип | Параметры |
|
||||
|---|---|
|
||||
| `ensure_issue` | `temp_key` (`new:<n>`), `issue_type` (`task`/`epic`), `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels`, `epic_key`, `assignee` |
|
||||
| `ensure_fields` | `key` и подмножество: `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels_add`, `labels_remove`, `epic_key`, `assignee` |
|
||||
| `ensure_comment` | `key`, `marker`, `body` |
|
||||
| `ensure_status` | `key`, `target` (имя статуса или список-путь) |
|
||||
| `ensure_link` | `source`, `target`, `link_type`, `replace` |
|
||||
| `ensure_sprint` | `key`, `sprint` (имя или `backlog`) |
|
||||
| `refresh` | `keys` или `jql` |
|
||||
|
||||
Ключ в параметре может быть временным `new:<n>`: команда выдаётся после `ensure_issue` с тем же `temp_key` из того же
|
||||
пакета. В хвост пакета хаб сам добавляет `refresh` по затронутым ключам. Пример пакета (`POST /batches`):
|
||||
|
||||
```json
|
||||
{"title": "1538 в спринт 2", "author": "claude", "commands": [
|
||||
{"type": "ensure_fields", "params": {"key": "AURORA-1538", "duedate": "2026-10-23"}},
|
||||
{"type": "ensure_link", "params": {"source": "AURORA-1537", "target": "AURORA-1538", "link_type": "связанные", "replace": true}},
|
||||
{"type": "ensure_sprint", "params": {"key": "AURORA-1538", "sprint": "Спринт 2"}}
|
||||
]}
|
||||
```
|
||||
|
||||
Ответ: `{id, estimate_seconds}`. Зазоры между записями в Jira по умолчанию: `ensure_issue` и `ensure_fields` 120 с,
|
||||
`ensure_comment`, `ensure_status`, `ensure_link`, `ensure_sprint` 30 с, `refresh` 2 с (настройка `pacing`).
|
||||
|
||||
## Пример сценария
|
||||
|
||||
1. `jira-hub serve` на Mac mini, агент запущен на виртуалке.
|
||||
2. В Claude: `refresh` с `full` загружает задачи в зеркало; `jira-hub import` переносит заметки из CSV.
|
||||
3. `batch_create` ставит пакет; `batch_wait` ждёт результата; `issue` показывает итог.
|
||||
|
||||
## Эксплуатация
|
||||
|
||||
- Резервные копии: перед первой записывающей командой пакета хаб копирует базу в `backups/<дата-время>-<batch>.sqlite`
|
||||
и хранит последние `backup_keep` копий (по умолчанию 30).
|
||||
- Автообновление: если `auto_refresh_minutes > 0` (по умолчанию 60), агент на связи и очередь пуста дольше интервала,
|
||||
хаб ставит пакет «Автообновление».
|
||||
- Настройки: `PUT /settings` или инструмент `settings_set`; защищены `settings_version`, `schema_version`, `tokens`.
|
||||
- Команда «нет ответа от агента»: команда в `claimed` без отчёта. Дождитесь агента (перезапущенный агент отчитается) или
|
||||
отмените: `POST /commands/{id}/cancel` с `{"force": true}`. Хаб сам команды не повторяет; если факт уже виден после
|
||||
`refresh`, команда становится `confirmed_by_state`. Повтор: `command_retry`.
|
||||
- Автозапуск `serve` через launchd: `~/Library/LaunchAgents/ru.aepif.jira-hub.plist`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>Label</key><string>ru.aepif.jira-hub</string>
|
||||
<key>ProgramArguments</key><array>
|
||||
<string>uv</string><string>run</string><string>--project</string>
|
||||
<string>/Users/andrey.epifancev/projects/awp-ca/jira-hub</string>
|
||||
<string>jira-hub</string><string>serve</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key><dict>
|
||||
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key><true/>
|
||||
<key>KeepAlive</key><true/>
|
||||
<key>StandardErrorPath</key><string>/tmp/jira-hub.err.log</string>
|
||||
</dict></plist>
|
||||
```
|
||||
|
||||
Загрузить: `launchctl load ~/Library/LaunchAgents/ru.aepif.jira-hub.plist`.
|
||||
|
||||
## Тесты
|
||||
|
||||
```bash
|
||||
uv run pytest
|
||||
```
|
||||
68
src/jira_hub/cli.py
Normal file
68
src/jira_hub/cli.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""Команды jira-hub: init (настройки из config.example.json), serve (HTTP), import (CSV-пакет), mcp (stdio), tokens."""
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
from . import db, importer, paths, settings
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
ap = argparse.ArgumentParser(prog="jira-hub", description="Мост Jira для АРМ КА: хаб")
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("init", help="перенести настройки проекта из config.example.json, создать токены")
|
||||
p.add_argument("--config", required=True)
|
||||
p.add_argument("--force", action="store_true")
|
||||
p = sub.add_parser("serve", help="запустить HTTP API")
|
||||
p.add_argument("--host", default="0.0.0.0")
|
||||
p.add_argument("--port", type=int)
|
||||
p = sub.add_parser("import", help="импорт CSV-пакета admin/jira-sync в заметки")
|
||||
p.add_argument("folder")
|
||||
sub.add_parser("mcp", help="MCP-сервер по stdio")
|
||||
sub.add_parser("tokens", help="показать токены агента и клиента")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
if args.cmd == "serve":
|
||||
from . import api
|
||||
hub = api.Hub(paths.db_path())
|
||||
conn = hub.conn()
|
||||
port = args.port or int(settings.get(conn, "port") or 8790)
|
||||
conn.close()
|
||||
api.serve(hub, args.host, port)
|
||||
return 0
|
||||
if args.cmd == "mcp":
|
||||
from . import mcp_server
|
||||
mcp_server.run()
|
||||
return 0
|
||||
|
||||
conn = db.connect(paths.db_path())
|
||||
settings.ensure_defaults(conn)
|
||||
try:
|
||||
if args.cmd == "init":
|
||||
with open(args.config, encoding="utf-8") as f:
|
||||
cfg = json.load(f)
|
||||
try:
|
||||
keys = settings.init_from_config(conn, cfg, force=args.force)
|
||||
except RuntimeError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return 1
|
||||
print("перенесено: " + ", ".join(keys))
|
||||
_print_tokens(conn)
|
||||
return 0
|
||||
if args.cmd == "import":
|
||||
print(json.dumps(importer.import_package(conn, args.folder), ensure_ascii=False, indent=1))
|
||||
return 0
|
||||
if args.cmd == "tokens":
|
||||
_print_tokens(conn)
|
||||
return 0
|
||||
finally:
|
||||
conn.close()
|
||||
return 2
|
||||
|
||||
|
||||
def _print_tokens(conn) -> None:
|
||||
tok = settings.tokens(conn)
|
||||
print(f"агентский токен: {tok['agent']}\nклиентский токен: {tok['client']}\nбаза: {paths.db_path()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
25
tests/test_cli.py
Normal file
25
tests/test_cli.py
Normal file
@ -0,0 +1,25 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from jira_hub import cli, db, paths, settings
|
||||
|
||||
FIXTURE = Path(__file__).parent / "fixtures" / "config.example.json"
|
||||
|
||||
|
||||
def test_init_import_tokens(tmp_path, monkeypatch, capsys):
|
||||
monkeypatch.setenv("JIRA_HUB_HOME", str(tmp_path / "home"))
|
||||
assert cli.main(["init", "--config", str(FIXTURE)]) == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "transitions_map" in out and "агентский токен" in out
|
||||
assert cli.main(["init", "--config", str(FIXTURE)]) == 1
|
||||
assert "уже инициализирован" in capsys.readouterr().err
|
||||
conn = db.connect(paths.db_path())
|
||||
assert settings.get(conn, "board_id") == 4216
|
||||
(tmp_path / "pkg").mkdir()
|
||||
(tmp_path / "pkg" / "tasks.csv").write_text("Код,Статус по факту,Вид работы\nAURORA-1,В работе,UI\n", encoding="utf-8")
|
||||
assert cli.main(["import", str(tmp_path / "pkg")]) == 0
|
||||
rep = json.loads(capsys.readouterr().out)
|
||||
assert rep["notes"] == 1 and rep["missing_keys"] == ["AURORA-1"]
|
||||
assert cli.main(["tokens"]) == 0
|
||||
assert settings.tokens(conn)["client"] in capsys.readouterr().out
|
||||
conn.close()
|
||||
Loading…
x
Reference in New Issue
Block a user