Skip to content

Commit a81cc53

Browse files
committed
refactor: migrate tests to use edx-platform
This commit refactors the testing infrastructure to leverage edx-platform's test configuration instead of custom mock backends, improving test reliability and reducing maintenance overhead. Key changes: Test Infrastructure: - Configure pytest with 60-second timeout protection to prevent hanging tests - Add pytest-timeout plugin to requirements/test.in - Remove TOX dependency in favor of direct virtual environment usage Makefile Enhancements: - Add automatic MongoDB Docker container management (mongo:7) - Implement virtual environment auto-creation and validation - Add Python 3.11 version checking with clear error messages - Create new test targets: `test` (auto-managed MongoDB) and `test-keep-mongo` - Add MongoDB lifecycle targets: mongo-start, mongo-stop, mongo-status, mongo-ping - Update test execution to run from edx-platform context with proper PYTHONPATH - Add coverage reporting with combine and include filters Settings Refactoring: - Migrate from custom mock backends to edx-platform test settings - Remove deprecated NAU_*_MODULE override settings from nau_openedx_extensions/settings/test.py - Add MongoDB connection configuration for modulestore tests - Configure CONTENTSTORE and update_module_store_settings for Mongo integration - Remove test-specific settings from app configs (apps.py files) - Create separate test_settings.py for coursecertificate app if needed GitHub Actions CI: - Add MongoDB version to test matrix for documentation - Add Docker verification step to ensure container support - Add 60-minute timeout to test execution - Update checkout and requirements steps to use proper working directories - Configure cache paths for both edx-platform and nau-openedx-extensions Additional Improvements: - Add detailed logging to exception handlers in partner_integration views - Clean up code formatting and improve error messages - Add comprehensive Makefile help documentation This migration enables: - Direct use of edx-platform factories and models in tests - Real MongoDB testing via Docker containers - Consistent test environment between local and CI - Reduced test maintenance by depending on edx-platform test infrastructure - Automatic timeout protection preventing indefinite test hangs related to: fccn/nau-technical#674
1 parent 63634d3 commit a81cc53

File tree

22 files changed

+557
-211
lines changed

22 files changed

+557
-211
lines changed

.github/workflows/ci.yml

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,96 @@ on:
66
branches:
77
- nau/*.master
88

9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
913
jobs:
1014

11-
test:
15+
test-and-lint:
1216
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
include:
20+
- edx_platform_repository: fccn/edx-platform
21+
edx_platform_ref: nau/redwood.master
22+
python-version: "3.11"
1323
steps:
14-
- uses: actions/checkout@v4
24+
- name: Checkout nau-openedx-extensions
25+
uses: actions/checkout@v4
26+
with:
27+
path: nau-openedx-extensions
28+
29+
- name: Cache edx-platform repository
30+
uses: actions/cache@v4
31+
with:
32+
path: edx-platform
33+
key: edx-platform-${{ matrix.edx_platform_repository }}-${{ matrix.edx_platform_ref }}-${{ github.sha }}
34+
restore-keys: |
35+
edx-platform-${{ matrix.edx_platform_repository }}-${{ matrix.edx_platform_ref }}-
36+
edx-platform-${{ matrix.edx_platform_repository }}-
1537
16-
- name: Clone edx-platform
17-
run: |
18-
git clone --depth 1 https://github.com/openedx/edx-platform.git ${{ github.workspace }}/../edx-platform
38+
- name: Checkout edx-platform
39+
uses: actions/checkout@v4
40+
with:
41+
repository: ${{ matrix.edx_platform_repository }}
42+
ref: ${{ matrix.edx_platform_ref }}
43+
path: edx-platform
1944

20-
- uses: actions/setup-python@v5
45+
- name: Setup Python
46+
uses: actions/setup-python@v5
2147
with:
22-
python-version: '3.11'
23-
cache: 'pip'
24-
cache-dependency-path: requirements/*.txt
48+
python-version: '${{ matrix.python-version }}'
2549

26-
- run: make requirements
27-
- run: make test
50+
- name: Install system requirements
51+
run: sudo apt update && sudo apt install -y libxmlsec1-dev
2852

29-
lint:
30-
runs-on: ubuntu-latest
31-
steps:
32-
- uses: actions/checkout@v4
33-
- uses: actions/setup-python@v5
53+
- name: Verify Docker is available
54+
run: docker --version
55+
56+
- name: Cache virtual environment
57+
uses: actions/cache@v4
3458
with:
35-
python-version: '3.11'
36-
cache: 'pip'
37-
cache-dependency-path: requirements/*.txt
38-
- run: make requirements
39-
- run: make lint
59+
path: nau-openedx-extensions/venv
60+
key: venv-v3-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('edx-platform/requirements/edx/testing.txt', 'nau-openedx-extensions/requirements/*.txt') }}
61+
restore-keys: |
62+
venv-v3-${{ runner.os }}-py${{ matrix.python-version }}-
63+
64+
- name: Install requirements
65+
run: EDX_PLATFORM_PATH=${{ github.workspace }}/edx-platform make -C nau-openedx-extensions requirements
66+
67+
- name: Run lint
68+
run: EDX_PLATFORM_PATH=${{ github.workspace }}/edx-platform make -C nau-openedx-extensions lint
69+
70+
- name: Run tests
71+
run: EDX_PLATFORM_PATH=${{ github.workspace }}/edx-platform make -C nau-openedx-extensions test
72+
timeout-minutes: 60
4073

4174
check_miss_run_update_translations:
4275
runs-on: ubuntu-latest
76+
strategy:
77+
matrix:
78+
include:
79+
- python-version: "3.11"
4380
steps:
44-
- uses: actions/checkout@v4
45-
- uses: actions/setup-python@v5
81+
- name: Checkout nau-openedx-extensions
82+
uses: actions/checkout@v4
83+
84+
- name: Setup Python
85+
uses: actions/setup-python@v5
4686
with:
47-
python-version: '3.11'
48-
cache: 'pip'
49-
cache-dependency-path: requirements/*.txt
50-
- run: make requirements
51-
- run: make check_miss_run_update_translations
87+
python-version: '${{ matrix.python-version }}'
88+
89+
- name: Cache pip packages
90+
uses: actions/cache@v4
91+
with:
92+
path: ~/.cache/pip
93+
key: pip-translations-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('requirements/translations.txt') }}
94+
restore-keys: |
95+
pip-translations-${{ runner.os }}-py${{ matrix.python-version }}
96+
97+
- name: Install translation requirements
98+
run: python -m pip install -r requirements/translations.txt
99+
100+
- name: Check translations
101+
run: make check_miss_run_update_translations

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

Makefile

Lines changed: 182 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,203 @@
77
.DEFAULT_GOAL := help
88
.PHONY: help
99

10-
ifdef TOXENV
11-
TOX := tox -- #to isolate each tox environment if TOXENV is defined
12-
endif
10+
# ==============================================================================
11+
# VARIABLES
12+
# ==============================================================================
13+
# current directory relative to the Makefile file
14+
ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
15+
16+
# By default use the sibling edx-platform folder
17+
# but you can use other folder, you just have to change this environment variable like:
18+
# EDX_PLATFORM_PATH=/nau make test
19+
# make EDX_PLATFORM_PATH=/nau test
20+
EDX_PLATFORM_PATH ?= $(shell dirname $(ROOT_DIR))/edx-platform
21+
22+
# Virtual environment
23+
VENV_DIR := $(ROOT_DIR)/venv
24+
VENV_BIN := $(VENV_DIR)/bin
25+
PYTHON := $(VENV_BIN)/python
26+
PIP := $(VENV_BIN)/pip
27+
COVERAGE := $(VENV_BIN)/coverage
28+
PYTEST := $(VENV_BIN)/pytest
29+
30+
# MongoDB variables for testing
31+
MONGO_CONTAINER_NAME := nau-test-mongodb
32+
MONGO_PORT := 27017
33+
MONGO_IMAGE := mongo:7
1334

1435

1536
help: ## display this help message
1637
@echo "Please use \`make <target>' where <target> is one of"
1738
@grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}'
1839

40+
_prerequire: ## Check that edx-platform directory exists
41+
@if [ ! -d "${EDX_PLATFORM_PATH}" ]; then { echo "edx-platform directory doesn't exist.\n EDX_PLATFORM_PATH=${EDX_PLATFORM_PATH}\nPlease check if that directory exists or change the default value using:\n EDX_PLATFORM_PATH=~/<different path>/edx-platform make <target>" ; exit 1; } fi
42+
.PHONY: _prerequire
43+
1944
clean: ## delete most git-ignored files
20-
find . -name '__pycache__' -exec rm -rf {} +
21-
find . -name '*.pyc' -exec rm -f {} +
22-
find . -name '*.pyo' -exec rm -f {} +
23-
find . -name '*~' -exec rm -f {} +
24-
echo "cleaned"
45+
@find . -name '__pycache__' -exec rm -rf {} +
46+
@find . -name '*.pyc' -exec rm -f {} +
47+
@find . -name '*.pyo' -exec rm -f {} +
48+
@find . -name '*~' -exec rm -f {} +
49+
@-rm -rf .coverage .coverage.*
50+
@echo "✓ Cleaned"
2551
# rm -rf venv +
2652
.PHONY: clean
2753

28-
requirements: ## Install requirements
29-
python -m pip install -r requirements/base.txt
30-
python -m pip install -r requirements/translations.txt
31-
python -m pip install -r requirements/test.txt
54+
_check_python: ## Check Python version is 3.11
55+
@python_version=$$(python3 --version 2>&1 | grep -oP '3\.11\.\d+'); \
56+
if [ -z "$$python_version" ]; then \
57+
echo "Error: Python 3.11 is required but not found."; \
58+
echo "Current version: $$(python3 --version)"; \
59+
exit 1; \
60+
else \
61+
echo "✓ Using Python $$python_version"; \
62+
fi
63+
.PHONY: _check_python
64+
65+
_venv: _check_python ## Create virtual environment if it doesn't exist
66+
@if [ ! -d "$(VENV_DIR)" ]; then \
67+
echo "Creating virtual environment with Python 3.11..."; \
68+
python3 -m venv $(VENV_DIR); \
69+
echo "✓ Virtual environment created at $(VENV_DIR)"; \
70+
fi
71+
@venv_python_version=$$($(PYTHON) --version 2>&1 | grep -oP '3\.11\.\d+'); \
72+
if [ -z "$$venv_python_version" ]; then \
73+
echo "Error: Virtual environment is not using Python 3.11"; \
74+
echo "Virtual environment Python: $$($(PYTHON) --version)"; \
75+
echo "Please remove the venv directory and try again: rm -rf $(VENV_DIR)"; \
76+
exit 1; \
77+
fi
78+
.PHONY: _venv
79+
80+
requirements: | _prerequire _venv ## Install requirements from both edx-platform and nau-openedx-extensions
81+
@echo "Installing edx-platform testing requirements..."
82+
$(PIP) install -r ${EDX_PLATFORM_PATH}/requirements/edx/testing.txt
83+
@echo "Installing edx-platform in editable mode..."
84+
$(PIP) install -e ${EDX_PLATFORM_PATH}
85+
@echo "Installing nau-openedx-extensions test requirements..."
86+
$(PIP) install -r $(ROOT_DIR)/requirements/test.in
87+
@echo "Installing nau-openedx-extensions in editable mode..."
88+
$(PIP) install -e $(ROOT_DIR)
89+
@echo "✓ All requirements installed successfully"
3290
.PHONY: requirements
3391

34-
test: clean requirements
35-
$(TOX) PYTHONPATH=$(PWD):../edx-platform \
36-
DJANGO_SETTINGS_MODULE=lms.envs.tutor.production \
37-
coverage run --source="nau_openedx_extensions" -m pytest --rootdir=/.. nau_openedx_extensions
38-
$(TOX) coverage report --fail-under=5
92+
# ==============================================================================
93+
# MONGODB TARGETS
94+
# ==============================================================================
95+
96+
mongo-start: ## Start MongoDB container for testing (localhost only)
97+
@if docker ps -a --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
98+
if docker ps --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
99+
echo "MongoDB container '$(MONGO_CONTAINER_NAME)' is already running"; \
100+
else \
101+
echo "Starting existing MongoDB container '$(MONGO_CONTAINER_NAME)'..."; \
102+
docker start $(MONGO_CONTAINER_NAME); \
103+
$(MAKE) --no-print-directory mongo-ping; \
104+
fi \
105+
else \
106+
echo "Creating and starting MongoDB container '$(MONGO_CONTAINER_NAME)'..."; \
107+
docker run -d --name $(MONGO_CONTAINER_NAME) -p 127.0.0.1:$(MONGO_PORT):27017 $(MONGO_IMAGE); \
108+
$(MAKE) --no-print-directory mongo-ping; \
109+
fi
110+
.PHONY: mongo-start
111+
112+
mongo-stop: ## Stop and remove MongoDB container
113+
@if docker ps --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
114+
echo "Stopping MongoDB container '$(MONGO_CONTAINER_NAME)'..."; \
115+
docker stop $(MONGO_CONTAINER_NAME); \
116+
docker rm $(MONGO_CONTAINER_NAME); \
117+
echo "MongoDB container stopped and removed"; \
118+
else \
119+
if docker ps -a --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
120+
echo "Removing stopped MongoDB container '$(MONGO_CONTAINER_NAME)'..."; \
121+
docker rm $(MONGO_CONTAINER_NAME); \
122+
else \
123+
echo "MongoDB container '$(MONGO_CONTAINER_NAME)' is not running"; \
124+
fi \
125+
fi
126+
.PHONY: mongo-stop
127+
128+
mongo-status: ## Check MongoDB container status
129+
@if docker ps --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
130+
echo "✓ MongoDB container '$(MONGO_CONTAINER_NAME)' is running"; \
131+
docker ps --filter "name=$(MONGO_CONTAINER_NAME)" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"; \
132+
else \
133+
if docker ps -a --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
134+
echo "✗ MongoDB container '$(MONGO_CONTAINER_NAME)' exists but is not running"; \
135+
else \
136+
echo "✗ MongoDB container '$(MONGO_CONTAINER_NAME)' does not exist"; \
137+
fi; \
138+
exit 1; \
139+
fi
140+
.PHONY: mongo-status
141+
142+
mongo-ping: ## Wait for MongoDB to be ready
143+
@echo "Waiting for MongoDB to be ready..."
144+
@for i in 1 2 3 4 5 6 7 8 9 10; do \
145+
if docker exec $(MONGO_CONTAINER_NAME) mongosh --quiet --eval "db.runCommand({ ping: 1 })" >/dev/null 2>&1; then \
146+
echo "✓ MongoDB is ready"; \
147+
exit 0; \
148+
fi; \
149+
echo " Attempt $$i/10: MongoDB not ready yet, waiting..."; \
150+
sleep 1; \
151+
done; \
152+
echo "✗ MongoDB failed to start within 10 seconds"; \
153+
exit 1
154+
.PHONY: mongo-ping
155+
156+
_mongo_ensure_running: ## Internal: ensure MongoDB is running
157+
@if ! docker ps --format '{{.Names}}' | grep -q "^$(MONGO_CONTAINER_NAME)$$"; then \
158+
echo "MongoDB is not running. Starting MongoDB container..."; \
159+
$(MAKE) --no-print-directory mongo-start; \
160+
fi
161+
.PHONY: _mongo_ensure_running
162+
163+
_mongo_cleanup: ## Internal: cleanup MongoDB after tests
164+
@trap '$(MAKE) --no-print-directory mongo-stop' EXIT; \
165+
$(MAKE) --no-print-directory _run_tests ARGS="$(ARGS)"
166+
.PHONY: _mongo_cleanup
167+
168+
_run_tests: clean | _prerequire _venv ## Internal: run the actual tests
169+
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
170+
arg_2="$${args:-${ARGS}}" && \
171+
arg_3="$${arg_2:=$(ROOT_DIR)/nau_openedx_extensions}" && \
172+
cd ${EDX_PLATFORM_PATH} && \
173+
PYTHONPATH=${EDX_PLATFORM_PATH}:$(ROOT_DIR) \
174+
EDXAPP_TEST_MONGO_HOST=localhost \
175+
EDXAPP_TEST_MONGO_PORT=$(MONGO_PORT) \
176+
$(COVERAGE) run --source="$(ROOT_DIR)/nau_openedx_extensions" -m pytest --ds=nau_openedx_extensions.settings.test --nomigrations --reuse-db -o django_find_project=false $${arg_3}
177+
@cd ${EDX_PLATFORM_PATH} && $(COVERAGE) combine || true
178+
@cd ${EDX_PLATFORM_PATH} && $(COVERAGE) report --include="$(ROOT_DIR)/nau_openedx_extensions/*" --fail-under=5
179+
@echo "✓ Tests passed"
180+
.PHONY: _run_tests
181+
182+
test: _prerequire ## Run tests with auto-managed MongoDB (starts, runs tests, stops). Usage: make test `pwd`/[path/to/test_file.py::TestClass::test_method]
183+
@$(MAKE) --no-print-directory mongo-start
184+
@cleanup() { $(MAKE) --no-print-directory mongo-stop; }; \
185+
trap cleanup EXIT INT TERM; \
186+
args="$(filter-out $@,$(MAKECMDGOALS))"; \
187+
$(MAKE) --no-print-directory _run_tests ARGS="$$args"
39188
.PHONY: test
40189

41-
lint: ## Run linters to check code style
42-
$(TOX) pylint ./nau_openedx_extensions
43-
$(TOX) pycodestyle ./nau_openedx_extensions
44-
$(TOX) isort --check-only --diff ./nau_openedx_extensions
190+
test-keep-mongo: _prerequire ## Run tests with MongoDB (starts if needed, keeps running after). Usage: make test-keep-mongo `pwd`/[path/to/test_file.py::TestClass::test_method]
191+
@$(MAKE) --no-print-directory _mongo_ensure_running
192+
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
193+
$(MAKE) --no-print-directory _run_tests ARGS="$$args"
194+
.PHONY: test-keep-mongo
195+
196+
lint: | _prerequire _venv ## Run linters to check code style
197+
@cd ${EDX_PLATFORM_PATH} && PYTHONPATH=${EDX_PLATFORM_PATH}:$(ROOT_DIR) $(VENV_BIN)/pylint $(ROOT_DIR)/nau_openedx_extensions
198+
@cd ${EDX_PLATFORM_PATH} && PYTHONPATH=${EDX_PLATFORM_PATH}:$(ROOT_DIR) $(VENV_BIN)/pycodestyle $(ROOT_DIR)/nau_openedx_extensions
199+
@cd ${EDX_PLATFORM_PATH} && PYTHONPATH=${EDX_PLATFORM_PATH}:$(ROOT_DIR) $(VENV_BIN)/isort --check-only --diff $(ROOT_DIR)/nau_openedx_extensions
200+
@echo "✓ Linting passed"
45201
.PHONY: lint
46202

47-
lint-fix: ## Fix Python import sort
48-
$(TOX) isort ./nau_openedx_extensions
49-
$(TOX) autopep8 --in-place --aggressive --aggressive ./nau_openedx_extensions/*.py
203+
lint-fix: | _prerequire _venv ## Fix Python import sort
204+
@cd ${EDX_PLATFORM_PATH} && PYTHONPATH=${EDX_PLATFORM_PATH}:$(ROOT_DIR) $(VENV_BIN)/isort $(ROOT_DIR)/nau_openedx_extensions
205+
@cd ${EDX_PLATFORM_PATH} && PYTHONPATH=${EDX_PLATFORM_PATH}:$(ROOT_DIR) $(VENV_BIN)/autopep8 --in-place --aggressive --aggressive $(ROOT_DIR)/nau_openedx_extensions/*.py
206+
@echo "✓ Linting fixes applied"
50207
.PHONY: lint-fix
51208

52209
# Define PIP_COMPILE_OPTS=-v to get more information during make upgrade.
@@ -81,7 +238,7 @@ compile-requirements: pre-requirements ## Re-compile *.in requirements to *.txt
81238
.PHONY: compile-requirements
82239

83240
upgrade: ## update the pip requirements files to use the latest releases satisfying our constraints
84-
$(MAKE) compile-requirements COMPILE_OPTS="--upgrade"
241+
$(MAKE) --no-print-directory compile-requirements COMPILE_OPTS="--upgrade"
85242
.PHONY: upgrade
86243

87244
# TODO: define somewhere else

nau_openedx_extensions/apps.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@ class NauOpenEdxConfig(AppConfig):
2121
},
2222
"settings_config": {
2323
"lms.djangoapp": {
24-
"test": {"relative_path": "settings.test"},
2524
"common": {"relative_path": "settings.common"},
2625
"production": {"relative_path": "settings.production"},
2726
},
28-
'cms.djangoapp': {
27+
"cms.djangoapp": {
2928
'common': {'relative_path': 'settings.common'},
30-
'test': {'relative_path': 'settings.test'},
31-
"production": {"relative_path": "settings.production"},
32-
},
29+
'production': {'relative_path': 'settings.production'},
30+
}
3331
},
3432
"view_context_config": {
3533
"lms.djangoapp": {

nau_openedx_extensions/coursecertificate/apps.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class CoursecertificateConfig(AppConfig):
1313
"settings_config": {
1414
"lms.djangoapp": {
1515
"common": {"relative_path": "settings.common"},
16-
"test": {"relative_path": "settings.test"},
1716
"production": {"relative_path": "settings.production"},
1817
},
1918
},

0 commit comments

Comments
 (0)