Update admin theme/layout and refresh README details.
Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def _super_headers(client) -> dict[str, str]:
|
||||
login = client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"email": "admin@compton.example", "password": "Admin1234"},
|
||||
)
|
||||
assert login.status_code == 200
|
||||
return {"Authorization": f"Bearer {login.json()['access_token']}"}
|
||||
|
||||
|
||||
def test_admin_create_user_rejects_weak_password(client):
|
||||
headers = _super_headers(client)
|
||||
response = client.post(
|
||||
"/api/v1/admin/users",
|
||||
headers=headers,
|
||||
json={
|
||||
"email": "weak-pass@example.com",
|
||||
"password": "password",
|
||||
"role": "user",
|
||||
"is_superuser": False,
|
||||
"status": "active",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 422
|
||||
@@ -0,0 +1,73 @@
|
||||
from app.core.security import hash_password
|
||||
from app.modules.users import repository
|
||||
|
||||
|
||||
def _admin_headers(client):
|
||||
login = client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"email": "admin@compton.example", "password": "Admin1234"},
|
||||
)
|
||||
token = login.json()["access_token"]
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def _plain_admin_headers(client):
|
||||
email = "ops-admin@compton.example"
|
||||
if not repository.get_user_by_email(email):
|
||||
repository.create_user(
|
||||
email=email,
|
||||
password_hash=hash_password("Admin1234"),
|
||||
role="admin",
|
||||
is_superuser=False,
|
||||
status="active",
|
||||
)
|
||||
login = client.post("/api/v1/auth/login", json={"email": email, "password": "Admin1234"})
|
||||
token = login.json()["access_token"]
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def test_admin_users_list(client):
|
||||
response = client.get("/api/v1/admin/users", headers=_admin_headers(client))
|
||||
assert response.status_code == 200
|
||||
assert "data" in response.json()
|
||||
|
||||
|
||||
def test_non_superuser_cannot_patch_settings(client):
|
||||
response = client.patch(
|
||||
"/api/v1/admin/settings",
|
||||
json={"values": {"enable_docs": False}},
|
||||
headers=_plain_admin_headers(client),
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json()["detail"] == "SUPERUSER_ONLY"
|
||||
|
||||
|
||||
def test_superuser_can_patch_settings(client):
|
||||
response = client.patch(
|
||||
"/api/v1/admin/settings",
|
||||
json={"values": {"enable_docs": False}},
|
||||
headers=_admin_headers(client),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert "values" in payload
|
||||
|
||||
|
||||
def test_superuser_can_create_and_delete_user(client):
|
||||
created = client.post(
|
||||
"/api/v1/admin/users",
|
||||
json={
|
||||
"email": "created-by-admin@compton.example",
|
||||
"password": "StrongPass123A",
|
||||
"role": "user",
|
||||
"is_superuser": False,
|
||||
"status": "active",
|
||||
},
|
||||
headers=_admin_headers(client),
|
||||
)
|
||||
assert created.status_code == 200
|
||||
user_id = created.json()["id"]
|
||||
|
||||
deleted = client.delete(f"/api/v1/admin/users/{user_id}", headers=_admin_headers(client))
|
||||
assert deleted.status_code == 200
|
||||
assert deleted.json()["status"] == "deleted"
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def _login(client, email: str, password: str) -> dict[str, str]:
|
||||
response = client.post("/api/v1/auth/login", json={"email": email, "password": password})
|
||||
assert response.status_code == 200
|
||||
return {"Authorization": f"Bearer {response.json()['access_token']}"}
|
||||
|
||||
|
||||
def test_superuser_can_read_install_secrets(client):
|
||||
headers = _login(client, "admin@compton.example", "Admin1234")
|
||||
response = client.get("/api/v1/admin/secrets", headers=headers)
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert "secrets_status" in payload
|
||||
assert payload["secrets_status"]["postgres_password"] in {"configured", "missing"}
|
||||
assert "POSTGRES_PASSWORD" not in str(payload)
|
||||
|
||||
|
||||
def test_non_superuser_cannot_read_install_secrets(client):
|
||||
headers = _login(client, "ops@compton.example", "OpsAdmin1234")
|
||||
response = client.get("/api/v1/admin/secrets", headers=headers)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_superuser_can_reveal_db_password(client):
|
||||
headers = _login(client, "admin@compton.example", "Admin1234")
|
||||
response = client.post(
|
||||
"/api/v1/admin/secrets/reveal",
|
||||
headers=headers,
|
||||
json={"key": "database_password"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["key"] == "database_password"
|
||||
assert "value" in response.json()
|
||||
@@ -0,0 +1,33 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.modules.admin.service import patch_user
|
||||
from app.modules.users import repository
|
||||
from app.modules.users.repository import create_user, get_user_by_email
|
||||
from app.core.security import hash_password
|
||||
|
||||
|
||||
def test_admin_cannot_self_demote():
|
||||
admin = get_user_by_email("admin@compton.example")
|
||||
try:
|
||||
patch_user(admin, admin.id, "user", None)
|
||||
assert False, "Expected self-demotion error"
|
||||
except ValueError as exc:
|
||||
assert str(exc) == "SELF_DEMOTION_FORBIDDEN"
|
||||
|
||||
|
||||
def test_admin_can_promote_user():
|
||||
admin = get_user_by_email("admin@compton.example")
|
||||
regular = create_user("sample@example.com", hash_password("Valid123"), role="user", status="active")
|
||||
result = patch_user(admin, regular.id, "admin", None)
|
||||
assert result["role"] == "admin"
|
||||
|
||||
|
||||
def test_last_admin_protected():
|
||||
admin = get_user_by_email("admin@compton.example")
|
||||
target = create_user("target@example.com", hash_password("Valid123"), role="admin", status="active")
|
||||
with patch.object(repository, "count_admins", return_value=1):
|
||||
try:
|
||||
patch_user(admin, target.id, "user", None)
|
||||
assert False, "Expected last-admin protection"
|
||||
except ValueError as exc:
|
||||
assert str(exc) == "LAST_ADMIN_PROTECTED"
|
||||
Reference in New Issue
Block a user