diff --git a/Makefile b/Makefile index 40befcf..3334362 100644 --- a/Makefile +++ b/Makefile @@ -63,19 +63,18 @@ piptools: ## install pinned version of pip-compile and pip-sync requirements: piptools ## install development environment requirements pip-sync -q requirements/dev.txt requirements/private.* -test: ## Run the tests +test: clean ## run tests in the current virtualenv mkdir -p var - rm -rf .coverage - python -m coverage run --rcfile=.coveragerc ./test.py --noinput + DJANGO_SETTINGS_MODULE=test_settings pytest diff_cover: test ## find diff lines that need test coverage diff-cover coverage.xml -test-all: quality pii_check ## run tests on every supported Python/Django combination +test-all: quality ## run tests on every supported Python/Django combination tox tox -e docs -validate: quality pii_check test ## run tests and quality checks +validate: quality test ## run tests and quality checks selfcheck: ## check that the Makefile is well-formed @echo "The Makefile is well-formed." diff --git a/README.rst b/README.rst index 5e17b24..c3d55c5 100644 --- a/README.rst +++ b/README.rst @@ -16,11 +16,10 @@ Purpose Django app plugin for fetching and verifying tags for xblock skills. -TODO: The ``README.rst`` file should start with a brief description of the repository and its purpose. -It should be described in the context of other repositories under the ``openedx`` -organization. It should make clear where this fits in to the overall Open edX -codebase and should be oriented towards people who are new to the Open edX -project. +Set ``SHOW_SKILL_VERIFICATION_PROBABILITY`` in your django settings to configure +probability of displaying verification form. Values in range 0 to 1 are +allowed, where 0 means never and 1 means always display. Default value is 0.5 +i.e. 50% chance of displaying the form. Getting Started *************** diff --git a/requirements/base.in b/requirements/base.in index 5288d49..1caf40d 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -3,4 +3,6 @@ Django # Web application framework django-model-utils # Provides TimeStampedModel abstract base class +# TODO: update below dependencies when its commits are merged to upstream git+https://github.com/open-craft/openedx-events.git@77ad965c4ac5157b861a19b8cb6b9240883008ba#egg=openedx_events +git+https://github.com/open-craft/openedx-filters.git@d0206cf84f2e5e22b1a99d06566d88839d40b9fe#egg=openedx-filters diff --git a/requirements/base.txt b/requirements/base.txt index 3298623..4c9b9c9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -14,6 +14,7 @@ django==3.2.16 # -r requirements/base.in # django-model-utils # openedx-events + # openedx-filters django-model-utils==4.3.1 # via -r requirements/base.in edx-opaque-keys[django]==2.3.0 @@ -22,6 +23,8 @@ fastavro==1.7.0 # via openedx-events openedx_events @ git+https://github.com/open-craft/openedx-events.git@77ad965c4ac5157b861a19b8cb6b9240883008ba # via -r requirements/base.in +openedx-filters @ git+https://github.com/open-craft/openedx-filters.git@d0206cf84f2e5e22b1a99d06566d88839d40b9fe + # via -r requirements/base.in pbr==5.11.0 # via stevedore pymongo==3.13.0 diff --git a/requirements/ci.txt b/requirements/ci.txt index 4038c91..1f895a3 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -10,11 +10,11 @@ charset-normalizer==2.1.1 # via requests codecov==2.1.12 # via -r requirements/ci.in -coverage==7.0.0 +coverage==7.0.1 # via codecov distlib==0.3.6 # via virtualenv -filelock==3.8.2 +filelock==3.9.0 # via # tox # virtualenv @@ -22,7 +22,7 @@ idna==3.4 # via requests packaging==22.0 # via tox -platformdirs==2.6.0 +platformdirs==2.6.2 # via virtualenv pluggy==1.0.0 # via tox diff --git a/requirements/dev.txt b/requirements/dev.txt index acc6aed..d84864f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -34,11 +34,11 @@ boto==2.49.0 # via # -r requirements/quality.txt # xblock-sdk -boto3==1.26.35 +boto3==1.26.40 # via # -r requirements/quality.txt # fs-s3fs -botocore==1.29.35 +botocore==1.29.40 # via # -r requirements/quality.txt # boto3 @@ -85,7 +85,7 @@ cookiecutter==2.1.1 # via # -r requirements/quality.txt # xblock-sdk -coverage[toml]==7.0.0 +coverage[toml]==7.0.1 # via # -r requirements/ci.txt # -r requirements/quality.txt @@ -110,6 +110,7 @@ django==3.2.16 # djangorestframework # edx-i18n-tools # openedx-events + # openedx-filters # xblock-sdk django-model-utils==4.3.1 # via -r requirements/quality.txt @@ -127,7 +128,7 @@ edx-opaque-keys[django]==2.3.0 # via # -r requirements/quality.txt # openedx-events -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # -r requirements/quality.txt # pytest @@ -135,7 +136,7 @@ fastavro==1.7.0 # via # -r requirements/quality.txt # openedx-events -filelock==3.8.2 +filelock==3.9.0 # via # -r requirements/ci.txt # tox @@ -202,10 +203,12 @@ mccabe==0.7.0 # via # -r requirements/quality.txt # pylint -mock==4.0.3 +mock==5.0.0 # via -r requirements/quality.txt openedx_events @ git+https://github.com/open-craft/openedx-events.git@77ad965c4ac5157b861a19b8cb6b9240883008ba # via -r requirements/quality.txt +openedx-filters @ git+https://github.com/open-craft/openedx-filters.git@d0206cf84f2e5e22b1a99d06566d88839d40b9fe + # via -r requirements/quality.txt packaging==22.0 # via # -r requirements/ci.txt @@ -226,7 +229,7 @@ pep517==0.13.0 # build pip-tools==6.12.1 # via -r requirements/pip-tools.txt -platformdirs==2.6.0 +platformdirs==2.6.2 # via # -r requirements/ci.txt # -r requirements/quality.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index d6986c2..267399c 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -35,11 +35,11 @@ boto==2.49.0 # via # -r requirements/test.txt # xblock-sdk -boto3==1.26.35 +boto3==1.26.40 # via # -r requirements/test.txt # fs-s3fs -botocore==1.29.35 +botocore==1.29.40 # via # -r requirements/test.txt # boto3 @@ -73,7 +73,7 @@ cookiecutter==2.1.1 # via # -r requirements/test.txt # xblock-sdk -coverage[toml]==7.0.0 +coverage[toml]==7.0.1 # via # -r requirements/test.txt # pytest-cov @@ -87,6 +87,7 @@ django==3.2.16 # django-pyfs # djangorestframework # openedx-events + # openedx-filters # xblock-sdk django-model-utils==4.3.1 # via -r requirements/test.txt @@ -96,7 +97,7 @@ django-pyfs==3.2.0 # xblock-sdk djangorestframework==3.14.0 # via -r requirements/test.txt -doc8==1.0.0 +doc8==1.1.1 # via -r requirements/doc.in docutils==0.19 # via @@ -108,7 +109,7 @@ edx-opaque-keys[django]==2.3.0 # via # -r requirements/test.txt # openedx-events -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # -r requirements/test.txt # pytest @@ -138,7 +139,7 @@ importlib-metadata==5.2.0 # keyring # sphinx # twine -importlib-resources==5.10.1 +importlib-resources==5.10.2 # via keyring iniconfig==1.1.1 # via @@ -182,12 +183,14 @@ markupsafe==2.1.1 # -r requirements/test.txt # jinja2 # xblock -mock==4.0.3 +mock==5.0.0 # via -r requirements/test.txt more-itertools==9.0.0 # via jaraco-classes openedx_events @ git+https://github.com/open-craft/openedx-events.git@77ad965c4ac5157b861a19b8cb6b9240883008ba # via -r requirements/test.txt +openedx-filters @ git+https://github.com/open-craft/openedx-filters.git@d0206cf84f2e5e22b1a99d06566d88839d40b9fe + # via -r requirements/test.txt packaging==22.0 # via # -r requirements/test.txt @@ -292,7 +295,7 @@ six==1.16.0 # python-dateutil snowballstemmer==2.2.0 # via sphinx -sphinx==5.3.0 +sphinx==6.0.0 # via -r requirements/doc.in sphinxcontrib-applehelp==1.0.2 # via sphinx diff --git a/requirements/quality.txt b/requirements/quality.txt index efb01d5..df7b01f 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -33,11 +33,11 @@ boto==2.49.0 # via # -r requirements/test.txt # xblock-sdk -boto3==1.26.35 +boto3==1.26.40 # via # -r requirements/test.txt # fs-s3fs -botocore==1.29.35 +botocore==1.29.40 # via # -r requirements/test.txt # boto3 @@ -71,7 +71,7 @@ cookiecutter==2.1.1 # via # -r requirements/test.txt # xblock-sdk -coverage[toml]==7.0.0 +coverage[toml]==7.0.1 # via # -r requirements/test.txt # pytest-cov @@ -85,6 +85,7 @@ django==3.2.16 # django-pyfs # djangorestframework # openedx-events + # openedx-filters # xblock-sdk django-model-utils==4.3.1 # via -r requirements/test.txt @@ -100,7 +101,7 @@ edx-opaque-keys[django]==2.3.0 # via # -r requirements/test.txt # openedx-events -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via # -r requirements/test.txt # pytest @@ -164,10 +165,12 @@ markupsafe==2.1.1 # xblock mccabe==0.7.0 # via pylint -mock==4.0.3 +mock==5.0.0 # via -r requirements/test.txt openedx_events @ git+https://github.com/open-craft/openedx-events.git@77ad965c4ac5157b861a19b8cb6b9240883008ba # via -r requirements/test.txt +openedx-filters @ git+https://github.com/open-craft/openedx-filters.git@d0206cf84f2e5e22b1a99d06566d88839d40b9fe + # via -r requirements/test.txt packaging==22.0 # via # -r requirements/test.txt @@ -176,7 +179,7 @@ pbr==5.11.0 # via # -r requirements/test.txt # stevedore -platformdirs==2.6.0 +platformdirs==2.6.2 # via pylint pluggy==1.0.0 # via diff --git a/requirements/test.txt b/requirements/test.txt index 0f195c6..71067a6 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -21,9 +21,9 @@ binaryornot==0.4.4 # via cookiecutter boto==2.49.0 # via xblock-sdk -boto3==1.26.35 +boto3==1.26.40 # via fs-s3fs -botocore==1.29.35 +botocore==1.29.40 # via # boto3 # s3transfer @@ -41,7 +41,7 @@ code-annotations==1.3.0 # via -r requirements/test.in cookiecutter==2.1.1 # via xblock-sdk -coverage[toml]==7.0.0 +coverage[toml]==7.0.1 # via pytest-cov # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt @@ -50,6 +50,7 @@ coverage[toml]==7.0.0 # django-pyfs # djangorestframework # openedx-events + # openedx-filters # xblock-sdk django-model-utils==4.3.1 # via -r requirements/base.txt @@ -61,7 +62,7 @@ edx-opaque-keys[django]==2.3.0 # via # -r requirements/base.txt # openedx-events -exceptiongroup==1.0.4 +exceptiongroup==1.1.0 # via pytest fastavro==1.7.0 # via @@ -101,10 +102,12 @@ markupsafe==2.1.1 # via # jinja2 # xblock -mock==4.0.3 +mock==5.0.0 # via -r requirements/test.in openedx_events @ git+https://github.com/open-craft/openedx-events.git@77ad965c4ac5157b861a19b8cb6b9240883008ba # via -r requirements/base.txt +openedx-filters @ git+https://github.com/open-craft/openedx-filters.git@d0206cf84f2e5e22b1a99d06566d88839d40b9fe + # via -r requirements/base.txt packaging==22.0 # via pytest pbr==5.11.0 diff --git a/skill_tagging/pipeline.py b/skill_tagging/pipeline.py new file mode 100644 index 0000000..9c7fff7 --- /dev/null +++ b/skill_tagging/pipeline.py @@ -0,0 +1,76 @@ +""" +Module that contains the openedx_filters pipeline steps. +""" +import logging +import random + +import pkg_resources +from django.conf import settings +from django.template import Context, Template +from openedx_filters import PipelineStep + +logger = logging.getLogger(__name__) +DEFAULT_PROBABILITY = 0.5 + + +class AddVerticalBlockSkillVerificationSection(PipelineStep): + """ + Adds extra HTML to the fragment. + + Example Usage: + + .. code-block:: + + "OPENEDX_FILTERS_CONFIG": { + "org.openedx.learning.vertical_block.render.completed.v1": { + "fail_sliently": false, + "pipeline": [ + "skill_tagging.pipeline.AddVerticalBlockSkillVerificationSection" + ] + } + } + """ + def resource_string(self, path): + """Handy helper for getting resources from our kit.""" + data = pkg_resources.resource_string(__name__, path) + return data.decode("utf8") + + def fetch_related_skills(self, block): + """Checks `has_verified_tags` and fetchs related skills.""" + has_verified_tags = getattr(block, "has_verified_tags", None) + if has_verified_tags is None or has_verified_tags is True: + return [] + fetch_tags = getattr(block, "fetch_skill_tags", None) + if fetch_tags is None: + return [] + tags = fetch_tags() + return tags + + def should_run_filter(self): + """Determines whether we should run filter and display form.""" + # random returns a number between 0 and 1 (inclusive). + probability = getattr(settings, "SHOW_SKILL_VERIFICATION_PROBABILITY", DEFAULT_PROBABILITY) + return random.random() < probability + + def run_filter(self, block, fragment, context, view): # pylint: disable=arguments-differ + """Pipeline Step implementing the Filter""" + + skills = self.fetch_related_skills(block) + if not skills or not self.should_run_filter(): + return {"block": block, "fragment": fragment, "context": context, "view": view} + verify_tags_url = block.runtime.handler_url(block, "verify_tags") + html = self.resource_string("static/tagging.html") + css = self.resource_string("static/tagging.css") + js = self.resource_string("static/tagging.js") + image = self.resource_string("static/brainstorming.svg") + data = { + "skills": skills, + "verify_tags_url": verify_tags_url, + "image": image, + } + template_str = f'{html}' + template = Template(template_str) + context = Context(data) + tags_div = template.render(context) + fragment.content = f"{fragment.content}{tags_div}" + return {"block": block, "fragment": fragment, "context": context, "view": view} diff --git a/skill_tagging/skill_tagging_mixin.py b/skill_tagging/skill_tagging_mixin.py index 4ec21b1..e36a3e7 100644 --- a/skill_tagging/skill_tagging_mixin.py +++ b/skill_tagging/skill_tagging_mixin.py @@ -35,7 +35,7 @@ class SkillTaggingMixin: scope=Scope.user_state ) - def _fetch_skill_tags(self): + def fetch_skill_tags(self): """ Fetch skill tags for the XBlock by calling taxonomy api. """ @@ -79,32 +79,20 @@ def _fetch_skill_tags(self): return results[0].get('skills', []) @XBlock.json_handler - def fetch_tags(self, data, suffix=''): # pylint: disable=unused-argument - """ - Handler for fetching skill tags associated with this XBlock - """ - return self._fetch_skill_tags() - - @XBlock.json_handler - def verify_tags(self, tags, suffix=''): # pylint: disable=unused-argument + def verify_tags(self, data, suffix=''): # pylint: disable=unused-argument """ Handler to verify tags """ + + verified_skills = data.get("verified_skills", []) + ignored_skills = data.get("ignored_skills", []) usage_key = str(self.scope_ids.usage_id) - verified_skill_ids = [] - ignored_skill_ids = [] - if not self.has_verified_tags: - skills = self._fetch_skill_tags() - for skill in skills: - if skill['name'] in tags: - verified_skill_ids.append(skill['id']) - else: - ignored_skill_ids.append(skill['id']) + if not self.has_verified_tags and (verified_skills or ignored_skills): XBLOCK_SKILL_VERIFIED.send_event( xblock_info=XBlockSkillVerificationData( usage_key=usage_key, - verified_skills=verified_skill_ids, - ignored_skills=ignored_skill_ids, + verified_skills=verified_skills, + ignored_skills=ignored_skills, ) ) self.has_verified_tags = True diff --git a/skill_tagging/static/brainstorming.svg b/skill_tagging/static/brainstorming.svg new file mode 100644 index 0000000..5c1dcbe --- /dev/null +++ b/skill_tagging/static/brainstorming.svg @@ -0,0 +1 @@ + diff --git a/skill_tagging/static/tagging.css b/skill_tagging/static/tagging.css new file mode 100644 index 0000000..fde57a0 --- /dev/null +++ b/skill_tagging/static/tagging.css @@ -0,0 +1,113 @@ +.tag-verification-container { + margin-top: 20px; + background: #F2F0EF; + border-radius: 6px; + padding: 70px 65px; + display: flex; +} + +@media (max-width: 767px) { + .tag-verification-image-container { + display: none; + } +} + +.tag-verification-container h1 { + font-style: normal; + font-weight: 700; + font-size: 32px; + line-height: 28px; + margin-bottom: 20px; +} + +.tag-verification-container button { + background: #00262B; + padding: 10px 16px; + font-weight: 500; + font-size: 18px; + line-height: 24px; + color: #FFFFFF; + box-shadow: none; +} + +.tag-verification-container button:hover, .tag-verification-container button:focus { + background: #00262B; + color: #FFFFFF; + border: 1px solid #00262B; + box-shadow: none; +} + +.tag-verification-container p { + font-style: normal; + font-weight: 400; + font-size: 18px; + line-height: 28px; +} +.tag-verification-tags-container { + flex-direction: row; + align-items: center; + display: flex; + gap: 11px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.tag-verification-chip { + display: inline-block; + background-color: white; + font-size: 13px; + padding: 2px 8px; + box-sizing: border-box; + border: 1px solid #F2F0EF; + border-radius: 6px; + color: #002121; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 24px; +} + +.tag-verification-chip.tag-verification-chip-hover:hover { + background: #ccc; +} + +.tag-verification-chip-clickable { + cursor: pointer; +} + +.tag-verification-tags-container input[type=checkbox] { + position: absolute; + /* set same dimensions as the SVG */ + width: 1em; + height: 1em; + /* hide it */ + opacity: 0.00000; +} + +.tag-verification-tags-container input[type=checkbox]:checked + label { + border: 1px solid #00262B; +} + +.tag-verification-tags-container input[type=checkbox]:focus + label { + background: #ccc; +} + +.tag-verification-none-selected-container { + margin-bottom: 25px; + height: 28px; + display: flex; + align-items: center; +} + +.tag-verification-none-selected-container label { + font-weight: 400; + font-size: 14px; + line-height: 28px; + margin: 0px; +} + +.tag-verification-none-selected-container input { + margin-right: 8px; + width: 18px; + height: 18px; +} diff --git a/skill_tagging/static/tagging.html b/skill_tagging/static/tagging.html new file mode 100644 index 0000000..47cd57b --- /dev/null +++ b/skill_tagging/static/tagging.html @@ -0,0 +1,49 @@ +
+ Congratulations, you've reached the end of this unit!
+ Help future students by selecting the skills or subjects you learned from this unit.
+
Some
") + + @override_settings( + SHOW_SKILL_VERIFICATION_PROBABILITY=0, + ) + def test_pipeline_does_nothing_when_probability_set_to_zero(self, mock_get_api_client): + """ + Check that the input fragment is unchanged when there is no + configuration for a course. + """ + mock_get_api_client.return_value = self.get_mock_api_response() + _, fragment, _, _ = VerticalBlockRenderCompleted.run_filter( + block=self.block, context={}, fragment=self.original_fragement, view={} + ) + self.assertEqual(fragment.content, self.original_fragement.content) + self.assertNotIn("SKILL-0", fragment.content) + + @override_settings( + SHOW_SKILL_VERIFICATION_PROBABILITY=1, + ) + def test_pipeline_adds_edit_link_when_probability_set_to_one(self, mock_get_api_client): + """Check that verification div is added with skills.""" + mock_get_api_client.return_value = self.get_mock_api_response() + _, fragment, _, _ = VerticalBlockRenderCompleted.run_filter( + block=self.block, context={}, fragment=self.original_fragement, view={} + ) + + self.assertIn(self.original_fragement.content, fragment.content) + self.assertIn("SKILL-0", fragment.content) + self.assertIn("SKILL-5", fragment.content) diff --git a/tests/test_skill_tagging_mixin.py b/tests/test_skill_tagging_mixin.py index dcd7af9..0fbab09 100644 --- a/tests/test_skill_tagging_mixin.py +++ b/tests/test_skill_tagging_mixin.py @@ -3,83 +3,34 @@ """ from __future__ import absolute_import -import json import unittest from unittest.mock import Mock, patch -from requests.models import Response -from rest_framework import status - -from .utils import TestCaseMixin, get_tagging_mixin, make_block +from test_utils import TestCaseMixin, get_tagging_mixin @patch('skill_tagging.skill_tagging_mixin.get_api_client') -class SkillTaggingMixinTests(unittest.TestCase, TestCaseMixin): +class SkillTaggingMixinTests(TestCaseMixin, unittest.TestCase): """ Test suite for SkillTaggingMixin """ - def setUp(self): - self.block = make_block() - self.patch_workbench() - fake_user = Mock() - fake_user.opt_attrs = { - 'edx-platform.user_role': 'student', - 'edx-platform.is_authenticated': True, - } - mock_user_service = Mock() - mock_user_service.get_current_user.return_value = fake_user - self.block.runtime.service = Mock(return_value=mock_user_service) - - def _mock_response(self, status_code, content=None): - """ - Generates a python core response. - """ - mock_response = Response() - mock_response.status_code = status_code - # pylint: disable=protected-access - mock_response._content = json.dumps(content).encode('utf-8') - return mock_response - - # pylint: disable=unused-argument - def test_mixin_fields(self, mock_get_api_client): + def test_mixin_fields(self, _): """ Test for mixin field and methods """ self.tagging_mixin = get_tagging_mixin() self.assertTrue(hasattr(self.tagging_mixin, 'has_verified_tags')) - self.assertTrue(hasattr(self.tagging_mixin, 'fetch_tags')) + self.assertTrue(hasattr(self.tagging_mixin, 'fetch_skill_tags')) self.assertTrue(hasattr(self.tagging_mixin, 'verify_tags')) def test_fetch_tags(self, mock_get_api_client): """ Test that fetch_tags method works as expected """ - sample_output = { - "results": [{ - "id": 1, - "skills": [ - { - "id": 1, - "name": "SKILL-0" - }, - { - "id": 6, - "name": "SKILL-5" - }, - ], - }] - } - api_client = Mock( - get=Mock( - return_value=self._mock_response( - status.HTTP_200_OK, - sample_output - ) - ) - ) + api_client = self.get_mock_api_response() mock_get_api_client.return_value = api_client - resp = self.call_handler(self.FETCH_TAGS_HANDLER, data={}) + resp = self.block.fetch_skill_tags() assert mock_get_api_client.call_count == 1 assert api_client.get.call_count == 1 expected_response = [ @@ -89,39 +40,17 @@ def test_fetch_tags(self, mock_get_api_client): self.assertEqual(resp, expected_response) @patch('skill_tagging.skill_tagging_mixin.XBLOCK_SKILL_VERIFIED') - def test_verify_tags(self, mock_event, mock_get_api_client): + def test_verify_tags(self, mock_event, _): """ Test that verify_tags method works as expected """ - sample_output = { - "results": [{ - "id": 1, - "skills": [ - { - "id": 1, - "name": "SKILL-0" - }, - { - "id": 6, - "name": "SKILL-5" - }, - ], - }] - } - api_client = Mock( - get=Mock( - return_value=self._mock_response( - status.HTTP_200_OK, - sample_output - ) - ) - ) - mock_get_api_client.return_value = api_client mock_event.return_value = Mock(send_event=Mock()) - tags = ['SKILL-0', 'SKILL-5'] + selected_tags = [1, 6] + ignored_tags = [] self.assertFalse(self.block.has_verified_tags) - self.call_handler(self.VERIFY_TAGS_HANDLER, data=tags) - assert mock_get_api_client.call_count == 1 - assert api_client.get.call_count == 1 + self.call_handler(self.VERIFY_TAGS_HANDLER, data={ + "verified_skills": selected_tags, + "ignored_skills": ignored_tags, + }) assert mock_event.send_event.call_count == 1 self.assertTrue(self.block.has_verified_tags) diff --git a/tox.ini b/tox.ini index 3a1af48..ce71be9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,26 +1,48 @@ [tox] envlist = py38-django{32,40}, quality +[pycodestyle] +exclude = .git,.tox,migrations +max-line-length = 120 + +[isort] +line_length = 120 +known_edx = +known_django = django +known_djangoapp = model_utils +known_first_party = taxonomy +include_trailing_comma = true +multi_line_output = 3 +sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGOAPP,EDX,FIRSTPARTY,LOCALFOLDER + +[pytest] +DJANGO_SETTINGS_MODULE = test_settings +addopts = --cov skill_tagging --cov-report term-missing --cov-report xml +norecursedirs = .* docs requirements site-packages + [testenv] -whitelist_externals = +whitelist_externals = make -deps = +deps = django32: Django>=3.2,<4.0 django40: Django>=4.0,<4.1 -r{toxinidir}/requirements/test.txt -commands = +commands = make test [testenv:quality] -whitelist_externals = +whitelist_externals = make rm touch -deps = + pylint + pycodestyle + isort +deps = -r{toxinidir}/requirements/quality.txt -commands = +commands = touch tests/__init__.py pylint skill_tagging tests rm tests/__init__.py