You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

359 lines
12 KiB

from __future__ import annotations
from pathlib import Path
from unittest.mock import Mock
from app import create_app
from app import cli as cli_module
from app.extensions import db
from app.models import Account, Household, HouseholdMember, OptionItem
from app.services.demo_seed import seed_stress_households
def test_seed_admin_command_creates_admin_account() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
result = runner.invoke(
args=[
"seed-admin",
"--username",
"owner",
"--display-name",
"主人",
"--password",
"OwnerPass123!",
],
)
assert result.exit_code == 0
assert "created" in result.output
with app.app_context():
account = db.session.execute(
db.select(Account).where(Account.username == "owner"),
).scalar_one()
assert account.role == "admin"
assert account.display_name == "主人"
assert account.password_hash != "OwnerPass123!"
assert account.check_password("OwnerPass123!") is True
def test_seed_admin_command_is_idempotent() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
first_result = runner.invoke(args=["seed-admin"])
second_result = runner.invoke(args=["seed-admin"])
assert first_result.exit_code == 0
assert second_result.exit_code == 0
assert "already exists" in second_result.output
with app.app_context():
accounts = db.session.execute(db.select(Account)).scalars().all()
assert len(accounts) == 1
def test_seed_options_command_creates_baseline_option_items() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
result = runner.invoke(args=["seed-options"])
assert result.exit_code == 0
assert "Seeded" in result.output
with app.app_context():
option_groups = {
option_item.option_group
for option_item in db.session.execute(db.select(OptionItem)).scalars().all()
}
assert {"relation_category", "gift_method", "gift_scene"}.issubset(option_groups)
assert "relation_detail" not in option_groups
assert "tag" not in option_groups
def test_seed_all_command_creates_admin_and_options() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
result = runner.invoke(args=["seed-all"])
assert result.exit_code == 0
assert "Admin account 'admin'" in result.output
assert "Seeded" in result.output
with app.app_context():
admin_account = db.session.execute(
db.select(Account).where(Account.username == "admin"),
).scalar_one_or_none()
option_count = db.session.execute(db.select(OptionItem)).scalars().all()
assert admin_account is not None
assert len(option_count) > 0
def test_seed_all_requires_initialized_tables(tmp_path: Path) -> None:
db_path = tmp_path / "seed-missing-tables.db"
app = create_app("testing")
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}"
with app.app_context():
db.session.remove()
db.drop_all()
runner = app.test_cli_runner()
result = runner.invoke(args=["seed-all"])
assert result.exit_code != 0
assert "Run 'flask db upgrade' or 'flask init-db' first." in result.output
def test_seed_demo_command_creates_demo_accounts_and_households() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
baseline_result = runner.invoke(args=["seed-all"])
assert baseline_result.exit_code == 0
runner = app.test_cli_runner()
result = runner.invoke(args=["seed-demo"])
assert result.exit_code == 0
assert "Seeded 3 demo accounts." in result.output
assert "Seeded 3 demo households." in result.output
assert "editor-demo / EditorDemo123!" in result.output
assert "quick-editor-demo / QuickEditor123!" in result.output
with app.app_context():
demo_accounts = db.session.execute(
db.select(Account).where(Account.username.in_(["editor-demo", "entry-demo", "quick-editor-demo"])),
).scalars().all()
demo_households = db.session.execute(
db.select(Household).where(Household.household_code.in_(["D001", "D002", "D003"])),
).scalars().all()
member_count = db.session.execute(db.select(HouseholdMember)).scalars().all()
assert len(demo_accounts) == 3
assert len(demo_households) == 3
assert len(member_count) >= 8
def test_seed_demo_command_is_idempotent() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
assert runner.invoke(args=["seed-all"]).exit_code == 0
runner = app.test_cli_runner()
first_result = runner.invoke(args=["seed-demo"])
second_result = runner.invoke(args=["seed-demo"])
assert first_result.exit_code == 0
assert second_result.exit_code == 0
assert "Seeded 0 demo accounts." in second_result.output
assert "Seeded 0 demo households." in second_result.output
def test_seed_stress_command_creates_large_dataset() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
assert runner.invoke(args=["seed-all"]).exit_code == 0
runner = app.test_cli_runner()
result = runner.invoke(
args=[
"seed-stress",
"--household-count",
"12",
"--members-per-household",
"4",
"--prefix",
"STRESS-",
],
)
assert result.exit_code == 0
assert "Seeded 12 stress households." in result.output
assert "Seeded 48 stress household members." in result.output
with app.app_context():
stress_households = db.session.execute(
db.select(Household).where(Household.household_code.like("STRESS-%")),
).scalars().all()
stress_members = db.session.execute(
db.select(HouseholdMember)
.join(Household, HouseholdMember.household_id == Household.id)
.where(Household.household_code.like("STRESS-%")),
).scalars().all()
assert len(stress_households) == 12
assert len(stress_members) == 48
assert all(household.head_name.startswith("压力户主") for household in stress_households)
assert all(household.household_code.startswith("STRESS-") for household in stress_households)
def test_seed_stress_command_is_idempotent_for_same_prefix() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
assert runner.invoke(args=["seed-all"]).exit_code == 0
runner = app.test_cli_runner()
first_result = runner.invoke(args=["seed-stress", "--household-count", "5", "--members-per-household", "3"])
second_result = runner.invoke(args=["seed-stress", "--household-count", "5", "--members-per-household", "3"])
assert first_result.exit_code == 0
assert second_result.exit_code == 0
assert "Seeded 0 stress households." in second_result.output
assert "Seeded 0 stress household members." in second_result.output
with app.app_context():
households = db.session.execute(
db.select(Household).where(Household.household_code.like("STRESS-%")),
).scalars().all()
members = db.session.execute(
db.select(HouseholdMember)
.join(Household, HouseholdMember.household_id == Household.id)
.where(Household.household_code.like("STRESS-%")),
).scalars().all()
assert len(households) == 5
assert len(members) == 15
def test_seed_stress_command_is_prefix_safe() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
assert runner.invoke(args=["seed-all"]).exit_code == 0
runner = app.test_cli_runner()
first_prefix_result = runner.invoke(
args=["seed-stress", "--household-count", "3", "--members-per-household", "2", "--prefix", "A-"]
)
second_prefix_result = runner.invoke(
args=["seed-stress", "--household-count", "3", "--members-per-household", "2", "--prefix", "B-"]
)
assert first_prefix_result.exit_code == 0
assert second_prefix_result.exit_code == 0
assert "Seeded 3 stress households." in first_prefix_result.output
assert "Seeded 3 stress households." in second_prefix_result.output
with app.app_context():
a_prefix_households = db.session.execute(
db.select(Household).where(Household.household_code.like("A-%")),
).scalars().all()
b_prefix_households = db.session.execute(
db.select(Household).where(Household.household_code.like("B-%")),
).scalars().all()
assert len(a_prefix_households) == 3
assert len(b_prefix_households) == 3
def test_seed_stress_households_normalizes_prefix_and_counts() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
assert runner.invoke(args=["seed-all"]).exit_code == 0
created_households, created_members = seed_stress_households(
household_count=2,
members_per_household=3,
household_code_prefix=" stress ",
)
db.session.commit()
assert created_households == 2
assert created_members == 6
normalized_prefix_households = db.session.execute(
db.select(Household).where(Household.household_code.like("STRESS%")),
).scalars().all()
assert len(normalized_prefix_households) == 2
def test_seed_stress_command_rejects_invalid_arguments() -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
runner = app.test_cli_runner()
assert runner.invoke(args=["seed-all"]).exit_code == 0
runner = app.test_cli_runner()
zero_households = runner.invoke(args=["seed-stress", "--household-count", "0"])
blank_prefix = runner.invoke(args=["seed-stress", "--prefix", " "])
assert zero_households.exit_code != 0
assert "household_count must be greater than 0" in zero_households.output
assert blank_prefix.exit_code != 0
assert "household_code_prefix cannot be empty" in blank_prefix.output
def test_dev_bootstrap_command_runs_setup_steps_and_skips_server(monkeypatch) -> None:
app = create_app("testing")
with app.app_context():
db.create_all()
recorded_calls: list[tuple[list[str], str]] = []
def fake_run_python_command(
args: list[str],
*,
env: dict[str, str],
cwd: str,
label: str,
) -> None:
_ = env
_ = cwd
recorded_calls.append((args, label))
run_mock = Mock()
monkeypatch.setattr(cli_module, "_run_python_command", fake_run_python_command)
monkeypatch.setattr(cli_module.Flask, "run", run_mock)
runner = app.test_cli_runner()
result = runner.invoke(args=["dev-bootstrap", "--skip-server", "--port", "5010"])
assert result.exit_code == 0
assert [label for _, label in recorded_calls] == [
"Applying database migrations",
"Seeding baseline admin and options",
"Seeding demo accounts and households",
]
assert recorded_calls[0][0][-3:] == ["flask", "db", "upgrade"]
assert recorded_calls[1][0][-2:] == ["flask", "seed-all"]
assert recorded_calls[2][0][-2:] == ["flask", "seed-demo"]
assert "Service URL: http://127.0.0.1:5010" in result.output
assert "Skipped starting the Flask development server." in result.output
run_mock.assert_not_called()