aboutsummaryrefslogtreecommitdiff
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