assistant/apps/server/tests/test_bootstrap.py

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"])