about summary refs log tree commit diff
path: root/tests/unit/auth/test_admin_users.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/auth/test_admin_users.py')
-rw-r--r--tests/unit/auth/test_admin_users.py190
1 files changed, 190 insertions, 0 deletions
diff --git a/tests/unit/auth/test_admin_users.py b/tests/unit/auth/test_admin_users.py
new file mode 100644
index 0000000..eaa5bcd
--- /dev/null
+++ b/tests/unit/auth/test_admin_users.py
@@ -0,0 +1,190 @@
+"""Tests for admin user-management: model functions and HTTP endpoints."""
+import pytest
+
+from gn_auth.auth.db import sqlite3 as db
+from gn_auth.auth.authorisation.users.admin.models import (
+    create_verified_user,
+    grant_sysadmin_role)
+
+from tests.unit.auth import conftest
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+def _credential_count(conn, user_id: str) -> int:
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            "SELECT COUNT(*) AS cnt FROM user_credentials WHERE user_id=?",
+            (user_id,))
+        return cursor.fetchone()["cnt"]
+
+
+@pytest.mark.unit_test
+def test_create_verified_user_sets_verified_flag(conn_after_auth_migrations):
+    """
+    GIVEN: a database with migrations applied
+    WHEN: create_verified_user is called with valid email, name and password
+    THEN: the returned user has verified=True and the flag is persisted in the DB
+    """
+    conn = conn_after_auth_migrations
+    user = create_verified_user(conn, "new@example.org", "New User", "s3cr3t")
+    assert user.verified is True
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            "SELECT verified FROM users WHERE user_id=?", (str(user.user_id),))
+        row = cursor.fetchone()
+    assert row is not None
+    assert bool(row["verified"]) is True
+
+
+@pytest.mark.unit_test
+def test_create_verified_user_has_no_roles(conn_after_auth_migrations):
+    """
+    GIVEN: a database with migrations applied
+    WHEN: create_verified_user is called with valid email, name and password
+    THEN: the new user is assigned no roles
+    """
+    conn = conn_after_auth_migrations
+    user = create_verified_user(conn, "noroles@example.org", "No Roles User", "s3cr3t")
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            "SELECT COUNT(*) AS cnt FROM user_roles WHERE user_id=?",
+            (str(user.user_id),))
+        row = cursor.fetchone()
+    assert row["cnt"] == 0
+
+
+@pytest.mark.unit_test
+def test_create_verified_user_stores_credentials(conn_after_auth_migrations):
+    """
+    GIVEN: a database with migrations applied
+    WHEN: create_verified_user is called with valid email, name and password
+    THEN: a password credential row is stored for the new user
+    """
+    conn = conn_after_auth_migrations
+    user = create_verified_user(conn, "creds@example.org", "Creds User", "s3cr3t")
+    assert _credential_count(conn, str(user.user_id)) == 1
+
+
+@pytest.mark.unit_test
+def test_create_verified_user_raises_on_duplicate_email(conn_after_auth_migrations):
+    """
+    GIVEN: a user already exists with a given email
+    WHEN: create_verified_user is called with the same email
+    THEN: an exception is raised
+    """
+    conn = conn_after_auth_migrations
+    create_verified_user(conn, "dupe@example.org", "First User", "s3cr3t")
+    with pytest.raises(Exception):
+        create_verified_user(conn, "dupe@example.org", "Second User", "s3cr3t")
+
+
+# ---------------------------------------------------------------------------
+# HTTP endpoint tests: POST /auth/system/administration/users/create
+# ---------------------------------------------------------------------------
+
+_NEW_USER_BODY = {"email": "newbie@example.org", "name": "Newbie", "password": "s3cr3t"}
+_CREATE_URL = "/auth/system/administration/users/create"
+
+
+@pytest.mark.unit_test
+def test_create_user_endpoint_no_token_returns_401(fxtr_app):
+    """
+    GIVEN: no Authorization header
+    WHEN: POST /auth/system/administration/users/create
+    THEN: 401 is returned
+    """
+    with fxtr_app.test_client() as http:
+        res = http.post(_CREATE_URL, json=_NEW_USER_BODY)
+    assert res.status_code == 401
+
+
+@pytest.mark.unit_test
+def test_create_user_endpoint_non_admin_returns_403(fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid token belonging to a non-admin user
+    WHEN: POST /auth/system/administration/users/create
+    THEN: 403 is returned
+    """
+    _conn, clients = fxtr_oauth2_clients
+    user = conftest.TEST_USERS[3]  # unaff@iliated.user — no privileges
+    mocker.patch(
+        "gn_auth.auth.system.admin.users.require_oauth.acquire",
+        conftest.get_tokeniser(
+            user,
+            tuple(c for c in clients if c.user == user)[0]))
+    with fxtr_app.test_client() as http:
+        res = http.post(
+            _CREATE_URL,
+            json=_NEW_USER_BODY,
+            headers={"Authorization": "Bearer some-mocked-token"})
+    assert res.status_code == 403
+
+
+def _setup_admin_mock(conn, clients, mocker):
+    """Grant sysadmin role and mock the token for sys@admin.user."""
+    admin = conftest.TEST_USERS[4]
+    with db.cursor(conn) as cursor:
+        grant_sysadmin_role(cursor, admin)
+    mocker.patch(
+        "gn_auth.auth.system.admin.users.require_oauth.acquire",
+        conftest.get_tokeniser(
+            admin,
+            tuple(c for c in clients if c.user == admin)[0]))
+    return admin
+
+
+@pytest.mark.unit_test
+def test_create_user_endpoint_admin_returns_201(fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid system-admin token and a valid request body
+    WHEN: POST /auth/system/administration/users/create
+    THEN: 201 is returned
+    """
+    conn, clients = fxtr_oauth2_clients
+    _setup_admin_mock(conn, clients, mocker)
+    with fxtr_app.test_client() as http:
+        res = http.post(
+            _CREATE_URL,
+            json={**_NEW_USER_BODY, "password": "s3cr3tP4ssw0rd"},
+            headers={"Authorization": "Bearer some-mocked-token"})
+    assert res.status_code == 201
+
+
+@pytest.mark.unit_test
+def test_create_user_endpoint_admin_returns_new_user(fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid system-admin token and a valid request body
+    WHEN: POST /auth/system/administration/users/create
+    THEN: the response body contains the new user's email and name
+    """
+    conn, clients = fxtr_oauth2_clients
+    _setup_admin_mock(conn, clients, mocker)
+    with fxtr_app.test_client() as http:
+        res = http.post(
+            _CREATE_URL,
+            json={**_NEW_USER_BODY, "password": "s3cr3tP4ssw0rd"},
+            headers={"Authorization": "Bearer some-mocked-token"})
+    data = res.get_json()
+    assert data.get("email") == _NEW_USER_BODY["email"]
+    assert data.get("name") == _NEW_USER_BODY["name"]
+    assert res.status_code == 201
+
+
+@pytest.mark.unit_test
+def test_create_user_endpoint_short_password_returns_400(
+        fxtr_app, mocker, fxtr_oauth2_clients):
+    """
+    GIVEN: a valid system-admin token and a request body with a short password
+    WHEN: POST /auth/system/administration/users/create
+    THEN: 400 is returned (password must be at least 8 characters)
+    """
+    conn, clients = fxtr_oauth2_clients
+    _setup_admin_mock(conn, clients, mocker)
+    with fxtr_app.test_client() as http:
+        res = http.post(
+            _CREATE_URL,
+            json={**_NEW_USER_BODY, "password": "short"},
+            headers={"Authorization": "Bearer some-mocked-token"})
+    assert res.status_code == 400