"""Which archive figures a user who may only edit their own archives can set. The archive's cost and filament weight are what later runs of it are charged from. Callers who may edit every archive set both; callers limited to their own archives may fill in a weight only for an archive without its 3MF, which has none and cannot be printed again. """ import pytest from httpx import AsyncClient from backend.tests.integration.test_printer_scope_1727 import _admin_token, _auth, _group, _user OWN = ["archives:read_own", "archives:update_own"] async def _owner_and_archive(async_client, archive_factory, printer_factory, *, grams, cost, file_path=None): admin = await _admin_token(async_client) group = await _group(async_client, admin, "own_archives", permissions=OWN) jwt, user_id = await _user(async_client, admin, "owner", [group]) printer = await printer_factory() extra = {} if file_path is None else {"file_path": file_path} archive = await archive_factory( printer.id, created_by_id=user_id, filament_used_grams=grams, cost=cost, filament_type="PLA", **extra ) return admin, jwt, archive.id @pytest.mark.asyncio @pytest.mark.integration async def test_owner_cannot_set_the_cost(async_client: AsyncClient, archive_factory, printer_factory): _, jwt, archive_id = await _owner_and_archive(async_client, archive_factory, printer_factory, grams=100.0, cost=2.5) response = await async_client.patch(f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json={"cost": 0.01}) assert response.status_code == 403 after = await async_client.get(f"/api/v1/archives/{archive_id}", headers=_auth(jwt)) assert after.json()["cost"] == 2.5 @pytest.mark.asyncio @pytest.mark.integration async def test_owner_cannot_change_an_existing_weight(async_client: AsyncClient, archive_factory, printer_factory): _, jwt, archive_id = await _owner_and_archive(async_client, archive_factory, printer_factory, grams=100.0, cost=2.5) response = await async_client.patch( f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json={"filament_used_grams": 90000} ) assert response.status_code == 403 after = await async_client.get(f"/api/v1/archives/{archive_id}", headers=_auth(jwt)) assert (after.json()["filament_used_grams"], after.json()["cost"]) == (100.0, 2.5) @pytest.mark.asyncio @pytest.mark.integration async def test_owner_fills_a_missing_weight(async_client: AsyncClient, archive_factory, printer_factory): _, jwt, archive_id = await _owner_and_archive( async_client, archive_factory, printer_factory, grams=None, cost=None, file_path="" ) response = await async_client.patch( f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json={"filament_used_grams": 120} ) assert response.status_code == 200 assert (response.json()["filament_used_grams"], response.json()["cost"]) == (120, 3.0) @pytest.mark.asyncio @pytest.mark.integration async def test_filling_the_weight_keeps_an_existing_cost(async_client: AsyncClient, archive_factory, printer_factory): _, jwt, archive_id = await _owner_and_archive( async_client, archive_factory, printer_factory, grams=None, cost=7.0, file_path="" ) response = await async_client.patch( f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json={"filament_used_grams": 120} ) assert response.status_code == 200 assert response.json()["cost"] == 7.0 @pytest.mark.asyncio @pytest.mark.integration async def test_owner_still_edits_everything_else(async_client: AsyncClient, archive_factory, printer_factory): _, jwt, archive_id = await _owner_and_archive(async_client, archive_factory, printer_factory, grams=100.0, cost=2.5) response = await async_client.patch( f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json={"notes": "fine", "filament_used_grams": 100.0}, ) assert response.status_code == 200 assert response.json()["notes"] == "fine" @pytest.mark.asyncio @pytest.mark.integration async def test_editing_every_archive_sets_both(async_client: AsyncClient, archive_factory, printer_factory): admin, _, archive_id = await _owner_and_archive( async_client, archive_factory, printer_factory, grams=100.0, cost=2.5 ) weight = await async_client.patch( f"/api/v1/archives/{archive_id}", headers=_auth(admin), json={"filament_used_grams": 200} ) cost = await async_client.patch(f"/api/v1/archives/{archive_id}", headers=_auth(admin), json={"cost": 9.99}) assert (weight.status_code, weight.json()["cost"]) == (200, 5.0) assert (cost.status_code, cost.json()["cost"]) == (200, 9.99) @pytest.mark.asyncio @pytest.mark.integration async def test_owner_cannot_fill_the_weight_of_an_archive_with_its_3mf( async_client: AsyncClient, archive_factory, printer_factory ): """It can be printed again, and those runs are charged from the weight.""" _, jwt, archive_id = await _owner_and_archive(async_client, archive_factory, printer_factory, grams=None, cost=None) response = await async_client.patch( f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json={"filament_used_grams": 0.01} ) assert response.status_code == 403 @pytest.mark.asyncio @pytest.mark.integration @pytest.mark.parametrize("body", [{"filament_used_grams": None}, {"cost": None}]) async def test_owner_cannot_clear_either_figure(async_client: AsyncClient, archive_factory, printer_factory, body): _, jwt, archive_id = await _owner_and_archive(async_client, archive_factory, printer_factory, grams=100.0, cost=2.5) response = await async_client.patch(f"/api/v1/archives/{archive_id}", headers=_auth(jwt), json=body) assert response.status_code == 403