Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K_ty6CYdlQyH-3LoJh2VDIQ" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K_ty6CYdlQyH-3LoJh2VDIQ" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="string" />
<qti-outcome-declaration identifier="SCORE" cardinality="single" base-type="float" />
<qti-item-body>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K_ty6CYdlQyH-3LoJh2VDIQ" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K_ty6CYdlQyH-3LoJh2VDIQ" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="string">
<qti-correct-response>
<qti-value>Nothing</qti-value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K_ty6CYdlQyH-3LoJh2VDIQ" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K_ty6CYdlQyH-3LoJh2VDIQ" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="multiple" base-type="float">
<qti-correct-response>
<qti-value>1</qti-value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K3d3d3d3d3d3d3d3d3d3d3Q" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="K3d3d3d3d3d3d3d3d3d3d3Q" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
<qti-correct-response>
<qti-value>choice_0</qti-value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="Kq83vEjRWeJCrze8SNFZ4kA" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="Kq83vEjRWeJCrze8SNFZ4kA" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="multiple" base-type="identifier">
<qti-correct-response>
<qti-value>choice_0</qti-value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="KEjRWeJCrze8SNFZ4kKvN7w" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="KEjRWeJCrze8SNFZ4kKvN7w" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
<qti-correct-response>
<qti-value>choice_0</qti-value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="KEjRWeJCrze8SNFZ4kKvN7w" title="Test Question 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="KEjRWeJCrze8SNFZ4kKvN7w" title="Test Question 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
<qti-correct-response>
<qti-value>choice_0</qti-value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_assessment_item_with_catalog_info_orders_between_item_body_and_response
assessment_item = AssessmentItem(
identifier="item1",
title="Test",
language="en-US",
xml__lang="en-US",
item_body=item_body,
catalog_info=catalog_info,
response_processing=response_processing,
Expand All @@ -78,7 +78,7 @@ def test_true_false_question(self):
title="BG true false example "
adaptive="false"
time-dependent="false"
language="en-US"
xml:lang="en-US"
tool-name="kolibri"
tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
Expand Down Expand Up @@ -139,7 +139,7 @@ def test_true_false_question(self):
assessment_item = AssessmentItem(
identifier="beginnersguide007",
title="BG true false example ",
language="EN-US",
xml__lang="EN-US",
time_dependent=False,
item_body=item_body,
response_declaration=[response_declaration],
Expand All @@ -162,7 +162,7 @@ def test_multiple_choice_question(self):
title="BG Choice example"
adaptive="false"
time-dependent="false"
language="en-US"
xml:lang="en-US"
tool-name="kolibri"
tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="multiple" base-type="identifier">
Expand Down Expand Up @@ -257,7 +257,7 @@ def test_multiple_choice_question(self):
assessment_item = AssessmentItem(
identifier="beginnersguide008",
title="BG Choice example",
language="EN-US",
xml__lang="EN-US",
time_dependent=False,
item_body=item_body,
response_declaration=[response_declaration],
Expand All @@ -277,7 +277,7 @@ def test_long_text_question(self):
title="BG Postcard example"
adaptive="false"
time-dependent="false"
language="en-US"
xml:lang="en-US"
tool-name="kolibri"
tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="string" />
Expand Down Expand Up @@ -350,7 +350,7 @@ def test_long_text_question(self):
assessment_item = AssessmentItem(
identifier="beginnersguide009",
title="BG Postcard example",
language="en-US",
xml__lang="en-US",
time_dependent=False,
item_body=item_body,
response_declaration=[response_declaration],
Expand All @@ -369,7 +369,7 @@ def test_missing_word_question(self):
title="BG Missing Word example"
adaptive="false"
time-dependent="false"
language="en-US"
xml:lang="en-US"
tool-name="kolibri"
tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="string">
Expand Down Expand Up @@ -460,7 +460,7 @@ def test_missing_word_question(self):
assessment_item = AssessmentItem(
identifier="beginnersguide010",
title="BG Missing Word example",
language="en-US",
xml__lang="en-US",
time_dependent=False,
item_body=item_body,
response_declaration=[response_declaration],
Expand All @@ -480,7 +480,7 @@ def test_numerical_entry_question(self):
title="Numerical Entry Question"
adaptive="false"
time-dependent="false"
language="en-US"
xml:lang="en-US"
tool-name="kolibri"
tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="float">
Expand Down Expand Up @@ -525,7 +525,7 @@ def test_numerical_entry_question(self):
assessment_item = AssessmentItem(
identifier="numerical-entry-item",
title="Numerical Entry Question",
language="en-US",
xml__lang="en-US",
time_dependent=False,
item_body=ItemBody(
children=[
Expand Down
4 changes: 2 additions & 2 deletions contentcuration/contentcuration/tests/utils/qti/test_qti.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ def test_assessment_item_element(self):
assessment_item_element = AssessmentItem(
identifier="item_1",
title="Test Assessment Item",
language="en-US",
xml__lang="en-US",
item_body=item_body,
)
expected_xml = '<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="item_1" title="Test Assessment Item" adaptive="false" language="en-US" tool-name="kolibri" tool-version="0.1"><qti-item-body><p>Test Item Body Content</p></qti-item-body></qti-assessment-item>' # noqa: E501
expected_xml = '<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="item_1" title="Test Assessment Item" adaptive="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1"><qti-item-body><p>Test Item Body Content</p></qti-item-body></qti-assessment-item>' # noqa: E501
self.assertEqual(assessment_item_element.to_xml_string(), expected_xml)

def test_prompt_element(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from lxml import etree

from contentcuration.utils.assessment.qti.validation import _compiled_schema
from contentcuration.utils.assessment.qti.validation import _compiled_schematron
from contentcuration.utils.assessment.qti.validation import validate_qti_item


Expand All @@ -24,7 +25,7 @@ def _item_xml(identifier, title, response_declaration, item_body):
'xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 '
'https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" '
'identifier="%s" title="%s" adaptive="false" time-dependent="false" '
'language="en-US" tool-name="kolibri" tool-version="0.1">'
'xml:lang="en-US" tool-name="kolibri" tool-version="0.1">'
"%s"
'<qti-outcome-declaration identifier="SCORE" cardinality="single" base-type="float" />'
"<qti-item-body>%s</qti-item-body>"
Expand Down Expand Up @@ -77,6 +78,25 @@ def test_rejects_missing_required_attribute(self):
self.assertFalse(result.is_valid)
self.assertTrue(any("time-dependent" in e.message for e in result.errors))

def test_rejects_undeclared_root_attribute(self):
xml = VALID_CHOICE_ITEM.replace('xml:lang="en-US"', 'language="en-US"')
result = validate_qti_item(xml)
self.assertFalse(result.is_valid)
self.assertTrue(
any(
"Invalid XML attribute" in e.message and "language" in e.message
for e in result.errors
)
)

def test_accepts_data_prefixed_root_attribute(self):
xml = VALID_CHOICE_ITEM.replace(
'tool-name="kolibri"', 'data-foo="bar" tool-name="kolibri"'
)
result = validate_qti_item(xml)
self.assertTrue(result.is_valid)
self.assertEqual(result.errors, [])

def test_rejects_malformed_xml_without_raising(self):
result = validate_qti_item("<qti-assessment-item><unclosed>")
self.assertFalse(result.is_valid)
Expand Down Expand Up @@ -161,3 +181,14 @@ def test_schema_compiled_once_across_multiple_validate_calls(self):
validate_qti_item(ORDER_INTERACTION_ITEM)
self.assertEqual(_compiled_schema.cache_info().misses, 1)
self.assertEqual(_compiled_schema.cache_info().hits, 2)


class SchematronReuseTests(unittest.TestCase):
def test_schematron_compiled_once_across_multiple_validate_calls(self):
_compiled_schematron.cache_clear()
self.addCleanup(_compiled_schematron.cache_clear)
validate_qti_item(VALID_CHOICE_ITEM)
validate_qti_item(MATCH_INTERACTION_ITEM)
validate_qti_item(ORDER_INTERACTION_ITEM)
self.assertEqual(_compiled_schematron.cache_info().misses, 1)
self.assertEqual(_compiled_schematron.cache_info().hits, 2)
Original file line number Diff line number Diff line change
Expand Up @@ -1637,7 +1637,7 @@ def test_exercise_with_image(self):
_normalize_xml(actual_manifest_xml),
)

self.assertEqual(exercise_file.checksum, "cd5a770d35fa1c25092331ee00f4ce4a")
self.assertEqual(exercise_file.checksum, "08f316508478ea4710b96473d0e5e1f3")

def test_image_resizing(self):
# Create a base image file
Expand Down Expand Up @@ -1707,7 +1707,7 @@ def test_image_resizing(self):

# Expected QTI item XML content with MathML conversion
expected_item_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="{qti_id}" title="Test QTI Exercise 1" adaptive="false" time-dependent="false" language="en-US" tool-name="kolibri" tool-version="0.1">
<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqtiasi_v3p0 https://purl.imsglobal.org/spec/qti/v3p0/schema/xsd/imsqti_asiv3p0p1_v1p0.xsd" identifier="{qti_id}" title="Test QTI Exercise 1" adaptive="false" time-dependent="false" xml:lang="en-US" tool-name="kolibri" tool-version="0.1">
<qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
<qti-correct-response>
<qti-value>choice_0</qti-value>
Expand Down Expand Up @@ -1843,7 +1843,7 @@ def test_multiple_question_types_mixed(self):
_normalize_xml(actual_manifest_xml),
)

self.assertEqual(exercise_file.checksum, "f15370f74b06b59bca6e289fe0e9cb87")
self.assertEqual(exercise_file.checksum, "f4689243ba9f9b5e1abdc874d2d7527d")

def test_unsupported_question_type(self):
"""Test that unsupported question types raise appropriate errors"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
"</qti-response-declaration>",
'<qti-choice-interaction response-identifier="RESPONSE" max-choices="1" '
'min-choices="0" orientation="vertical"><qti-prompt>Select the correct answer. '
'<img src="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.png" '
'srcset="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.png 1x, cccccccccccccccccccccccccccccccc.png 2x" '
'alt="diagram" /><object data="dddddddddddddddddddddddddddddddd.pdf" '
# srcset belongs on <source>, not <img> — QTI 3.0's ImgDType has no such attribute.
"<picture>"
'<source srcset="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.png 1x, cccccccccccccccccccccccccccccccc.png 2x" />'
'<img src="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.png" alt="diagram" />'
"</picture>"
'<object data="dddddddddddddddddddddddddddddddd.pdf" '
'type="application/pdf"></object></qti-prompt>'
'<qti-simple-choice identifier="choice_0" show-hide="show" fixed="false">Option A</qti-simple-choice>'
'<qti-simple-choice identifier="choice_1" show-hide="show" fixed="false">'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ class AssessmentItem(QTIBase):
label: Optional[str] = None
adaptive: bool = False
time_dependent: Optional[bool] = None
language: BCP47Language
xml__lang: BCP47Language
tool_name: str = "kolibri"
tool_version: str = "0.1"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def build_perseus_custom_interaction_item(
item = AssessmentItem(
identifier=identifier,
title=title,
language=language,
xml__lang=language,
adaptive=False,
time_dependent=False,
response_declaration=[
Expand Down Expand Up @@ -327,7 +327,7 @@ def convert_legacy_assessment_item_to_qti(
qti_item = AssessmentItem(
identifier=qti_item_id,
title=item.title,
language=item.language,
xml__lang=item.language,
adaptive=False,
time_dependent=False,
response_declaration=[response_declaration],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ XSD files backing `contentcuration.utils.assessment.qti.validation`. Source: QTI
SSML markup for text-to-speech hints).
Source: https://purl.imsglobal.org/spec/ssml/v1p1/schema/xsd/

`validation.py` also runs the Schematron rules `imsqti_itemv3p0p1_v1p0.xsd` carries inline
in `xs:appinfo` — without them the XSD's `extensionAnyTypeLax.Attr` wildcard admits any
attribute. Do not strip `xs:appinfo`.

All `schemaLocation` attributes that originally pointed at `https://purl.imsglobal.org/...`
have been rewritten to local relative filenames so the schema compiles with no network
access at runtime — required since bulk publish and ricecooker upload validate many items
Expand Down
Loading
Loading