Files
computer-playbook/tests/unit/python/roles/web-app-baserow/files/test_bootstrap_admin.py
T
kevinveenbirkenbachandClaude Opus 5.5 9364abfe26 feat(web-app-baserow): carry the corporate design and sign the administrator in by e-mail
templates/style.css.j2 maps the colors of Baserow's compiled stylesheet
onto the design tokens, with the vendor pseudo-element lists split so no
browser drops a rule. templates/design.js.j2 sets the favicon, and
meta/services.yml declares a 160x32 logo and a 32x32 symbol slot.
files/playwright/test-design.js checks the tokens in both modes, the
sign-in page, the signed-in interface, the API documentation, logo,
title and the collapsed sidebar symbol, and captures the gallery.

Baserow's sign-in form asks for the e-mail address and authenticates it
against the username. files/python/bootstrap_admin.py therefore stores
the superuser under its normalized e-mail address. It moves a superuser
kept under the username, or found by its first name after the address
changed, onto the current address, gives an adopted account staff and
superuser rights, realigns the password through check_password,
completes the profile and closes the first-administrator signup page.
It prints BOOTSTRAP_STATE, which the task's changed_when reads.

Ported from feature/store-i18n as identical text, so the later merge
keeps these hunks clean: the platform_administrator account in
roles/user-administrator/meta/users.yml as store-i18n has it now
(username platformadmin, administrator role), roles/web-app-baserow/
meta/users.yml and the BASEROW_BOOTSTRAP_ADMIN_* switch (3c72c2002a and
its follow-ups), and the argv form of the migration, bootstrap and MCP
tasks, which hand only the secrets through -e NAME plus environment
(177255329f). The bootstrap task keeps its own changed_when, because
store-i18n's 'created' never matches this script's output; the
password realign of 23e642a3f3 is part of the script.
templates/playwright.env.j2 reads ADMIN_* from platform_administrator,
the README persona note names the e-mail identifier, and TODO.md is
gone with the argv port that resolves it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 22:22:49 +02:00

233 lines
8.3 KiB
Python

from __future__ import annotations
import io
import os
import runpy
import sys
import unittest
from contextlib import redirect_stdout
from types import ModuleType
from unittest.mock import MagicMock, patch
from . import PROJECT_ROOT
SCRIPT_PATH = PROJECT_ROOT / "roles/web-app-baserow/files/python/bootstrap_admin.py"
DEFAULT_ENV = {
"BOOTSTRAP_ADMIN_USERNAME": "alice",
"BOOTSTRAP_ADMIN_EMAIL": "Alice@Example.com",
"BOOTSTRAP_ADMIN_PASSWORD": "s3cret",
}
EMAIL = "alice@example.com"
def _fake_modules(
user_cls: MagicMock, profile_cls: MagicMock, settings: MagicMock
) -> dict[str, ModuleType]:
contrib_auth = ModuleType("django.contrib.auth")
contrib_auth.get_user_model = MagicMock(return_value=user_cls)
contrib = ModuleType("django.contrib")
contrib.auth = contrib_auth
django = ModuleType("django")
django.contrib = contrib
baserow_core_models = ModuleType("baserow.core.models")
baserow_core_models.UserProfile = profile_cls
baserow_core_handler = ModuleType("baserow.core.handler")
baserow_core_handler.CoreHandler = MagicMock(
return_value=MagicMock(get_settings=MagicMock(return_value=settings))
)
baserow_core_user_utils = ModuleType("baserow.core.user.utils")
baserow_core_user_utils.normalize_email_address = lambda value: (
value.strip().lower()
)
baserow_core_user = ModuleType("baserow.core.user")
baserow_core_user.utils = baserow_core_user_utils
baserow_core = ModuleType("baserow.core")
baserow_core.models = baserow_core_models
baserow_core.handler = baserow_core_handler
baserow_core.user = baserow_core_user
baserow = ModuleType("baserow")
baserow.core = baserow_core
return {
"baserow": baserow,
"baserow.core": baserow_core,
"baserow.core.handler": baserow_core_handler,
"baserow.core.models": baserow_core_models,
"baserow.core.user": baserow_core_user,
"baserow.core.user.utils": baserow_core_user_utils,
"django": django,
"django.contrib": contrib,
"django.contrib.auth": contrib_auth,
}
class TestBootstrapAdmin(unittest.TestCase):
def _run(
self,
*,
existing: dict[str, MagicMock],
signup_page: bool,
verified: bool,
env: dict[str, str] | None = None,
superusers: dict[str, MagicMock] | None = None,
) -> tuple[str, MagicMock, MagicMock, MagicMock]:
created_user = MagicMock(name="created_user")
manager = MagicMock(name="objects")
def _filter(**lookup: object) -> MagicMock:
if "username" in lookup:
match = existing.get(lookup["username"])
elif lookup.get("is_superuser") is True:
match = (superusers or {}).get(lookup.get("first_name"))
else:
match = None
query = MagicMock(name="query")
query.first.return_value = match
query.order_by.return_value.first.return_value = match
return query
manager.filter.side_effect = _filter
manager.create.return_value = created_user
user_cls = MagicMock(name="User")
user_cls.objects = manager
profile = MagicMock(name="profile")
profile.email_verified = verified
profile.completed_onboarding = verified
profile_cls = MagicMock(name="UserProfile")
profile_cls.objects.get_or_create.return_value = (profile, False)
settings = MagicMock(name="settings")
settings.show_admin_signup_page = signup_page
buf = io.StringIO()
with (
patch.dict(os.environ, env if env is not None else DEFAULT_ENV, clear=True),
patch.dict(sys.modules, _fake_modules(user_cls, profile_cls, settings)),
redirect_stdout(buf),
):
runpy.run_path(str(SCRIPT_PATH), run_name="__main__")
return buf.getvalue().strip(), manager, profile, settings
def test_creates_the_superuser_under_its_email_address(self) -> None:
out, manager, profile, settings = self._run(
existing={}, signup_page=True, verified=False
)
manager.create.assert_called_once_with(
username=EMAIL,
email=EMAIL,
first_name="alice",
is_staff=True,
is_superuser=True,
)
manager.create.return_value.set_password.assert_called_once_with("s3cret")
profile.save.assert_called_once_with(
update_fields=["email_verified", "completed_onboarding"]
)
self.assertFalse(settings.show_admin_signup_page)
settings.save.assert_called_once_with(update_fields=["show_admin_signup_page"])
self.assertEqual(out, "BOOTSTRAP_STATE=changed")
def test_moves_a_superuser_stored_under_the_username_to_its_email_address(
self,
) -> None:
legacy = MagicMock(name="legacy", username="alice", email=EMAIL, first_name="")
out, manager, _profile, _settings = self._run(
existing={"alice": legacy}, signup_page=False, verified=True
)
manager.create.assert_not_called()
legacy.set_password.assert_not_called()
self.assertEqual(legacy.username, EMAIL)
self.assertEqual(legacy.first_name, "alice")
legacy.save.assert_called_once_with(update_fields=["username", "first_name"])
self.assertEqual(out, "BOOTSTRAP_STATE=changed")
def test_converges_a_rotated_password_on_the_existing_superuser(self) -> None:
current = MagicMock(
name="current", username=EMAIL, email=EMAIL, first_name="alice"
)
current.check_password.return_value = False
out, manager, _profile, _settings = self._run(
existing={EMAIL: current}, signup_page=False, verified=True
)
manager.create.assert_not_called()
current.check_password.assert_called_once_with("s3cret")
current.set_password.assert_called_once_with("s3cret")
current.save.assert_called_once_with(update_fields=["password"])
self.assertEqual(out, "BOOTSTRAP_STATE=changed")
def test_moves_the_superuser_to_a_changed_email_address(self) -> None:
renamed = MagicMock(
name="renamed",
username="alice@old.example",
email="alice@old.example",
first_name="alice",
)
out, manager, _profile, _settings = self._run(
existing={},
signup_page=False,
verified=True,
superusers={"alice": renamed},
)
manager.filter.assert_any_call(first_name="alice", is_superuser=True)
manager.create.assert_not_called()
self.assertEqual(renamed.username, EMAIL)
self.assertEqual(renamed.email, EMAIL)
renamed.save.assert_called_once_with(update_fields=["username", "email"])
self.assertEqual(out, "BOOTSTRAP_STATE=changed")
def test_promotes_an_adopted_account_to_superuser(self) -> None:
adopted = MagicMock(
name="adopted",
username=EMAIL,
email=EMAIL,
first_name="Alice",
is_staff=False,
is_superuser=False,
)
out, manager, _profile, _settings = self._run(
existing={EMAIL: adopted}, signup_page=False, verified=True
)
manager.create.assert_not_called()
self.assertTrue(adopted.is_staff)
self.assertTrue(adopted.is_superuser)
adopted.save.assert_called_once_with(update_fields=["is_staff", "is_superuser"])
self.assertEqual(out, "BOOTSTRAP_STATE=changed")
def test_reports_unchanged_when_everything_converged(self) -> None:
current = MagicMock(
name="current", username=EMAIL, email=EMAIL, first_name="alice"
)
out, manager, profile, settings = self._run(
existing={EMAIL: current}, signup_page=False, verified=True
)
manager.create.assert_not_called()
current.set_password.assert_not_called()
current.save.assert_not_called()
profile.save.assert_not_called()
settings.save.assert_not_called()
self.assertEqual(out, "BOOTSTRAP_STATE=unchanged")
def test_missing_env_raises_keyerror(self) -> None:
with self.assertRaises(KeyError):
self._run(existing={}, signup_page=True, verified=False, env={})
if __name__ == "__main__":
unittest.main()