Skip to content

Commit

Permalink
feat: tests wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rayzhou-bit committed Jan 10, 2025
1 parent f317880 commit 81619f8
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 43 deletions.
18 changes: 11 additions & 7 deletions cms/djangoapps/contentstore/core/course_optimizer_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def generate_broken_links_descriptor(json_content, request_user):

usage_key = usage_key_with_run(block_id)
block = get_xblock(usage_key, request_user)
_update_node_tree_and_dictionary(
xblock_node_tree, xblock_dictionary = _update_node_tree_and_dictionary(
block=block,
link=link,
is_locked=is_locked_flag,
Expand Down Expand Up @@ -103,14 +103,16 @@ def _update_node_tree_and_dictionary(block, link, is_locked, node_tree, dictiona
...,
}
"""
updated_tree, updated_dictionary = node_tree, dictionary

path = _get_node_path(block)
current_node = node_tree
current_node = updated_tree
xblock_id = ''

# Traverse the path and build the tree structure
for xblock in path:
xblock_id = xblock.location.block_id
dictionary.setdefault(xblock_id,
updated_dictionary.setdefault(xblock_id,
{
'display_name': xblock.display_name,
'category': getattr(xblock, 'category', ''),
Expand All @@ -120,18 +122,20 @@ def _update_node_tree_and_dictionary(block, link, is_locked, node_tree, dictiona
current_node = current_node.setdefault(xblock_id, {})

# Add block-level details for the last xblock in the path (URL and broken/locked links)
dictionary[xblock_id].setdefault('url',
updated_dictionary[xblock_id].setdefault('url',
f'/course/{block.course_id}/editor/{block.category}/{block.location}'
)
if is_locked:
dictionary[xblock_id].setdefault('locked_links', []).append(link)
updated_dictionary[xblock_id].setdefault('locked_links', []).append(link)
else:
dictionary[xblock_id].setdefault('broken_links', []).append(link)
updated_dictionary[xblock_id].setdefault('broken_links', []).append(link)

return updated_tree, updated_dictionary


def _get_node_path(block):
"""
Retrieves the path frmo the course root node to a specific block, excluding the root.
Retrieves the path from the course root node to a specific block, excluding the root.
** Example Path structure **
[chapter_node, sequential_node, vertical_node, html_node]
Expand Down
213 changes: 184 additions & 29 deletions cms/djangoapps/contentstore/core/tests/test_course_optimizer_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,203 @@
import unittest
from unittest.mock import Mock, patch

from cms.djangoapps.contentstore.core.course_optimizer_provider import generate_broken_links_descriptor
# from ..course_optimizer_provider import generate_broken_links_descriptor
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.core.course_optimizer_provider import (
generate_broken_links_descriptor,
_update_node_tree_and_dictionary,
_get_node_path,
_create_dto_from_node_tree_recursive
)

class TestLinkCheck(ModuleStoreTestCase):
class TestLinkCheck(CourseTestCase):
"""
Tests for the link check functionality
"""
@patch('cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers.get_xblock')
@patch('cms.djangoapps.contentstore.xblock_storage_handlers.xblock_helpers.usage_key_with_run')
def test_success(self):
def setUp(self):
global MOCK_TREE
global MOCK_XBLOCK_DICTIONARY
MOCK_TREE = {
'chapter_1': {
'sequential_1': {
'vertical_1': {
'block_1': {}
}
}
}
}
MOCK_XBLOCK_DICTIONARY = {
'chapter_1': {
'display_name': 'Chapter 1',
'category': 'chapter'
},
'sequential_1': {
'display_name': 'Sequential 1',
'category': 'sequential'
},
'vertical_1': {
'display_name': 'Vertical 1',
'category': 'vertical'
},
'block_1': {
'display_name': 'Block 1',
'url': '/block/1',
'broken_links': ['broken_link_1', 'broken_link_2'],
'locked_links': ['locked_link']
}
}


def test_recursive_empty(self):
expected = _create_dto_from_node_tree_recursive({}, {})
self.assertEqual(None, expected)


def test_recursive_leaf_node(self):
expected_result = {
'blocks': [
{
'id': 'block_1',
'displayName': 'Block 1',
'url': '/block/1',
'brokenLinks': ['broken_link_1', 'broken_link_2'],
'lockedLinks': ['locked_link']
}
]
}
expected = _create_dto_from_node_tree_recursive(
MOCK_TREE['chapter_1']['sequential_1']['vertical_1'],
MOCK_XBLOCK_DICTIONARY
)
self.assertEqual(expected_result, expected)


def test_recursive_full_tree(self):
expected_result = {
'sections': [
{
'id': 'chapter_1',
'displayName': 'Chapter 1',
'subsections': [
{
'id': 'sequential_1',
'displayName': 'Sequential 1',
'units': [
{
'id': 'vertical_1',
'displayName': 'Vertical 1',
'blocks': [
{
'id': 'block_1',
'displayName': 'Block 1',
'url': '/block/1',
'brokenLinks': ['broken_link_1', 'broken_link_2'],
'lockedLinks': ['locked_link']
}
]
}
]
}
]
}
]
}

expected = _create_dto_from_node_tree_recursive(MOCK_TREE, MOCK_XBLOCK_DICTIONARY)
self.assertEqual(expected_result, expected)


def test_get_node_path(self):
mock_course = Mock()
mock_section = Mock(
location=Mock(block_id='section_id'),
display_name='Section Name'
)
mock_subsection = Mock(
location=Mock(block_id='subsection_id'),
display_name='Subsection Name'
)
mock_unit = Mock(
location=Mock(block_id='unit_id'),
display_name='Unit Name'
)
mock_block = Mock(
course_id='course-v1:test+course+2024',
location=Mock(block_id='block_id'),
display_name='Block Name',
category='html'
)
mock_course.get_parent.return_value = None
mock_section.get_parent.return_value = mock_course
mock_subsection.get_parent.return_value = mock_section
mock_unit.get_parent.return_value = mock_subsection
mock_block.get_parent.return_value = mock_unit

expected_result = [mock_course, mock_section, mock_subsection, mock_unit, mock_block]

result = _get_node_path(mock_unit)
self.assertEqual(expected_result, result)


# @patch('cms.djangoapps.contentstore.core.course_optimizer_provider._create_dto_from_node_tree_recursive')
@patch('cms.djangoapps.contentstore.core.course_optimizer_provider._update_node_tree_and_dictionary')
@patch('cms.djangoapps.contentstore.core.course_optimizer_provider.get_xblock')
@patch('cms.djangoapps.contentstore.core.course_optimizer_provider.usage_key_with_run')
def test_generate_broken_links_descriptor_returns_correct_result(
self,
mock_usage_key_with_run,
mock_get_xblock,
mock_update_node_tree_and_dictionary,
# mock_create_dto_from_node_tree_recursive
):
"""
Test generate_broken_links_descriptor to return expected dto
"""
# Mock data
mock_block = Mock()
mock_block.location.block_id = "block_id"
mock_block.display_name = "Block Name"
mock_block.course_id = "course-v1:test+course+2024"
mock_block.category = "html"
mock_block.get_parent.side_effect = [
Mock(location=Mock(block_id="unit_id"), display_name="Unit Name"),
Mock(location=Mock(block_id="subsection_id"), display_name="Subsection Name"),
Mock(location=Mock(block_id="section_id"), display_name="Section Name"),
None,
]
mock_course = Mock()
mock_section = Mock(
location=Mock(block_id='section_id'),
display_name='Section Name'
)
mock_section.get_parent.side_effect = mock_course
mock_subsection = Mock(
location=Mock(block_id='subsection_id'),
display_name='Subsection Name'
)
mock_subsection.get_parent.side_effect = mock_section
mock_unit = Mock(
location=Mock(block_id='unit_id'),
display_name='Unit Name'
)
mock_unit.get_parent.side_effect = mock_subsection
mock_block = Mock(
course_id='course-v1:test+course+2024',
location=Mock(block_id='block_id'),
display_name='Block Name'
)
mock_block.get_parent.side_effect = mock_unit
mock_block.category = 'html'
# mock_block.get_parent.side_effect = [
# Mock(location=Mock(block_id="unit_id"), display_name="Unit Name"),
# Mock(location=Mock(block_id="subsection_id"), display_name="Subsection Name"),
# Mock(location=Mock(block_id="section_id"), display_name="Section Name"),
# None,
# ]

# Mocking
# Mock functions
mock_usage_key_with_run.return_value = "mock_usage_key"
mock_get_xblock.return_value = mock_block
mock_update_node_tree_and_dictionary.return_value = Mock()
# mock_create_dto_from_node_tree_recursive.return_value = 'test'

# Test input
# Mock input
mock_json_content = [
["block_id", "http://example.com/broken-link1", False],
["block_id", "http://example.com/locked-link1", True],
["block_id", "http://example.com/broken-link2", False],
]
request_user = Mock()

# Call the function
result = generate_broken_links_descriptor(mock_json_content, request_user)

# Expected structure
# Expected output
expected_result = {
'sections': [
{
Expand Down Expand Up @@ -77,10 +235,7 @@ def test_success(self):
]
}

self.assertEqual(result, expected_result)


# def test_exception(self):
# Call the function
result = generate_broken_links_descriptor(mock_json_content, request_user)

# if __name__ == '__main__':
# unittest.main()
self.assertEqual(result, expected_result)
7 changes: 0 additions & 7 deletions cms/djangoapps/contentstore/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,13 +1110,6 @@ def check_broken_links(self, user_id, course_key_string, language):
"""
Checks for broken links in a course. Store the results in a file.
"""
URL_STATUS = {
'success': '200 OK',
'forbidden': '403 Forbidden',
'failure': 'Request Failed',
'error': 'Request Error'
}

def validate_user():
"""Validate if the user exists. Otherwise log error. """
try:
Expand Down
57 changes: 57 additions & 0 deletions cms/djangoapps/contentstore/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,60 @@ def test_register_exams_failure(self, _mock_register_exams_proctoring, _mock_reg
_mock_register_exams_proctoring.side_effect = Exception('boom!')
update_special_exams_and_publish(str(self.course.id))
course_publish.assert_called()


@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE)
class CourseLinkCheckTestCase(CourseTestCase):
def test_user_does_not_exist_raises_exception(self):
raise NotImplementedError

def test_no_course_access_raises_exception(self):
raise NotImplementedError

def test_hash_tags_stripped_from_url_lists(self):
raise NotImplementedError

def test_urls_out_count_equals_urls_in_count_when_no_hashtags(self):
raise NotImplementedError

def test_http_and_https_recognized_as_studio_url_schemes(self):
raise NotImplementedError

def test_file_not_recognized_as_studio_url_scheme(self):
raise NotImplementedError

def test_url_substitution_on_static_prefixes(self):
raise NotImplementedError

def test_url_substitution_on_forward_slash_prefixes(self):
raise NotImplementedError

def test_url_subsitution_on_containers(self):
raise NotImplementedError

def test_optimization_occurs_on_published_version(self):
raise NotImplementedError

def test_number_of_scanned_blocks_equals_blocks_in_course(self):
raise NotImplementedError

def test_every_detected_link_is_validated(self):
raise NotImplementedError

def test_link_validation_is_batched(self):
raise NotImplementedError

def test_all_links_in_link_list_longer_than_batch_size_are_validated(self):
raise NotImplementedError

def test_no_retries_on_403_access_denied_links(self):
raise NotImplementedError

def test_retries_attempted_on_connection_errors(self):
raise NotImplementedError

def test_max_number_of_retries_is_respected(self):
raise NotImplementedError

def test_scan_generates_file_named_by_course_key(self):
raise NotImplementedError

0 comments on commit 81619f8

Please sign in to comment.