Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions fossology/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging

from fossology.exceptions import AuthorizationError, FossologyApiError
from fossology.obj import Folder
from fossology.obj import Folder, FolderContent

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
Expand Down Expand Up @@ -232,4 +232,53 @@ def move_folder(self, folder, parent):
:rtype: Folder() object
:raises FossologyApiError: if the REST call failed
"""
return self._put_folder("move", folder, parent)
return self._put_folder("move", folder, parent)

def list_folder_contents(self, folder: Folder) -> list[FolderContent]:
"""List the contents of a folder

API Endpoint: GET /folders/{id}/contents

:param folder: the folder to list the contents of
:type folder: Folder
:return: the list of contents (uploads and subfolders) of the folder
:rtype: list[FolderContent]
:raises FossologyApiError: if the REST call failed
:raises AuthorizationError: if the REST call is not authorized
"""
response = self.session.get(f"{self.api}/folders/{folder.id}/contents")

if response.status_code == 200:
return [FolderContent.from_json(item) for item in response.json()]
elif response.status_code == 403:
description = f"Folder {folder.id} is not accessible"
raise AuthorizationError(description, response)
elif response.status_code == 404:
description = f"Folder {folder.id} does not exist"
raise FossologyApiError(description, response)
else:
description = f"Unable to get contents of folder {folder.name} (id={folder.id})"
raise FossologyApiError(description, response)

def unlink_folder_content(self, content_id: int):
"""Unlink a content from its folder

API Endpoint: PUT /folders/contents/{contentId}/unlink

:param content_id: the id of the folder content to unlink (see
:func:`~fossology.folders.Folders.list_folder_contents`)
:type content_id: int
:raises FossologyApiError: if the REST call failed
"""
response = self.session.put(
f"{self.api}/folders/contents/{content_id}/unlink"
)

if response.status_code == 200:
logger.info(f"Folder content {content_id} has been unlinked")
elif response.status_code == 404:
description = f"Folder content {content_id} does not exist"
raise FossologyApiError(description, response)
else:
description = f"Unable to unlink folder content {content_id}"
raise FossologyApiError(description, response)
17 changes: 17 additions & 0 deletions fossology/obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ def from_json_v2(cls, json_dict):
)


class FolderContent(object):
"""FOSSology folder content (an upload or a subfolder linked in a folder)."""

def __init__(self, id=None, content=None, removable=None, **kwargs):
self.id = id
self.content = content
self.removable = removable
self.additional_info = kwargs

def __str__(self):
return f"Folder content {self.content} ({self.id}), removable={self.removable}"

@classmethod
def from_json(cls, json_dict):
return cls(**json_dict)


class Findings(object):
"""FOSSology license findings."""

Expand Down
66 changes: 65 additions & 1 deletion tests/test_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

import secrets
import time
from unittest.mock import MagicMock

import pytest
import responses

from fossology import Fossology
from fossology.exceptions import AuthorizationError, FossologyApiError
from fossology.obj import Folder
from fossology.obj import Folder, FolderContent


@responses.activate
Expand Down Expand Up @@ -201,3 +202,66 @@ def test_delete_folder_error(foss_server: str, foss: Fossology):
with pytest.raises(FossologyApiError) as excinfo:
foss.delete_folder(folder)
assert f"Unable to delete folder {folder.id}" in str(excinfo.value)


def test_list_folder_contents(foss: Fossology):
name = "FolderContentsTest"
subfolder = foss.create_folder(foss.rootFolder, name, "list contents test")
contents = foss.list_folder_contents(foss.rootFolder)
assert isinstance(contents, list)
assert contents
assert all(isinstance(content, FolderContent) for content in contents)
match = [c for c in contents if c.content and name in c.content]
assert match, f"{name} not found in folder contents"
assert match[0].id is not None
assert "Folder content" in str(match[0])
foss.delete_folder(subfolder)


@responses.activate
def test_list_folder_contents_unauthorized(foss_server: str, foss: Fossology):
responses.add(
responses.GET,
f"{foss_server}/api/v1/folders/{foss.rootFolder.id}/contents",
status=403,
)
with pytest.raises(AuthorizationError) as excinfo:
foss.list_folder_contents(foss.rootFolder)
assert f"Folder {foss.rootFolder.id} is not accessible" in str(excinfo.value)


@responses.activate
def test_list_folder_contents_not_found(foss_server: str, foss: Fossology):
responses.add(
responses.GET,
f"{foss_server}/api/v1/folders/{foss.rootFolder.id}/contents",
status=404,
)
with pytest.raises(FossologyApiError) as excinfo:
foss.list_folder_contents(foss.rootFolder)
assert f"Folder {foss.rootFolder.id} does not exist" in str(excinfo.value)


@responses.activate
def test_unlink_folder_content(foss_server: str, foss: Fossology, monkeypatch):
mocked_logger = MagicMock()
monkeypatch.setattr("fossology.folders.logger", mocked_logger)
responses.add(
responses.PUT,
f"{foss_server}/api/v1/folders/contents/42/unlink",
status=200,
)
foss.unlink_folder_content(42)
mocked_logger.info.assert_called_once()


@responses.activate
def test_unlink_folder_content_error(foss_server: str, foss: Fossology):
responses.add(
responses.PUT,
f"{foss_server}/api/v1/folders/contents/999/unlink",
status=404,
)
with pytest.raises(FossologyApiError) as excinfo:
foss.unlink_folder_content(999)
assert "Folder content 999 does not exist" in str(excinfo.value)