From 1999b3d3530f09c7e4b17ec15ceda10ce1bf5fa2 Mon Sep 17 00:00:00 2001 From: Irtaza Akram Date: Mon, 2 Mar 2026 11:36:13 +0500 Subject: [PATCH 1/3] fix: python & javascript & openedx-platform tests --- MANIFEST.in | 6 +- test_settings.py | 75 +++ xblocks_contrib/problem/__init__.py | 2 +- xblocks_contrib/problem/assets/karma.conf.js | 118 +++++ xblocks_contrib/problem/assets/package.json | 34 ++ .../problem/assets/spec/display_spec.js | 43 +- .../problem/assets/static/js/display.js | 2 +- .../problem/assets/webpack.config.js | 186 +++++++ .../problem/assets/webpack.prod.config.js | 17 + xblocks_contrib/problem/capa/apps.py | 50 ++ xblocks_contrib/problem/capa/responsetypes.py | 25 +- .../capa/safe_exec/tests/test_safe_exec.py | 22 +- xblocks_contrib/problem/capa_block.py | 460 +++++++++++++++++- xblocks_contrib/problem/tests/__init__.py | 250 ++++++++++ .../problem/tests/test_capa_block.py | 49 +- .../problem/tests/test_stringify.py | 5 +- 16 files changed, 1272 insertions(+), 72 deletions(-) create mode 100644 xblocks_contrib/problem/assets/karma.conf.js create mode 100644 xblocks_contrib/problem/assets/package.json create mode 100644 xblocks_contrib/problem/assets/webpack.config.js create mode 100644 xblocks_contrib/problem/assets/webpack.prod.config.js create mode 100644 xblocks_contrib/problem/capa/apps.py create mode 100644 xblocks_contrib/problem/tests/__init__.py diff --git a/MANIFEST.in b/MANIFEST.in index 903c7036..8454f1fb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,7 @@ include LICENSE.txt include README.rst include requirements/base.in include requirements/constraints.txt -recursive-include xblocks_contrib *.html *.png *.gif *.jpg *.jpeg *.svg +recursive-include xblocks_contrib *.html *.js *.css *.jar *.png *.gif *.jpg *.jpeg *.svg recursive-include xblocks_contrib/*/public *.js *.css recursive-include xblocks_contrib/*/static *.js *.css @@ -16,3 +16,7 @@ global-exclude webpack*.config.js global-exclude karma*.js global-exclude package*.json global-exclude .gitignore + +include xblocks_contrib/problem/capa/tests/test_util.py +include xblocks_contrib/problem/capa/tests/helpers.py +include xblocks_contrib/problem/capa/tests/response_xml_factory.py diff --git a/test_settings.py b/test_settings.py index 545087f1..3d96b5f4 100644 --- a/test_settings.py +++ b/test_settings.py @@ -2,9 +2,16 @@ Minimal Django settings for running tests on xblocks_contrib. """ +from pathlib import Path + +from django.http import HttpResponse +from django.urls import re_path + +BASE_DIR = Path(__file__).resolve().parent INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", + "submissions", ] DATABASES = { @@ -12,3 +19,71 @@ "ENGINE": "django.db.backends.sqlite3", } } + + +def dummy_callback(_request, *_args, **_kwargs): + """ + Minimal placeholder view for test URL patterns. + Underscored arguments silence lint warnings for unused variables. + """ + return HttpResponse("OK") + + +ROOT_URLCONF = type( + "URLConf", + (), + { + "urlpatterns": [ + re_path( + r"^courses/(?P.+)/xqueue/(?P[^/]+)/(?P.+)/(?P[^/]+)$", + dummy_callback, + name="xqueue_callback", + ), + ] + }, +) + +LMS_ROOT_URL = "http://localhost:8000" + +# Code jail REST service +ENABLE_CODEJAIL_REST_SERVICE = False + +# Set the python package.module.function that is reponsible of +# calling the remote service in charge of jailed code execution +CODE_JAIL_REST_SERVICE_REMOTE_EXEC = "xblocks_contrib.problem.capa.safe_exec.remote_exec.send_safe_exec_request_v0" + +# Set the codejail remote service host +CODE_JAIL_REST_SERVICE_HOST = "http://127.0.0.1:8550" + +# Set the number of seconds LMS will wait to establish an internal +# connection to the codejail remote service. +CODE_JAIL_REST_SERVICE_CONNECT_TIMEOUT = 0.5 # time in seconds + +# Set the number of seconds LMS/CMS will wait for a response from the +# codejail remote service endpoint. +CODE_JAIL_REST_SERVICE_READ_TIMEOUT = 3.5 # time in seconds + +# CAPA External Code Evaluation # + +# Used with XQueue +XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds +XQUEUE_INTERFACE = { + "url": "http://localhost:18040", + "basic_auth": ["edx", "edx"], + "django_auth": {"username": "lms", "password": "password"}, +} + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + BASE_DIR / "xblocks_contrib" / "problem" / "capa" / "templates", + ], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + ], + }, + }, +] diff --git a/xblocks_contrib/problem/__init__.py b/xblocks_contrib/problem/__init__.py index ca5bda74..95757cba 100644 --- a/xblocks_contrib/problem/__init__.py +++ b/xblocks_contrib/problem/__init__.py @@ -2,4 +2,4 @@ Init for the ProblemBlock. """ -from .problem import ProblemBlock +from .capa_block import ProblemBlock diff --git a/xblocks_contrib/problem/assets/karma.conf.js b/xblocks_contrib/problem/assets/karma.conf.js new file mode 100644 index 00000000..75df6d46 --- /dev/null +++ b/xblocks_contrib/problem/assets/karma.conf.js @@ -0,0 +1,118 @@ +/* eslint-env node */ +'use strict'; + +const path = require('path'); + +const uiToolkitRoot = path.dirname(require.resolve('edx-ui-toolkit/package.json')); + +module.exports = function (config) { + config.set({ + basePath: '', + + frameworks: ['jasmine'], + + files: [ + { pattern: require.resolve('jquery/dist/jquery.js'), included: true }, + { pattern: require.resolve('jasmine-jquery/lib/jasmine-jquery.js'), included: true }, + { pattern: require.resolve('underscore/underscore.js'), included: true }, + + { pattern: 'static/js/vendor/codemirror-compressed.js', included: true }, + + { pattern: path.join(uiToolkitRoot, 'src/js/utils/global-loader.js'), included: true }, + { pattern: path.join(uiToolkitRoot, 'src/js/utils/string-utils.js'), included: true }, + { pattern: path.join(uiToolkitRoot, 'src/js/utils/html-utils.js'), included: true }, + + { pattern: 'spec_helpers/ajax_prefix.js', included: true }, + { pattern: 'spec_helpers/i18n.js', included: true }, + { pattern: 'spec_helpers/logger.js', included: true }, + { pattern: 'spec_helpers/accessibility_tools.js', included: true }, + { pattern: 'spec_helpers/add_ajax_prefix.js', included: true }, + { pattern: 'spec_helpers/helper.js', included: true }, + + { pattern: 'spec_helpers/jasmine-waituntil.js', included: true }, + { pattern: 'spec_helpers/jasmine-extensions.js', included: true }, + { pattern: 'spec_helpers/jasmine-imagediff.js', included: true }, + + { pattern: 'static/js/xmodule.js', included: true }, + { pattern: 'static/js/javascript_loader.js', included: true }, + { pattern: 'static/js/collapsible.js', included: true }, + { pattern: 'static/js/display.js', included: true }, + { pattern: 'static/js/imageinput.js', included: true }, + { pattern: 'static/js/schematic.js', included: true }, + + { pattern: '../capa/static/js/capa/**/*.js', included: false }, + + { pattern: '../capa/static/js/capa/src/jschannel.js', included: true }, + { pattern: '../capa/static/js/capa/src/jsinput.js', included: true }, + { pattern: '../capa/static/js/capa/src/formula_equation_preview.js', included: true }, + { pattern: '../capa/static/js/capa/symbolic_mathjax_preprocessor.js', included: true }, + { pattern: '../capa/static/js/capa/annotationinput.js', included: true }, + { pattern: '../capa/static/js/capa/choicetextinput.js', included: true }, + + { pattern: 'fixtures/**/*.html', included: false, served: true }, + { pattern: 'fixtures/**/*.underscore', included: false, served: true }, + { pattern: '../capa/static/js/capa/fixtures/**/*.html', included: false, served: true }, + { pattern: '../capa/static/js/capa/genex/**/*', included: false, served: true }, + { pattern: '../capa/static/js/capa/protex/**/*', included: false, served: true }, + + { pattern: 'spec/*_spec.js', included: true }, + { pattern: '../capa/static/js/capa/spec/*_spec.js', included: true }, + + { pattern: 'karma_runner.js', included: true } + ], + + exclude: [ + '**/public/**', + '../capa/static/js/capa/jsinput/jsinput_example.js' + ], + proxies: { + '/spec/javascripts/fixturesfixtures/js/capa/fixtures/': '/absolute' + path.resolve(__dirname, '../capa/static/js/capa/fixtures').replace(/\\/g, '/') + '/', + '/spec/javascripts/fixturesfixtures/js/': '/base/static/js/', + '/spec/javascripts/fixturesfixtures/': '/base/fixtures/', + }, + + preprocessors: { + 'static/js/**/*.js': ['sourcemap'], + 'spec/**/*.js': ['sourcemap'] + }, + + plugins: [ + 'karma-jasmine', + 'karma-requirejs', + 'karma-firefox-launcher', + 'karma-sourcemap-loader', + 'karma-coverage', + require('karma-spec-reporter') + ], + + reporters: ['spec', 'coverage'], + + coverageReporter: { + dir: 'coverage/', + reporters: [ + { type: 'html', subdir: 'report-html' }, + { type: 'lcov', subdir: 'report-lcov' } + ] + }, + + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: false, + browsers: ['FirefoxNoUpdates'], + + customLaunchers: { + FirefoxNoUpdates: { + base: 'Firefox', + prefs: { + 'app.update.auto': false, + 'app.update.enabled': false + } + } + }, + + singleRun: true, + concurrency: Infinity, + client: { captureConsole: true } + }); +}; diff --git a/xblocks_contrib/problem/assets/package.json b/xblocks_contrib/problem/assets/package.json new file mode 100644 index 00000000..719c90ae --- /dev/null +++ b/xblocks_contrib/problem/assets/package.json @@ -0,0 +1,34 @@ +{ + "name": "problem-xblock", + "private": true, + "description": "CAPA Problem XBlock.", + "scripts": { + "build": "webpack --config=webpack.prod.config.js", + "build-dev": "webpack --config=webpack.config.js", + "test": "karma start karma.conf.js", + "test-ci": "xvfb-run --auto-servernum karma start karma.conf.js" + }, + "dependencies": { + "babel-loader": "^9", + "edx-ui-toolkit": "^1", + "exports-loader": "^0.6", + "imports-loader": "^0.8", + "jquery": "^2", + "requirejs": "^2", + "underscore": "^1" + }, + "devDependencies": { + "copy-webpack-plugin": "^13", + "jasmine-core": "2.6.4", + "jasmine-jquery": "git+https://git@github.com/velesin/jasmine-jquery.git#ebad463d592d3fea00c69f26ea18a930e09c7b58", + "karma-coverage": "^2", + "karma-firefox-launcher": "^2", + "karma-jasmine": "^0.3", + "karma-requirejs": "^1", + "karma-sourcemap-loader": "^0.4", + "karma-spec-reporter": "0.0.20", + "karma": "^0.13", + "string-replace-loader": "^3", + "webpack-cli": "^5" + } +} diff --git a/xblocks_contrib/problem/assets/spec/display_spec.js b/xblocks_contrib/problem/assets/spec/display_spec.js index f55106f3..843ea6a9 100644 --- a/xblocks_contrib/problem/assets/spec/display_spec.js +++ b/xblocks_contrib/problem/assets/spec/display_spec.js @@ -6,6 +6,7 @@ */ describe("Problem", function () { const problem_content_default = readFixtures("problem_content.html"); + var mockRuntime = {}; beforeEach(function () { // Stub MathJax @@ -36,7 +37,7 @@ describe("Problem", function () { describe("constructor", function () { it("set the element from html", function () { - this.problem999 = new Problem(`\ + this.problem999 = new Problem(mockRuntime,`\
\
\ }); it("set the element from loadFixtures", function () { - this.problem1 = new Problem($(".xblock-student_view")); + this.problem1 = new Problem(mockRuntime, $(".xblock-student_view")); expect(this.problem1.element_id).toBe("problem_1"); }); }); @@ -58,7 +59,7 @@ data-url='/problem/quiz/'> \ beforeEach(function () { spyOn(window, "update_schematics"); MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]); - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); }); it("set mathjax typeset", () => expect(MathJax.Hub.Queue).toHaveBeenCalled()); @@ -94,7 +95,7 @@ data-url='/problem/quiz/'> \ beforeEach(function () { spyOn(window, "update_schematics"); MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]); - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); return $(this).html(readFixtures("problem_content_1240.html")); }); @@ -109,7 +110,7 @@ data-url='/problem/quiz/'> \ describe("renderProgressState", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); }); const testProgessData = function ( @@ -212,7 +213,7 @@ data-url='/problem/quiz/'> \ describe("render", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.bind = this.problem.bind; spyOn(this.problem, "bind"); }); @@ -251,7 +252,7 @@ data-url='/problem/quiz/'> \ beforeEach(function () { // Insert an input of type file outside of the problem. $(".xblock-student_view").after(''); - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); spyOn(this.problem, "submit"); }); @@ -263,7 +264,7 @@ data-url='/problem/quiz/'> \ describe("submit", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.problem.answers = "foo=1&bar=2"; }); @@ -409,7 +410,7 @@ data-url='/problem/quiz/'> \ describe("submit button on problems", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.submitDisabled = (disabled) => { if (disabled) { expect(this.problem.submitButton).toHaveAttr("disabled"); @@ -495,7 +496,7 @@ data-url='/problem/quiz/'> \ describe("reset", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); }); it("log the problem_reset event", function () { @@ -603,7 +604,7 @@ data-url='/problem/quiz/'> \ describe("show problem with column in id", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.problem.el.prepend('
'); }); @@ -636,7 +637,7 @@ data-url='/problem/quiz/'> \ describe("show", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.problem.el.prepend('
'); }); @@ -739,7 +740,7 @@ data-url='/problem/quiz/'> \ }; beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.problem.el.prepend(_.template(imageinput_html)(DEFAULTS)); }); @@ -903,7 +904,7 @@ data-url='/problem/quiz/'> \ describe("save", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.problem.answers = "foo=1&bar=2"; }); @@ -993,7 +994,7 @@ data-url='/problem/quiz/'> \ describe("refreshMath", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); $("#input_example_1").val("E=mc^2"); this.problem.refreshMath({ target: $("#input_example_1").get(0) }); }); @@ -1008,7 +1009,7 @@ data-url='/problem/quiz/'> \ describe("updateMathML", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.stubbedJax.root.toMathML.and.returnValue(""); }); @@ -1036,7 +1037,7 @@ data-url='/problem/quiz/'> \ describe("refreshAnswers", function () { beforeEach(function () { - this.problem = new Problem($(".xblock-student_view")); + this.problem = new Problem(mockRuntime, $(".xblock-student_view")); this.problem.el.html(`\