clickhouse-users-cli 0.1.6__tar.gz → 0.1.7__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.
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/PKG-INFO +12 -9
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/README.md +11 -8
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/__init__.py +1 -1
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/i18n.py +4 -10
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/session.py +3 -1
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/PKG-INFO +12 -9
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/SOURCES.txt +8 -1
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/pyproject.toml +8 -0
- clickhouse_users_cli-0.1.7/tests/test_app.py +154 -0
- clickhouse_users_cli-0.1.7/tests/test_db.py +128 -0
- clickhouse_users_cli-0.1.7/tests/test_i18n.py +47 -0
- clickhouse_users_cli-0.1.7/tests/test_session.py +54 -0
- clickhouse_users_cli-0.1.7/tests/test_sql_builder.py +93 -0
- clickhouse_users_cli-0.1.7/tests/test_style.py +22 -0
- clickhouse_users_cli-0.1.7/tests/test_validators.py +53 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/LICENSE +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/app.py +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/db.py +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/sql_builder.py +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/style.py +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/validators.py +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/dependency_links.txt +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/entry_points.txt +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/requires.txt +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/top_level.txt +0 -0
- {clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clickhouse-users-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: CLI interativa para criar usuarios ClickHouse com privilegio minimo (least privilege)
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Keywords: clickhouse,cli,users,access-control,least-privilege
|
|
@@ -59,8 +59,8 @@ ch-users
|
|
|
59
59
|
|---|---|
|
|
60
60
|
| Instalar / atualizar | `pip install -U clickhouse-users-cli` |
|
|
61
61
|
| Rodar | `ch-users` |
|
|
62
|
-
| Do código-fonte | `
|
|
63
|
-
| Requisitos | Python `>= 3.10` + conta admin no ClickHouse (ex.: `default`) |
|
|
62
|
+
| Do código-fonte | `uv sync` + `uv run ch-users` |
|
|
63
|
+
| Requisitos | Python `>= 3.10` ([UV](https://docs.astral.sh/uv/) gerencia tudo via `uv.lock`) + conta admin no ClickHouse (ex.: `default`) |
|
|
64
64
|
| Idioma | Automático pelo sistema operacional: PT em sistemas em português, EN nos demais. Force com `CH_USERS_LANG=pt` ou `CH_USERS_LANG=en` |
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -164,11 +164,13 @@ Princípios: **opt-in explícito** (nada pré-marcado), **escape correto** (back
|
|
|
164
164
|
## 🧑💻 Desenvolvimento
|
|
165
165
|
|
|
166
166
|
```powershell
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
python
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
uv sync # instala tudo (usa uv.lock)
|
|
168
|
+
uv run ch-users # roda o CLI do fonte
|
|
169
|
+
uv run python main.py # alternativa direta
|
|
170
|
+
uv run pytest -q # suíte de testes (44 testes, sem banco)
|
|
171
|
+
uv build # gera sdist + wheel em dist/
|
|
172
|
+
uvx twine check dist/* # valida (inclusive este README)
|
|
173
|
+
uv publish # publica (UV_PUBLISH_TOKEN)
|
|
172
174
|
```
|
|
173
175
|
|
|
174
176
|
Versão em fonte única: `clickhouse_users_cli/__init__.py` (`__version__`).
|
|
@@ -177,8 +179,9 @@ Versão em fonte única: `clickhouse_users_cli/__init__.py` (`__version__`).
|
|
|
177
179
|
|
|
178
180
|
```
|
|
179
181
|
main.py # entrypoint (dev local)
|
|
180
|
-
requirements.txt
|
|
181
182
|
pyproject.toml # build + metadados PyPI (comando: ch-users)
|
|
183
|
+
uv.lock # dependências travadas
|
|
184
|
+
.python-version # Python do projeto (uv)
|
|
182
185
|
clickhouse_users_cli/
|
|
183
186
|
__init__.py # __version__
|
|
184
187
|
app.py # fluxos interativos (criar, listar, gerenciar, sessão)
|
|
@@ -35,8 +35,8 @@ ch-users
|
|
|
35
35
|
|---|---|
|
|
36
36
|
| Instalar / atualizar | `pip install -U clickhouse-users-cli` |
|
|
37
37
|
| Rodar | `ch-users` |
|
|
38
|
-
| Do código-fonte | `
|
|
39
|
-
| Requisitos | Python `>= 3.10` + conta admin no ClickHouse (ex.: `default`) |
|
|
38
|
+
| Do código-fonte | `uv sync` + `uv run ch-users` |
|
|
39
|
+
| Requisitos | Python `>= 3.10` ([UV](https://docs.astral.sh/uv/) gerencia tudo via `uv.lock`) + conta admin no ClickHouse (ex.: `default`) |
|
|
40
40
|
| Idioma | Automático pelo sistema operacional: PT em sistemas em português, EN nos demais. Force com `CH_USERS_LANG=pt` ou `CH_USERS_LANG=en` |
|
|
41
41
|
|
|
42
42
|
---
|
|
@@ -140,11 +140,13 @@ Princípios: **opt-in explícito** (nada pré-marcado), **escape correto** (back
|
|
|
140
140
|
## 🧑💻 Desenvolvimento
|
|
141
141
|
|
|
142
142
|
```powershell
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
python
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
uv sync # instala tudo (usa uv.lock)
|
|
144
|
+
uv run ch-users # roda o CLI do fonte
|
|
145
|
+
uv run python main.py # alternativa direta
|
|
146
|
+
uv run pytest -q # suíte de testes (44 testes, sem banco)
|
|
147
|
+
uv build # gera sdist + wheel em dist/
|
|
148
|
+
uvx twine check dist/* # valida (inclusive este README)
|
|
149
|
+
uv publish # publica (UV_PUBLISH_TOKEN)
|
|
148
150
|
```
|
|
149
151
|
|
|
150
152
|
Versão em fonte única: `clickhouse_users_cli/__init__.py` (`__version__`).
|
|
@@ -153,8 +155,9 @@ Versão em fonte única: `clickhouse_users_cli/__init__.py` (`__version__`).
|
|
|
153
155
|
|
|
154
156
|
```
|
|
155
157
|
main.py # entrypoint (dev local)
|
|
156
|
-
requirements.txt
|
|
157
158
|
pyproject.toml # build + metadados PyPI (comando: ch-users)
|
|
159
|
+
uv.lock # dependências travadas
|
|
160
|
+
.python-version # Python do projeto (uv)
|
|
158
161
|
clickhouse_users_cli/
|
|
159
162
|
__init__.py # __version__
|
|
160
163
|
app.py # fluxos interativos (criar, listar, gerenciar, sessão)
|
|
@@ -215,11 +215,11 @@ STRINGS: dict[str, dict[str, str]] = {
|
|
|
215
215
|
"v_pass_long": "Máximo 128 caracteres.",
|
|
216
216
|
"v_pick_one": "Selecione pelo menos 1 item (espaço marca, enter confirma).",
|
|
217
217
|
# Camada de banco
|
|
218
|
-
"db_no_dep": "Dependência 'clickhouse-connect'
|
|
218
|
+
"db_no_dep": "Dependência 'clickhouse-connect' ausente. Rode: uv sync",
|
|
219
219
|
"db_connect_fail": "Falha ao conectar em {proto}://{host}:{port}: {e}",
|
|
220
220
|
"db_not_connected": "Não conectado. Chame .connect() primeiro.",
|
|
221
221
|
# Sessão YAML
|
|
222
|
-
"sess_no_yaml": "Dependência 'pyyaml'
|
|
222
|
+
"sess_no_yaml": "Dependência 'pyyaml' ausente. Rode: uv sync",
|
|
223
223
|
"sess_incomplete": "Arquivo de sessão incompleto (falta {e}). Apague e salve de novo.",
|
|
224
224
|
"sess_read_fail": "Não foi possível ler a sessão salva: {e}",
|
|
225
225
|
# SQL builder
|
|
@@ -428,11 +428,11 @@ STRINGS: dict[str, dict[str, str]] = {
|
|
|
428
428
|
"v_pass_long": "Maximum 128 characters.",
|
|
429
429
|
"v_pick_one": "Select at least 1 item (space toggles, enter confirms).",
|
|
430
430
|
# DB layer
|
|
431
|
-
"db_no_dep": "Missing 'clickhouse-connect' dependency. Run:
|
|
431
|
+
"db_no_dep": "Missing 'clickhouse-connect' dependency. Run: uv sync",
|
|
432
432
|
"db_connect_fail": "Failed to connect to {proto}://{host}:{port}: {e}",
|
|
433
433
|
"db_not_connected": "Not connected. Call .connect() first.",
|
|
434
434
|
# YAML session
|
|
435
|
-
"sess_no_yaml": "Missing 'pyyaml' dependency. Run:
|
|
435
|
+
"sess_no_yaml": "Missing 'pyyaml' dependency. Run: uv sync",
|
|
436
436
|
"sess_incomplete": "Saved session file is incomplete (missing {e}). Delete it and save again.",
|
|
437
437
|
"sess_read_fail": "Could not read the saved session: {e}",
|
|
438
438
|
# SQL builder
|
|
@@ -457,12 +457,6 @@ def detect_language() -> str:
|
|
|
457
457
|
candidates.append(loc[0])
|
|
458
458
|
except Exception:
|
|
459
459
|
pass
|
|
460
|
-
try:
|
|
461
|
-
dloc = locale.getdefaultlocale()
|
|
462
|
-
if dloc and dloc[0]:
|
|
463
|
-
candidates.append(dloc[0])
|
|
464
|
-
except Exception:
|
|
465
|
-
pass
|
|
466
460
|
for var in ("LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"):
|
|
467
461
|
candidates.append(os.environ.get(var, ""))
|
|
468
462
|
for raw in candidates:
|
|
@@ -63,7 +63,9 @@ def load_session() -> ConnectionInfo:
|
|
|
63
63
|
)
|
|
64
64
|
except KeyError as e:
|
|
65
65
|
raise RuntimeError(t("sess_incomplete", e=e)) from e
|
|
66
|
-
except
|
|
66
|
+
except Exception as e:
|
|
67
|
+
# OSError, ValueError, TypeError, yaml.YAMLError… — qualquer falha de
|
|
68
|
+
# leitura/parse vira mensagem amigável em vez de stack trace.
|
|
67
69
|
raise RuntimeError(t("sess_read_fail", e=e)) from e
|
|
68
70
|
|
|
69
71
|
|
{clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clickhouse-users-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: CLI interativa para criar usuarios ClickHouse com privilegio minimo (least privilege)
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Keywords: clickhouse,cli,users,access-control,least-privilege
|
|
@@ -59,8 +59,8 @@ ch-users
|
|
|
59
59
|
|---|---|
|
|
60
60
|
| Instalar / atualizar | `pip install -U clickhouse-users-cli` |
|
|
61
61
|
| Rodar | `ch-users` |
|
|
62
|
-
| Do código-fonte | `
|
|
63
|
-
| Requisitos | Python `>= 3.10` + conta admin no ClickHouse (ex.: `default`) |
|
|
62
|
+
| Do código-fonte | `uv sync` + `uv run ch-users` |
|
|
63
|
+
| Requisitos | Python `>= 3.10` ([UV](https://docs.astral.sh/uv/) gerencia tudo via `uv.lock`) + conta admin no ClickHouse (ex.: `default`) |
|
|
64
64
|
| Idioma | Automático pelo sistema operacional: PT em sistemas em português, EN nos demais. Force com `CH_USERS_LANG=pt` ou `CH_USERS_LANG=en` |
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -164,11 +164,13 @@ Princípios: **opt-in explícito** (nada pré-marcado), **escape correto** (back
|
|
|
164
164
|
## 🧑💻 Desenvolvimento
|
|
165
165
|
|
|
166
166
|
```powershell
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
python
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
uv sync # instala tudo (usa uv.lock)
|
|
168
|
+
uv run ch-users # roda o CLI do fonte
|
|
169
|
+
uv run python main.py # alternativa direta
|
|
170
|
+
uv run pytest -q # suíte de testes (44 testes, sem banco)
|
|
171
|
+
uv build # gera sdist + wheel em dist/
|
|
172
|
+
uvx twine check dist/* # valida (inclusive este README)
|
|
173
|
+
uv publish # publica (UV_PUBLISH_TOKEN)
|
|
172
174
|
```
|
|
173
175
|
|
|
174
176
|
Versão em fonte única: `clickhouse_users_cli/__init__.py` (`__version__`).
|
|
@@ -177,8 +179,9 @@ Versão em fonte única: `clickhouse_users_cli/__init__.py` (`__version__`).
|
|
|
177
179
|
|
|
178
180
|
```
|
|
179
181
|
main.py # entrypoint (dev local)
|
|
180
|
-
requirements.txt
|
|
181
182
|
pyproject.toml # build + metadados PyPI (comando: ch-users)
|
|
183
|
+
uv.lock # dependências travadas
|
|
184
|
+
.python-version # Python do projeto (uv)
|
|
182
185
|
clickhouse_users_cli/
|
|
183
186
|
__init__.py # __version__
|
|
184
187
|
app.py # fluxos interativos (criar, listar, gerenciar, sessão)
|
{clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/SOURCES.txt
RENAMED
|
@@ -14,4 +14,11 @@ clickhouse_users_cli.egg-info/SOURCES.txt
|
|
|
14
14
|
clickhouse_users_cli.egg-info/dependency_links.txt
|
|
15
15
|
clickhouse_users_cli.egg-info/entry_points.txt
|
|
16
16
|
clickhouse_users_cli.egg-info/requires.txt
|
|
17
|
-
clickhouse_users_cli.egg-info/top_level.txt
|
|
17
|
+
clickhouse_users_cli.egg-info/top_level.txt
|
|
18
|
+
tests/test_app.py
|
|
19
|
+
tests/test_db.py
|
|
20
|
+
tests/test_i18n.py
|
|
21
|
+
tests/test_session.py
|
|
22
|
+
tests/test_sql_builder.py
|
|
23
|
+
tests/test_style.py
|
|
24
|
+
tests/test_validators.py
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Testes dos fluxos interativos com prompts simulados (sem console real)."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from clickhouse_users_cli import app
|
|
8
|
+
from clickhouse_users_cli.i18n import set_language
|
|
9
|
+
from clickhouse_users_cli.sql_builder import GrantScope
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture(autouse=True)
|
|
13
|
+
def _lang():
|
|
14
|
+
set_language("pt")
|
|
15
|
+
yield
|
|
16
|
+
set_language("pt")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _patch_factories(monkeypatch, **answers):
|
|
20
|
+
"""Substitui os factories do questionary por retornos fixos."""
|
|
21
|
+
for name, value in answers.items():
|
|
22
|
+
monkeypatch.setattr(app.Q, name, MagicMock(return_value=MagicMock()))
|
|
23
|
+
monkeypatch.setattr(app, "_ask_or_abort", MagicMock(side_effect=lambda prompt: answers.get("__ask__")))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_ask_profile_questions_by_mode(monkeypatch):
|
|
27
|
+
asked = {}
|
|
28
|
+
|
|
29
|
+
def fake_select(msg, **kw):
|
|
30
|
+
asked["q"] = msg
|
|
31
|
+
return MagicMock()
|
|
32
|
+
|
|
33
|
+
monkeypatch.setattr(app.Q, "select", fake_select)
|
|
34
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: "readonly")
|
|
35
|
+
app.ask_profile()
|
|
36
|
+
assert asked["q"] == "Que tipo de usuário criar?"
|
|
37
|
+
app.ask_profile(mode="edit")
|
|
38
|
+
assert asked["q"] == "Qual o novo tipo de acesso?"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_ask_profile_question_english(monkeypatch):
|
|
42
|
+
set_language("en")
|
|
43
|
+
monkeypatch.setattr(app.Q, "select", lambda msg, **kw: MagicMock())
|
|
44
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: "readonly")
|
|
45
|
+
with patch.object(app, "step") as step:
|
|
46
|
+
app.ask_profile()
|
|
47
|
+
assert "User type" in step.call_args[0][0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_ask_databases_all_and_partial(monkeypatch):
|
|
51
|
+
admin = MagicMock()
|
|
52
|
+
admin.list_databases.return_value = ["vendas", "system"]
|
|
53
|
+
captured = {}
|
|
54
|
+
|
|
55
|
+
def fake_checkbox(msg, choices, validate, style, qmark, instruction):
|
|
56
|
+
captured["values"] = [c.value for c in choices]
|
|
57
|
+
captured["checked"] = [c.checked for c in choices]
|
|
58
|
+
return MagicMock()
|
|
59
|
+
|
|
60
|
+
monkeypatch.setattr(app.Q, "checkbox", fake_checkbox)
|
|
61
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: ["__ALL__"])
|
|
62
|
+
assert app.ask_databases(admin) == ["vendas", "system"]
|
|
63
|
+
assert captured["values"][0] == "__ALL__"
|
|
64
|
+
assert captured["checked"] == [False] * len(captured["values"])
|
|
65
|
+
|
|
66
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: ["vendas"])
|
|
67
|
+
assert app.ask_databases(admin) == ["vendas"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_ask_tables_all_and_specific(monkeypatch):
|
|
71
|
+
admin = MagicMock()
|
|
72
|
+
admin.list_tables.return_value = ["pedidos", "clientes"]
|
|
73
|
+
monkeypatch.setattr(app.Q, "checkbox", MagicMock(return_value=MagicMock()))
|
|
74
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: ["__ALL__"])
|
|
75
|
+
scopes = app.ask_tables(admin, ["vendas"])
|
|
76
|
+
assert scopes == [GrantScope(database="vendas", table=None)]
|
|
77
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: ["pedidos"])
|
|
78
|
+
scopes = app.ask_tables(admin, ["vendas"])
|
|
79
|
+
assert scopes == [GrantScope(database="vendas", table="pedidos")]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_ask_privileges_profiles():
|
|
83
|
+
assert app.ask_privileges("readonly") == (["SHOW", "SELECT"], False)
|
|
84
|
+
assert app.ask_privileges("admin") == (["ALL"], True)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_ask_privileges_custom(monkeypatch):
|
|
88
|
+
monkeypatch.setattr(app.Q, "checkbox", MagicMock(return_value=MagicMock()))
|
|
89
|
+
monkeypatch.setattr(app.Q, "confirm", MagicMock(return_value=MagicMock()))
|
|
90
|
+
monkeypatch.setattr(app, "_ask_or_abort", MagicMock(side_effect=[["SELECT"], False]))
|
|
91
|
+
assert app.ask_privileges("custom") == (["SELECT"], False)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_flow_edit_grants_revokes_first(monkeypatch):
|
|
95
|
+
admin = MagicMock()
|
|
96
|
+
admin.conn.username = "default"
|
|
97
|
+
monkeypatch.setattr(app, "ask_profile", lambda mode="create": "readwrite")
|
|
98
|
+
monkeypatch.setattr(app, "ask_privileges", lambda profile, mode="create": (["SHOW", "SELECT"], False))
|
|
99
|
+
monkeypatch.setattr(app, "ask_databases", lambda admin, mode="create": ["vendas"])
|
|
100
|
+
monkeypatch.setattr(app, "ask_tables",
|
|
101
|
+
lambda admin, dbs, mode="create": [GrantScope(database="vendas", table="pedidos")])
|
|
102
|
+
monkeypatch.setattr(app.Q, "confirm", MagicMock(return_value=MagicMock()))
|
|
103
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: True)
|
|
104
|
+
app.flow_edit_grants(admin, "ana")
|
|
105
|
+
stmts = admin.execute_statements.call_args[0][0]
|
|
106
|
+
assert stmts[0] == "REVOKE ALL ON *.* FROM `ana`"
|
|
107
|
+
assert stmts[1] == "GRANT SHOW, SELECT ON `vendas`.`pedidos` TO `ana`"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_flow_edit_grants_cancelled_runs_nothing(monkeypatch):
|
|
111
|
+
admin = MagicMock()
|
|
112
|
+
admin.conn.username = "default"
|
|
113
|
+
monkeypatch.setattr(app, "ask_profile", lambda mode="create": "readonly")
|
|
114
|
+
monkeypatch.setattr(app, "ask_privileges", lambda profile, mode="create": (["SHOW"], False))
|
|
115
|
+
monkeypatch.setattr(app, "ask_databases", lambda admin, mode="create": ["vendas"])
|
|
116
|
+
monkeypatch.setattr(app, "ask_tables",
|
|
117
|
+
lambda admin, dbs, mode="create": [GrantScope(database="vendas", table=None)])
|
|
118
|
+
monkeypatch.setattr(app.Q, "confirm", MagicMock(return_value=MagicMock()))
|
|
119
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: False)
|
|
120
|
+
app.flow_edit_grants(admin, "ana")
|
|
121
|
+
admin.execute_statements.assert_not_called()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_maybe_load_without_file_returns_none(monkeypatch):
|
|
125
|
+
monkeypatch.setattr("clickhouse_users_cli.session.has_saved_session", lambda: False)
|
|
126
|
+
ask = MagicMock(side_effect=AssertionError("nao deveria perguntar"))
|
|
127
|
+
monkeypatch.setattr(app, "_ask_or_abort", ask)
|
|
128
|
+
assert app.maybe_load_saved_session() is None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_maybe_load_use_saved(monkeypatch):
|
|
132
|
+
from clickhouse_users_cli.db import ConnectionInfo
|
|
133
|
+
|
|
134
|
+
conn = ConnectionInfo(host="h", port=8123, secure=False, username="u", password="p")
|
|
135
|
+
monkeypatch.setattr("clickhouse_users_cli.session.has_saved_session", lambda: True)
|
|
136
|
+
monkeypatch.setattr("clickhouse_users_cli.session.load_session", lambda: conn)
|
|
137
|
+
monkeypatch.setattr(app.Q, "select", MagicMock(return_value=MagicMock()))
|
|
138
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: "use")
|
|
139
|
+
assert app.maybe_load_saved_session() is conn
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_main_menu_forget_flag(monkeypatch):
|
|
143
|
+
seen = {}
|
|
144
|
+
|
|
145
|
+
def fake_select(msg, choices, default, style, qmark, instruction):
|
|
146
|
+
seen["values"] = [c.value for c in choices]
|
|
147
|
+
return MagicMock()
|
|
148
|
+
|
|
149
|
+
monkeypatch.setattr(app.Q, "select", fake_select)
|
|
150
|
+
monkeypatch.setattr(app, "_ask_or_abort", lambda prompt: "exit")
|
|
151
|
+
app.ask_main_action(show_forget=False)
|
|
152
|
+
assert "forget" not in seen["values"]
|
|
153
|
+
app.ask_main_action(show_forget=True)
|
|
154
|
+
assert "forget" in seen["values"]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Testes da camada ClickHouse com cliente falso (sem rede)."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import types
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from clickhouse_users_cli.db import SYSTEM_DATABASES, ClickHouseAdmin, ConnectionInfo
|
|
10
|
+
from clickhouse_users_cli.i18n import set_language
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(autouse=True)
|
|
14
|
+
def _lang():
|
|
15
|
+
set_language("pt")
|
|
16
|
+
yield
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FakeClient:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.queries = []
|
|
22
|
+
self.commands = []
|
|
23
|
+
|
|
24
|
+
def query(self, sql):
|
|
25
|
+
self.queries.append(sql)
|
|
26
|
+
|
|
27
|
+
class R:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
r = R()
|
|
31
|
+
if sql == "SHOW USERS":
|
|
32
|
+
r.result_rows = [("default",), ("ana",)]
|
|
33
|
+
elif sql.startswith("SHOW GRANTS FOR"):
|
|
34
|
+
r.result_rows = [("GRANT SELECT ON db.t TO ana",)]
|
|
35
|
+
elif sql.startswith("SHOW CREATE USER"):
|
|
36
|
+
r.result_rows = [("CREATE USER ana HOST ANY",)]
|
|
37
|
+
elif sql == "SHOW DATABASES":
|
|
38
|
+
r.result_rows = [("vendas",), ("system",)]
|
|
39
|
+
elif sql.startswith("SHOW TABLES FROM"):
|
|
40
|
+
r.result_rows = [("pedidos",), ("clientes",)]
|
|
41
|
+
else:
|
|
42
|
+
r.result_rows = [("1",)]
|
|
43
|
+
return r
|
|
44
|
+
|
|
45
|
+
def command(self, sql):
|
|
46
|
+
self.commands.append(sql)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _admin():
|
|
50
|
+
a = ClickHouseAdmin(ConnectionInfo(host="h", port=8123, secure=False,
|
|
51
|
+
username="default", password=""))
|
|
52
|
+
a._client = FakeClient()
|
|
53
|
+
return a
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_client_requires_connect():
|
|
57
|
+
a = ClickHouseAdmin(ConnectionInfo(host="h", port=1, secure=False, username="u", password=""))
|
|
58
|
+
with pytest.raises(RuntimeError):
|
|
59
|
+
_ = a.client
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_list_users_sorted():
|
|
63
|
+
assert _admin().list_users() == ["ana", "default"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_show_grants_escapes_ident():
|
|
67
|
+
a = _admin()
|
|
68
|
+
assert a.show_grants("ana") == ["GRANT SELECT ON db.t TO ana"]
|
|
69
|
+
assert a.show_create_user("ana") == "CREATE USER ana HOST ANY"
|
|
70
|
+
assert a.client.queries[-2].startswith("SHOW GRANTS FOR `ana`")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_databases_system_last():
|
|
74
|
+
assert _admin().list_databases() == ["vendas", "system"]
|
|
75
|
+
assert "system" in SYSTEM_DATABASES
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_manage_commands():
|
|
79
|
+
a = _admin()
|
|
80
|
+
a.deactivate_user("ana")
|
|
81
|
+
a.activate_user("ana")
|
|
82
|
+
a.drop_user("ana")
|
|
83
|
+
assert a.client.commands == [
|
|
84
|
+
"ALTER USER `ana` HOST NONE",
|
|
85
|
+
"ALTER USER `ana` HOST ANY",
|
|
86
|
+
"DROP USER IF EXISTS `ana`",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_execute_statements_runs_all():
|
|
91
|
+
a = _admin()
|
|
92
|
+
a.execute_statements(["A", "B"])
|
|
93
|
+
assert a.client.commands == ["A", "B"]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_connect_missing_dependency():
|
|
97
|
+
monkeypatch_target = sys.modules.get("clickhouse_connect")
|
|
98
|
+
sys.modules["clickhouse_connect"] = None # força ImportError no import interno
|
|
99
|
+
try:
|
|
100
|
+
a = ClickHouseAdmin(ConnectionInfo(host="h", port=1, secure=False, username="u", password=""))
|
|
101
|
+
with pytest.raises(RuntimeError, match="clickhouse-connect"):
|
|
102
|
+
a.connect()
|
|
103
|
+
finally:
|
|
104
|
+
if monkeypatch_target is None:
|
|
105
|
+
sys.modules.pop("clickhouse_connect", None)
|
|
106
|
+
else:
|
|
107
|
+
sys.modules["clickhouse_connect"] = monkeypatch_target
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_connect_success_and_failure_wrap():
|
|
111
|
+
mod = types.ModuleType("clickhouse_connect")
|
|
112
|
+
|
|
113
|
+
def ok_client(**kw):
|
|
114
|
+
return FakeClient()
|
|
115
|
+
|
|
116
|
+
def boom_client(**kw):
|
|
117
|
+
raise ConnectionError("refused")
|
|
118
|
+
|
|
119
|
+
sys.modules["clickhouse_connect"] = mod
|
|
120
|
+
try:
|
|
121
|
+
mod.get_client = ok_client
|
|
122
|
+
a = ClickHouseAdmin(ConnectionInfo(host="h", port=8123, secure=False, username="u", password=""))
|
|
123
|
+
assert a.connect() is a
|
|
124
|
+
mod.get_client = boom_client
|
|
125
|
+
with pytest.raises(RuntimeError, match="Falha ao conectar"):
|
|
126
|
+
a.connect()
|
|
127
|
+
finally:
|
|
128
|
+
sys.modules.pop("clickhouse_connect", None)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Testes do i18n — paridade PT/EN, detecção e interpolação."""
|
|
2
|
+
|
|
3
|
+
import locale
|
|
4
|
+
|
|
5
|
+
from clickhouse_users_cli.i18n import STRINGS, detect_language, get_language, set_language, t
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_key_parity():
|
|
9
|
+
assert set(STRINGS["pt"]) == set(STRINGS["en"])
|
|
10
|
+
assert len(STRINGS["pt"]) > 100
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_interpolation_and_fallback():
|
|
14
|
+
set_language("pt")
|
|
15
|
+
assert t("q_port", port="8123") == "Porta HTTP(S) [8123]:"
|
|
16
|
+
assert t("connected", username="u", host="h", port=1) == "Conectado como 'u' em h:1"
|
|
17
|
+
assert t("chave_que_nao_existe") == "chave_que_nao_existe"
|
|
18
|
+
set_language("en")
|
|
19
|
+
assert t("q_port", port="8123") == "HTTP(S) port [8123]:"
|
|
20
|
+
assert get_language() == "en"
|
|
21
|
+
set_language("pt")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _detect_with(env: dict, loc=(None, None)):
|
|
25
|
+
import os
|
|
26
|
+
from unittest.mock import patch
|
|
27
|
+
|
|
28
|
+
with patch.dict(os.environ, env, clear=False):
|
|
29
|
+
with patch.object(locale, "getlocale", return_value=loc), patch.object(
|
|
30
|
+
locale, "getdefaultlocale", return_value=(None, None)
|
|
31
|
+
):
|
|
32
|
+
# limpa variáveis que poderiam interferir
|
|
33
|
+
for var in ("LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"):
|
|
34
|
+
os.environ.pop(var, None)
|
|
35
|
+
for k, v in env.items():
|
|
36
|
+
os.environ[k] = v
|
|
37
|
+
return detect_language()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_detect_language():
|
|
41
|
+
assert _detect_with({"LANG": "pt_BR.UTF-8"}) == "pt"
|
|
42
|
+
assert _detect_with({"LANG": "Portuguese_Brazil"}) == "pt"
|
|
43
|
+
assert _detect_with({"LANG": "en_US.UTF-8"}) == "en"
|
|
44
|
+
assert _detect_with({"LANG": "C"}) == "en"
|
|
45
|
+
assert _detect_with({}) == "en"
|
|
46
|
+
assert _detect_with({"CH_USERS_LANG": "pt", "LANG": "en_US.UTF-8"}) == "pt"
|
|
47
|
+
assert _detect_with({"CH_USERS_LANG": "en", "LANG": "pt_BR.UTF-8"}) == "en"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Testes da sessão YAML — roundtrip em diretório temporário."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from clickhouse_users_cli import session as s
|
|
6
|
+
from clickhouse_users_cli.db import ConnectionInfo
|
|
7
|
+
from clickhouse_users_cli.i18n import set_language
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def _tmp_session(tmp_path, monkeypatch):
|
|
12
|
+
monkeypatch.setattr(s, "SESSION_DIR", tmp_path)
|
|
13
|
+
monkeypatch.setattr(s, "SESSION_FILE", tmp_path / "connection.yaml")
|
|
14
|
+
set_language("pt")
|
|
15
|
+
yield
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _conn():
|
|
19
|
+
return ConnectionInfo(host="db.empresa.com", port=8443, secure=True,
|
|
20
|
+
username="default", password="s3cr3t!")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_roundtrip():
|
|
24
|
+
assert not s.has_saved_session()
|
|
25
|
+
path = s.save_session(_conn())
|
|
26
|
+
assert path.is_file()
|
|
27
|
+
assert s.has_saved_session()
|
|
28
|
+
back = s.load_session()
|
|
29
|
+
assert (back.host, back.port, back.secure, back.username, back.password) == \
|
|
30
|
+
("db.empresa.com", 8443, True, "default", "s3cr3t!")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_delete():
|
|
34
|
+
s.save_session(_conn())
|
|
35
|
+
assert s.delete_session() is True
|
|
36
|
+
assert not s.has_saved_session()
|
|
37
|
+
assert s.delete_session() is False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_load_missing_is_friendly():
|
|
41
|
+
with pytest.raises(RuntimeError):
|
|
42
|
+
s.load_session()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_load_corrupt_is_friendly():
|
|
46
|
+
s.SESSION_FILE.write_text("{{{ nao: [yaml valido", encoding="utf-8")
|
|
47
|
+
with pytest.raises(RuntimeError):
|
|
48
|
+
s.load_session()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_load_incomplete_points_missing_key():
|
|
52
|
+
s.SESSION_FILE.write_text("host: x\n", encoding="utf-8")
|
|
53
|
+
with pytest.raises(RuntimeError, match="incompleto"):
|
|
54
|
+
s.load_session()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Testes do construtor de SQL — puros, sem banco."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from clickhouse_users_cli.sql_builder import (
|
|
6
|
+
GrantScope,
|
|
7
|
+
UserSpec,
|
|
8
|
+
build_activate_user_sql,
|
|
9
|
+
build_all_statements,
|
|
10
|
+
build_create_user_sql,
|
|
11
|
+
build_deactivate_user_sql,
|
|
12
|
+
build_drop_user_sql,
|
|
13
|
+
build_edit_grants_statements,
|
|
14
|
+
build_grant_sql,
|
|
15
|
+
build_revoke_all_sql,
|
|
16
|
+
build_show_create_user_sql,
|
|
17
|
+
build_show_grants_sql,
|
|
18
|
+
escape_host,
|
|
19
|
+
escape_ident,
|
|
20
|
+
escape_string,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_escape_ident():
|
|
25
|
+
assert escape_ident("vendas") == "`vendas`"
|
|
26
|
+
assert escape_ident("a`b") == "`a``b`"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_escape_string():
|
|
30
|
+
assert escape_string("abc") == "'abc'"
|
|
31
|
+
assert escape_string("o'b") == "'o\\'b'"
|
|
32
|
+
assert escape_string("a\\b") == "'a\\\\b'"
|
|
33
|
+
assert escape_host("10.0.0.5") == "'10.0.0.5'"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_on_clause():
|
|
37
|
+
assert GrantScope(database="*").on_clause() == "*.*"
|
|
38
|
+
assert GrantScope(database="vendas", table=None).on_clause() == "`vendas`.*"
|
|
39
|
+
assert GrantScope(database="vendas", table="*").on_clause() == "`vendas`.*"
|
|
40
|
+
assert GrantScope(database="vendas", table="pedidos").on_clause() == "`vendas`.`pedidos`"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _spec(**kw):
|
|
44
|
+
base = dict(username="ana", password="s3cret!!", privileges=["SHOW", "SELECT"],
|
|
45
|
+
scopes=[GrantScope(database="vendas", table="pedidos")])
|
|
46
|
+
base.update(kw)
|
|
47
|
+
return UserSpec(**base)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_create_user_any_host():
|
|
51
|
+
sql = build_create_user_sql(_spec())
|
|
52
|
+
assert sql == ("CREATE USER IF NOT EXISTS `ana` IDENTIFIED WITH plaintext_password"
|
|
53
|
+
" BY 's3cret!!' HOST ANY")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_create_user_custom_hosts_and_no_ine():
|
|
57
|
+
sql = build_create_user_sql(_spec(hosts=["LOCALHOST", "10.0.0.5"], if_not_exists=False))
|
|
58
|
+
assert "IF NOT EXISTS" not in sql
|
|
59
|
+
assert "HOST 'LOCALHOST', HOST '10.0.0.5'" in sql
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_grant_sql_and_grant_option():
|
|
63
|
+
stmts = build_grant_sql(_spec(scopes=[GrantScope(database="v", table=None)], grant_option=True))
|
|
64
|
+
assert stmts == ["GRANT SHOW, SELECT ON `v`.* TO `ana` WITH GRANT OPTION"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_grant_sql_validates():
|
|
68
|
+
with pytest.raises(ValueError):
|
|
69
|
+
build_grant_sql(_spec(scopes=[]))
|
|
70
|
+
with pytest.raises(ValueError):
|
|
71
|
+
build_grant_sql(_spec(privileges=[]))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_all_statements_order():
|
|
75
|
+
stmts = build_all_statements(_spec())
|
|
76
|
+
assert stmts[0].startswith("CREATE USER")
|
|
77
|
+
assert stmts[1].startswith("GRANT")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_manage_statements():
|
|
81
|
+
assert build_show_grants_sql("ana") == "SHOW GRANTS FOR `ana`"
|
|
82
|
+
assert build_show_create_user_sql("ana") == "SHOW CREATE USER `ana`"
|
|
83
|
+
assert build_deactivate_user_sql("ana") == "ALTER USER `ana` HOST NONE"
|
|
84
|
+
assert build_activate_user_sql("ana") == "ALTER USER `ana` HOST ANY"
|
|
85
|
+
assert build_drop_user_sql("ana") == "DROP USER IF EXISTS `ana`"
|
|
86
|
+
assert build_drop_user_sql("ana", if_exists=False) == "DROP USER `ana`"
|
|
87
|
+
assert build_revoke_all_sql("ana") == "REVOKE ALL ON *.* FROM `ana`"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_edit_grants_statements_revoke_first():
|
|
91
|
+
stmts = build_edit_grants_statements("ana", ["SELECT"], [GrantScope(database="v", table="t")], False)
|
|
92
|
+
assert stmts[0] == "REVOKE ALL ON *.* FROM `ana`"
|
|
93
|
+
assert stmts[1] == "GRANT SELECT ON `v`.`t` TO `ana`"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Smoke tests visuais — nada pode quebrar ao renderizar."""
|
|
2
|
+
|
|
3
|
+
from clickhouse_users_cli import style
|
|
4
|
+
from clickhouse_users_cli.i18n import set_language
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_render_pt_and_en(capfd):
|
|
8
|
+
for lang in ("pt", "en"):
|
|
9
|
+
set_language(lang)
|
|
10
|
+
style.banner()
|
|
11
|
+
style.step("4/6 · Teste", "subtítulo")
|
|
12
|
+
style.success("ok")
|
|
13
|
+
style.error("falhou")
|
|
14
|
+
style.warn("atenção")
|
|
15
|
+
style.info("dica")
|
|
16
|
+
table = style.make_table("Título")
|
|
17
|
+
table.add_column("A")
|
|
18
|
+
table.add_row("1")
|
|
19
|
+
style.console.print(table)
|
|
20
|
+
set_language("pt")
|
|
21
|
+
out, _ = capfd.readouterr()
|
|
22
|
+
assert "ClickHouse Users CLI" in out
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Testes dos validadores (mensagens em PT e EN)."""
|
|
2
|
+
|
|
3
|
+
from clickhouse_users_cli.i18n import set_language
|
|
4
|
+
from clickhouse_users_cli.validators import (
|
|
5
|
+
validate_at_least_one,
|
|
6
|
+
validate_host,
|
|
7
|
+
validate_new_password,
|
|
8
|
+
validate_port,
|
|
9
|
+
validate_required,
|
|
10
|
+
validate_username,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_required():
|
|
15
|
+
set_language("pt")
|
|
16
|
+
assert validate_required(" x ") is True
|
|
17
|
+
assert validate_required(" ") != True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_host():
|
|
21
|
+
set_language("pt")
|
|
22
|
+
assert validate_host("localhost") is True
|
|
23
|
+
assert validate_host("10.0.0.5") is True
|
|
24
|
+
assert validate_host("db.empresa.com") is True
|
|
25
|
+
assert validate_host("") == "Informe o host (ex.: localhost ou 10.0.0.5)."
|
|
26
|
+
assert "inválido" in validate_host("??").lower()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_port():
|
|
30
|
+
set_language("pt")
|
|
31
|
+
assert validate_port("8123") is True
|
|
32
|
+
assert "numérica" in validate_port("abc")
|
|
33
|
+
assert "65535" in validate_port("99999")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_username():
|
|
37
|
+
set_language("pt")
|
|
38
|
+
assert validate_username("analyst_julho") is True
|
|
39
|
+
assert "reservado" in validate_username("default")
|
|
40
|
+
assert validate_username("1abc") != True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_password():
|
|
44
|
+
set_language("en")
|
|
45
|
+
assert validate_new_password("12345678") is True
|
|
46
|
+
assert "Minimum 8" in validate_new_password("abc")
|
|
47
|
+
assert "Maximum 128" in validate_new_password("x" * 129)
|
|
48
|
+
set_language("pt")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_at_least_one():
|
|
52
|
+
assert validate_at_least_one(["a"]) is True
|
|
53
|
+
assert validate_at_least_one([]) != True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/sql_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
{clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli/validators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clickhouse_users_cli-0.1.6 → clickhouse_users_cli-0.1.7}/clickhouse_users_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|