129 lines
3.9 KiB
Python
129 lines
3.9 KiB
Python
"""CLI: python -m cyclone users {create,list,disable,reset-password,set-role}.
|
|
|
|
Uses Click's CliRunner (matches the existing parse-837/parse-835 CLI tests).
|
|
The full CLI group lives in ``cyclone.cli.main`` — we exercise the
|
|
``users`` subgroup through the top-level ``main`` so the wiring is real.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from sqlalchemy import delete, select
|
|
|
|
from cyclone.auth import users
|
|
from cyclone.cli import main as cli_main
|
|
from cyclone.db import Session as DbSession
|
|
from cyclone.db import SessionLocal, User
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear():
|
|
with SessionLocal()() as db:
|
|
db.execute(delete(DbSession))
|
|
db.execute(delete(User))
|
|
db.commit()
|
|
yield
|
|
with SessionLocal()() as db:
|
|
db.execute(delete(DbSession))
|
|
db.execute(delete(User))
|
|
db.commit()
|
|
|
|
|
|
def test_create_user_via_cli():
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli_main,
|
|
[
|
|
"users", "create", "cli-user",
|
|
"--role", "viewer",
|
|
"--password", "clipassword1",
|
|
],
|
|
input="", # don't prompt
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
with SessionLocal()() as db:
|
|
u = users.get_by_username(db, "cli-user")
|
|
assert u is not None
|
|
assert u.role == "viewer"
|
|
|
|
|
|
def test_list_users_via_cli():
|
|
from click.testing import CliRunner
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="listed", password="hunter2hunter2", role="user")
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli_main, ["users", "list"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "listed" in result.output
|
|
|
|
|
|
def test_disable_user_via_cli():
|
|
from click.testing import CliRunner
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="todie", password="hunter2hunter2", role="user")
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli_main, ["users", "disable", "todie"])
|
|
assert result.exit_code == 0, result.output
|
|
with SessionLocal()() as db:
|
|
u = users.get_by_username(db, "todie")
|
|
assert u.disabled_at is not None
|
|
|
|
|
|
def test_reset_password_via_cli():
|
|
from click.testing import CliRunner
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="pwchange", password="oldpassword1", role="user")
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli_main,
|
|
[
|
|
"users", "reset-password", "pwchange",
|
|
"--password", "newpassword1",
|
|
],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
with SessionLocal()() as db:
|
|
u = users.get_by_username(db, "pwchange")
|
|
assert users.verify_password("newpassword1", u.password_hash)
|
|
|
|
|
|
def test_set_role_via_cli():
|
|
from click.testing import CliRunner
|
|
with SessionLocal()() as db:
|
|
users.create(db, username="promote", password="hunter2hunter2", role="viewer")
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli_main,
|
|
["users", "set-role", "promote", "--role", "admin"],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
with SessionLocal()() as db:
|
|
u = users.get_by_username(db, "promote")
|
|
assert u.role == "admin"
|
|
|
|
|
|
def test_create_rejects_short_password():
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
cli_main,
|
|
[
|
|
"users", "create", "weak",
|
|
"--role", "viewer",
|
|
"--password", "short",
|
|
],
|
|
)
|
|
# Click surfaces validation failures with a non-zero exit code.
|
|
assert result.exit_code != 0, result.output
|
|
with SessionLocal()() as db:
|
|
rows = db.execute(select(User).where(User.username == "weak")).scalars().all()
|
|
assert rows == []
|
|
|
|
|
|
def test_disable_unknown_user_exits_nonzero():
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli_main, ["users", "disable", "ghost"])
|
|
assert result.exit_code != 0, result.output
|