53 lines
2.4 KiB
Python
53 lines
2.4 KiB
Python
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from apps.server.auth import Auth, password_hash, password_matches
|
|
from apps.server.db import Store, identifier, now
|
|
from apps.server.main import create_app
|
|
|
|
|
|
class UnusedWorker:
|
|
async def close(self):
|
|
pass
|
|
|
|
|
|
def test_default_admin_is_hashed_and_can_log_in(tmp_path):
|
|
app = create_app(tmp_path, UnusedWorker(), database_url="")
|
|
with TestClient(app, base_url="http://127.0.0.1") as client:
|
|
status = client.get("/api/auth/status").json()
|
|
assert status["initialized"] and not status["authenticated"]
|
|
client.headers["X-CSRF-Token"] = status["csrf_token"]
|
|
login = client.post("/api/auth/login", json={"username": "admin", "password": "123456"})
|
|
assert login.status_code == 200
|
|
assert login.json()["user"]["username"] == "admin"
|
|
stored = app.state.store.one("SELECT password_hash FROM operators WHERE username='admin'")["password_hash"]
|
|
assert stored.startswith("scrypt$") and password_matches("123456", stored)
|
|
|
|
|
|
@pytest.mark.parametrize("username", ["admin", "existing-maintainer"])
|
|
def test_restart_preserves_existing_account_and_password(tmp_path, username):
|
|
store = Store(tmp_path)
|
|
stored = password_hash("Existing-password-4831")
|
|
account_id, created_at = identifier("operator"), now()
|
|
store.execute("INSERT INTO operators VALUES(?,?,?,?)", (account_id, username, stored, created_at))
|
|
before = store.rows("SELECT * FROM operators")
|
|
# Repeated normal starts cannot replace a changed admin hash or add an admin
|
|
# alongside a different already initialized maintainer.
|
|
first = create_app(tmp_path, UnusedWorker(), database_url="")
|
|
second = create_app(tmp_path, UnusedWorker(), database_url="")
|
|
assert first.state.store.rows("SELECT * FROM operators") == before
|
|
assert second.state.store.rows("SELECT * FROM operators") == before
|
|
assert not password_matches("123456", stored)
|
|
|
|
|
|
def test_concurrent_bootstrap_creates_exactly_one_admin(tmp_path):
|
|
store = Store(tmp_path)
|
|
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
created = list(pool.map(lambda _: Auth(store).bootstrap_admin(), range(4)))
|
|
assert created.count(True) == 1 and created.count(False) == 3
|
|
rows = store.rows("SELECT * FROM operators")
|
|
assert len(rows) == 1 and rows[0]["username"] == "admin"
|
|
assert password_matches("123456", rows[0]["password_hash"])
|