Skip to content
Merged
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
7 changes: 7 additions & 0 deletions xblocks_contrib/problem/capa_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,13 @@ def get_progress(self):
else:
raw_earned = raw_possible = 0
Comment thread
marslanabdulrauf marked this conversation as resolved.

if not self.attempts:
# Nothing has ever been submitted for this user/block, so there's no
# real grade history to protect. Reflect the problem's current
# definition instead of a stale persisted score (e.g. left over from
# before an author edited the problem after it was first previewed).
raw_possible = self.lcp.get_max_score()

if raw_possible > 0:
if self.weight is not None:
# Progress objects expect total > 0
Expand Down
72 changes: 72 additions & 0 deletions xblocks_contrib/problem/tests/test_capa_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2742,6 +2742,78 @@ def test_get_progress_calculate_progress_fraction(self, mock_progress):
other_block.get_progress()
mock_progress.assert_called_with(1, 1)

@patch("xblocks_contrib.problem.capa_block.Progress")
def test_get_progress_ignores_stale_score_when_unattempted(self, mock_progress):
"""
If a problem has never been attempted, get_progress() should reflect the
problem's current max_score() rather than a stale persisted score -- e.g.
one left over from a preview/bind that happened before the problem's
content was edited to add a real scorable response.
"""
mock_progress.return_value = True
block = CapaFactory.create(attempts=0)
block.weight = 1
# Simulate a stale persisted score, as if this block had been bound/previewed
# before its current content (with a scorable response worth 1 point) existed.
block.score = Score(raw_earned=0, raw_possible=0)

block.get_progress()

mock_progress.assert_called_with(0, 1)

@patch("xblocks_contrib.problem.capa_block.Progress")
def test_get_progress_keeps_historical_score_when_attempted(self, mock_progress):
"""
Once a problem has been attempted, get_progress() must not silently swap in
the current max_score() -- the historical score/denominator the learner was
actually graded against must be preserved, even if the problem's content or
weight has since changed.
"""
mock_progress.return_value = True
block = CapaFactory.create(attempts=1)
block.weight = None
# Historical score computed against an older version of the problem (e.g.
# before the author edited it, changing its current max_score() to 1).
block.score = Score(raw_earned=1, raw_possible=2)

block.get_progress()

mock_progress.assert_called_with(1, 2)

def test_get_progress_uses_lcp_get_max_score_when_unattempted(self):
"""
get_progress() should use self.lcp.get_max_score() -- not the separate
max_score() helper, which would re-parse the problem from scratch --
to reflect the problem's current point value when it hasn't been
attempted yet.
"""
block = CapaFactory.create(attempts=0)
block.weight = None

# If max_score() were called it would re-parse the problem and return a
# value consistent with the current XML (1) -- give it an obviously wrong
# value instead so we can tell whether it was actually invoked.
block.max_score = Mock(return_value=999)

_, total = block.get_display_progress()

block.max_score.assert_not_called()
assert total == 1

def test_get_progress_shows_genuine_zero_not_stale_value(self):
"""
A never-attempted problem with no scorable response should show 0 points
possible, even if a stale non-zero score was previously persisted -- 0
because the problem is genuinely worth 0 points must not be treated the
same as "0 because max_score() couldn't be computed", which would wrongly
fall back to the old value.
"""
block = CapaFactory.create(xml="<problem/>", attempts=0)
block.weight = None
block.score = Score(raw_earned=0, raw_possible=5)

assert block.get_progress() is None

@ddt.data(
("never", True, None),
("never", False, None),
Expand Down