- Rename project to stealergram throughout - Add pyproject.toml (replaces requirements.txt split, folds pytest.ini) - Replace all em-dashes with hyphens across all source files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
3.9 KiB
Python
109 lines
3.9 KiB
Python
"""
|
|
Tests for web/db.py - user store and refresh token management.
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
|
|
@pytest.fixture
|
|
def tmp_db(tmp_path, monkeypatch):
|
|
"""Point web.db at a temp file for each test."""
|
|
import web.db as db_mod
|
|
db_path = tmp_path / "test_web.db"
|
|
monkeypatch.setattr(db_mod, "DB_FILE", db_path)
|
|
db_mod.init_db()
|
|
return db_mod
|
|
|
|
|
|
class TestInitDb:
|
|
def test_creates_superadmin_on_first_run(self, tmp_db):
|
|
import os
|
|
admin_user = os.environ["WEB_ADMIN_USER"]
|
|
user = tmp_db.get_user_by_username(admin_user)
|
|
assert user is not None
|
|
assert user["role"] == "superadmin"
|
|
assert user["is_active"] == 1
|
|
|
|
def test_second_init_does_not_duplicate(self, tmp_db):
|
|
import os
|
|
admin_user = os.environ["WEB_ADMIN_USER"]
|
|
tmp_db.init_db() # second call
|
|
users = tmp_db.list_users()
|
|
assert len([u for u in users if u["username"] == admin_user]) == 1
|
|
|
|
|
|
class TestUserCRUD:
|
|
def test_create_and_get_user(self, tmp_db):
|
|
uid = tmp_db.create_user("alice", "pass1", "reader")
|
|
user = tmp_db.get_user_by_id(uid)
|
|
assert user["username"] == "alice"
|
|
assert user["role"] == "reader"
|
|
|
|
def test_get_by_username(self, tmp_db):
|
|
tmp_db.create_user("bob", "pass2", "admin")
|
|
user = tmp_db.get_user_by_username("bob")
|
|
assert user is not None
|
|
assert user["role"] == "admin"
|
|
|
|
def test_update_role(self, tmp_db):
|
|
uid = tmp_db.create_user("carol", "pass3", "reader")
|
|
tmp_db.update_user(uid, role="admin")
|
|
user = tmp_db.get_user_by_id(uid)
|
|
assert user["role"] == "admin"
|
|
|
|
def test_update_password_is_hashed(self, tmp_db):
|
|
from web.auth import verify_password
|
|
uid = tmp_db.create_user("dave", "oldpass", "reader")
|
|
tmp_db.update_user(uid, password="newpass")
|
|
user = tmp_db.get_user_by_id(uid)
|
|
assert verify_password("newpass", user["password_hash"])
|
|
assert not verify_password("oldpass", user["password_hash"])
|
|
|
|
def test_deactivate_user(self, tmp_db):
|
|
uid = tmp_db.create_user("eve", "pass4", "reader")
|
|
tmp_db.deactivate_user(uid)
|
|
# get_user_by_username filters is_active=1
|
|
assert tmp_db.get_user_by_username("eve") is None
|
|
# get_user_by_id still returns the row
|
|
user = tmp_db.get_user_by_id(uid)
|
|
assert user["is_active"] == 0
|
|
|
|
def test_list_users(self, tmp_db):
|
|
tmp_db.create_user("u1", "p", "reader")
|
|
tmp_db.create_user("u2", "p", "admin")
|
|
users = tmp_db.list_users()
|
|
usernames = [u["username"] for u in users]
|
|
assert "u1" in usernames
|
|
assert "u2" in usernames
|
|
|
|
|
|
class TestRefreshTokens:
|
|
def test_store_and_validate(self, tmp_db):
|
|
from datetime import datetime, timedelta, timezone
|
|
import uuid
|
|
jti = str(uuid.uuid4())
|
|
expires_at = datetime.now(timezone.utc) + timedelta(days=7)
|
|
tmp_db.store_refresh_token(jti, "user-x", expires_at)
|
|
assert tmp_db.is_refresh_token_valid(jti)
|
|
|
|
def test_revoked_token_invalid(self, tmp_db):
|
|
from datetime import datetime, timedelta, timezone
|
|
import uuid
|
|
jti = str(uuid.uuid4())
|
|
expires_at = datetime.now(timezone.utc) + timedelta(days=7)
|
|
tmp_db.store_refresh_token(jti, "user-x", expires_at)
|
|
tmp_db.revoke_refresh_token(jti)
|
|
assert not tmp_db.is_refresh_token_valid(jti)
|
|
|
|
def test_expired_token_invalid(self, tmp_db):
|
|
from datetime import datetime, timedelta, timezone
|
|
import uuid
|
|
jti = str(uuid.uuid4())
|
|
expires_at = datetime.now(timezone.utc) - timedelta(seconds=1)
|
|
tmp_db.store_refresh_token(jti, "user-x", expires_at)
|
|
assert not tmp_db.is_refresh_token_valid(jti)
|
|
|
|
def test_unknown_jti_invalid(self, tmp_db):
|
|
assert not tmp_db.is_refresh_token_valid("nonexistent-jti")
|