From 91433704f4567dad544cc8d199e364865e154c35 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama <64033729+BryanttV@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:42:41 -0500 Subject: [PATCH] feat: add integration tests for users api `AP-1239` (#275) --- .github/workflows/integration-test.yml | 14 +- CHANGELOG.md | 15 + Makefile | 5 +- eox_core/__init__.py | 2 +- .../v1/tests/integration}/__init__.py | 0 .../v1/tests/integration}/conftest.py | 0 .../api/v1/tests/integration/test_views.py | 544 ++++++++++++++++++ .../tests/integration/__init__.py | 0 .../tests/integration/test_backends.py | 21 + eox_core/settings/test.py | 9 + eox_core/tests/tutor/integration.sh | 13 - .../tests/tutor/integration_test_tutor.py | 48 -- eox_core/tests/tutor/pytest.ini | 7 - eox_core/urls.py | 4 +- fixtures/initial_data.json | 137 +++++ requirements/base.txt | 65 ++- requirements/django42.txt | 2 +- requirements/pip-tools.txt | 12 +- requirements/test.in | 1 + requirements/test.txt | 80 ++- requirements/tox.txt | 10 +- scripts/execute_integration_tests.sh | 2 + setup.cfg | 4 +- 23 files changed, 827 insertions(+), 168 deletions(-) rename eox_core/{tests/tutor => api/v1/tests/integration}/__init__.py (100%) rename eox_core/{tests/tutor => api/v1/tests/integration}/conftest.py (100%) create mode 100644 eox_core/api/v1/tests/integration/test_views.py create mode 100644 eox_core/edxapp_wrapper/tests/integration/__init__.py create mode 100644 eox_core/edxapp_wrapper/tests/integration/test_backends.py delete mode 100644 eox_core/tests/tutor/integration.sh delete mode 100644 eox_core/tests/tutor/integration_test_tutor.py delete mode 100644 eox_core/tests/tutor/pytest.ini create mode 100644 fixtures/initial_data.json create mode 100644 scripts/execute_integration_tests.sh diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 404bef326..f2e5ad859 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -7,13 +7,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - tutor_version: ["<17.0.0", "<18.0.0", "<19.0.0"] + tutor_version: ['<18.0.0', '<19.0.0'] steps: - - uses: actions/checkout@v4 - with: - path: eox-core - - uses: eduNEXT/integration-test-in-tutor@main + - name: Run Integration Tests + uses: eduNEXT/integration-test-in-tutor@mjh/run-integration-tests-outside-container with: tutor_version: ${{ matrix.tutor_version }} - app_name: "eox-core" - shell_file_to_run: "eox_core/tests/tutor/integration.sh" + app_name: 'eox-core' + openedx_extra_pip_requeriments: 'eox-tenant' + shell_file_to_run: 'scripts/execute_integration_tests.sh' + fixtures_file: 'fixtures/initial_data.json' diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9e1b9c3..dfd95b679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,21 @@ Please do not update the unreleased notes. +## [v10.6.0](https://github.com/eduNEXT/eox-core/compare/v10.5.1...v10.6.0) - (2024-09-17) + +### Added + +- **Integration Tests**: A new set of integration tests was added to validate + the Users API. These tests ensure the correct behavior of endpoints + interacting with Open edX components. + +### Changed + +- **Unit Tests**: The unit tests were moved to a new directory, `/unit`, to + separate them from the integration tests. This change aims to improve the + organization of the tests and make it easier to identify the different types + of tests. + ## [v10.5.1](https://github.com/eduNEXT/eox-core/compare/v10.5.0...v10.5.1) - (2024-07-19) ### Fixed diff --git a/Makefile b/Makefile index d9d2cb606..aafdbd991 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ clean: ## Remove generated byte code, coverage reports, and build artifacts python-test: clean ## Run tests $(TOX) pip install -r requirements/test.txt --exists-action w - $(TOX) coverage run --source="." -m pytest ./eox_core + $(TOX) coverage run --source="." -m pytest ./eox_core --ignore-glob='**/integration/*' $(TOX) coverage report --fail-under=70 python-quality-test: @@ -49,6 +49,9 @@ python-quality-test: run-tests: python-test python-quality-test +run-integration-tests: install-dev-dependencies + pytest ./eox_core --ignore-glob='**/unit/*' --ignore-glob='**/edxapp_wrapper/*' + upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade upgrade: ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in pip install -qr requirements/pip-tools.txt diff --git a/eox_core/__init__.py b/eox_core/__init__.py index f935bd618..ac708996f 100644 --- a/eox_core/__init__.py +++ b/eox_core/__init__.py @@ -1,4 +1,4 @@ """ Init for main eox-core app """ -__version__ = '10.5.1' +__version__ = '10.6.0' diff --git a/eox_core/tests/tutor/__init__.py b/eox_core/api/v1/tests/integration/__init__.py similarity index 100% rename from eox_core/tests/tutor/__init__.py rename to eox_core/api/v1/tests/integration/__init__.py diff --git a/eox_core/tests/tutor/conftest.py b/eox_core/api/v1/tests/integration/conftest.py similarity index 100% rename from eox_core/tests/tutor/conftest.py rename to eox_core/api/v1/tests/integration/conftest.py diff --git a/eox_core/api/v1/tests/integration/test_views.py b/eox_core/api/v1/tests/integration/test_views.py new file mode 100644 index 000000000..905a19599 --- /dev/null +++ b/eox_core/api/v1/tests/integration/test_views.py @@ -0,0 +1,544 @@ +""" +Integration test suite for the API v1 views. +""" + +from __future__ import annotations + +import requests +from ddt import data as ddt_data +from ddt import ddt, unpack +from django.conf import settings as ds +from django.test import TestCase +from django.urls import reverse +from rest_framework import status + +settings = ds.INTEGRATION_TEST_SETTINGS + +FAKE_USER_DATA = iter( + [ + { + "username": "athickpenny0", + "email": "athickpenny0@indiegogo.com", + "fullname": "Antoni Thickpenny", + "password": "jD3_u)67VfHce", + "activate_user": True, + "mailing_address": "70736 Haas Parkway", + "year_of_birth": 1996, + "gender": "f", + "level_of_education": "p", + "city": "Solikamsk", + "goals": "Maecenas leo odio, condimentum id", + }, + { + "username": "smenchenton1", + "email": "smenchenton1@networksolutions.com", + "fullname": "Sonia Menchenton", + "password": "hO9\\Pni)", + "activate_user": True, + "mailing_address": "7543 Eagle Crest Terrace", + "year_of_birth": 1998, + "gender": "m", + "level_of_education": "m", + "city": "Diourbel", + "goals": "Sed sagittis.", + }, + { + "username": "arealff2", + "email": "arealff2@phpbb.com", + "fullname": "Alexis Realff", + "password": 'zY2(yq!(>4"_', + "activate_user": True, + "mailing_address": "0 Grim Drive", + "year_of_birth": 2005, + "gender": "f", + "level_of_education": "b", + "city": "Gongyi", + "goals": "Proin interdum mauris non ligula pellentesque ultrices.", + }, + { + "username": "ddilon3", + "email": "ddilon3@geocities.com", + "fullname": "Dotty Dilon", + "password": 'mV9"3zRdRr#bTP', + "activate_user": True, + "mailing_address": "6859 Lerdahl Road", + "year_of_birth": 2002, + "gender": "m", + "level_of_education": "a", + "city": "La Curva", + "goals": "Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl.", + }, + { + "username": "lties4", + "email": "lties4@addthis.com", + "fullname": "Loreen Ties", + "password": 'iU6@R"`/t3>/DT', + "activate_user": True, + "mailing_address": "64 Clemons Terrace", + "year_of_birth": 1994, + "gender": "f", + "level_of_education": "hs", + "city": "Xianyang", + "goals": "In est risus, auctor sed, tristique in, tempus sit amet, sem.", + }, + { + "username": "lbattisson5", + "email": "lbattisson5@360.cn", + "fullname": "Lynett Battisson", + "password": "oB3?1JWFM\\=S>", + "activate_user": True, + "mailing_address": "89 Vermont Pass", + "year_of_birth": 2000, + "gender": "m", + "level_of_education": "jhs", + "city": "Lyudinovo", + "goals": "Phasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.", + }, + { + "username": "jniset6", + "email": "jniset6@virginia.edu", + "fullname": "Jacquelin Niset", + "password": "aC2/anhtj", + "activate_user": True, + "mailing_address": "83984 8th Pass", + "year_of_birth": 2003, + "gender": "f", + "level_of_education": "el", + "city": "Erie", + "goals": "Donec quis orci eget orci vehicula condimentum.", + }, + { + "username": "mdoumenc7", + "email": "mdoumenc7@chicagotribune.com", + "fullname": "Martin Doumenc", + "password": "dQ9't{0(", + "activate_user": True, + "mailing_address": "8 Scoville Plaza", + "year_of_birth": 1993, + "gender": "f", + "level_of_education": "none", + "city": "Boaco", + "goals": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae.", + }, + { + "username": "lchampagne8", + "email": "lchampagne8@buzzfeed.com", + "fullname": "Locke Champagne", + "password": "tW7_SYC1qUf!K'0C", + "activate_user": True, + "mailing_address": "79 Milwaukee Place", + "year_of_birth": 1996, + "gender": "f", + "level_of_education": "other", + "city": "Linjiang", + "goals": "In hac habitasse platea dictumst.", + }, + { + "username": "zczyz9", + "email": "zczyz9@gravatar.com", + "fullname": "Zolly Czyz", + "password": "jN3~/&1ZMG%", + "activate_user": True, + "mailing_address": "5 Victoria Place", + "year_of_birth": 2000, + "gender": "f", + "level_of_education": "p", + "city": "'s-Hertogenbosch", + "goals": "Suspendisse potenti.", + }, + { + "username": "athickpenny0", + "email": "athickpenny0@indiegogo.com", + "fullname": "Antoni Thickpenny", + "password": "jD3_u)67VfHce", + "activate_user": False, + "mailing_address": "70736 Haas Parkway", + "year_of_birth": 1996, + "gender": "f", + "level_of_education": "m", + "city": "Solikamsk", + "goals": "Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo.", + }, + ] +) + + +class BaseAPIIntegrationTest(TestCase): + """ + Base class for the integration test suite. + """ + + def setUp(self): + """ + Set up the test suite. + """ + self.default_site = self.get_tenant_data() + self.tenant_x = self.get_tenant_data("tenant-x") + self.tenant_y = self.get_tenant_data("tenant-y") + + def get_tenant_data(self, prefix: str = "") -> dict: + """ + Get the tenant data. + + If no prefix is provided, the default site data is returned. + + Args: + prefix (str): The tenant prefix. + + Returns: + dict: The tenant data. + """ + domain = f"{prefix}.{settings['LMS_BASE']}" if prefix else settings["LMS_BASE"] + return { + "base_url": f"http://{domain}", + "domain": domain, + } + + def get_access_token(self, tenant_base_url: str) -> str: + """ + Get an access token for a tenant. + + Args: + tenant_base_url (str): The tenant base URL. + + Returns: + str: The access token. + """ + data = { + "client_id": settings["CLIENT_ID"], + "client_secret": settings["CLIENT_SECRET"], + "grant_type": "client_credentials", + } + url = f"{tenant_base_url}/oauth2/access_token/" + response = requests.post(url, data=data, timeout=settings["API_TIMEOUT"]) + return response.json()["access_token"] + + # pylint: disable=too-many-arguments + def make_request( + self, + tenant: dict, + method: str, + url: str, + json: dict | None = None, + data: dict | None = None, + params: dict | None = None, + with_auth: bool = True, + ) -> requests.Response: + """ + Make a request to a tenant. + + Args: + tenant (dict): The tenant data. + method (str): The HTTP method ('GET', 'POST', etc.). + url (str): The URL to make the request to. + json (dict, optional): The JSON data for POST, PATCH and PUT requests. + data (dict, optional): The data for POST, PATCH and PUT requests. + params (dict, optional): The parameters for GET and DELETE requests. + with_auth (bool, optional): Whether to include the access token in the request headers. + + Returns: + requests.Response: The response object. + """ + headers = {"Host": tenant["domain"]} + if with_auth: + access_token = self.get_access_token(tenant["base_url"]) + headers["Authorization"] = f"Bearer {access_token}" + full_url = f"{tenant['base_url']}/{url}" + + method = method.upper() + if method not in ("GET", "POST", "PATCH"): + raise ValueError(f"Unsupported HTTP method: {method}") + + return requests.request( + method, + full_url, + json=json, + data=data, + params=params, + headers=headers, + timeout=settings["API_TIMEOUT"], + ) + + +@ddt +class TestUsersAPIIntegration(BaseAPIIntegrationTest): + """Integration test suite for the Users API""" + + def setUp(self): + """Set up the test suite""" + self.user_url = f"{settings['EOX_CORE_API_BASE']}{reverse('eox-api:eox-api:edxapp-user')}" + self.user_updater_url = f"{settings['EOX_CORE_API_BASE']}{reverse('eox-api:eox-api:edxapp-user-updater')}" + super().setUp() + + def create_user_in_tenant(self, tenant: dict, user_data: dict) -> requests.Response: + """ + Create a new user in a tenant. + + Args: + tenant (dict): The tenant data. + user_data (dict): The user data. + + Returns: + requests.Response: The response object. + """ + return self.make_request(tenant, "POST", url=self.user_url, data=user_data) + + def get_user_in_tenant(self, tenant: dict, params: dict | None = None) -> requests.Response: + """ + Get a user in a tenant by username or email. + + Args: + tenant (dict): The tenant data. + params (dict, optional): The query parameters for the request. + + Returns: + requests.Response: The response object. + """ + return self.make_request(tenant, "GET", url=self.user_url, params=params) + + def update_user_in_tenant(self, tenant: dict, user_data: dict) -> requests.Response: + """ + Update a user in a tenant. + + Args: + tenant (dict): The tenant data. + user_data (dict): The user data. + + Returns: + requests.Response: The response object. + """ + return self.make_request(tenant, "PATCH", url=self.user_updater_url, json=user_data) + + @ddt_data( + {"is_staff": False, "is_superuser": False}, + {"is_staff": True, "is_superuser": False}, + {"is_staff": False, "is_superuser": True}, + {"is_staff": True, "is_superuser": True}, + ) + def test_create_user_in_tenant_success(self, permissions: dict) -> None: + """ + Test creating a user in a tenant. + + Open edX definitions tested: + - `create_edxapp_user` + - `check_edxapp_account_conflicts` + + Expected result: + - The status code is 200. + - The user is created successfully in the tenant with the provided data. + """ + data = next(FAKE_USER_DATA) + data.update(permissions) + + response = self.create_user_in_tenant(self.tenant_x, data) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response_data["email"], data["email"]) + self.assertEqual(response_data["username"], data["username"]) + self.assertTrue(response_data["is_active"]) + self.assertFalse(response_data["is_staff"]) + self.assertFalse(response_data["is_superuser"]) + + def test_create_user_missing_required_fields(self) -> None: + """ + Test creating a user in a tenant with invalid data. + + Open edX definitions tested: + - `check_edxapp_account_conflicts` + + Expected result: + - The status code is 400. + - The response contains the missing fields. + - The user is not created in the tenant. + """ + data = next(FAKE_USER_DATA) + del data["email"] + del data["username"] + + response = self.create_user_in_tenant(self.tenant_x, data) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("email", response_data) + self.assertIn("username", response_data) + + def test_create_user_in_tenant_user_already_exists(self) -> None: + """ + Test creating a user in a tenant that already exists. + + Open edX definitions tested: + - `check_edxapp_account_conflicts` + + Expected result: + - The status code is 400. + - The response contains an error message. + - The user is not created in the tenant. + """ + data = next(FAKE_USER_DATA) + self.create_user_in_tenant(self.tenant_x, data) + + response = self.create_user_in_tenant(self.tenant_x, data) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("non_field_errors", response_data) + + @ddt_data("username", "email") + def test_get_user_in_tenant_success(self, query_param: str) -> None: + """ + Test getting a user in a tenant. + + Open edX definitions tested: + - `get_edxapp_user` + + Expected result: + - The status code is 200. + - The response contains the user data. + """ + data = next(FAKE_USER_DATA) + self.create_user_in_tenant(self.tenant_x, data) + + response = self.get_user_in_tenant(self.tenant_x, {query_param: data[query_param]}) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response_data[query_param], data[query_param]) + + def test_get_user_of_another_tenant(self) -> None: + """ + Test getting a user that belongs to another tenant. + + Open edX definitions tested: + - `get_edxapp_user` + + Expected result: + - The status code is 404. + - The response contains an error message. + """ + data = next(FAKE_USER_DATA) + self.create_user_in_tenant(self.tenant_x, data) + + response = self.get_user_in_tenant(self.tenant_y, {"username": data["username"]}) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertIn("detail", response_data) + self.assertEqual( + response_data["detail"], + f"No user found by {{'username': '{data['username']}'}} on site {self.tenant_y['domain']}.", + ) + + @ddt_data( + ("username", "user-not-found"), + ("email", "user-not-found@mail.com"), + ) + @unpack + def test_get_user_in_tenant_user_not_found(self, param: str, value: str) -> None: + """ + Test getting a user in a tenant that does not exist. + + Open edX definitions tested: + - `get_edxapp_user` + + Expected result: + - The status code is 404. + - The response contains an error message. + """ + response = self.get_user_in_tenant(self.tenant_x, {param: value}) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertIn("detail", response_data) + self.assertEqual( + response_data["detail"], + f"No user found by {{'{param}': '{value}'}} on site {self.tenant_x['domain']}.", + ) + + def test_update_user_in_tenant_success(self) -> None: + """ + Test updating a user in a tenant. + + Open edX definitions tested: + - `get_edxapp_user` + - `get_user_profile` + - `check_edxapp_account_conflicts` + - `get_user_read_only_serializer` + + Expected result: + - The status code is 200. + - The user is updated successfully in the tenant with the provided data. + """ + data = next(FAKE_USER_DATA) + self.create_user_in_tenant(self.tenant_x, data) + updated_data = next(FAKE_USER_DATA) + updated_data["username"] = data["username"] + updated_data["email"] = data["email"] + + response = self.update_user_in_tenant(self.tenant_x, user_data=updated_data) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response_data["username"], data["username"]) + self.assertEqual(response_data["email"], data["email"]) + self.assertEqual(response_data["name"], updated_data["fullname"]) + self.assertEqual(response_data["mailing_address"], updated_data["mailing_address"]) + self.assertEqual(response_data["year_of_birth"], updated_data["year_of_birth"]) + self.assertEqual(response_data["gender"], updated_data["gender"]) + self.assertEqual(response_data["level_of_education"], updated_data["level_of_education"]) + self.assertEqual(response_data["goals"], updated_data["goals"]) + self.assertTrue(response_data["is_active"]) + + @ddt_data( + ("username", "user-not-found"), + ("email", "user-not-found@mail.com"), + ) + @unpack + def test_update_user_in_tenant_user_not_found(self, param: str, value: str) -> None: + """ + Test updating a user in a tenant that does not exist. + + Open edX definitions tested: + - `get_edxapp_user` + + Expected result: + - The status code is 404. + - The response contains an error message. + """ + response = self.update_user_in_tenant(self.tenant_x, {param: value}) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertIn("detail", response_data) + self.assertEqual( + response_data["detail"], + f"No user found by {{'{param}': '{value}'}} on site {self.tenant_x['domain']}.", + ) + + +class TestInfoView(BaseAPIIntegrationTest): + """ + Integration test suite for the info view. + """ + + def setUp(self): + """ + Set up the test suite. + """ + self.url = f"{settings['EOX_CORE_API_BASE']}{reverse('eox-info')}" + super().setUp() + + def test_info_view_success(self) -> None: + """Test the info view. + + Expected result: + - The status code is 200. + - The response contains the version, name and git commit hash. + """ + response = self.make_request(self.default_site, "GET", url=self.url, with_auth=False) + + response_data = response.json() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("version", response_data) + self.assertIn("name", response_data) + self.assertIn("git", response_data) diff --git a/eox_core/edxapp_wrapper/tests/integration/__init__.py b/eox_core/edxapp_wrapper/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/eox_core/edxapp_wrapper/tests/integration/test_backends.py b/eox_core/edxapp_wrapper/tests/integration/test_backends.py new file mode 100644 index 000000000..73c1b10fe --- /dev/null +++ b/eox_core/edxapp_wrapper/tests/integration/test_backends.py @@ -0,0 +1,21 @@ +""" +This module tests the backends of the edxapp_wrapper +""" + + +# pylint: disable=import-outside-toplevel,unused-import +def test_current_settings_code_imports(): + """ + Running this imports means that our backends import the right signature + """ + # isort:skip + import eox_core.edxapp_wrapper.backends.bearer_authentication_j_v1 + import eox_core.edxapp_wrapper.backends.certificates_m_v1 + import eox_core.edxapp_wrapper.backends.comments_service_users_j_v1 + import eox_core.edxapp_wrapper.backends.configuration_helpers_h_v1 + import eox_core.edxapp_wrapper.backends.coursekey_m_v1 + import eox_core.edxapp_wrapper.backends.edxfuture_o_v1 + import eox_core.edxapp_wrapper.backends.grades_h_v1 + import eox_core.edxapp_wrapper.backends.pre_enrollment_l_v1 + import eox_core.edxapp_wrapper.backends.storages_i_v1 + import eox_core.edxapp_wrapper.backends.third_party_auth_l_v1 diff --git a/eox_core/settings/test.py b/eox_core/settings/test.py index 8accd5e53..2b3d296cf 100644 --- a/eox_core/settings/test.py +++ b/eox_core/settings/test.py @@ -104,3 +104,12 @@ def plugin_settings(settings): # pylint: disable=function-redefined }, 'DEFAULT': 'software_secure', } + +# Integration test settings +INTEGRATION_TEST_SETTINGS = { + "EOX_CORE_API_BASE": "eox-core", + "LMS_BASE": "local.edly.io", + "API_TIMEOUT": 5, + "CLIENT_ID": "client_id", + "CLIENT_SECRET": "client_secret", +} diff --git a/eox_core/tests/tutor/integration.sh b/eox_core/tests/tutor/integration.sh deleted file mode 100644 index c3e366ebc..000000000 --- a/eox_core/tests/tutor/integration.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# This script installs the package in the edxapp environment, installs test requirements from Open edX and runs the tests using the Tutor settings. -echo "Install package" -pip install -e /openedx/eox-core -python manage.py lms makemigrations -python manage.py lms migrate - -echo "Install test-requirements" -make test-requirements - -echo "Run tests" -pytest -s --ds=lms.envs.tutor.test /openedx/eox-core/eox_core/tests/tutor diff --git a/eox_core/tests/tutor/integration_test_tutor.py b/eox_core/tests/tutor/integration_test_tutor.py deleted file mode 100644 index c6c7fe49f..000000000 --- a/eox_core/tests/tutor/integration_test_tutor.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Test integration file. -""" -from django.test import TestCase, override_settings - - -@override_settings(ALLOWED_HOSTS=['testserver'], SITE_ID=2) -class TutorIntegrationTestCase(TestCase): - """ - Tests integration with openedx - """ - - def setUp(self): - """ - Set up the base URL for the tests - """ - self.base_url = 'http://local.edly.io' - - # pylint: disable=import-outside-toplevel,unused-import - def test_current_settings_code_imports(self): - """ - Running this imports means that our backends import the right signature - """ - import eox_core.edxapp_wrapper.backends.bearer_authentication_j_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.certificates_m_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.comments_service_users_j_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.configuration_helpers_h_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.coursekey_m_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.edxfuture_o_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.grades_h_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.pre_enrollment_l_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.storages_i_v1 # isort:skip - import eox_core.edxapp_wrapper.backends.third_party_auth_l_v1 # isort:skip - - def test_info_view(self): - """ - Tests the info view endpoint in Tutor - """ - info_view_url = f'{self.base_url}/eox-core/eox-info' - - response = self.client.get(info_view_url) - - self.assertEqual(response.status_code, 200) - - response_data = response.json() - self.assertIn('version', response_data) - self.assertIn('name', response_data) - self.assertIn('git', response_data) diff --git a/eox_core/tests/tutor/pytest.ini b/eox_core/tests/tutor/pytest.ini deleted file mode 100644 index f3b746245..000000000 --- a/eox_core/tests/tutor/pytest.ini +++ /dev/null @@ -1,7 +0,0 @@ -[pytest] -python_files = integration_test_*.py -filterwarnings = - default - # We ignore every warning while we actually get the testing infrastructure - # running for different version of tutor in gh actions - ignore: diff --git a/eox_core/urls.py b/eox_core/urls.py index c6df9702d..793e13c3f 100644 --- a/eox_core/urls.py +++ b/eox_core/urls.py @@ -7,8 +7,8 @@ app_name = 'eox_core' # pylint: disable=invalid-name -urlpatterns = [ # pylint: disable=invalid-name - re_path(r'^eox-info$', views.info_view), +urlpatterns = [ + re_path(r'^eox-info$', views.info_view, name='eox-info'), re_path(r'^api/', include('eox_core.api.urls', namespace='eox-api')), re_path(r'^data-api/', include('eox_core.api.data.v1.urls', namespace='eox-data-api')), re_path(r'^api-docs/$', docs_ui_view, name='apidocs-ui'), diff --git a/fixtures/initial_data.json b/fixtures/initial_data.json new file mode 100644 index 000000000..d4d13f606 --- /dev/null +++ b/fixtures/initial_data.json @@ -0,0 +1,137 @@ +[ + { + "model": "sites.site", + "pk": 8, + "fields": { + "domain": "tenant-x.local.edly.io", + "name": "Tenant X" + } + }, + { + "model": "sites.site", + "pk": 9, + "fields": { + "domain": "tenant-y.local.edly.io", + "name": "Tenant Y" + } + }, + { + "model": "eox_tenant.route", + "pk": 1, + "fields": { + "domain": "tenant-x.local.edly.io", + "config": 1 + } + }, + { + "model": "eox_tenant.route", + "pk": 2, + "fields": { + "domain": "tenant-y.local.edly.io", + "config": 2 + } + }, + { + "model": "eox_tenant.tenantorganization", + "pk": 1, + "fields": { + "name": "TenantX" + } + }, + { + "model": "eox_tenant.tenantorganization", + "pk": 2, + "fields": { + "name": "TenantY" + } + }, + { + "model": "organizations.organization", + "pk": 1, + "fields": { + "created": "2024-09-11T17:15:30.708Z", + "modified": "2024-09-11T17:15:30.708Z", + "name": "TenantX", + "short_name": "Tenant-x", + "description": null, + "logo": "", + "active": true + } + }, + { + "model": "organizations.organization", + "pk": 2, + "fields": { + "created": "2024-09-11T17:15:35.694Z", + "modified": "2024-09-11T17:15:35.694Z", + "name": "TenantY", + "short_name": "Tenant-y", + "description": null, + "logo": "", + "active": true + } + }, + { + "model": "eox_tenant.tenantconfig", + "pk": 1, + "fields": { + "external_key": "tenant-x-key", + "lms_configs": "{\n \"EDNX_USE_SIGNAL\": true,\n \"PLATFORM_NAME\": \"Tenant X\",\n \"SITE_NAME\": \"tenant-x.local.edly.io\",\n \"course_org_filter\": [\n \"TenantX\"\n ]\n}", + "studio_configs": "{}", + "theming_configs": "{}", + "meta": "{}", + "organizations": [ + 1 + ] + } + }, + { + "model": "eox_tenant.tenantconfig", + "pk": 2, + "fields": { + "external_key": "tenant-y-key", + "lms_configs": "{\n \"EDNX_USE_SIGNAL\": true,\n \"PLATFORM_NAME\": \"Tenant Y\",\n \"SITE_NAME\": \"tenant-y.local.edly.io\",\n \"course_org_filter\": [\n \"TenantY\"\n ]\n}", + "studio_configs": "{}", + "theming_configs": "{}", + "meta": "{}", + "organizations": [ + 2 + ] + } + }, + { + "model": "auth.user", + "pk": 4, + "fields": { + "password": "pbkdf2_sha256$600000$6PZlT8H5P7ouzl2ozvZ5r7$2MKoXKlQux0KrN40OVtDnEiKawspCudlCKj1m0WnsIM=", + "last_login": "2024-09-03T21:55:08.123Z", + "is_superuser": true, + "username": "admin-eox-core", + "first_name": "", + "last_name": "", + "email": "admin-eox-core@mail.com", + "is_staff": true, + "is_active": true, + "date_joined": "2024-08-30T20:33:17.023Z", + "groups": [], + "user_permissions": [] + } + }, + { + "model": "oauth2_provider.application", + "pk": 4, + "fields": { + "client_id": "client_id", + "user": 4, + "redirect_uris": "http://local.edly.io/\r\nhttp://tenant-x.local.edly.io/\r\nhttp://tenant-y.local.edly.io/", + "client_type": "confidential", + "authorization_grant_type": "client-credentials", + "client_secret": "client_secret", + "name": "eox-core-app", + "skip_authorization": false, + "created": "2024-09-06T13:30:17.347Z", + "updated": "2024-09-06T13:30:17.347Z", + "algorithm": "" + } + } +] diff --git a/requirements/base.txt b/requirements/base.txt index 03a79cc59..33ddb4447 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -10,7 +10,7 @@ appdirs==1.4.4 # via fs asgiref==3.8.1 # via django -attrs==23.2.0 +attrs==24.2.0 # via openedx-events backports-zoneinfo[tzdata]==0.2.1 ; python_version < "3.9" # via @@ -25,9 +25,9 @@ celery==5.4.0 # via # -r requirements/base.in # event-tracking -certifi==2024.7.4 +certifi==2024.8.30 # via requests -cffi==1.16.0 +cffi==1.17.1 # via # cryptography # pynacl @@ -50,7 +50,7 @@ click-repl==0.3.0 # via celery code-annotations==1.8.0 # via edx-toggles -cryptography==42.0.8 +cryptography==43.0.1 # via # jwcrypto # pyjwt @@ -59,7 +59,7 @@ defusedxml==0.8.0rc2 # via # python3-openid # social-auth-core -django==4.2.13 +django==4.2.16 # via # -c https://raw.githubusercontent.com/openedx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.in @@ -91,7 +91,7 @@ django-filter==23.5 # -r requirements/base.in django-ipware==7.0.1 # via edx-proctoring -django-model-utils==4.5.1 +django-model-utils==5.0.0 # via # edx-proctoring # edx-when @@ -112,7 +112,7 @@ django-waffle==4.1.0 # edx-drf-extensions # edx-proctoring # edx-toggles -django-webpack-loader==3.1.0 +django-webpack-loader==3.1.1 # via edx-proctoring djangorestframework==3.15.1 # via @@ -127,11 +127,11 @@ drf-jwt==1.19.2 # via edx-drf-extensions drf-yasg==1.21.7 # via edx-api-doc-tools -edx-api-doc-tools==1.8.0 +edx-api-doc-tools==2.0.0 # via -r requirements/base.in edx-ccx-keys==1.3.0 # via openedx-events -edx-django-utils==5.14.2 +edx-django-utils==5.15.0 # via # edx-drf-extensions # edx-rest-api-client @@ -139,12 +139,12 @@ edx-django-utils==5.14.2 # edx-when # event-tracking # openedx-events -edx-drf-extensions==10.3.0 +edx-drf-extensions==10.4.0 # via # -r requirements/base.in # edx-proctoring # edx-when -edx-opaque-keys[django]==2.10.0 +edx-opaque-keys[django]==2.11.0 # via # -r requirements/base.in # edx-ccx-keys @@ -154,19 +154,21 @@ edx-opaque-keys[django]==2.10.0 # openedx-events edx-proctoring==4.18.1 # via -r requirements/base.in -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via edx-proctoring edx-toggles==5.2.0 # via event-tracking edx-when==2.5.0 # via edx-proctoring event-tracking==2.4.0 - # via edx-proctoring -fastavro==1.9.4 + # via + # -c https://raw.githubusercontent.com/openedx/edx-lint/master/edx_lint/files/common_constraints.txt + # edx-proctoring +fastavro==1.9.7 # via openedx-events fs==2.4.16 # via xblock -idna==3.7 +idna==3.10 # via requests inflection==0.5.1 # via drf-yasg @@ -176,9 +178,9 @@ jsonfield==3.1.0 # via edx-proctoring jwcrypto==1.5.6 # via django-oauth-toolkit -kombu==5.3.7 +kombu==5.4.1 # via celery -lxml==5.2.2 +lxml==5.3.0 # via xblock mako==1.3.5 # via xblock @@ -187,20 +189,20 @@ markupsafe==2.1.5 # jinja2 # mako # xblock -newrelic==9.11.0 +newrelic==9.13.0 # via edx-django-utils oauthlib==3.2.2 # via # django-oauth-toolkit # requests-oauthlib # social-auth-core -openedx-events==9.11.0 +openedx-events==9.14.1 # via # -r requirements/base.in # event-tracking packaging==24.1 # via drf-yasg -pbr==6.0.0 +pbr==6.1.0 # via stevedore prompt-toolkit==3.0.47 # via click-repl @@ -210,7 +212,7 @@ pycparser==2.22 # via cffi pycryptodomex==3.20.0 # via edx-proctoring -pyjwt[crypto]==2.8.0 +pyjwt[crypto]==2.9.0 # via # drf-jwt # edx-drf-extensions @@ -234,13 +236,13 @@ python-slugify==8.0.4 # via code-annotations python3-openid==3.2.0 # via social-auth-core -pytz==2024.1 +pytz==2024.2 # via # drf-yasg # edx-proctoring # event-tracking # xblock -pyyaml==6.0.1 +pyyaml==6.0.2 # via # code-annotations # drf-yasg @@ -251,17 +253,16 @@ requests==2.32.3 # edx-drf-extensions # edx-rest-api-client # requests-oauthlib - # slumber # social-auth-core requests-oauthlib==2.0.0 # via social-auth-core -rules==3.4 +rules==3.5 # via edx-proctoring semantic-version==2.10.0 # via edx-drf-extensions shortuuid==1.0.13 # via django-oauth2-provider -simplejson==3.19.2 +simplejson==3.19.3 # via xblock six==1.16.0 # via @@ -270,13 +271,11 @@ six==1.16.0 # event-tracking # fs # python-dateutil -slumber==0.7.1 - # via edx-rest-api-client social-auth-core==4.5.4 # via -r requirements/base.in -sqlparse==0.5.0 +sqlparse==0.5.1 # via django -stevedore==5.2.0 +stevedore==5.3.0 # via # code-annotations # edx-django-utils @@ -295,7 +294,7 @@ tzdata==2024.1 # celery uritemplate==4.1.1 # via drf-yasg -urllib3==2.2.2 +urllib3==2.2.3 # via requests vine==5.1.0 # via @@ -306,9 +305,9 @@ wcwidth==0.2.13 # via prompt-toolkit web-fragments==2.2.0 # via xblock -webob==1.8.7 +webob==1.8.8 # via xblock -xblock==4.0.1 +xblock==5.1.0 # via edx-when # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/django42.txt b/requirements/django42.txt index 87c017cd9..64aaf996f 100644 --- a/requirements/django42.txt +++ b/requirements/django42.txt @@ -1 +1 @@ -Django==4.2.13 +django==4.2.16 diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index 69e02c906..ed1314ea9 100644 --- a/requirements/pip-tools.txt +++ b/requirements/pip-tools.txt @@ -4,14 +4,12 @@ # # make upgrade # -build==1.2.1 +build==1.2.2 # via pip-tools click==8.1.7 # via pip-tools -importlib-metadata==6.11.0 - # via - # -c https://raw.githubusercontent.com/openedx/edx-lint/master/edx_lint/files/common_constraints.txt - # build +importlib-metadata==8.5.0 + # via build packaging==24.1 # via build pip-tools==7.4.1 @@ -24,9 +22,9 @@ tomli==2.0.1 # via # build # pip-tools -wheel==0.43.0 +wheel==0.44.0 # via pip-tools -zipp==3.19.2 +zipp==3.20.2 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/test.in b/requirements/test.in index 0d6433ae2..2d3a4954d 100644 --- a/requirements/test.in +++ b/requirements/test.in @@ -11,3 +11,4 @@ pytest-django testfixtures django-countries pyyaml +ddt diff --git a/requirements/test.txt b/requirements/test.txt index 9897e68f5..10bab3f44 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -19,7 +19,7 @@ asgiref==3.8.1 # django-countries astroid==2.15.8 # via pylint -attrs==23.2.0 +attrs==24.2.0 # via # -r requirements/base.txt # openedx-events @@ -39,11 +39,11 @@ celery==5.4.0 # via # -r requirements/base.txt # event-tracking -certifi==2024.7.4 +certifi==2024.8.30 # via # -r requirements/base.txt # requests -cffi==1.16.0 +cffi==1.17.1 # via # -r requirements/base.txt # cryptography @@ -77,14 +77,16 @@ code-annotations==1.8.0 # via # -r requirements/base.txt # edx-toggles -coverage==7.5.4 +coverage==7.6.1 # via -r requirements/test.in -cryptography==42.0.8 +cryptography==43.0.1 # via # -r requirements/base.txt # jwcrypto # pyjwt # social-auth-core +ddt==1.7.2 + # via -r requirements/test.in defusedxml==0.8.0rc2 # via # -r requirements/base.txt @@ -128,7 +130,7 @@ django-ipware==7.0.1 # via # -r requirements/base.txt # edx-proctoring -django-model-utils==4.5.1 +django-model-utils==5.0.0 # via # -r requirements/base.txt # edx-proctoring @@ -151,7 +153,7 @@ django-waffle==4.1.0 # edx-drf-extensions # edx-proctoring # edx-toggles -django-webpack-loader==3.1.0 +django-webpack-loader==3.1.1 # via # -r requirements/base.txt # edx-proctoring @@ -172,13 +174,13 @@ drf-yasg==1.21.7 # via # -r requirements/base.txt # edx-api-doc-tools -edx-api-doc-tools==1.8.0 +edx-api-doc-tools==2.0.0 # via -r requirements/base.txt edx-ccx-keys==1.3.0 # via # -r requirements/base.txt # openedx-events -edx-django-utils==5.14.2 +edx-django-utils==5.15.0 # via # -r requirements/base.txt # edx-drf-extensions @@ -187,12 +189,12 @@ edx-django-utils==5.14.2 # edx-when # event-tracking # openedx-events -edx-drf-extensions==10.3.0 +edx-drf-extensions==10.4.0 # via # -r requirements/base.txt # edx-proctoring # edx-when -edx-opaque-keys[django]==2.10.0 +edx-opaque-keys[django]==2.11.0 # via # -r requirements/base.txt # edx-ccx-keys @@ -202,7 +204,7 @@ edx-opaque-keys[django]==2.10.0 # openedx-events edx-proctoring==4.18.1 # via -r requirements/base.txt -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via # -r requirements/base.txt # edx-proctoring @@ -216,15 +218,16 @@ edx-when==2.5.0 # edx-proctoring event-tracking==2.4.0 # via + # -c https://raw.githubusercontent.com/openedx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt # edx-proctoring -exceptiongroup==1.2.1 +exceptiongroup==1.2.2 # via pytest -factory-boy==3.3.0 +factory-boy==3.3.1 # via -r requirements/test.in -faker==26.0.0 +faker==28.4.1 # via factory-boy -fastavro==1.9.4 +fastavro==1.9.7 # via # -r requirements/base.txt # openedx-events @@ -232,7 +235,7 @@ fs==2.4.16 # via # -r requirements/base.txt # xblock -idna==3.7 +idna==3.10 # via # -r requirements/base.txt # requests @@ -256,13 +259,13 @@ jwcrypto==1.5.6 # via # -r requirements/base.txt # django-oauth-toolkit -kombu==5.3.7 +kombu==5.4.1 # via # -r requirements/base.txt # celery lazy-object-proxy==1.10.0 # via astroid -lxml==5.2.2 +lxml==5.3.0 # via # -r requirements/base.txt # xblock @@ -280,7 +283,7 @@ mccabe==0.7.0 # via pylint mock==5.1.0 # via -r requirements/test.in -newrelic==9.11.0 +newrelic==9.13.0 # via # -r requirements/base.txt # edx-django-utils @@ -290,7 +293,7 @@ oauthlib==3.2.2 # django-oauth-toolkit # requests-oauthlib # social-auth-core -openedx-events==9.11.0 +openedx-events==9.14.1 # via # -r requirements/base.txt # event-tracking @@ -299,11 +302,11 @@ packaging==24.1 # -r requirements/base.txt # drf-yasg # pytest -pbr==6.0.0 +pbr==6.1.0 # via # -r requirements/base.txt # stevedore -platformdirs==4.2.2 +platformdirs==4.3.6 # via pylint pluggy==1.5.0 # via pytest @@ -327,7 +330,7 @@ pycryptodomex==3.20.0 # via # -r requirements/base.txt # edx-proctoring -pyjwt[crypto]==2.8.0 +pyjwt[crypto]==2.9.0 # via # -r requirements/base.txt # drf-jwt @@ -348,11 +351,11 @@ pynacl==1.5.0 # via # -r requirements/base.txt # edx-django-utils -pytest==8.2.2 +pytest==8.3.3 # via # -r requirements/test.in # pytest-django -pytest-django==4.8.0 +pytest-django==4.9.0 # via -r requirements/test.in python-dateutil==2.9.0.post0 # via @@ -373,14 +376,14 @@ python3-openid==3.2.0 # via # -r requirements/base.txt # social-auth-core -pytz==2024.1 +pytz==2024.2 # via # -r requirements/base.txt # drf-yasg # edx-proctoring # event-tracking # xblock -pyyaml==6.0.1 +pyyaml==6.0.2 # via # -r requirements/base.txt # -r requirements/test.in @@ -394,13 +397,12 @@ requests==2.32.3 # edx-drf-extensions # edx-rest-api-client # requests-oauthlib - # slumber # social-auth-core requests-oauthlib==2.0.0 # via # -r requirements/base.txt # social-auth-core -rules==3.4 +rules==3.5 # via # -r requirements/base.txt # edx-proctoring @@ -412,7 +414,7 @@ shortuuid==1.0.13 # via # -r requirements/base.txt # django-oauth2-provider -simplejson==3.19.2 +simplejson==3.19.3 # via # -r requirements/base.txt # xblock @@ -423,17 +425,13 @@ six==1.16.0 # event-tracking # fs # python-dateutil -slumber==0.7.1 - # via - # -r requirements/base.txt - # edx-rest-api-client social-auth-core==4.5.4 # via -r requirements/base.txt -sqlparse==0.5.0 +sqlparse==0.5.1 # via # -r requirements/base.txt # django -stevedore==5.2.0 +stevedore==5.3.0 # via # -r requirements/base.txt # code-annotations @@ -449,7 +447,7 @@ tomli==2.0.1 # via # pylint # pytest -tomlkit==0.12.5 +tomlkit==0.13.2 # via pylint typing-extensions==4.12.2 # via @@ -470,7 +468,7 @@ uritemplate==4.1.1 # via # -r requirements/base.txt # drf-yasg -urllib3==2.2.2 +urllib3==2.2.3 # via # -r requirements/base.txt # requests @@ -488,13 +486,13 @@ web-fragments==2.2.0 # via # -r requirements/base.txt # xblock -webob==1.8.7 +webob==1.8.8 # via # -r requirements/base.txt # xblock wrapt==1.16.0 # via astroid -xblock==4.0.1 +xblock==5.1.0 # via # -r requirements/base.txt # edx-when diff --git a/requirements/tox.txt b/requirements/tox.txt index 2b3ff9acd..33638b49d 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -4,7 +4,7 @@ # # make upgrade # -cachetools==5.3.3 +cachetools==5.5.0 # via tox chardet==5.2.0 # via tox @@ -12,7 +12,7 @@ colorama==0.4.6 # via tox distlib==0.3.8 # via virtualenv -filelock==3.15.4 +filelock==3.16.1 # via # tox # virtualenv @@ -20,7 +20,7 @@ packaging==24.1 # via # pyproject-api # tox -platformdirs==4.2.2 +platformdirs==4.3.6 # via # tox # virtualenv @@ -32,7 +32,7 @@ tomli==2.0.1 # via # pyproject-api # tox -tox==4.16.0 +tox==4.19.0 # via -r requirements/tox.in -virtualenv==20.26.3 +virtualenv==20.26.4 # via tox diff --git a/scripts/execute_integration_tests.sh b/scripts/execute_integration_tests.sh new file mode 100644 index 000000000..3408c29b2 --- /dev/null +++ b/scripts/execute_integration_tests.sh @@ -0,0 +1,2 @@ +#!/bin/bash +make run-integration-tests diff --git a/setup.cfg b/setup.cfg index 502e5dbb2..b48a0e6df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 10.5.1 +current_version = 10.6.0 commit = False tag = False @@ -10,7 +10,7 @@ DJANGO_SETTINGS_MODULE = eox_core.settings.test [coverage:run] data_file = .coverage -omit = +omit = venv/* */backends/* node_modules/*