allure-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- allure_cli-0.1.0/LICENSE +21 -0
- allure_cli-0.1.0/MANIFEST.in +4 -0
- allure_cli-0.1.0/PKG-INFO +111 -0
- allure_cli-0.1.0/README.md +87 -0
- allure_cli-0.1.0/allure_cli/__init__.py +7 -0
- allure_cli-0.1.0/allure_cli/__main__.py +6 -0
- allure_cli-0.1.0/allure_cli/cli.py +110 -0
- allure_cli-0.1.0/allure_cli/client.py +145 -0
- allure_cli-0.1.0/allure_cli.egg-info/PKG-INFO +111 -0
- allure_cli-0.1.0/allure_cli.egg-info/SOURCES.txt +14 -0
- allure_cli-0.1.0/allure_cli.egg-info/dependency_links.txt +1 -0
- allure_cli-0.1.0/allure_cli.egg-info/entry_points.txt +2 -0
- allure_cli-0.1.0/allure_cli.egg-info/top_level.txt +1 -0
- allure_cli-0.1.0/pyproject.toml +34 -0
- allure_cli-0.1.0/requirements.txt +1 -0
- allure_cli-0.1.0/setup.cfg +4 -0
allure_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Allure CLI Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: allure-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI для Allure TestOps: получение Allure ID тест-кейса по названию
|
|
5
|
+
Author: Allure CLI Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adolmatov/allure_cli
|
|
8
|
+
Project-URL: Repository, https://github.com/adolmatov/allure_cli
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/adolmatov/allure_cli/issues
|
|
10
|
+
Keywords: allure,testops,testing,qa,cli
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Allure CLI
|
|
26
|
+
|
|
27
|
+
CLI для работы с Allure TestOps. Основная задача — получение Allure ID тест-кейса по названию.
|
|
28
|
+
|
|
29
|
+
## Требования
|
|
30
|
+
|
|
31
|
+
- Python 3.10+
|
|
32
|
+
- Внешние зависимости не нужны (только stdlib)
|
|
33
|
+
|
|
34
|
+
## Установка
|
|
35
|
+
|
|
36
|
+
Чтобы вызывать команду просто как `allure_cli` (без `python -m`):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd "${ALLURE_CLI_ROOT:-$HOME/apps}/allure_cli"
|
|
40
|
+
pip install -e .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
После этого команда `allure_cli` будет доступна в PATH (в том же окружении Python, куда ставили).
|
|
44
|
+
|
|
45
|
+
## Настройка
|
|
46
|
+
|
|
47
|
+
Переменные окружения (или аргументы `--url`, `--token`, `--project`):
|
|
48
|
+
|
|
49
|
+
| Переменная | Описание |
|
|
50
|
+
|------------|----------|
|
|
51
|
+
| `ALLURE_ENDPOINT` или `ALLURE_TESTOPS_URL` | Базовый URL Allure TestOps (например, `https://allure-testops.example.com`) |
|
|
52
|
+
| `ALLURE_TOKEN` | API-токен (создаётся в Allure: профиль → API Tokens) |
|
|
53
|
+
| `ALLURE_PROJECT_ID` | ID проекта (например, `211`) |
|
|
54
|
+
| `ALLURE_CLI_ROOT` | Каталог, в котором лежит пакет `allure_cli` (по умолчанию `$HOME/apps`). Нужен для запуска через `run.sh` из любой директории. |
|
|
55
|
+
|
|
56
|
+
**Постоянно (zsh/bash):** добавьте в `~/.zshrc` или `~/.bashrc` и перезапустите терминал (или выполните `source ~/.zshrc` / `source ~/.bashrc`):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Allure TestOps CLI
|
|
60
|
+
export ALLURE_ENDPOINT="https://allure-testops.example.com"
|
|
61
|
+
export ALLURE_PROJECT_ID="YOUR_PROJECT_ID"
|
|
62
|
+
export ALLURE_TOKEN="<YOUR_TOKEN>"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Подставьте свой URL, ID проекта и токен. После этого вызывать `allure_cli` можно без `export` в каждой сессии.
|
|
66
|
+
|
|
67
|
+
## Запуск
|
|
68
|
+
|
|
69
|
+
**Вариант 1 — после установки (`pip install -e .`):** команда `allure_cli` доступна из любой директории:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export ALLURE_ENDPOINT=https://allure-testops.example.com
|
|
73
|
+
export ALLURE_PROJECT_ID=211
|
|
74
|
+
export ALLURE_TOKEN=<ваш_токен>
|
|
75
|
+
|
|
76
|
+
allure_cli "Автоответ в чат"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Вариант 2 — без установки, через скрипт-обёртку:**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
"${ALLURE_CLI_ROOT:-$HOME/apps}/allure_cli/run.sh" "Автоответ в чат"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Вариант 3 — без установки, через python -m** (из каталога-родителя пакета, т.е. где лежит папка `allure_cli`):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd "${ALLURE_CLI_ROOT:-$HOME/apps}"
|
|
89
|
+
PYTHONPATH="$(pwd)" python -m allure_cli "Автоответ в чат"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Примеры
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Поиск по подстроке названия — выводит id и name
|
|
96
|
+
allure_cli "Автоответ в чат"
|
|
97
|
+
|
|
98
|
+
# Только ID, по одному на строку
|
|
99
|
+
allure_cli -q "Автоответ в чат"
|
|
100
|
+
|
|
101
|
+
# Параметры через аргументы
|
|
102
|
+
allure_cli --url https://allure-testops.example.com --project 211 --token $ALLURE_TOKEN "запрос"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Вывод (обычный режим): `id` и название тест-кейса; при наличии — `fullName` со следующей строки. Значение **id** — это Allure ID для декоратора `AllureID("...")` в коде тестов.
|
|
106
|
+
|
|
107
|
+
## Авторизация
|
|
108
|
+
|
|
109
|
+
Используется схема из [документации ТестОпс](https://docs.qatools.ru/api): API-токен обменивается на JWT через `POST /api/uaa/oauth/token`, далее запросы к API — с заголовком `Authorization: Bearer <jwt>`.
|
|
110
|
+
|
|
111
|
+
JWT кэшируется на диск (`~/.cache/allure_cli/` или `$XDG_CACHE_HOME/allure_cli/`), чтобы не запрашивать новый токен при каждом вызове. При ответе 401 от API кэш сбрасывается и токен запрашивается заново автоматически.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Allure CLI
|
|
2
|
+
|
|
3
|
+
CLI для работы с Allure TestOps. Основная задача — получение Allure ID тест-кейса по названию.
|
|
4
|
+
|
|
5
|
+
## Требования
|
|
6
|
+
|
|
7
|
+
- Python 3.10+
|
|
8
|
+
- Внешние зависимости не нужны (только stdlib)
|
|
9
|
+
|
|
10
|
+
## Установка
|
|
11
|
+
|
|
12
|
+
Чтобы вызывать команду просто как `allure_cli` (без `python -m`):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd "${ALLURE_CLI_ROOT:-$HOME/apps}/allure_cli"
|
|
16
|
+
pip install -e .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
После этого команда `allure_cli` будет доступна в PATH (в том же окружении Python, куда ставили).
|
|
20
|
+
|
|
21
|
+
## Настройка
|
|
22
|
+
|
|
23
|
+
Переменные окружения (или аргументы `--url`, `--token`, `--project`):
|
|
24
|
+
|
|
25
|
+
| Переменная | Описание |
|
|
26
|
+
|------------|----------|
|
|
27
|
+
| `ALLURE_ENDPOINT` или `ALLURE_TESTOPS_URL` | Базовый URL Allure TestOps (например, `https://allure-testops.example.com`) |
|
|
28
|
+
| `ALLURE_TOKEN` | API-токен (создаётся в Allure: профиль → API Tokens) |
|
|
29
|
+
| `ALLURE_PROJECT_ID` | ID проекта (например, `211`) |
|
|
30
|
+
| `ALLURE_CLI_ROOT` | Каталог, в котором лежит пакет `allure_cli` (по умолчанию `$HOME/apps`). Нужен для запуска через `run.sh` из любой директории. |
|
|
31
|
+
|
|
32
|
+
**Постоянно (zsh/bash):** добавьте в `~/.zshrc` или `~/.bashrc` и перезапустите терминал (или выполните `source ~/.zshrc` / `source ~/.bashrc`):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Allure TestOps CLI
|
|
36
|
+
export ALLURE_ENDPOINT="https://allure-testops.example.com"
|
|
37
|
+
export ALLURE_PROJECT_ID="YOUR_PROJECT_ID"
|
|
38
|
+
export ALLURE_TOKEN="<YOUR_TOKEN>"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Подставьте свой URL, ID проекта и токен. После этого вызывать `allure_cli` можно без `export` в каждой сессии.
|
|
42
|
+
|
|
43
|
+
## Запуск
|
|
44
|
+
|
|
45
|
+
**Вариант 1 — после установки (`pip install -e .`):** команда `allure_cli` доступна из любой директории:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export ALLURE_ENDPOINT=https://allure-testops.example.com
|
|
49
|
+
export ALLURE_PROJECT_ID=211
|
|
50
|
+
export ALLURE_TOKEN=<ваш_токен>
|
|
51
|
+
|
|
52
|
+
allure_cli "Автоответ в чат"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Вариант 2 — без установки, через скрипт-обёртку:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
"${ALLURE_CLI_ROOT:-$HOME/apps}/allure_cli/run.sh" "Автоответ в чат"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Вариант 3 — без установки, через python -m** (из каталога-родителя пакета, т.е. где лежит папка `allure_cli`):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cd "${ALLURE_CLI_ROOT:-$HOME/apps}"
|
|
65
|
+
PYTHONPATH="$(pwd)" python -m allure_cli "Автоответ в чат"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Примеры
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Поиск по подстроке названия — выводит id и name
|
|
72
|
+
allure_cli "Автоответ в чат"
|
|
73
|
+
|
|
74
|
+
# Только ID, по одному на строку
|
|
75
|
+
allure_cli -q "Автоответ в чат"
|
|
76
|
+
|
|
77
|
+
# Параметры через аргументы
|
|
78
|
+
allure_cli --url https://allure-testops.example.com --project 211 --token $ALLURE_TOKEN "запрос"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Вывод (обычный режим): `id` и название тест-кейса; при наличии — `fullName` со следующей строки. Значение **id** — это Allure ID для декоратора `AllureID("...")` в коде тестов.
|
|
82
|
+
|
|
83
|
+
## Авторизация
|
|
84
|
+
|
|
85
|
+
Используется схема из [документации ТестОпс](https://docs.qatools.ru/api): API-токен обменивается на JWT через `POST /api/uaa/oauth/token`, далее запросы к API — с заголовком `Authorization: Bearer <jwt>`.
|
|
86
|
+
|
|
87
|
+
JWT кэшируется на диск (`~/.cache/allure_cli/` или `$XDG_CACHE_HOME/allure_cli/`), чтобы не запрашивать новый токен при каждом вызове. При ответе 401 от API кэш сбрасывается и токен запрашивается заново автоматически.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI: get Allure TestOps test case ID(s) by name.
|
|
3
|
+
Env: ALLURE_ENDPOINT (or ALLURE_TESTOPS_URL), ALLURE_TOKEN, ALLURE_PROJECT_ID.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from .client import find_by_name
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _env(key: str, fallback_key: str | None = None) -> str | None:
|
|
14
|
+
v = os.environ.get(key)
|
|
15
|
+
if v is not None:
|
|
16
|
+
return v
|
|
17
|
+
if fallback_key:
|
|
18
|
+
return os.environ.get(fallback_key)
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> int:
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
description="Get Allure TestOps test case ID(s) by name (search substring)."
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"query",
|
|
28
|
+
nargs="?",
|
|
29
|
+
default=None,
|
|
30
|
+
help="Search query (substring of test case name). If omitted, read from stdin.",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--url",
|
|
34
|
+
default=_env("ALLURE_ENDPOINT") or _env("ALLURE_TESTOPS_URL"),
|
|
35
|
+
help="Allure TestOps base URL (default: ALLURE_ENDPOINT or ALLURE_TESTOPS_URL)",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--token",
|
|
39
|
+
default=_env("ALLURE_TOKEN"),
|
|
40
|
+
help="API token (default: ALLURE_TOKEN)",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--project",
|
|
44
|
+
type=int,
|
|
45
|
+
default=(int(os.environ["ALLURE_PROJECT_ID"]) if os.environ.get("ALLURE_PROJECT_ID") else None),
|
|
46
|
+
help="Project ID (default: ALLURE_PROJECT_ID)",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--size",
|
|
50
|
+
type=int,
|
|
51
|
+
default=50,
|
|
52
|
+
help="Max results (default: 50)",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"-q", "--quiet",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help="Print only IDs, one per line",
|
|
58
|
+
)
|
|
59
|
+
args = parser.parse_args()
|
|
60
|
+
|
|
61
|
+
query = args.query
|
|
62
|
+
if query is None:
|
|
63
|
+
query = sys.stdin.read().strip()
|
|
64
|
+
if not query:
|
|
65
|
+
parser.error("Provide query as argument or via stdin")
|
|
66
|
+
return 2
|
|
67
|
+
|
|
68
|
+
if not args.url:
|
|
69
|
+
print("Error: --url or ALLURE_ENDPOINT/ALLURE_TESTOPS_URL required", file=sys.stderr)
|
|
70
|
+
return 2
|
|
71
|
+
if not args.token:
|
|
72
|
+
print("Error: --token or ALLURE_TOKEN required", file=sys.stderr)
|
|
73
|
+
return 2
|
|
74
|
+
if args.project is None:
|
|
75
|
+
print("Error: --project or ALLURE_PROJECT_ID required", file=sys.stderr)
|
|
76
|
+
return 2
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
cases = find_by_name(
|
|
80
|
+
args.url,
|
|
81
|
+
args.token,
|
|
82
|
+
args.project,
|
|
83
|
+
query,
|
|
84
|
+
size=args.size,
|
|
85
|
+
)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
88
|
+
return 1
|
|
89
|
+
|
|
90
|
+
if not cases:
|
|
91
|
+
if not args.quiet:
|
|
92
|
+
print(f"No test cases found for: {query!r}", file=sys.stderr)
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
if args.quiet:
|
|
96
|
+
for c in cases:
|
|
97
|
+
print(c["id"])
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
for c in cases:
|
|
101
|
+
name = c.get("name", "")
|
|
102
|
+
full = c.get("fullName", "")
|
|
103
|
+
print(f"{c['id']}\t{name}")
|
|
104
|
+
if full and full != name:
|
|
105
|
+
print(f" {full}")
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Allure TestOps API client.
|
|
3
|
+
Auth: exchange API token for JWT (Bearer) per https://docs.qatools.ru/api
|
|
4
|
+
JWT cached on disk; refreshed automatically on 401.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
_AUTH_HINT = "Проверьте ALLURE_TOKEN: создайте новый API-токен в Allure TestOps (профиль → API Tokens) и обновите переменную окружения."
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _cache_dir() -> Path:
|
|
20
|
+
base = os.environ.get("XDG_CACHE_HOME") or os.path.join(os.path.expanduser("~"), ".cache")
|
|
21
|
+
return Path(base) / "allure_cli"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cache_key(base_url: str, api_token: str) -> str:
|
|
25
|
+
return hashlib.sha256(f"{base_url.rstrip('/')}:{api_token}".encode()).hexdigest()[:32]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _jwt_cache_path(base_url: str, api_token: str) -> Path:
|
|
29
|
+
return _cache_dir() / f"jwt_{_cache_key(base_url, api_token)}.json"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _read_jwt_cache(path: Path) -> str | None:
|
|
33
|
+
try:
|
|
34
|
+
data = json.loads(path.read_text())
|
|
35
|
+
token = data.get("access_token")
|
|
36
|
+
return token if token else None
|
|
37
|
+
except (OSError, json.JSONDecodeError, TypeError):
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _write_jwt_cache(path: Path, access_token: str) -> None:
|
|
42
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
path.write_text(json.dumps({"access_token": access_token}), encoding="utf-8")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def clear_jwt_cache(base_url: str, api_token: str) -> None:
|
|
47
|
+
"""Remove cached JWT for this url+token (e.g. after 401)."""
|
|
48
|
+
path = _jwt_cache_path(base_url, api_token)
|
|
49
|
+
try:
|
|
50
|
+
path.unlink(missing_ok=True)
|
|
51
|
+
except OSError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_jwt(base_url: str, api_token: str) -> str:
|
|
56
|
+
"""Return JWT from cache if present, otherwise exchange API token for JWT via OAuth and cache it."""
|
|
57
|
+
path = _jwt_cache_path(base_url, api_token)
|
|
58
|
+
cached = _read_jwt_cache(path)
|
|
59
|
+
if cached:
|
|
60
|
+
return cached
|
|
61
|
+
|
|
62
|
+
url = f"{base_url.rstrip('/')}/api/uaa/oauth/token"
|
|
63
|
+
data = urllib.parse.urlencode({
|
|
64
|
+
"grant_type": "apitoken",
|
|
65
|
+
"scope": "openid",
|
|
66
|
+
"token": api_token,
|
|
67
|
+
}).encode()
|
|
68
|
+
req = urllib.request.Request(
|
|
69
|
+
url,
|
|
70
|
+
data=data,
|
|
71
|
+
method="POST",
|
|
72
|
+
headers={
|
|
73
|
+
"Accept": "application/json",
|
|
74
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
try:
|
|
78
|
+
with urllib.request.urlopen(req) as resp:
|
|
79
|
+
body = json.loads(resp.read().decode())
|
|
80
|
+
except urllib.error.HTTPError as e:
|
|
81
|
+
clear_jwt_cache(base_url, api_token)
|
|
82
|
+
if e.code == 401:
|
|
83
|
+
raise RuntimeError(f"Ошибка авторизации (401): неверный или истёкший API-токен. {_AUTH_HINT}") from e
|
|
84
|
+
raise RuntimeError(f"Ошибка при получении JWT: HTTP {e.code}: {e.read().decode() if e.fp else ''}") from e
|
|
85
|
+
access_token = body.get("access_token")
|
|
86
|
+
if not access_token:
|
|
87
|
+
raise RuntimeError(f"OAuth response missing access_token: {body}")
|
|
88
|
+
_write_jwt_cache(path, access_token)
|
|
89
|
+
return access_token
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def search_test_cases(
|
|
93
|
+
base_url: str,
|
|
94
|
+
jwt: str,
|
|
95
|
+
project_id: int,
|
|
96
|
+
rql: str,
|
|
97
|
+
*,
|
|
98
|
+
page: int = 0,
|
|
99
|
+
size: int = 20,
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
"""Search test cases by AQL. Returns API response with 'content' list."""
|
|
102
|
+
params = {"projectId": project_id, "rql": rql, "page": page, "size": size}
|
|
103
|
+
query = urllib.parse.urlencode(params)
|
|
104
|
+
url = f"{base_url.rstrip('/')}/api/testcase/__search?{query}"
|
|
105
|
+
req = urllib.request.Request(
|
|
106
|
+
url,
|
|
107
|
+
method="GET",
|
|
108
|
+
headers={
|
|
109
|
+
"Authorization": f"Bearer {jwt}",
|
|
110
|
+
"Accept": "application/json",
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
try:
|
|
114
|
+
with urllib.request.urlopen(req) as resp:
|
|
115
|
+
return json.loads(resp.read().decode())
|
|
116
|
+
except urllib.error.HTTPError as e:
|
|
117
|
+
if e.code == 401:
|
|
118
|
+
raise RuntimeError(f"Ошибка авторизации (401) при поиске тест-кейсов. {_AUTH_HINT}") from e
|
|
119
|
+
raise RuntimeError(f"Ошибка API: HTTP {e.code}: {e.read().decode() if e.fp else ''}") from e
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def find_by_name(
|
|
123
|
+
base_url: str,
|
|
124
|
+
api_token: str,
|
|
125
|
+
project_id: int,
|
|
126
|
+
name_query: str,
|
|
127
|
+
*,
|
|
128
|
+
size: int = 50,
|
|
129
|
+
) -> list[dict[str, Any]]:
|
|
130
|
+
"""
|
|
131
|
+
Get test cases whose name contains name_query (AQL: name ~= "...").
|
|
132
|
+
Returns list of test case dicts (id, name, fullName, ...).
|
|
133
|
+
On 401 (expired JWT) clears cache, fetches new JWT, retries once.
|
|
134
|
+
"""
|
|
135
|
+
jwt = get_jwt(base_url, api_token)
|
|
136
|
+
rql = f'name ~= "{name_query}"'
|
|
137
|
+
try:
|
|
138
|
+
result = search_test_cases(base_url, jwt, project_id, rql, page=0, size=size)
|
|
139
|
+
except RuntimeError as e:
|
|
140
|
+
if "401" not in str(e):
|
|
141
|
+
raise
|
|
142
|
+
clear_jwt_cache(base_url, api_token)
|
|
143
|
+
jwt = get_jwt(base_url, api_token)
|
|
144
|
+
result = search_test_cases(base_url, jwt, project_id, rql, page=0, size=size)
|
|
145
|
+
return result.get("content") or []
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: allure-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI для Allure TestOps: получение Allure ID тест-кейса по названию
|
|
5
|
+
Author: Allure CLI Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adolmatov/allure_cli
|
|
8
|
+
Project-URL: Repository, https://github.com/adolmatov/allure_cli
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/adolmatov/allure_cli/issues
|
|
10
|
+
Keywords: allure,testops,testing,qa,cli
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Allure CLI
|
|
26
|
+
|
|
27
|
+
CLI для работы с Allure TestOps. Основная задача — получение Allure ID тест-кейса по названию.
|
|
28
|
+
|
|
29
|
+
## Требования
|
|
30
|
+
|
|
31
|
+
- Python 3.10+
|
|
32
|
+
- Внешние зависимости не нужны (только stdlib)
|
|
33
|
+
|
|
34
|
+
## Установка
|
|
35
|
+
|
|
36
|
+
Чтобы вызывать команду просто как `allure_cli` (без `python -m`):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd "${ALLURE_CLI_ROOT:-$HOME/apps}/allure_cli"
|
|
40
|
+
pip install -e .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
После этого команда `allure_cli` будет доступна в PATH (в том же окружении Python, куда ставили).
|
|
44
|
+
|
|
45
|
+
## Настройка
|
|
46
|
+
|
|
47
|
+
Переменные окружения (или аргументы `--url`, `--token`, `--project`):
|
|
48
|
+
|
|
49
|
+
| Переменная | Описание |
|
|
50
|
+
|------------|----------|
|
|
51
|
+
| `ALLURE_ENDPOINT` или `ALLURE_TESTOPS_URL` | Базовый URL Allure TestOps (например, `https://allure-testops.example.com`) |
|
|
52
|
+
| `ALLURE_TOKEN` | API-токен (создаётся в Allure: профиль → API Tokens) |
|
|
53
|
+
| `ALLURE_PROJECT_ID` | ID проекта (например, `211`) |
|
|
54
|
+
| `ALLURE_CLI_ROOT` | Каталог, в котором лежит пакет `allure_cli` (по умолчанию `$HOME/apps`). Нужен для запуска через `run.sh` из любой директории. |
|
|
55
|
+
|
|
56
|
+
**Постоянно (zsh/bash):** добавьте в `~/.zshrc` или `~/.bashrc` и перезапустите терминал (или выполните `source ~/.zshrc` / `source ~/.bashrc`):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Allure TestOps CLI
|
|
60
|
+
export ALLURE_ENDPOINT="https://allure-testops.example.com"
|
|
61
|
+
export ALLURE_PROJECT_ID="YOUR_PROJECT_ID"
|
|
62
|
+
export ALLURE_TOKEN="<YOUR_TOKEN>"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Подставьте свой URL, ID проекта и токен. После этого вызывать `allure_cli` можно без `export` в каждой сессии.
|
|
66
|
+
|
|
67
|
+
## Запуск
|
|
68
|
+
|
|
69
|
+
**Вариант 1 — после установки (`pip install -e .`):** команда `allure_cli` доступна из любой директории:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export ALLURE_ENDPOINT=https://allure-testops.example.com
|
|
73
|
+
export ALLURE_PROJECT_ID=211
|
|
74
|
+
export ALLURE_TOKEN=<ваш_токен>
|
|
75
|
+
|
|
76
|
+
allure_cli "Автоответ в чат"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Вариант 2 — без установки, через скрипт-обёртку:**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
"${ALLURE_CLI_ROOT:-$HOME/apps}/allure_cli/run.sh" "Автоответ в чат"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Вариант 3 — без установки, через python -m** (из каталога-родителя пакета, т.е. где лежит папка `allure_cli`):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd "${ALLURE_CLI_ROOT:-$HOME/apps}"
|
|
89
|
+
PYTHONPATH="$(pwd)" python -m allure_cli "Автоответ в чат"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Примеры
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Поиск по подстроке названия — выводит id и name
|
|
96
|
+
allure_cli "Автоответ в чат"
|
|
97
|
+
|
|
98
|
+
# Только ID, по одному на строку
|
|
99
|
+
allure_cli -q "Автоответ в чат"
|
|
100
|
+
|
|
101
|
+
# Параметры через аргументы
|
|
102
|
+
allure_cli --url https://allure-testops.example.com --project 211 --token $ALLURE_TOKEN "запрос"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Вывод (обычный режим): `id` и название тест-кейса; при наличии — `fullName` со следующей строки. Значение **id** — это Allure ID для декоратора `AllureID("...")` в коде тестов.
|
|
106
|
+
|
|
107
|
+
## Авторизация
|
|
108
|
+
|
|
109
|
+
Используется схема из [документации ТестОпс](https://docs.qatools.ru/api): API-токен обменивается на JWT через `POST /api/uaa/oauth/token`, далее запросы к API — с заголовком `Authorization: Bearer <jwt>`.
|
|
110
|
+
|
|
111
|
+
JWT кэшируется на диск (`~/.cache/allure_cli/` или `$XDG_CACHE_HOME/allure_cli/`), чтобы не запрашивать новый токен при каждом вызове. При ответе 401 от API кэш сбрасывается и токен запрашивается заново автоматически.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
requirements.txt
|
|
6
|
+
allure_cli/__init__.py
|
|
7
|
+
allure_cli/__main__.py
|
|
8
|
+
allure_cli/cli.py
|
|
9
|
+
allure_cli/client.py
|
|
10
|
+
allure_cli.egg-info/PKG-INFO
|
|
11
|
+
allure_cli.egg-info/SOURCES.txt
|
|
12
|
+
allure_cli.egg-info/dependency_links.txt
|
|
13
|
+
allure_cli.egg-info/entry_points.txt
|
|
14
|
+
allure_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
allure_cli
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "allure-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI для Allure TestOps: получение Allure ID тест-кейса по названию"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Allure CLI Contributors"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["allure", "testops", "testing", "qa", "cli"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Software Development :: Testing",
|
|
25
|
+
"Topic :: Utilities",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/adolmatov/allure_cli"
|
|
30
|
+
Repository = "https://github.com/adolmatov/allure_cli"
|
|
31
|
+
"Bug Tracker" = "https://github.com/adolmatov/allure_cli/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
allure_cli = "allure_cli.cli:main"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# No dependencies — uses Python stdlib only (urllib, json, argparse).
|