Modify methods to filter resources based on user access

This commit is contained in:
Sushant Marella 2024-04-23 11:56:32 -04:00 committed by pmoharana-cmd
parent e8068ff2ea
commit 6cc0d697d4
2 changed files with 161 additions and 79 deletions

View File

@ -8,87 +8,83 @@ from ..models.user_model import User
from .exceptions import ResourceNotFoundException from .exceptions import ResourceNotFoundException
"""
Field reference:
id: int | None = None
name: str = Field(..., max_length=150, description="The name of the resource")
summary: str = Field(..., max_length=300, description="The summary of the resource")
link: str = Field(..., max_length=150, description="link to the resource")
programtype: ProgramTypeEnum
created_at: Optional[datetime]
"""
class ResourceService: class ResourceService:
def __init__(self, session: Session = Depends(db_session)): def __init__(self, session: Session = Depends(db_session)):
self._session = session self._session = session
def all(self) -> list[Resource]: def all(self, user: User) -> list[Resource]:
""" """
Retrieves all Resources from the table Retrieves all Resources from the table that the user has access to
Parameters:
user: a valid User model representing the currently logged in User
Returns: Returns:
list[Resource]: list of all `Resource` list[Resource]: list of accessible `Resource` for the user
""" """
# Select all entries in the `Resource` table # Assuming user has 'categories' attribute listing accessible resource categories
query = select(ResourceEntity) accessible_categories = user.categories
query = select(ResourceEntity).where(ResourceEntity.category.in_(accessible_categories))
entities = self._session.scalars(query).all() entities = self._session.scalars(query).all()
# Convert entries to a model and return
return [entity.to_model() for entity in entities] return [entity.to_model() for entity in entities]
def create(self, subject: User, resource: Resource) -> Resource: def create(self, user: User, resource: Resource) -> Resource:
""" """
Creates a resource based on the input object and adds it to the table. Creates a resource based on the input object and adds it to the table if the user has the right to do so.
Parameters:
user: a valid User model representing the currently logged in User
resource: Resource object to add to table
Returns: Returns:
Resource: Object added to table Resource: Object added to table
""" """
# Check if user has admin permissions # Assuming we check user's right to create resources in specific categories
# TODO if resource.category not in user.categories:
raise PermissionError("User does not have permission to add resources to this category")
# Create new resource object resource_entity = ResourceEntity.from_model(resource)
recource_entity = ResourceEntity.from_model(resource) self._session.add(resource_entity)
self._session.add(recource_entity)
self._session.commit() self._session.commit()
# Return added object return resource_entity.to_model()
return recource_entity.to_model()
def get_by_id(self, id: int) -> Resource: def get_by_id(self, user: User, id: int) -> Resource:
""" """
Gets a resource based on the resource id Gets a resource based on the resource id that the user has access to
Parameters:
user: a valid User model representing the currently logged in User
id: int, the id of the resource
Returns: Returns:
Resource Resource
"""
# Query the resource table with id Raises:
ResourceNotFoundException: If no resource is found with id
"""
accessible_categories = user.categories
resource = ( resource = (
self._session.query(ResourceEntity) self._session.query(ResourceEntity)
.filter(ResourceEntity.id == id) .filter(ResourceEntity.id == id, ResourceEntity.category.in_(accessible_categories))
.one_or_none() .one_or_none()
) )
# Check if result is null
if resource is None: if resource is None:
raise ResourceNotFoundException(f"No resource found with id: {id}") raise ResourceNotFoundException(f"No resource found with id: {id}")
return resource.to_model() return resource.to_model()
def update(self, subject: User, resource: ResourceEntity) -> Resource: def update(self, user: User, resource: ResourceEntity) -> Resource:
""" """
Update the resource Update the resource if the user has access
If none found with that id, a debug description is displayed.
Parameters: Parameters:
subject: a valid User model representing the currently logged in User user: a valid User model representing the currently logged in User
resource (Resource): Resource to add to table resource (ResourceEntity): Resource to update
Returns: Returns:
Resource: Updated resource object Resource: Updated resource object
@ -96,70 +92,50 @@ class ResourceService:
Raises: Raises:
ResourceNotFoundException: If no resource is found with the corresponding ID ResourceNotFoundException: If no resource is found with the corresponding ID
""" """
# Check if user has permission to update the resource
if resource.category not in user.categories:
raise PermissionError("User does not have permission to update this category")
# Check if user has admin permissions
# TODO
# Query the resource table with matching id
obj = self._session.get(ResourceEntity, resource.id) if resource.id else None obj = self._session.get(ResourceEntity, resource.id) if resource.id else None
# Check if result is null
if obj is None: if obj is None:
raise ResourceNotFoundException( raise ResourceNotFoundException(f"No resource found with matching id: {resource.id}")
f"No resource found with matching id: {resource.id}"
)
# Update resource object obj.update_from_model(resource) # Assuming an update method exists
obj.name = resource.name
obj.summary = resource.summary
obj.link = resource.link
obj.programtype = resource.programtype
# Save Changes
self._session.commit() self._session.commit()
# Return updated object
return obj.to_model() return obj.to_model()
def delete(self, subject: User, id: int) -> None: def delete(self, user: User, id: int) -> None:
""" """
Delete resource based on id Delete resource based on id that the user has access to
If no resource exists, a debug description is displayed.
Parameters: Parameters:
subject: a valid User model representing the currently logged in User user: a valid User model representing the currently logged in User
id: a string representing a unique resource id id: int, a unique resource id
Raises: Raises:
ResourceNotFoundException: If no resource is found with the corresponding id ResourceNotFoundException: If no resource is found with the corresponding id
""" """
accessible_categories = user.categories
# Check if user has admin permissions
# TODO
# Query the resource table with matching id
resource = ( resource = (
self._session.query(ResourceEntity) self._session.query(ResourceEntity)
.filter(ResourceEntity.id == id) .filter(ResourceEntity.id == id, ResourceEntity.category.in_(accessible_categories))
.one_or_none() .one_or_none()
) )
# Check if result is null
if resource is None: if resource is None:
raise ResourceNotFoundException(f"No resource found with matching id: {id}") raise ResourceNotFoundException(f"No resource found with matching id: {id}")
# Delete object and commit
self._session.delete(resource) self._session.delete(resource)
# Save Changes
self._session.commit() self._session.commit()
def get_by_slug(self, search_string: str) -> list[Resource]: def get_by_slug(self, user: User, search_string: str) -> list[Resource]:
""" """
Get a list of resources given a search string Get a list of resources given a search string that the user has access to
If none retrieved, a debug description is displayed.
Parameters: Parameters:
user: a valid User model representing the currently logged in User
search_string: a string to search resources by search_string: a string to search resources by
Returns: Returns:
@ -168,16 +144,15 @@ class ResourceService:
Raises: Raises:
ResourceNotFoundException if no resource is found with the corresponding slug ResourceNotFoundException if no resource is found with the corresponding slug
""" """
accessible_categories = user.categories
# Query the resource with matching slug
query = select(ResourceEntity).where( query = select(ResourceEntity).where(
or_( or_(
ResourceEntity.title.ilike(f"%{search_string}%"), ResourceEntity.title.ilike(f"%{search_string}%"),
ResourceEntity.details.ilike(f"%{search_string}%"), ResourceEntity.details.ilike(f"%{search_string}%"),
ResourceEntity.location.ilike(f"%{search_string}%"), ResourceEntity.location.ilike(f"%{search_string}%")
) ),
ResourceEntity.category.in_(accessible_categories)
) )
entities = self._session.scalars(query).all() entities = self._session.scalars(query).all()
# Convert entries to a model and return
return [entity.to_model() for entity in entities] return [entity.to_model() for entity in entities]

View File

@ -0,0 +1,107 @@
import pytest
from sqlalchemy.orm import Session
from sqlalchemy.exc import NoResultFound
from .resource_service import ResourceService
from .models.resource_model import Resource
from .entities.resource_entity import ResourceEntity
from .models.user_model import User
from .exceptions import ResourceNotFoundException
# Example of a Resource and User object creation for use in tests
@pytest.fixture
def sample_resource():
return Resource(id=1, name="Sample Resource", summary="A brief summary", link="http://example.com", programtype="TypeA")
@pytest.fixture
def sample_user():
return User(id=1, username="admin", is_admin=True)
@pytest.fixture
def resource_service(mocker):
# Mock the session and its methods
mock_session = mocker.MagicMock(spec=Session)
return ResourceService(session=mock_session)
def test_all(resource_service, mocker):
# Setup
mock_query_all = mocker.MagicMock(return_value=[ResourceEntity(id=1, name="Resource One"), ResourceEntity(id=2, name="Resource Two")])
mocker.patch.object(resource_service._session, 'scalars', return_value=mock_query_all)
# Execution
results = resource_service.all()
# Verification
assert len(results) == 2
assert results[0].id == 1
assert results[1].name == "Resource Two"
def test_create(resource_service, mocker, sample_resource, sample_user):
# Mock the add and commit methods of session
mocker.patch.object(resource_service._session, 'add')
mocker.patch.object(resource_service._session, 'commit')
# Execution
result = resource_service.create(sample_user, sample_resource)
# Verification
resource_service._session.add.assert_called_once()
resource_service._session.commit.assert_called_once()
assert result.id == sample_resource.id
assert result.name == sample_resource.name
def test_get_by_id_found(resource_service, mocker):
# Setup
resource_entity = ResourceEntity(id=1, name="Existing Resource")
mocker.patch.object(resource_service._session, 'query', return_value=mocker.MagicMock(one_or_none=mocker.MagicMock(return_value=resource_entity)))
# Execution
result = resource_service.get_by_id(1)
# Verification
assert result.id == 1
assert result.name == "Existing Resource"
def test_get_by_id_not_found(resource_service, mocker):
# Setup
mocker.patch.object(resource_service._session, 'query', return_value=mocker.MagicMock(one_or_none=mocker.MagicMock(return_value=None)))
# Execution & Verification
with pytest.raises(ResourceNotFoundException):
resource_service.get_by_id(999)
def test_update(resource_service, mocker, sample_resource, sample_user):
# Setup
mocker.patch.object(resource_service._session, 'get', return_value=sample_resource)
mocker.patch.object(resource_service._session, 'commit')
# Execution
result = resource_service.update(sample_user, sample_resource)
# Verification
assert result.id == sample_resource.id
resource_service._session.commit.assert_called_once()
def test_delete(resource_service, mocker):
# Setup
mock_resource = ResourceEntity(id=1, name="Delete Me")
mocker.patch.object(resource_service._session, 'query', return_value=mocker.MagicMock(one_or_none=mocker.MagicMock(return_value=mock_resource)))
mocker.patch.object(resource_service._session, 'delete')
mocker.patch.object(resource_service._session, 'commit')
# Execution
resource_service.delete(sample_user(), 1)
# Verification
resource_service._session.delete.assert_called_with(mock_resource)
resource_service._session.commit.assert_called_once()
def test_get_by_slug(resource_service, mocker):
# Setup
mock_query_all = mocker.MagicMock(return_value=[ResourceEntity(id=1, name="Resource One"), ResourceEntity(id=2, name="Resource Two")])
mocker.patch.object(resource_service._session, 'scalars', return_value=mock_query_all)
# Execution
results = resource_service.get_by_slug("Resource")
# Verification