Skip to content

Commit

Permalink
PILOT-6667: add error handler when user dont have trash/delete permis…
Browse files Browse the repository at this point in the history
…sion within project (#192)

* add error handler when user dont have trash/delete permission within project

* fixup test cases

* change copyright to 2025
  • Loading branch information
colorzzr authored Jan 9, 2025
1 parent f1cdea1 commit 57b3300
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 18 deletions.
1 change: 1 addition & 0 deletions app/resources/custom_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class Error:
'DELETE_PATH_NOT_EXIST': 'Selected path: %s does not exist.',
'TRASH_FAIL': 'Failed to trash items: %s.',
'DELETE_FAIL': 'Failed to delete items: %s.',
'TRASH_FAIL_PERMISSION': 'Failed to delete items due to permission issue.',
'ALREADY_TRASHED': 'Selected path: %s is already in the trash. Please use permanent delete to remove it.',
# file metadata related error
'LOCAL_METADATA_FILE_EXISTS': 'Following metadata file already exists in the local directory: ',
Expand Down
23 changes: 21 additions & 2 deletions app/services/file_manager/file_trash/file_trash_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
from typing import List
from uuid import UUID

from httpx import HTTPStatusError

from app.configs.app_config import AppConfig
from app.models.item import ItemStatus
from app.services.clients.base_auth_client import BaseAuthClient
from app.services.output_manager import message_handler
from app.services.output_manager.error_handler import ECustomizedError
from app.services.output_manager.error_handler import SrvErrorHandler
from app.utils.aggregated import get_file_info_by_geid
from app.utils.aggregated import get_zone

Expand Down Expand Up @@ -53,7 +57,14 @@ def move_to_trash(self) -> Dict[str, Any]:
'source_id': self.parent_id,
'zone': self.zone,
}
response = self._delete(f'{self.project_code}/files', params=params)
try:
response = self._delete(f'{self.project_code}/files', params=params)
except HTTPStatusError as e:
response = e.response
if response.status_code == 403:
SrvErrorHandler.customized_handle(ECustomizedError.TRASH_FAIL_PERMISSION, True)
else:
SrvErrorHandler.customized_handle(ECustomizedError.TRASH_FAIL, True)

return response.json()

Expand All @@ -66,7 +77,15 @@ def permanently_delete(self) -> Dict[str, Any]:
'target_ids': self.object_ids,
'zone': self.zone,
}
response = self._delete(f'{self.project_code}/files/purge', params=params)

try:
response = self._delete(f'{self.project_code}/files/purge', params=params)
except HTTPStatusError as e:
response = e.response
if response.status_code == 403:
SrvErrorHandler.customized_handle(ECustomizedError.TRASH_FAIL_PERMISSION, True)
else:
SrvErrorHandler.customized_handle(ECustomizedError.DELETE_FAIL, True)

return response.json()

Expand Down
1 change: 1 addition & 0 deletions app/services/output_manager/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ECustomizedError(enum.Enum):
DELETE_PATH_NOT_EXIST = 'DELETE_PATH_NOT_EXIST'
TRASH_FAIL = 'TRASH_FAIL'
DELETE_FAIL = 'DELETE_FAIL'
TRASH_FAIL_PERMISSION = 'TRASH_FAIL_PERMISSION'
ALREADY_TRASHED = 'ALREADY_TRASHED'

# file metadata related error
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "app"
version = "3.10.0"
version = "3.10.1"
description = "This service is designed to support pilot platform"
authors = ["Indoc Systems"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@
# Contact Indoc Systems for any questions regarding the use of this source code.

import pytest
from httpx import HTTPStatusError

from app.configs.app_config import AppConfig
from app.models.item import ItemStatus
from app.services.file_manager.file_trash.file_trash_client import FileTrashClient
from app.services.output_manager.error_handler import ECustomizedError
from app.services.output_manager.error_handler import customized_error_msg
from tests.conftest import decoded_token


@pytest.mark.parametrize('status_code', [400, 403, 404, 500])
def test_trash_api_error_handling(httpx_mock, status_code):
@pytest.mark.parametrize(
'status_code, error_message',
[
(400, ECustomizedError.TRASH_FAIL),
(403, ECustomizedError.TRASH_FAIL_PERMISSION),
(404, ECustomizedError.TRASH_FAIL),
(500, ECustomizedError.TRASH_FAIL),
],
)
def test_trash_api_error_handling(httpx_mock, capfd, status_code, error_message):
test_project_code = 'test_code'
test_parent_id = 'test_parent_id'
test_object_ids = ['test_object_id']
Expand All @@ -33,14 +42,22 @@ def test_trash_api_error_handling(httpx_mock, status_code):
try:
result = file_trash_client.move_to_trash()
assert result == {}
except HTTPStatusError as e:
assert e.response.status_code == status_code
else:
raise AssertionError()


@pytest.mark.parametrize('status_code', [400, 403, 404, 500])
def test_permanent_delete_error_handling(httpx_mock, status_code):
except SystemExit:
out, _ = capfd.readouterr()

assert customized_error_msg(error_message) in out


@pytest.mark.parametrize(
'status_code, error_message',
[
(400, ECustomizedError.DELETE_FAIL),
(403, ECustomizedError.TRASH_FAIL_PERMISSION),
(404, ECustomizedError.DELETE_FAIL),
(500, ECustomizedError.DELETE_FAIL),
],
)
def test_permanent_delete_error_handling(httpx_mock, capfd, status_code, error_message):
test_project_code = 'test_code'
test_parent_id = 'test_parent_id'
test_object_ids = ['test_object_id']
Expand All @@ -61,10 +78,10 @@ def test_permanent_delete_error_handling(httpx_mock, status_code):
try:
result = file_trash_client.permanently_delete()
assert result == {}
except HTTPStatusError as e:
assert e.response.status_code == status_code
else:
assert AssertionError()
except SystemExit:
out, _ = capfd.readouterr()

assert customized_error_msg(error_message) in out


@pytest.mark.parametrize('file_status', [ItemStatus.TRASHED, ItemStatus.DELETED, ItemStatus.ACTIVE])
Expand Down Expand Up @@ -149,3 +166,6 @@ def test_check_file_status_not_matched(mocker, httpx_mock):
file_trash_client.max_status_check = 1
res = file_trash_client.check_status(ItemStatus.TRASHED)
assert res == ['test_parent_path/test_name']


# def test_trash_item_with_permission_denied

1 comment on commit 57b3300

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py00100% 
pilotcli.py0590%5, 7–10, 12–18, 21–22, 24–25, 27–28, 30–34, 36–39, 41, 43–44, 46–47, 50, 53–57, 59, 61–64, 66–71, 74–80, 83–85
commands
   __init__.py00100% 
   container_registry.py01959%16, 22–24, 31–33, 41–44, 50–53, 62–65
   dataset.py0989%20, 38–39, 47, 82–83, 92, 126, 130
   entry_point.py01185%41, 47, 75, 98–100, 102–106
   file.py03986%47, 145–148, 152, 157, 160–162, 220, 304–311, 313, 315, 329–335, 337, 366, 368, 372, 418, 421, 436–438, 444–445
   folder.py0196%23
   project.py0295%18, 61
   user.py0982%27, 44–45, 53–54, 62–63, 84–85
configs
   __init__.py00100% 
   app_config.py00100% 
   config.py00100% 
   user_config.py02183%35, 70, 79, 90, 117, 130, 140, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198
   utils.py03536%13–14, 16–19, 22–23, 25, 27, 31–32, 34, 36–37, 40, 43, 45–47, 49–51, 54–57, 59, 67, 69–72, 74–75
models
   __init__.py00100% 
   enums.py00100% 
   item.py00100% 
   service_meta_class.py0180%9
   singleton.py00100% 
   upload_form.py0433%28, 40–42
resources
   custom_error.py00100% 
   custom_help.py00100% 
services
   __init__.py00100% 
services/clients
   __init__.py00100% 
   base_auth_client.py0196%53
   base_client.py0295%52, 103
services/container_registry_manager
   container_registry_manager.py010817%19–20, 23–26, 29–33, 36–44, 48–60, 62–64, 68–82, 84–86, 90–99, 101–103, 107–110, 125–136, 140–143, 147–162, 164, 166–169
services/crypto
   __init__.py00100% 
   crypto.py01940%35, 43–47, 49, 59–61, 69–75, 77, 79
services/dataset_manager
   dataset_detail.py02271%38, 59, 66–71, 73–75, 77–86, 88
   dataset_download.py02282%53, 63–65, 74–76, 92, 94–95, 97–98, 100, 102–104, 112–115, 149, 164
   dataset_list.py0881%36–41, 43, 52
   model.py00100% 
services/file_manager
   __init__.py00100% 
   file_list.py01285%60–66, 86–88, 100, 102
   file_manifests.py08030%18–23, 41–45, 49–55, 57, 61–66, 68, 72–73, 77–81, 83, 86, 88–89, 91–92, 94–97, 102–108, 113–118, 121–127, 129–131, 134–140, 142–144, 146–149
   file_tag.py03931%23, 27–31, 33, 36–48, 50–52, 54–55, 59–61, 64–68, 70–73, 75–76
services/file_manager/file_download
   __init__.py00100% 
   download_client.py010555%52–61, 79–80, 122–126, 128–130, 142–143, 148–149, 153–162, 165–170, 180–181, 212, 217, 223–233, 235–236, 239–240, 245–246, 250–255, 259–260, 263–265, 267–278, 285, 290, 305, 309–311, 313–326, 328
   model.py00100% 
services/file_manager/file_metadata
   __init__.py00100% 
   file_metadata_client.py0395%98–100
   folder_client.py0491%50, 79–81
services/file_manager/file_move
   __init__.py00100% 
   file_move_client.py03759%60–64, 66, 74, 79–83, 86–94, 96, 98–103, 114, 116–120, 122, 172–173
services/file_manager/file_trash
   __init__.py00100% 
   file_trash_client.py0296%69, 90
   utils.py0392%39, 63, 70
services/file_manager/file_upload
   __init__.py00100% 
   exception.py00100% 
   file_upload.py02885%38–45, 103–105, 117, 146–147, 151, 156, 192, 215–217, 258–259, 261–263, 343, 346, 350
   models.py0591%24, 44, 150–152
   upload_client.py04576%101–104, 144, 216, 230–244, 246, 248–252, 254–258, 260–261, 391, 409, 411, 417–418, 423–426, 428–429
   upload_validator.py01957%26–32, 35–41, 44–45, 50, 52, 54
services/logger_services
   __init__.py00100% 
   debugging_log.py00100% 
   log_functions.py00100% 
services/output_manager
   __init__.py00100% 
   error_handler.py00100% 
   help_page.py0791%13–17, 19, 21
   message_handler.py05673%25, 47, 52, 67–69, 74, 84, 89, 102, 107, 114, 119, 124, 128, 144, 179, 189, 198, 216, 238, 282–293, 304–309, 311–317, 328, 332–333, 337–338, 340–341, 345, 349, 353
services/project_manager
   __init__.py00100% 
   project.py0684%40, 46–47, 49–51
services/user_authentication
   __init__.py00100% 
   decorator.py0293%27, 30
   token_manager.py0987%27, 46–50, 77–78, 95
   user_login_logout.py01684%29–30, 41, 126, 130–136, 138–140, 144–145
utils
   __init__.py00100% 
   aggregated.py02783%56, 66–68, 82–84, 116, 130–131, 144, 168–175, 177–178, 206, 214, 231–232, 240, 263
TOTAL363389775% 

Please sign in to comment.