@@ -1202,3 +1202,111 @@ async def test_ocr_with_iso_language_codes(
12021202 mock_workflow_instance .add_file_part .assert_called_once_with (
12031203 file , None , [{"type" : "ocr" , "language" : ["eng" , "deu" , "fra" ]}]
12041204 )
1205+
1206+
1207+ class TestNutrientClientRotate :
1208+ """Tests for NutrientClient rotate functionality."""
1209+
1210+ def _make_mock_workflow (self , mock_staged_workflow_builder ):
1211+ mock_workflow_instance = MagicMock ()
1212+ mock_output_stage = MagicMock ()
1213+ mock_output_stage .execute = AsyncMock (
1214+ return_value = {
1215+ "success" : True ,
1216+ "output" : {
1217+ "buffer" : b"test-buffer" ,
1218+ "mimeType" : "application/pdf" ,
1219+ "filename" : "output.pdf" ,
1220+ },
1221+ }
1222+ )
1223+ mock_workflow_instance .add_file_part .return_value = mock_workflow_instance
1224+ mock_workflow_instance .output_pdf .return_value = mock_output_stage
1225+ mock_staged_workflow_builder .return_value = mock_workflow_instance
1226+ return mock_workflow_instance
1227+
1228+ @patch ("nutrient_dws.client.StagedWorkflowBuilder" )
1229+ @pytest .mark .asyncio
1230+ async def test_rotate_with_negative_start_includes_prefix_pages (
1231+ self , mock_staged_workflow_builder , unit_client
1232+ ):
1233+ """Rotating last N pages with negative start must keep all preceding pages."""
1234+ mock_workflow = self ._make_mock_workflow (mock_staged_workflow_builder )
1235+
1236+ await unit_client .rotate ("document.pdf" , 90 , {"start" : - 3 , "end" : - 1 })
1237+
1238+ # Expect 2 add_file_part calls: prefix pages then rotated pages
1239+ assert mock_workflow .add_file_part .call_count == 2
1240+ # First call: prefix pages before the rotation range (pages 0 to -4)
1241+ first_call_pages = mock_workflow .add_file_part .call_args_list [0 ][0 ][1 ]
1242+ assert first_call_pages == {"pages" : {"start" : 0 , "end" : - 4 }}
1243+ # Second call: the rotated range with the rotate action
1244+ second_call_pages = mock_workflow .add_file_part .call_args_list [1 ][0 ][1 ]
1245+ assert second_call_pages == {"pages" : {"start" : - 3 , "end" : - 1 }}
1246+
1247+
1248+ class TestNutrientClientDeletePages :
1249+ """Tests for NutrientClient delete_pages functionality."""
1250+
1251+ def _make_mock_workflow (self , mock_staged_workflow_builder ):
1252+ mock_workflow_instance = MagicMock ()
1253+ mock_output_stage = MagicMock ()
1254+ mock_output_stage .execute = AsyncMock (
1255+ return_value = {
1256+ "success" : True ,
1257+ "output" : {
1258+ "buffer" : b"test-buffer" ,
1259+ "mimeType" : "application/pdf" ,
1260+ "filename" : "output.pdf" ,
1261+ },
1262+ }
1263+ )
1264+ mock_workflow_instance .add_file_part .return_value = mock_workflow_instance
1265+ mock_workflow_instance .output_pdf .return_value = mock_output_stage
1266+ mock_staged_workflow_builder .return_value = mock_workflow_instance
1267+ return mock_workflow_instance
1268+
1269+ @patch ("nutrient_dws.client.StagedWorkflowBuilder" )
1270+ @pytest .mark .asyncio
1271+ async def test_delete_last_page_using_negative_index (
1272+ self , mock_staged_workflow_builder , unit_client
1273+ ):
1274+ """delete_pages([-1]) must keep all pages except the last, not raise an error."""
1275+ mock_workflow = self ._make_mock_workflow (mock_staged_workflow_builder )
1276+
1277+ result = await unit_client .delete_pages ("document.pdf" , [- 1 ])
1278+
1279+ assert result ["buffer" ] == b"test-buffer"
1280+ assert mock_workflow .add_file_part .call_count == 1
1281+ kept_pages = mock_workflow .add_file_part .call_args_list [0 ][0 ][1 ]
1282+ assert kept_pages == {"pages" : {"start" : 0 , "end" : - 2 }}
1283+
1284+ @patch ("nutrient_dws.client.StagedWorkflowBuilder" )
1285+ @pytest .mark .asyncio
1286+ async def test_delete_multiple_pages_using_only_negative_indices (
1287+ self , mock_staged_workflow_builder , unit_client
1288+ ):
1289+ """delete_pages([-2, -1]) must keep all pages except the last two."""
1290+ mock_workflow = self ._make_mock_workflow (mock_staged_workflow_builder )
1291+
1292+ result = await unit_client .delete_pages ("document.pdf" , [- 2 , - 1 ])
1293+
1294+ assert result ["buffer" ] == b"test-buffer"
1295+ assert mock_workflow .add_file_part .call_count == 1
1296+ kept_pages = mock_workflow .add_file_part .call_args_list [0 ][0 ][1 ]
1297+ assert kept_pages == {"pages" : {"start" : 0 , "end" : - 3 }}
1298+
1299+ @patch ("nutrient_dws.client.StagedWorkflowBuilder" )
1300+ @pytest .mark .asyncio
1301+ async def test_delete_pages_with_mixed_positive_and_negative_indices (
1302+ self , mock_staged_workflow_builder , unit_client
1303+ ):
1304+ """delete_pages([0, -1]) must keep middle pages, not include the deleted last page."""
1305+ mock_workflow = self ._make_mock_workflow (mock_staged_workflow_builder )
1306+
1307+ result = await unit_client .delete_pages ("document.pdf" , [0 , - 1 ])
1308+
1309+ assert result ["buffer" ] == b"test-buffer"
1310+ assert mock_workflow .add_file_part .call_count == 1
1311+ kept_pages = mock_workflow .add_file_part .call_args_list [0 ][0 ][1 ]
1312+ assert kept_pages == {"pages" : {"start" : 1 , "end" : - 2 }}
0 commit comments