diff --git a/pytest.ini b/pytest.ini index 11c3a49d..179a37a5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] usefixtures = chdir_to_workspace -DJANGO_SETTINGS_MODULE = cc2olx.django_settings +DJANGO_SETTINGS_MODULE = cc2olx.settings diff --git a/tests/conftest.py b/tests/conftest.py index 31b10605..d6a14a77 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,6 @@ import shutil import zipfile -import xml.dom.minidom from pathlib import Path from tempfile import NamedTemporaryFile from xml.dom.minidom import parse @@ -13,8 +12,7 @@ from cc2olx.cli import parse_args from cc2olx.models import Cartridge -from cc2olx.olx import OlxExport -from cc2olx.settings import collect_settings +from cc2olx.parser import parse_options @pytest.fixture(scope="session") @@ -78,30 +76,38 @@ def studio_course_xml(fixtures_data_dir): return parse(course_xml_filename).toprettyxml() +@pytest.fixture(scope="session") +def relative_links_source() -> str: + """ + Provide a relative links source. + """ + return "https://relative.source.domain" + + @pytest.fixture -def settings(imscc_file, link_map_csv): +def options(imscc_file, link_map_csv, relative_links_source): """ - Basic settings fixture. + Basic options fixture. """ - parsed_args = parse_args(["-i", str(imscc_file), "-f", str(link_map_csv)]) + args = parse_args(["-i", str(imscc_file), "-f", str(link_map_csv), "-s", relative_links_source]) - _settings = collect_settings(parsed_args) + options = parse_options(args) - yield _settings + yield options - shutil.rmtree(_settings["workspace"], ignore_errors=True) + shutil.rmtree(options["workspace"], ignore_errors=True) @pytest.fixture -def cartridge(imscc_file, settings): - cartridge = Cartridge(imscc_file, settings["workspace"]) +def cartridge(imscc_file, options): + cartridge = Cartridge(imscc_file, options["workspace"]) cartridge.load_manifest_extracted() cartridge.normalize() yield cartridge - shutil.rmtree(str(settings["workspace"] / imscc_file.stem)) + shutil.rmtree(str(options["workspace"] / imscc_file.stem)) @pytest.fixture(scope="session") @@ -289,19 +295,3 @@ def expected_cleaned_cdata_containing_html(fixtures_data_dir: Path) -> str: """ html_without_cdata_path = fixtures_data_dir / "html_files/cleaned-cdata-containing-html.html" return html_without_cdata_path.read_text() - - -@pytest.fixture -def bare_olx_exporter(cartridge: Cartridge) -> OlxExport: - """ - Provides bare OLX exporter. - - Args: - cartridge (Cartridge): Cartridge class instance. - - Returns: - OlxExport: OlxExport instance. - """ - olx_exporter = OlxExport(cartridge) - olx_exporter.doc = xml.dom.minidom.Document() - return olx_exporter diff --git a/tests/fixtures_data/imscc_file/web_link_content.xml b/tests/fixtures_data/imscc_file/web_link_content.xml index 7d6b1880..d7a1ef83 100644 --- a/tests/fixtures_data/imscc_file/web_link_content.xml +++ b/tests/fixtures_data/imscc_file/web_link_content.xml @@ -1,5 +1,5 @@ Web Link Content - + diff --git a/tests/fixtures_data/studio_course_xml/course.xml b/tests/fixtures_data/studio_course_xml/course.xml index f494f616..f0162820 100644 --- a/tests/fixtures_data/studio_course_xml/course.xml +++ b/tests/fixtures_data/studio_course_xml/course.xml @@ -152,7 +152,17 @@ -

elearning.png

]]> + + + + + +

+ elearning.png +

+ + +]]>
@@ -227,10 +237,20 @@ -

extra_files/example.pdf

]]> + + + + + +

+ extra_files/example.pdf +

+ + +]]>
- Web Link Content]]> + Web Link Content]]> diff --git a/tests/test_content_parsers/__init__.py b/tests/test_content_parsers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_content_parsers/test_html.py b/tests/test_content_parsers/test_html.py new file mode 100644 index 00000000..bfbc51c1 --- /dev/null +++ b/tests/test_content_parsers/test_html.py @@ -0,0 +1,191 @@ +from pathlib import Path +from unittest.mock import MagicMock, Mock, patch + +import pytest + +from cc2olx.content_parsers import HtmlContentParser + + +class TestHtmlContentParser: + def test_parse_content_returns_default_content_if_there_is_no_resource_identifier(self): + parser = HtmlContentParser(Mock(), Mock()) + expected_content = {"html": "

MISSING CONTENT

"} + + actual_content = parser._parse_content(None) + + assert actual_content == expected_content + + def test_parse_content_returns_default_content_if_the_resource_is_missed_in_cartridge(self): + cartridge_mock = Mock(define_resource=Mock(return_value=None)) + parser = HtmlContentParser(cartridge_mock, Mock()) + expected_content = {"html": "

MISSING CONTENT

"} + + actual_content = parser._parse_content(Mock()) + + assert actual_content == expected_content + + @patch("cc2olx.content_parsers.html.logger") + def test_parse_content_logs_missing_resource(self, logger_mock): + cartridge_mock = Mock(define_resource=Mock(return_value=None)) + parser = HtmlContentParser(cartridge_mock, Mock()) + idref_mock = Mock() + + parser._parse_content(idref_mock) + + logger_mock.info.assert_called_once_with("Missing resource: %s", idref_mock) + + @patch("cc2olx.content_parsers.html.HtmlContentParser._parse_web_link_content", Mock(return_value=None)) + @patch("cc2olx.content_parsers.html.HtmlContentParser.is_known_unprocessed_resource_type", Mock(return_value=True)) + def test_parse_content_returns_default_content_for_known_unprocessed_resource_types(self): + parser = HtmlContentParser(MagicMock(), Mock()) + expected_content = {"html": "

MISSING CONTENT

"} + + actual_content = parser._parse_content(Mock()) + + assert actual_content == expected_content + + @pytest.mark.parametrize( + "resource_type", + [ + "imsbasiclti_xmlv1p2", + "imsbasiclti_xmlv1p3", + "imsqti_xmlv1p3/imscc_xmlv1p1/assessment", + "imsqti_xmlv1p3/imscc_xmlv1p3/assessment", + "imsdt_xmlv1p2", + "imsdt_xmlv1p3", + ], + ) + def test_known_unprocessed_resource_types_is_detected(self, resource_type): + parser = HtmlContentParser(Mock(), Mock()) + + assert parser.is_known_unprocessed_resource_type(resource_type) is True + + @pytest.mark.parametrize("resource_type", ["imsbasicabc_xmlv1p2", "imsexample_xmlv1p3", "not_cc_type", "imsscorm"]) + def test_not_known_unprocessed_resource_types_is_detected(self, resource_type): + parser = HtmlContentParser(Mock(), Mock()) + + assert parser.is_known_unprocessed_resource_type(resource_type) is False + + @pytest.mark.parametrize( + "resource_type", + ["unsupported_resource_type", "chess_game_xmlv1p1", "drag_and_drop_xmlv1p1", "imsab_xmlv1p2"], + ) + @patch("cc2olx.content_parsers.html.HtmlContentParser._parse_web_link_content", Mock(return_value=None)) + @patch("cc2olx.content_parsers.html.HtmlContentParser._parse_not_imported_content") + def test_parse_content_parses_not_imported_content(self, parse_not_imported_content_mock, resource_type): + cartridge_mock = Mock(define_resource=Mock(return_value={"type": "imsqti_xmlv1p2"})) + parser = HtmlContentParser(cartridge_mock, Mock()) + + actual_content = parser._parse_content(Mock()) + + assert actual_content == parse_not_imported_content_mock.return_value + + @patch("cc2olx.content_parsers.html.imghdr.what", Mock(return_value=None)) + def test_parse_webcontent_returns_default_content_for_unknown_webcontent_type_from_web_resources_dir(self): + parser = HtmlContentParser( + Mock(build_resource_file_path=Mock(return_value=Path("web_resources/unknown/path/to/file.ext"))), + Mock(), + ) + expected_content = {"html": "

MISSING CONTENT

"} + + actual_content = parser._parse_webcontent(Mock(), MagicMock()) + + assert actual_content == expected_content + + @patch("cc2olx.content_parsers.html.logger") + @patch("cc2olx.content_parsers.html.imghdr.what", Mock(return_value=None)) + def test_parse_webcontent_logs_skipping_webcontent(self, logger_mock): + resource_file_path = Path("web_resources/unknown/path/to/file.ext") + parser = HtmlContentParser(Mock(build_resource_file_path=Mock(return_value=resource_file_path)), Mock()) + + parser._parse_webcontent(Mock(), MagicMock()) + + logger_mock.info.assert_called_once_with("Skipping webcontent: %s", resource_file_path) + + @patch("cc2olx.content_parsers.html.logger") + @patch("cc2olx.content_parsers.html.open", Mock(side_effect=FileNotFoundError)) + def test_webcontent_html_file_reading_failure_is_logged(self, logger_mock): + parser = HtmlContentParser(Mock(), Mock()) + idref_mock = Mock() + resource_file_path_mock = Mock() + + with pytest.raises(FileNotFoundError): + parser._parse_webcontent_html_file(idref_mock, resource_file_path_mock) + + logger_mock.error.assert_called_once_with("Failure reading %s from id %s", resource_file_path_mock, idref_mock) + + @pytest.mark.parametrize( + "resource,message", + [ + ( + {"type": "some_type_mock", "href": "https://example.com/some/type/link/"}, + "Not imported content: type = 'some_type_mock', href = 'https://example.com/some/type/link/'", + ), + ({"type": "some_type_mock"}, "Not imported content: type = 'some_type_mock'"), + ], + ) + @patch("cc2olx.content_parsers.html.logger") + def test_not_imported_content_parsing_with_href_in_resource(self, logger_mock, resource, message): + parser = HtmlContentParser(Mock(), Mock()) + expected_content = {"html": message} + + actual_content = parser._parse_not_imported_content(resource) + + logger_mock.info.assert_called_once_with("%s", message) + assert actual_content == expected_content + + def test_parsing_results(self, cartridge): + parser = HtmlContentParser(cartridge, Mock()) + + assert parser.parse("resource_1_course") == { + "html": "Not imported content: type = 'associatedcontent/imscc_xmlv1p1/learning-application-resource', " + "href = 'course_settings/canvas_export.txt'" + } + + assert parser.parse("resource_3_vertical") == { + "html": '\n\n\n' + "Vertical\n" + '\n' + '\n' + '\n' + "\n\n" + 'fractal.jpg\n' + "

Fractal Image Fractal Image

\n' + "\n\n" + } + + assert parser.parse("resource_6_wiki_content") == { + "html": '\n\n\n' + "Vertical\n" + '\n' + '\n' + '\n' + "\n\n" + '

Lorem ipsum...

\nWiki Content' + "\n\n\n" + } + + assert parser.parse("resource_7_canvas_content") == { + "html": '\n\n\n' + "Vertical\n" + '\n' + '\n' + '\n' + "\n\n" + '

Lorem ipsum...

\nCanvas Content' + "\n\n\n" + } + + assert parser.parse("resource_module-|-introduction") == { + "html": '\n\n\n' + "Vertical\n" + '\n' + '\n' + '\n' + "\n\n" + '

Lorem ipsum...

\nWiki Content' + "\n\n\n" + } diff --git a/tests/test_content_parsers/test_lti.py b/tests/test_content_parsers/test_lti.py new file mode 100644 index 00000000..fc55841e --- /dev/null +++ b/tests/test_content_parsers/test_lti.py @@ -0,0 +1,18 @@ +from unittest.mock import Mock + +from cc2olx.content_parsers import LtiContentParser + + +class TestLtiContentParser: + def test_parsing_results(self, cartridge): + parser = LtiContentParser(cartridge, Mock()) + + assert parser.parse("resource_2_lti") == { + "title": "Learning Tools Interoperability", + "description": "https://www.imsglobal.org/activity/learning-tools-interoperability", + "launch_url": "https://lti.local/launch", + "height": "500", + "width": "500", + "custom_parameters": {}, + "lti_id": "learning_tools_interoperability", + } diff --git a/tests/test_content_parsers/test_qti.py b/tests/test_content_parsers/test_qti.py new file mode 100644 index 00000000..d27e33c7 --- /dev/null +++ b/tests/test_content_parsers/test_qti.py @@ -0,0 +1,42 @@ +from unittest.mock import MagicMock, Mock, PropertyMock, call, patch + +import pytest + +from cc2olx.content_parsers import QtiContentParser +from cc2olx.exceptions import QtiError + + +class TestQtiContentParser: + @pytest.mark.parametrize("cc_profile", ["unknown_profile", "cc.chess.v0p1", "cc.drag_and_drop.v0p1", "123"]) + def test_parse_problem_raises_qti_error_if_cc_profile_is_unknown(self, cc_profile): + parser = QtiContentParser(Mock(), Mock()) + problem_mock = MagicMock(profile=cc_profile) + + with pytest.raises(QtiError) as exc_info: + parser._parse_problem(problem_mock, Mock(), Mock()) + + assert str(exc_info.value) == f'Unknown cc_profile: "{cc_profile}"' + + @patch("cc2olx.content_parsers.qti.logger") + def test_parse_problem_logs_inability_to_process_problem(self, logger_mock): + parser = QtiContentParser(Mock(), Mock()) + ident_mock = MagicMock() + resource_file_path_mock = Mock() + cc_profile_mock = Mock() + problem_mock = Mock(profile=cc_profile_mock, attrib={"ident": ident_mock}) + expected_logger_info_call_args_list = [ + call("Problem with ID %s can't be converted.", ident_mock), + call(" Profile %s is not supported.", cc_profile_mock), + call(" At file %s.", resource_file_path_mock), + ] + + with patch( + "cc2olx.content_parsers.qti.QtiContentParser._problem_parsers_map", + new_callable=PropertyMock, + ) as problem_parsers_map_mock: + problem_parsers_map_mock.return_value = {cc_profile_mock: Mock(side_effect=NotImplementedError)} + + parser._parse_problem(problem_mock, Mock(), resource_file_path_mock) + + assert logger_mock.info.call_count == 3 + assert logger_mock.info.call_args_list == expected_logger_info_call_args_list diff --git a/tests/test_content_parsers/test_video.py b/tests/test_content_parsers/test_video.py new file mode 100644 index 00000000..f77d8b30 --- /dev/null +++ b/tests/test_content_parsers/test_video.py @@ -0,0 +1,24 @@ +from unittest.mock import Mock, patch + +from cc2olx.content_parsers import VideoContentParser + + +class TestVideoContentParser: + def test_parse_content_returns_none_if_there_is_no_resource_identifier(self): + parser = VideoContentParser(Mock(), Mock()) + + actual_content = parser._parse_content(None) + + assert actual_content is None + + @patch( + "cc2olx.content_parsers.video.VideoContentParser._parse_web_link_content", + Mock(return_value={"href": "youtube.com/watch?v=ABCDeF12345"}), + ) + def test_parse_content_parses_youtube_link(self): + parser = VideoContentParser(Mock(), Mock()) + expected_content = {"youtube": "ABCDeF12345"} + + actual_content = parser._parse_content(Mock()) + + assert actual_content == expected_content diff --git a/tests/test_main.py b/tests/test_main.py index 69d88842..f1066a23 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -5,14 +5,19 @@ from .utils import format_xml -def test_convert_one_file(settings, imscc_file, studio_course_xml): +def test_convert_one_file(options, imscc_file, studio_course_xml): """ Tests, that ``convert_one_file`` call for ``imscc`` file results in tar.gz archive with olx course. """ expected_tgz_members_num = 7 - convert_one_file(imscc_file, settings["workspace"], settings["link_file"]) + convert_one_file( + imscc_file, + options["workspace"], + options["link_file"], + relative_links_source=options["relative_links_source"], + ) tgz_path = str((imscc_file.parent / "output" / imscc_file.stem).with_suffix(".tar.gz")) @@ -28,36 +33,36 @@ def test_convert_one_file(settings, imscc_file, studio_course_xml): break -def test_main(mocker, imscc_file, settings): +def test_main(mocker, imscc_file, options): """ Tests, that invocation of main function results in converted ``.imscc`` file. """ mocker.patch("cc2olx.main.parse_args") - mocker.patch("cc2olx.main.collect_settings", return_value=settings) + mocker.patch("cc2olx.main.parse_options", return_value=options) main() # workspace has been created - assert settings["workspace"].exists() + assert options["workspace"].exists() # content of imscc has been extracted - assert (settings["workspace"] / imscc_file.stem).exists() + assert (options["workspace"] / imscc_file.stem).exists() # archived olx course has been generated - assert (settings["workspace"] / imscc_file.stem).with_suffix(".tar.gz").exists() + assert (options["workspace"] / imscc_file.stem).with_suffix(".tar.gz").exists() -def test_main_zip_output(mocker, settings): +def test_main_zip_output(mocker, options): """ Tests, that ``--result zip`` cli option works fine. """ - settings["output_format"] = RESULT_TYPE_ZIP + options["output_format"] = RESULT_TYPE_ZIP mocker.patch("cc2olx.main.parse_args") - mocker.patch("cc2olx.main.collect_settings", return_value=settings) + mocker.patch("cc2olx.main.parse_options", return_value=options) main() - assert settings["workspace"].with_suffix(".zip").exists() + assert options["workspace"].with_suffix(".zip").exists() diff --git a/tests/test_models.py b/tests/test_models.py index 0b26b07d..fab6e07d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -5,12 +5,12 @@ from cc2olx.models import Cartridge, ResourceFile -def test_cartridge_initialize(imscc_file, settings): +def test_cartridge_initialize(imscc_file, options): """ Tests, that ``Cartridge`` initializes without errors. """ - cartridge = Cartridge(imscc_file, settings["workspace"]) + cartridge = Cartridge(imscc_file, options["workspace"]) assert cartridge.normalized is None assert cartridge.resources is None @@ -19,12 +19,12 @@ def test_cartridge_initialize(imscc_file, settings): assert cartridge.file_path == imscc_file -def test_load_manifest_extracted(imscc_file, settings, temp_workspace_dir): +def test_load_manifest_extracted(imscc_file, options, temp_workspace_dir): """ Tests, that all resources and metadata are loaded fine. """ - cartridge = Cartridge(imscc_file, settings["workspace"]) + cartridge = Cartridge(imscc_file, options["workspace"]) cartridge.load_manifest_extracted() cartridge_version = "1.3.0" @@ -42,8 +42,8 @@ def test_load_manifest_extracted(imscc_file, settings, temp_workspace_dir): assert isinstance(cartridge.resources[0]["children"][0], ResourceFile) -def test_cartridge_normalize(imscc_file, settings): - cartridge = Cartridge(imscc_file, settings["workspace"]) +def test_cartridge_normalize(imscc_file, options): + cartridge = Cartridge(imscc_file, options["workspace"]) cartridge.load_manifest_extracted() cartridge.normalize() @@ -299,86 +299,3 @@ def test_cartridge_normalize(imscc_file, settings): "identifier": "org_1", "structure": "rooted-hierarchy", } - - -def test_cartridge_get_resource_content(cartridge): - assert cartridge.get_resource_content("resource_1_course") == ( - "html", - { - "html": "Unimported content: type = 'associatedcontent/imscc_xmlv1p1/learning-application-resource', " - "href = 'course_settings/canvas_export.txt'" - }, - ) - - assert cartridge.get_resource_content("resource_2_lti") == ( - "lti", - { - "title": "Learning Tools Interoperability", - "description": "https://www.imsglobal.org/activity/learning-tools-interoperability", - "launch_url": "https://lti.local/launch", - "height": "500", - "width": "500", - "custom_parameters": {}, - "lti_id": "learning_tools_interoperability", - }, - ) - - assert cartridge.get_resource_content("resource_3_vertical") == ( - "html", - { - "html": '\n\n\n' - "Vertical\n" - '\n' - '\n' - '\n' - "\n\n" - 'fractal.jpg\n' - "

Fractal Image Fractal Image

\n' - "\n\n" - }, - ) - - assert cartridge.get_resource_content("resource_6_wiki_content") == ( - "html", - { - "html": '\n\n\n' - "Vertical\n" - '\n' - '\n' - '\n' - "\n\n" - '

Lorem ipsum...

\nWiki Content' - "\n\n\n" - }, - ) - - assert cartridge.get_resource_content("resource_7_canvas_content") == ( - "html", - { - "html": '\n\n\n' - "Vertical\n" - '\n' - '\n' - '\n' - "\n\n" - '

Lorem ipsum...

\nCanvas Content' - "\n\n\n" - }, - ) - - assert cartridge.get_resource_content("resource_module-|-introduction") == ( - "html", - { - "html": '\n\n\n' - "Vertical\n" - '\n' - '\n' - '\n' - "\n\n" - '

Lorem ipsum...

\nWiki Content' - "\n\n\n" - }, - ) diff --git a/tests/test_olx.py b/tests/test_olx.py index a35d67c6..792f804f 100644 --- a/tests/test_olx.py +++ b/tests/test_olx.py @@ -1,16 +1,12 @@ import json -from unittest.mock import Mock - -import lxml import xml.dom.minidom from cc2olx import olx - from .utils import format_xml -def test_olx_export_xml(cartridge, link_map_csv, studio_course_xml): - xml = olx.OlxExport(cartridge, link_map_csv).xml() +def test_olx_export_xml(cartridge, link_map_csv, studio_course_xml, relative_links_source): + xml = olx.OlxExport(cartridge, link_map_csv, relative_links_source=relative_links_source).xml() assert format_xml(xml) == format_xml(studio_course_xml) @@ -25,132 +21,6 @@ def test_olx_export_wiki_page_disabled(cartridge, link_map_csv, studio_course_xm assert tab["is_hidden"] -def test_process_link(): - details = {"href": "https://example.com/path"} - details_with_youtube_link = {"href": "https://www.youtube.com/watch?v=gQ-cZRmHfs4&list=PL5B350D511278A56B"} - - assert olx.process_link(details) == ( - "html", - {"html": "".format(details["href"])}, - ) - - assert olx.process_link(details_with_youtube_link) == ( - "video", - {"youtube": "gQ-cZRmHfs4"}, - ) - - -class TestOlXExporeterHTMLProcessing: - """ - Test the OLX exporter for HTML parsing flow. - """ - - def test_html_cleaning_from_cdata( - self, - mocker, - bare_olx_exporter, - cdata_containing_html, - expected_cleaned_cdata_containing_html, - ): - """ - Test that CDATA cleaning function is called during HTML processing. - - Args: - mocker (MockerFixture): MockerFixture instance. - bare_olx_exporter (OlxExport): bare OLX exporter. - cdata_containing_html (str): HTML that contains CDATA tags. - expected_cleaned_cdata_containing_html (str): Expected HTML after - successful cleaning. - """ - details = {"html": cdata_containing_html} - - clean_from_cdata_mock = mocker.patch( - "cc2olx.olx.clean_from_cdata", - return_value=expected_cleaned_cdata_containing_html, - ) - - bare_olx_exporter._process_html(details) - - clean_from_cdata_mock.assert_called_once() - - def test_processed_html_content_is_wrapped_into_cdata(self, bare_olx_exporter, cdata_containing_html): - """ - Test that processed HTML content is wrapped into CDATA section. - - Args: - bare_olx_exporter (OlxExport): bare OLX exporter. - cdata_containing_html (str): HTML that contains CDATA tags. - """ - details = {"html": cdata_containing_html} - - result_html, *__ = bare_olx_exporter._process_html(details) - - assert isinstance(result_html.childNodes[0], xml.dom.minidom.CDATASection) - - -class TestOlXExporeterIframeParser: - """ - Test the olx exporter for iframe link parsing flow - """ - - def _get_oxl_exporter(self, cartridge, link_map_csv): - """ - Helper function to create olx exporter. - - Args: - cartridge ([Cartridge]): Cartridge class instance. - link_map_csv ([str]): Csv file path. - - Returns: - [OlxExport]: OlxExport instance. - """ - olx_exporter = olx.OlxExport(cartridge, link_file=link_map_csv) - olx_exporter.doc = xml.dom.minidom.Document() - return olx_exporter - - def test_process_html_for_iframe_video_blocks(self, cartridge, link_map_csv, iframe_content): - """ - Test if the iframe is getting parsed and video blocks being generated. - - Args: - cartridge ([Cartridge]): Cartridge class instance. - link_map_csv ([str]): Csv file path. - iframe_content ([str]): Html file content. - """ - olx_exporter = self._get_oxl_exporter(cartridge, link_map_csv) - _, video_olx = olx_exporter._process_html_for_iframe(iframe_content) - assert len(video_olx) == 1 - - def test_process_html_for_iframe_html_removed(self, cartridge, link_map_csv, iframe_content): - """ - Test if iframe is removed from html. - - Args: - cartridge ([Cartridge]): Cartridge class instance. - link_map_csv ([str]): Csv file path. - iframe_content ([str]): Html file content. - """ - olx_exporter = self._get_oxl_exporter(cartridge, link_map_csv) - html_str, _ = olx_exporter._process_html_for_iframe(iframe_content) - html = lxml.html.fromstring(html_str) - iframe = html.xpath("//iframe") - assert len(iframe) == 0 - - def test_create_olx_nodes(self, cartridge, link_map_csv, iframe_content): - """ - Test create olx nodes with html content. - - Args: - cartridge ([Cartridge]): Cartridge class instance. - link_map_csv ([str]): Csv file path. - iframe_content ([str]): Html file content. - """ - olx_exporter = self._get_oxl_exporter(cartridge, link_map_csv) - nodes = olx_exporter._create_olx_nodes("html", {"html": iframe_content}) - # Html xblock and video xblock - assert len(nodes) == 2 - - class TestOlxExporterLtiPolicy: def _get_oxl_exporter(self, cartridge, passports_csv): """ @@ -167,11 +37,10 @@ def _get_oxl_exporter(self, cartridge, passports_csv): olx_exporter.doc = xml.dom.minidom.Document() return olx_exporter - def test_lti_consumer_present_set_to_true(self, cartridge, passports_csv): + def test_lti_consumer_ids_are_defined(self, cartridge, passports_csv): olx_exporter = self._get_oxl_exporter(cartridge, passports_csv) _ = olx_exporter.xml() - assert olx_exporter.lti_consumer_present is True assert olx_exporter.lti_consumer_ids == {"external_tool_lti", "learning_tools_interoperability"} def test_policy_contains_advanced_module(self, cartridge, passports_csv, caplog): @@ -193,51 +62,3 @@ def test_policy_contains_advanced_module(self, cartridge, passports_csv, caplog) assert ["Missing LTI Passport for learning_tools_interoperability. Using default."] == [ rec.message for rec in caplog.records ] - - -class TestDiscussionParsing: - """ - Test the OLX exporter for discussion parsing flow. - """ - - def test_discussion_content_cleaning_from_cdata( - self, - mocker, - bare_olx_exporter, - cdata_containing_html, - expected_cleaned_cdata_containing_html, - ): - """ - Test that CDATA cleaning function is called during discussion parsing. - - Args: - mocker (MockerFixture): MockerFixture instance. - bare_olx_exporter (OlxExport): bare OLX exporter. - cdata_containing_html (str): HTML that contains CDATA tags. - expected_cleaned_cdata_containing_html (str): Expected HTML after - successful cleaning. - """ - details = {"dependencies": [], "title": Mock(), "text": cdata_containing_html} - - clean_from_cdata_mock = mocker.patch( - "cc2olx.olx.clean_from_cdata", - return_value=expected_cleaned_cdata_containing_html, - ) - - bare_olx_exporter._create_discussion_node(details) - - clean_from_cdata_mock.assert_called_once() - - def test_discussion_decription_is_wrapped_into_cdata(self, bare_olx_exporter, cdata_containing_html): - """ - Test that processed HTML content is wrapped into CDATA section. - - Args: - bare_olx_exporter (OlxExport): bare OLX exporter. - cdata_containing_html (str): HTML that contains CDATA tags. - """ - details = {"dependencies": [], "title": Mock(), "text": cdata_containing_html} - - discussion_decription_html, __ = bare_olx_exporter._create_discussion_node(details) - - assert isinstance(discussion_decription_html.childNodes[0], xml.dom.minidom.CDATASection) diff --git a/tests/test_olx_generators/__init__.py b/tests/test_olx_generators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_olx_generators/test_discussion.py b/tests/test_olx_generators/test_discussion.py new file mode 100644 index 00000000..fedc1146 --- /dev/null +++ b/tests/test_olx_generators/test_discussion.py @@ -0,0 +1,44 @@ +import xml.dom.minidom +from unittest.mock import Mock, patch + +from cc2olx.olx_generators import DiscussionOlxGenerator + + +class TestDiscussionOlxGenerator: + def test_discussion_content_cleaning_from_cdata( + self, + cdata_containing_html, + expected_cleaned_cdata_containing_html, + ): + """ + Test that CDATA cleaning function is called during discussion parsing. + + Args: + cdata_containing_html (str): HTML that contains CDATA tags. + expected_cleaned_cdata_containing_html (str): Expected HTML after + successful cleaning. + """ + generator = DiscussionOlxGenerator(Mock()) + content = {"dependencies": [], "title": Mock(), "text": cdata_containing_html} + + with patch( + "cc2olx.olx_generators.discussion.clean_from_cdata", + return_value=expected_cleaned_cdata_containing_html, + ) as clean_from_cdata_mock: + generator.create_nodes(content) + + clean_from_cdata_mock.assert_called_once() + + def test_discussion_description_is_wrapped_into_cdata(self, cdata_containing_html): + """ + Test that processed HTML content is wrapped into CDATA section. + + Args: + cdata_containing_html (str): HTML that contains CDATA tags. + """ + generator = DiscussionOlxGenerator(Mock()) + content = {"dependencies": [], "title": Mock(), "text": cdata_containing_html} + + discussion_description_html, __ = generator.create_nodes(content) + + assert isinstance(discussion_description_html.childNodes[0], xml.dom.minidom.CDATASection) diff --git a/tests/test_olx_generators/test_html.py b/tests/test_olx_generators/test_html.py new file mode 100644 index 00000000..ad9d4414 --- /dev/null +++ b/tests/test_olx_generators/test_html.py @@ -0,0 +1,65 @@ +import xml.dom.minidom +from unittest.mock import patch + +import lxml + +from cc2olx.dataclasses import OlxGeneratorContext +from cc2olx.iframe_link_parser import KalturaIframeLinkParser +from cc2olx.olx_generators import HtmlOlxGenerator + + +class TestHtmlOlxGenerator: + def test_process_html_for_iframe_provides_video_blocks(self, iframe_content, link_map_csv): + context = OlxGeneratorContext(iframe_link_parser=KalturaIframeLinkParser(link_map_csv), lti_consumer_ids=set()) + generator = HtmlOlxGenerator(context) + + _, video_olx = generator._process_html_for_iframe(iframe_content) + + assert len(video_olx) == 1 + assert video_olx[0].nodeName == "video" + + def test_process_html_for_iframe_removes_iframes_from_html(self, iframe_content, link_map_csv): + context = OlxGeneratorContext(iframe_link_parser=KalturaIframeLinkParser(link_map_csv), lti_consumer_ids=set()) + generator = HtmlOlxGenerator(context) + + html_str, _ = generator._process_html_for_iframe(iframe_content) + + html = lxml.html.fromstring(html_str) + iframe = html.xpath("//iframe") + assert len(iframe) == 0 + + def test_html_cleaning_from_cdata(self, cdata_containing_html, expected_cleaned_cdata_containing_html): + """ + Test that CDATA cleaning function is called during HTML processing. + + Args: + cdata_containing_html (str): HTML that contains CDATA tags. + expected_cleaned_cdata_containing_html (str): Expected HTML after + successful cleaning. + """ + context = OlxGeneratorContext(iframe_link_parser=None, lti_consumer_ids=set()) + generator = HtmlOlxGenerator(context) + content = {"html": cdata_containing_html} + + with patch( + "cc2olx.olx_generators.html.clean_from_cdata", + return_value=expected_cleaned_cdata_containing_html, + ) as clean_from_cdata_mock: + generator.create_nodes(content) + + clean_from_cdata_mock.assert_called_once() + + def test_processed_html_content_is_wrapped_into_cdata(self, cdata_containing_html): + """ + Test that processed HTML content is wrapped into CDATA section. + + Args: + cdata_containing_html (str): HTML that contains CDATA tags. + """ + context = OlxGeneratorContext(iframe_link_parser=None, lti_consumer_ids=set()) + generator = HtmlOlxGenerator(context) + content = {"html": cdata_containing_html} + + result_html, *__ = generator.create_nodes(content) + + assert isinstance(result_html.childNodes[0], xml.dom.minidom.CDATASection) diff --git a/tests/test_olx_generators/test_qti.py b/tests/test_olx_generators/test_qti.py new file mode 100644 index 00000000..0e563106 --- /dev/null +++ b/tests/test_olx_generators/test_qti.py @@ -0,0 +1,17 @@ +from unittest.mock import Mock + +import pytest + +from cc2olx.exceptions import QtiError +from cc2olx.olx_generators import QtiOlxGenerator + + +class TestQtiOlxGenerator: + @pytest.mark.parametrize("cc_profile", ["unknown_profile", "cc.chess.v0p1", "cc.drag_and_drop.v0p1", "123"]) + def test_create_nodes_raises_qti_error_if_cc_profile_is_unknown(self, cc_profile): + generator = QtiOlxGenerator(Mock()) + + with pytest.raises(QtiError) as exc_info: + generator.create_nodes([{"cc_profile": cc_profile}]) + + assert str(exc_info.value) == f'Unknown cc_profile: "{cc_profile}"' diff --git a/tests/test_olx_generators/test_video.py b/tests/test_olx_generators/test_video.py new file mode 100644 index 00000000..b82737f4 --- /dev/null +++ b/tests/test_olx_generators/test_video.py @@ -0,0 +1,14 @@ +from unittest.mock import Mock + +from cc2olx.olx_generators import VideoOlxGenerator + + +class TestVideoOlxGenerator: + def test_nodes_creation(self): + generator = VideoOlxGenerator(Mock()) + expected_video_xml = '