import asyncio import re import pytest from app.crud import role as role_crud from app.models.attachment_point import AttachmentPoint from app.models.attachment_point_name import AttachmentPointName from app.models.augmented_part import AugmentedPart from app.models.part_type import PartType from tests.test_scope_import import _scope_row, _workbook _JOB_URL_RE = re.compile(r'hx-get="([^"]+/jobs/[^"]+)"') async def _web_login(client, email, password="secret123"): resp = await client.post("/login", data={"email": email, "password": password}) assert resp.status_code == 303 async def _poll_until_done(client, url, max_tries=50): """Follow the same htmx polling loop a browser would, until no further ``hx-get`` job-status link is present in the response.""" body = "" for _ in range(max_tries): resp = await client.get(url) assert resp.status_code == 200 body = resp.text match = _JOB_URL_RE.search(body) if not match: return body url = match.group(1) await asyncio.sleep(0.02) raise AssertionError(f"job did not finish after {max_tries} polls: {body!r}") @pytest.mark.asyncio async def test_scope_import_runs_in_background( client, seeded, make_user, db_sessionmaker, monkeypatch ): monkeypatch.setattr("app.web.admin.AsyncSessionLocal", db_sessionmaker) async with db_sessionmaker() as db: perms = seeded["perms"] editor = await role_crud.create( db, name="scope-import-editor", permission_ids=[perms["augmented_parts:write"].id], ) editor_id = editor.id await make_user("importer@example.com", role_ids=[editor_id]) await _web_login(client, "importer@example.com") data = _workbook([_scope_row(product_number="BG-SCOPE-1")]) resp = await client.post( "/admin/scope-import", files={"file": ("scopes.xlsx", data, "application/octet-stream")}, ) assert resp.status_code == 200 assert "scope-import-status" in resp.text match = _JOB_URL_RE.search(resp.text) assert match, resp.text final_body = await _poll_until_done(client, match.group(1)) assert "1 created" in final_body @pytest.mark.asyncio async def test_augmented_parts_sync_runs_in_background( client, seeded, make_user, db_sessionmaker, monkeypatch ): monkeypatch.setattr("app.web.admin.AsyncSessionLocal", db_sessionmaker) async with db_sessionmaker() as db: perms = seeded["perms"] editor = await role_crud.create( db, name="sync-editor", permission_ids=[perms["augmented_parts:write"].id] ) editor_id = editor.id await make_user("syncer@example.com", role_ids=[editor_id]) await _web_login(client, "syncer@example.com") resp = await client.post("/admin/augmented-parts/sync") assert resp.status_code == 200 assert 'id="augmented-part-list"' in resp.text match = _JOB_URL_RE.search(resp.text) assert match, resp.text final_body = await _poll_until_done(client, match.group(1)) assert "Sync complete" in final_body @pytest.mark.asyncio async def test_fit_recompute_for_part_runs_in_background( client, seeded, make_user, db_sessionmaker, monkeypatch ): monkeypatch.setattr("app.web.admin.AsyncSessionLocal", db_sessionmaker) async with db_sessionmaker() as db: apn = AttachmentPointName(name="Objective") part_t = PartType(name="Scope") db.add_all([apn, part_t]) await db.flush() part = AugmentedPart( description="Test scope", colour_id=1, manufacturer_id=1, part_type_id=part_t.id, ) db.add(part) await db.flush() db.add( AttachmentPoint(augmented_part_id=part.id, attachment_point_name_id=apn.id) ) await db.commit() part_id = part.id perms = seeded["perms"] editor = await role_crud.create( db, name="fit-recompute-editor", permission_ids=[perms["fits:write"].id, perms["augmented_parts:read"].id], ) editor_id = editor.id await make_user("recomputer@example.com", role_ids=[editor_id]) await _web_login(client, "recomputer@example.com") resp = await client.post(f"/admin/augmented-parts/{part_id}/fits/recompute") assert resp.status_code == 200 assert f'id="ap-{part_id}-fits"' in resp.text match = _JOB_URL_RE.search(resp.text) assert match, resp.text final_body = await _poll_until_done(client, match.group(1)) assert "created" in final_body and "removed" in final_body @pytest.mark.asyncio async def test_fit_recompute_all_runs_in_background( client, seeded, make_user, db_sessionmaker, monkeypatch ): monkeypatch.setattr("app.web.admin.AsyncSessionLocal", db_sessionmaker) async with db_sessionmaker() as db: perms = seeded["perms"] editor = await role_crud.create( db, name="fit-recompute-all-editor", permission_ids=[perms["fits:write"].id] ) editor_id = editor.id await make_user("recomputerall@example.com", role_ids=[editor_id]) await _web_login(client, "recomputerall@example.com") resp = await client.post("/admin/augmented-parts/fits/recompute") assert resp.status_code == 200 assert "fits-recompute-status" in resp.text match = _JOB_URL_RE.search(resp.text) assert match, resp.text final_body = await _poll_until_done(client, match.group(1)) assert "Recompute complete" in final_body