8864b2021b
--supervisor-password non ha piu' un default committato: se il capoturno va creato e l'opzione manca, ne genera una casuale (secrets.token_urlsafe(12)) e la stampa una sola volta. --conferma-produzione richiesto quando APP_ENV (src.backend.config, stessa regola di #33) non e' dev/test. --replace elenca quante Measurement/ProductionRun/ProductionEvent cancellerebbe e si ferma senza cancellare finche' non si aggiunge --si. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
191 lines
6.8 KiB
Python
191 lines
6.8 KiB
Python
"""Tests for scripts/seed_collaudo.py (issue #17).
|
|
|
|
The script is not a package module (it is copied into the server container
|
|
and run directly), so it is loaded here via importlib from its file path.
|
|
"""
|
|
import asyncio
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from sqlalchemy import select
|
|
|
|
from src.backend.config import settings
|
|
from src.backend.models.orm.station import Station
|
|
from src.backend.models.orm.user import User
|
|
from src.backend.services.auth_service import create_user
|
|
from src.backend.tests.conftest import TestSessionFactory, test_engine
|
|
|
|
_SCRIPT_PATH = Path(__file__).resolve().parents[3] / "scripts" / "seed_collaudo.py"
|
|
|
|
|
|
def _load_script():
|
|
spec = importlib.util.spec_from_file_location("seed_collaudo", _SCRIPT_PATH)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
@pytest.fixture
|
|
def seed_collaudo(monkeypatch):
|
|
module = _load_script()
|
|
monkeypatch.setattr(module, "async_session_factory", TestSessionFactory)
|
|
return module
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_app_env(monkeypatch):
|
|
"""Every test in this file assumes the default (dev)."""
|
|
monkeypatch.setattr(settings, "app_env", "dev")
|
|
|
|
|
|
async def _make_admin_and_station(db_session) -> None:
|
|
admin = await create_user(
|
|
db_session, username="owner", password="OwnerPass1",
|
|
display_name="Owner", is_admin=True,
|
|
)
|
|
db_session.add(Station(
|
|
code="ST-DEFAULT", name="Default", created_by=admin.id,
|
|
))
|
|
await db_session.flush()
|
|
|
|
|
|
class TestReplaceRequiresSi:
|
|
"""--replace without --si only reports the impact, never deletes (#17c)."""
|
|
|
|
async def test_replace_without_si_does_not_delete(self, seed_collaudo, db_session):
|
|
await _make_admin_and_station(db_session)
|
|
|
|
# First run creates COLLAUDO-A/B.
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", False, "FirstPass123", True)
|
|
assert rc == 0
|
|
|
|
# Second run with --replace but no --si: must not touch anything.
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", True, "SecondPass123", False)
|
|
assert rc == 1
|
|
|
|
recipes = (await db_session.execute(
|
|
select(seed_collaudo.Recipe).where(
|
|
seed_collaudo.Recipe.code.in_(seed_collaudo.CODES)
|
|
)
|
|
)).scalars().all()
|
|
assert len(recipes) == 2
|
|
|
|
async def test_replace_with_si_deletes_and_recreates(self, seed_collaudo, db_session):
|
|
await _make_admin_and_station(db_session)
|
|
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", False, "FirstPass123", True)
|
|
assert rc == 0
|
|
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", True, "SecondPass123", True)
|
|
assert rc == 0
|
|
|
|
recipes = (await db_session.execute(
|
|
select(seed_collaudo.Recipe).where(
|
|
seed_collaudo.Recipe.code.in_(seed_collaudo.CODES)
|
|
)
|
|
)).scalars().all()
|
|
assert len(recipes) == 2
|
|
|
|
def test_main_with_fake_argv_replace_without_si_does_not_delete(
|
|
self, seed_collaudo, monkeypatch
|
|
):
|
|
"""The exact scenario from the analysis: main() with a fake argv,
|
|
--replace but no --si, must leave the recipes untouched.
|
|
|
|
Plain `def`, not `async def`: main() manages its own event loop via
|
|
asyncio.run(), which cannot be called from inside one already running
|
|
(as an `async def` test under pytest-asyncio would have).
|
|
"""
|
|
async def _setup():
|
|
async with TestSessionFactory() as session:
|
|
await _make_admin_and_station(session)
|
|
await session.commit()
|
|
|
|
asyncio.run(_setup())
|
|
assert asyncio.run(
|
|
seed_collaudo.seed("ST-DEFAULT", False, "FirstPass123", True)
|
|
) == 0
|
|
|
|
monkeypatch.setattr(
|
|
"sys.argv",
|
|
["seed_collaudo.py", "--station", "ST-DEFAULT", "--replace"],
|
|
)
|
|
rc = seed_collaudo.main()
|
|
assert rc == 1
|
|
|
|
async def _count():
|
|
async with TestSessionFactory() as session:
|
|
recipes = (await session.execute(
|
|
select(seed_collaudo.Recipe).where(
|
|
seed_collaudo.Recipe.code.in_(seed_collaudo.CODES)
|
|
)
|
|
)).scalars().all()
|
|
return len(recipes)
|
|
|
|
assert asyncio.run(_count()) == 2
|
|
|
|
|
|
class TestSupervisorPassword:
|
|
"""No default --supervisor-password: generated and printed once (#17a)."""
|
|
|
|
async def test_generates_random_password_when_not_given(
|
|
self, seed_collaudo, db_session
|
|
):
|
|
await _make_admin_and_station(db_session)
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", False, None, True)
|
|
assert rc == 0
|
|
|
|
supervisor = (await db_session.execute(
|
|
select(User).where(User.username == "capoturno")
|
|
)).scalar_one_or_none()
|
|
assert supervisor is not None
|
|
# A real password was set (create_user always hashes something).
|
|
assert supervisor.password_hash
|
|
|
|
async def test_uses_given_password_when_provided(self, seed_collaudo, db_session):
|
|
await _make_admin_and_station(db_session)
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", False, "ExplicitPass1", True)
|
|
assert rc == 0
|
|
|
|
from src.backend.services.auth_service import authenticate_user
|
|
|
|
user = await authenticate_user(db_session, "capoturno", "ExplicitPass1")
|
|
assert user is not None
|
|
|
|
async def test_existing_supervisor_password_untouched(
|
|
self, seed_collaudo, db_session
|
|
):
|
|
"""A second run (no --replace) must not need a password at all."""
|
|
await _make_admin_and_station(db_session)
|
|
assert await seed_collaudo.seed("ST-DEFAULT", False, "FirstPass123", True) == 0
|
|
|
|
# Recipes already exist, so seed() stops at the "already present"
|
|
# check before ever looking at supervisor_password.
|
|
rc = await seed_collaudo.seed("ST-DEFAULT", False, None, True)
|
|
assert rc == 1
|
|
|
|
|
|
class TestProductionGuard:
|
|
"""Refuses to run against APP_ENV outside dev/test without --conferma-produzione (#17b)."""
|
|
|
|
def test_main_refuses_in_production_without_confirmation(
|
|
self, seed_collaudo, monkeypatch
|
|
):
|
|
monkeypatch.setattr(settings, "app_env", "production")
|
|
monkeypatch.setattr(
|
|
"sys.argv", ["seed_collaudo.py", "--station", "ST-DEFAULT"]
|
|
)
|
|
rc = seed_collaudo.main()
|
|
assert rc == 2
|
|
|
|
def test_main_allows_dev_without_confirmation(self, seed_collaudo, monkeypatch):
|
|
monkeypatch.setattr(settings, "app_env", "dev")
|
|
# No admin/station set up: seed() itself will fail with rc=2, but that
|
|
# proves the production guard did not block it first.
|
|
monkeypatch.setattr(
|
|
"sys.argv", ["seed_collaudo.py", "--station", "NOPE"]
|
|
)
|
|
rc = seed_collaudo.main()
|
|
assert rc == 2 # "Nessun utente amministratore" / stazione inesistente
|