You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[QTI] Serve legacy assessment items as QTI through the API endpoint (dual-read) #6030
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
During a short transition window, the AssessmentItem API endpoint converts legacy items to QTI on read — via the reusable conversion (#6003) — so the frontend only ever receives QTI and needs no backward-compatibility handling. The one-time global backfill that makes this permanent and removes the dual-read is #6007.
Architecture decided with the maintainer across an iterative session: blanket QTI type with the item XML in raw_data; XSD-authoritative validation across all sources; a legacy→QTI global migration with an API-layer dual-read; ricecooker upload delegating to the AssessmentItem serializer; and a Perseus custom-interaction contract confirmed against the QTI 3.0 specification. Claude mapped the existing publish/validation/ricecooker code, proposed the breakdown, and drafted each issue; the maintainer steered every decision and reviewed throughout.
Acceptance criteria — where each is covered (PR #6073):
AC
Covered by
Endpoint returns QTI for a still-legacy row
DualReadTestCase.test_supported_legacy_types_returned_as_qti (list route, all five convertible types) and test_detail_route_converts (serialize_object reaches consolidate by a different path)
perseus_question rows returned unchanged
test_perseus_question_returned_unchanged — type and raw_data byte-identical. test_native_qti_item_returned_unchanged pins the same for stored QTI.
Conversion failures surfaced, not silently dropped
consolidate re-raises as LegacyConversionError; test_unconvertible_type_raises_on_list_route and test_unconvertible_type_is_not_a_404_on_detail_route. The second is the load-bearing one — serialize_object catches (IndexError, ValueError, TypeError) and turns them into a 404, so a bare ValueError would report a corrupt row as a missing one.
Tests cover the dual-read across supported legacy types
single_selection, multiple_selection, true_false, input_question, free_response — one subTest each, every one asserting the returned raw_data is XSD-valid and carries the expected interaction element. The answerless choice variant (the shape the editor writes for every newly added question) is covered separately at both layers: test_answerless_choice_item_is_returned_as_valid_qti and test_single_selection_no_answers.
Two things on the PR still need a maintainer decision rather than a code change; both are called out in the PR body under Reviewer guidance: the sequencing constraint against AssessmentTab.vue still mounting the legacy AssessmentEditor, and raising rather than log-and-skip on the read path, which is the opposite of what was agreed for publish on #6029.
Activity
rtibblesbot commentedon Aug 1, 2026
Plan: Make the
AssessmentItemAPI read path return QTI for still-legacy rows, so the frontend only ever receivestype='QTI'with item XML inraw_data.@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
rtibblesbot commentedon Aug 1, 2026
Acceptance criteria — where each is covered (PR #6073):
DualReadTestCase.test_supported_legacy_types_returned_as_qti(list route, all five convertible types) andtest_detail_route_converts(serialize_objectreachesconsolidateby a different path)perseus_questionrows returned unchangedtest_perseus_question_returned_unchanged—typeandraw_databyte-identical.test_native_qti_item_returned_unchangedpins the same for stored QTI.consolidatere-raises asLegacyConversionError;test_unconvertible_type_raises_on_list_routeandtest_unconvertible_type_is_not_a_404_on_detail_route. The second is the load-bearing one —serialize_objectcatches(IndexError, ValueError, TypeError)and turns them into a 404, so a bareValueErrorwould report a corrupt row as a missing one.single_selection,multiple_selection,true_false,input_question,free_response— one subTest each, every one asserting the returnedraw_datais XSD-valid and carries the expected interaction element. The answerless choice variant (the shape the editor writes for every newly added question) is covered separately at both layers:test_answerless_choice_item_is_returned_as_valid_qtiandtest_single_selection_no_answers.pytest contentcuration/contentcuration/tests/viewsets/test_assessmentitem.py contentcuration/contentcuration/tests/utils/qti— 305 passed.Two things on the PR still need a maintainer decision rather than a code change; both are called out in the PR body under Reviewer guidance: the sequencing constraint against
AssessmentTab.vuestill mounting the legacyAssessmentEditor, and raising rather than log-and-skip on the read path, which is the opposite of what was agreed for publish on #6029.rtibblesbot commentedon Aug 1, 2026
The bot has been waiting for a response for 2 hour(s). It will resume automatically when someone replies.