Skip to content

Commit

Permalink
Create tests for BB Api get decorator and download module
Browse files Browse the repository at this point in the history
Continuing the work on #20
  • Loading branch information
sanjacob committed Jun 3, 2023
1 parent dde7e4b commit 91255d7
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 72 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pytest-qt = "*"
python-lsp-server = {extras = ["all"], version = "*"}
isort = {extras = ["pipfile_deprecated_finder"], version = "*"}
black = "*"
pytest-mock = "*"

[requires]
python_version = "3.10"
34 changes: 21 additions & 13 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 104 additions & 9 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

"""BlackboardSync Tests"""
"""BlackboardSync API Tests"""

# Copyright (C) 2021, Jacob Sánchez Pérez

Expand All @@ -18,14 +18,109 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

from blackboard_sync.blackboard.api import SafeFormat
import pytest
import requests

class TestSafeFormat():
def test_missing(self):
safe_dict = SafeFormat({'a': 1})
assert safe_dict['b'] == ''
from blackboard_sync.blackboard.api import SafeFormat, BlackboardSession

def test_present(self):
safe_dict = SafeFormat({'a': 1})
assert safe_dict['a'] == 1
# SafeFormat

def test_missing():
safe_dict = SafeFormat({'a': 1})
assert safe_dict['b'] == ''

def test_present():
safe_dict = SafeFormat({'a': 1})
assert safe_dict['a'] == 1


# BlackboardSession

@pytest.fixture
def mock_bbsession(mocker):
"""Fixture for the MockBlackboardSession class."""
class MockBlackboardSession:
_base_url = 'https://api.example.com'
_timeout = 1
# Logger
logger = mocker.Mock()
# Requests mocks
response_mock = mocker.Mock()
response_mock.json.return_value = {}

session_mock = mocker.Mock()
session_mock.get.return_value = response_mock
_bb_session = session_mock

def __init__(self, return_value):
self.response_mock.json.return_value = return_value

@BlackboardSession.get('/test_endpoint/{optional_id}')
def api_operation(self, response):
return response

return MockBlackboardSession


@pytest.mark.parametrize("expected_error, response_data", [
('Server response empty', {}),
('Not authorized', {'status': 401, 'message': 'Error message'}),
('Private course', {'status': 403, 'message': 'Error message', 'code': 'bb-rest-course-is-private'}),
('Server responded with an error code', {'status': 418, 'message': 'Error message'})
])
def test_get_decorator_errors(mock_bbsession, expected_error, response_data):
with pytest.raises(ValueError) as excinfo:
mock_bbsession(response_data).api_operation()
assert str(excinfo.value) == expected_error


@pytest.mark.parametrize("results", [
{'a': 1, 'b': 2, 'c': 'd'},
])
def test_get_decorator_response(mock_bbsession, results):
s = mock_bbsession(results)
api_response = s.api_operation()
assert api_response == results


@pytest.mark.parametrize("results", [
[],
[1, 2, 3],
['a', 'b', 'c']
])
def test_get_decorator_results(mock_bbsession, results):
s = mock_bbsession({'results': results})
api_response = s.api_operation()
assert api_response == results


def test_get_decorator_endpoint(mock_bbsession, mocker):
s = mock_bbsession({'results': []})
api_response = s.api_operation()
s.session_mock.get.assert_called_once_with('https://api.example.com/learn/api/public/v1/test_endpoint', timeout=mocker.ANY, params=mocker.ANY)


def test_get_decorator_endpoint_with_id(mock_bbsession, mocker):
s = mock_bbsession({'results': []})
api_response = s.api_operation(optional_id='test_id')
s.session_mock.get.assert_called_once_with('https://api.example.com/learn/api/public/v1/test_endpoint/test_id', timeout=mocker.ANY, params=mocker.ANY)


def test_get_decorator_params(mock_bbsession, mocker):
s = mock_bbsession({'results': []})
expected = {'paramA': 1, 'paramB': 2, 'paramC': 'hello world'}
api_response = s.api_operation(**expected)
s.session_mock.get.assert_called_once_with(mocker.ANY, timeout=mocker.ANY, params=expected)


def test_get_decorator_params_with_endpoint_id(mock_bbsession, mocker):
s = mock_bbsession({'results': []})
expected = {'paramA': 1, 'paramB': 2, 'paramC': 'hello world', 'optional_id': 'passed'}
# Even API endpoint are passed as parameters, which should not be an issue
# but should be noted regardless (the server will simply ignore them)
api_response = s.api_operation(**expected)
s.session_mock.get.assert_called_once_with('https://api.example.com/learn/api/public/v1/test_endpoint/passed', timeout=mocker.ANY, params=expected)

# monkeypatch.setattr(requests, "Session", lambda *args, **kwargs: MockSession())
# s = BlackboardSession('base', None)
# s.fetch_announcements()
146 changes: 96 additions & 50 deletions tests/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import logging
import tempfile
from pathlib import Path
from unittest.mock import Mock, patch
from unittest.mock import Mock, patch, ANY

import pytest
from hypothesis import given, assume
Expand All @@ -28,69 +28,115 @@

from blackboard_sync.blackboard.api import BlackboardSession
from blackboard_sync.download import BlackboardDownload
from blackboard_sync.blackboard.blackboard import BBMembership, BBCourse


@pytest.fixture
def mock_session():
mock = Mock(spec=BlackboardSession)
mock.username = "example"
def mock_course():
mock = Mock(spec=BBCourse)
mock.code = 'TEST_BBC_CODE'
mock.title = 'TEST_BBC_TITLE'
mock.id = 'TEST_BBC_ID'
return mock

@pytest.fixture
def mock_membership(mock_course):
mock = Mock(spec=BBMembership)
mock.courseId = mock_course.id
return mock

class TestBlackboardDownload:
@patch('blackboard_sync.download.platform')
@given(url=pr.urls(), current_platform=st.sampled_from(['Windows', 'Darwin']))
def test_create_link_windows_darwin(self, url, current_platform, mock_platform):
mock_session = Mock(spec=BlackboardSession)
mock_session.username = 'example'

mock_platform.system.return_value = current_platform

with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
link_path = Path(tmp_path / "link")
real_path = link_path.with_suffix('.url')
assert not real_path.exists()

download = BlackboardDownload(mock_session, tmp_path)
download._create_desktop_link(link_path, url)
assert real_path.exists()
contents = f"[InternetShortcut]\nURL={url}"
@pytest.fixture
def mock_session(mock_membership, mock_course):
mock = Mock(spec=BlackboardSession)
mock.username = "example"
mock.fetch_user_memberships.return_value = [ mock_membership ]
mock.fetch_courses.return_value = mock_course
return mock

with real_path.open('r') as link_file:
assert link_file.read() == contents

@patch('blackboard_sync.download.platform')
@given(url=pr.urls(), current_platform=st.sampled_from(['Linux', '']))
def test_create_link_default(self, url, current_platform, mock_platform):
mock_session = Mock(spec=BlackboardSession)
mock_session.username = 'example'
@patch('blackboard_sync.download.platform')
@given(url=pr.urls(), current_platform=st.sampled_from(['Windows', 'Darwin']))
def test_create_link_windows_darwin(url, current_platform, mock_platform):
mock_session = Mock(spec=BlackboardSession)
mock_session.username = 'example'

mock_platform.system.return_value = current_platform
# Patch platform value so it can run independently of current OS.
mock_platform.system.return_value = current_platform

with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
link_path = Path(tmp_path / "link")
assert not link_path.exists()
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
link_path = Path(tmp_path / "link")
real_path = link_path.with_suffix('.url')
assert not real_path.exists()

download = BlackboardDownload(mock_session, tmp_path)
download._create_desktop_link(link_path, url)
assert link_path.exists()
download = BlackboardDownload(mock_session, tmp_path)
download._create_desktop_link(link_path, url)
assert real_path.exists()
contents = f"[InternetShortcut]\nURL={url}"

contents = f"[Desktop Entry]\nIcon=text-html\nType=Link\nURL[$e]={url}"
with real_path.open('r') as link_file:
assert link_file.read() == contents

with link_path.open('r') as link_file:
assert link_file.read() == contents
@patch('blackboard_sync.download.platform')
@given(url=pr.urls(), current_platform=st.sampled_from(['Linux', '']))
def test_create_link_default(url, current_platform, mock_platform):
mock_session = Mock(spec=BlackboardSession)
mock_session.username = 'example'

def test_download_location(self, mock_session, tmp_path):
download = BlackboardDownload(mock_session, tmp_path)
assert download.download_location == tmp_path
assert tmp_path.exists()
# Patch platform value so it can run independently of current OS.
mock_platform.system.return_value = current_platform

def test_default_data_source(self, mock_session, tmp_path):
download = BlackboardDownload(mock_session, tmp_path)
assert download.data_source == '_21_1'
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
link_path = Path(tmp_path / "link")
assert not link_path.exists()

def test_logger(self, mock_session, tmp_path):
download = BlackboardDownload(mock_session, tmp_path)
assert isinstance(download.logger, logging.Logger)
download._create_desktop_link(link_path, url)
assert link_path.exists()

contents = f"[Desktop Entry]\nIcon=text-html\nType=Link\nURL[$e]={url}"

with link_path.open('r') as link_file:
assert link_file.read() == contents

def test_download_location(mock_session, tmp_path):
download = BlackboardDownload(mock_session, tmp_path)
assert download.download_location == tmp_path
assert tmp_path.exists()

def test_default_data_source(mock_session, tmp_path):
download = BlackboardDownload(mock_session, tmp_path)
assert download.data_source == '_21_1'

def test_logger(mock_session, tmp_path):
download = BlackboardDownload(mock_session, tmp_path)
assert isinstance(download.logger, logging.Logger)

def test_download_method_call_fetch_user_memberships_with_username(mock_session, tmp_path):
expected_user = "test_username"
mock_session.fetch_user_memberships.return_value = []
mock_session.username = expected_user
download = BlackboardDownload(mock_session, tmp_path)
download.download()
mock_session.fetch_user_memberships.assert_called_once_with(user_id=expected_user, dataSourceId=ANY)

def test_download_method_call_fetch_courses_skip_private(mock_session, tmp_path):
expected_course_id = 'TEST_BBC_ID'
mock_session.fetch_courses.side_effect = ValueError('Private course')
download = BlackboardDownload(mock_session, tmp_path)
download.download()
mock_session.fetch_courses.assert_called_once_with(course_id=expected_course_id)

def test_download_method_call_fetch_courses_raise_error(mock_session, tmp_path):
mock_session.fetch_courses.side_effect = ValueError('Other error')
download = BlackboardDownload(mock_session, tmp_path)
with pytest.raises(ValueError) as excinfo:
download.download()
assert str(excinfo.value) == 'Other error'

def test_download_method_call_fetch_contents_with_id(mock_session, tmp_path):
mock_session.fetch_contents.return_value = []
download = BlackboardDownload(mock_session, tmp_path)
download.download()
mock_session.fetch_contents.assert_called_once_with(course_id='TEST_BBC_ID')

0 comments on commit 91255d7

Please sign in to comment.