Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2u/optimizer tests #36033

Draft
wants to merge 3 commits into
base: 2u/course-optimizer
Choose a base branch
from
Draft
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
Empty file.
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
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
"""
Tests for course optimizer
"""

import unittest
from unittest.mock import Mock, patch

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(CourseTestCase):
"""
Tests for the link check functionality
"""
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_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,
# ]

# 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'

# 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()

# Expected output
expected_result = {
'sections': [
{
'id': 'section_id',
'displayName': 'Section Name',
'subsections': [
{
'id': 'subsection_id',
'displayName': 'Subsection Name',
'units': [
{
'id': 'unit_id',
'displayName': 'Unit Name',
'blocks': [
{
'id': 'block_id',
'displayName': 'Block Name',
'url': '/course/course-v1:test+course+2024/editor/html/mock_usage_key',
'brokenLinks': [
"http://example.com/broken-link1",
"http://example.com/broken-link2",
],
'lockedLinks': ["http://example.com/broken-link1"],
},
]
}
]
}
]
}
]
}

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

self.assertEqual(result, expected_result)
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
Loading