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.fit import Fit, FitLabel from app.models.part_type import PartType from app.models.scope_info import ScopeInfo async def _seed_part_with_fit(db): apn = AttachmentPointName(name="Objective") scope_t = PartType(name="Scope") cover_t = PartType(name="Flip Cover") db.add_all([apn, scope_t, cover_t]) await db.flush() scope = AugmentedPart( description="Alpha 3-18x50", sku="ALP-318", colour_id=1, manufacturer_id=1, part_type_id=scope_t.id, ) cover = AugmentedPart( description="Flip cover 50mm", colour_id=1, manufacturer_id=1, part_type_id=cover_t.id, ) db.add_all([scope, cover]) await db.flush() db.add( ScopeInfo( augmented_part_id=scope.id, magnification_min=3, magnification_max=18, objective_thread_pitch="M52x0.75", ) ) ap_s = AttachmentPoint( augmented_part_id=scope.id, attachment_point_name_id=apn.id ) ap_c = AttachmentPoint( augmented_part_id=cover.id, attachment_point_name_id=apn.id ) db.add_all([ap_s, ap_c]) await db.flush() db.add( Fit( attachment_point_a_id=ap_s.id, attachment_point_b_id=ap_c.id, augmented_part_a_id=scope.id, augmented_part_b_id=cover.id, label=FitLabel.POSSIBLE, ) ) await db.commit() return {"scope_id": scope.id, "cover_id": cover.id} async def _web_login(client, email, password="secret123"): resp = await client.post("/login", data={"email": email, "password": password}) assert resp.status_code == 303 @pytest.mark.asyncio async def test_detail_shows_attributes_and_fits_readonly( client, seeded, make_user, db_sessionmaker ): async with db_sessionmaker() as db: ids = await _seed_part_with_fit(db) # The default "user" role has augmented_parts:read + fits:read, NOT fits:write. await make_user("reader@example.com", role_ids=[seeded["roles"]["user"].id]) await _web_login(client, "reader@example.com") resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}") assert resp.status_code == 200 body = resp.text # Attributes assert "Alpha 3-18x50" in body assert "ALP-318" in body assert "M52x0.75" in body # scope info assert "Objective" in body # attachment point # Fits are shown (the other part in the fit) assert "Flip cover 50mm" in body # ...but write controls are hidden for a read-only user assert "Add fit" not in body assert "Mark Exact" not in body assert "Recompute possible fits" not in body # ...and attachment-point add/edit/delete controls are hidden too assert 'name="attachment_point_name_id"' not in body assert "Delete this attachment point?" not in body @pytest.mark.asyncio async def test_detail_write_controls_for_attachment_point_editor( client, seeded, make_user, db_sessionmaker ): async with db_sessionmaker() as db: ids = await _seed_part_with_fit(db) perms = seeded["perms"] editor = await role_crud.create( db, name="ap-editor", permission_ids=[ perms["augmented_parts:read"].id, perms["attachment_points:read"].id, perms["attachment_points:write"].id, ], ) editor_id = editor.id await make_user("apeditor@example.com", role_ids=[editor_id]) await _web_login(client, "apeditor@example.com") resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}") assert resp.status_code == 200 body = resp.text assert 'name="attachment_point_name_id"' in body assert "Delete this attachment point?" in body @pytest.mark.asyncio async def test_detail_write_controls_for_fit_editor( client, seeded, make_user, db_sessionmaker ): async with db_sessionmaker() as db: ids = await _seed_part_with_fit(db) perms = seeded["perms"] editor = await role_crud.create( db, name="fit-editor", permission_ids=[ perms["augmented_parts:read"].id, perms["fits:read"].id, perms["fits:write"].id, ], ) editor_id = editor.id await make_user("editor@example.com", role_ids=[editor_id]) await _web_login(client, "editor@example.com") resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}") assert resp.status_code == 200 body = resp.text assert "Add fit" in body assert "Mark Exact" in body assert "Recompute possible fits" in body @pytest.mark.asyncio async def test_detail_404_for_missing_part(client, seeded, make_user): await make_user("reader2@example.com", role_ids=[seeded["roles"]["user"].id]) await _web_login(client, "reader2@example.com") resp = await client.get("/admin/augmented-parts/99999") assert resp.status_code == 404 @pytest.mark.asyncio async def test_detail_requires_read_permission(client, seeded, make_user, db_sessionmaker): async with db_sessionmaker() as db: ids = await _seed_part_with_fit(db) await make_user("plain@example.com") # no roles, not superuser await _web_login(client, "plain@example.com") resp = await client.get(f"/admin/augmented-parts/{ids['scope_id']}") assert resp.status_code == 403 @pytest.mark.asyncio async def test_augmented_parts_list_links_to_detail( client, seeded, make_user, db_sessionmaker ): async with db_sessionmaker() as db: ids = await _seed_part_with_fit(db) await make_user("reader3@example.com", role_ids=[seeded["roles"]["user"].id]) await _web_login(client, "reader3@example.com") resp = await client.get("/admin/augmented-parts") assert resp.status_code == 200 assert f'href="/admin/augmented-parts/{ids["scope_id"]}"' in resp.text