db.commit() return c def _seed_fixture(db, n_recipients=4, n_accounts=2, n_subjects=3, n_templates=2, use_invoice=True, account_status="authorized", **camp_over): """Full seed: user/list/subjects/templates/accounts/phones + campaign.""" u = _make_user(db) rl = _make_list(db, u.id, n=n_recipients) subs = _make_subjects(db, u.id, n=n_subjects) tpls = _make_templates(db, u.id, n=n_templates, use_invoice=use_invoice) accs = _make_accounts(db, u.id, n=n_accounts, status=account_status) phones = _make_phones(db, u.id, n=2) camp = _make_campaign(db, u.id, rl.id, [s.id for s in subs], [t.id for t in tpls], [a.id for a in accs], **camp_over) db.refresh(camp) return {"user": u, "list": rl, "subjects": subs, "templates": tpls, "accounts": accs, "phones": phones, "campaign": camp} def _async_materialize(db_engine, campaign, recipient_rows, **kw): """Run materialize_campaign with the app's own async engine (pytest-asyncio free: a private event loop drives the coroutine).""" import asyncio from app.db.session import AsyncSessionLocal async def runner(): async with AsyncSessionLocal() as s: # re-fetch campaign + rows bound to the async session c = await s.get(Campaign, campaign.id) ids = [r.id for r in recipient_rows] from sqlalchemy import select rows = (await s.execute(select(Recipient).where( Recipient.id.in_(ids)))).scalars().all() rows = sorted(rows, key=lambda r: ids.index(r.id)) result = await materialize_campaign(s, c, rows, **kw) await s.commit() return result return asyncio.run(runner()) # --------------------------------------------------------------------------- # Materialization # --------------------------------------------------------------------------- class TestMaterializeCampaign: def test_materializes_all_recipients_with_generated_values(self, db_engine): from sqlalchemy.orm import Session with Session(db_engine, expire_on_commit=False) as db: f = _seed_fixture(db, n_recipients=4) recs = db.query(Recipient).filter_by(list_id=f["list"].id).all() res = _async_materialize(db_engine, f["campaign"], recs) assert res["total"] == 4 assert res["queued"] == 4 rows = [r for r in res["rows"]] assert all(r.status == "pending" for r in rows) # every recipient got an account assignment (2 accounts, sequential rotation) assert all(r.assigned_account_id in {a.id for a in f["accounts"]} for r in rows) # generated values present assert all(r.generated_amount is not None for r in rows) assert all(r.generated_invoice_number for r in rows) # seq ordering 1..4 assert sorted(r.seq for r in rows) == [1, 2, 3, 4] def test_invoice_numbers_unique_per_campaign(self, db_engine): from sqlalchemy.orm import Session with Session(db_engine, expire_on_commit=False) as db: f = _seed_fixture(db, n_recipients=25) recs = db.query(Recipient).filter_by(list_id=f["list"].id).all() res = _async_materialize(db_engine, f["campaign"], recs) invs = {r.generated_invoice_number for r in res["rows"]}