Skip to content

Commit

Permalink
Merge pull request #302 from oat-sa/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
Businesspunk authored Oct 28, 2021
2 parents 8af0f5d + e2b4132 commit aee3872
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 63 deletions.
101 changes: 58 additions & 43 deletions src/qtism/runtime/pci/json/Unmarshaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,44 +146,7 @@ public function unmarshall($json)
// This is a base.
return $this->unmarshallUnit($json);
} elseif (in_array('list', $keys)) {
$keys = array_keys($json['list']);
if (isset($keys[0]) === false) {
$msg = 'No baseType provided for list.';
throw new UnmarshallingException($msg, UnmarshallingException::NOT_PCI);
}

$baseType = BaseType::getConstantByName($keys[0]);

if ($baseType === false) {
$msg = "Unknown QTI baseType '" . $keys[0] . "'.";
$code = UnmarshallingException::NOT_PCI;
throw new UnmarshallingException($msg, $code);
}

$returnValue = new MultipleContainer($baseType);

if (!is_array($json['list'][$keys[0]])) {
$msg = 'list is not an array';
throw new UnmarshallingException($msg, UnmarshallingException::NOT_PCI);
}

// This is a list.
foreach ($json['list'][$keys[0]] as $v) {
try {
if ($v === null) {
$returnValue[] = $this->unmarshallUnit(['base' => $v]);
} else {
$returnValue[] = $this->unmarshallUnit(['base' => [$keys[0] => $v]]);
}
} catch (InvalidArgumentException $e) {
$strBaseType = BaseType::getNameByConstant($baseType);
$msg = "A value is not compliant with the '${strBaseType}' baseType.";
$code = UnmarshallingException::NOT_PCI;
throw new UnmarshallingException($msg, $code);
}
}

return $returnValue;
return $this->unmarshallList($json);
} elseif (in_array('record', $keys)) {
// This is a record.
$returnValue = new RecordContainer();
Expand All @@ -199,14 +162,14 @@ public function unmarshall($json)
throw new UnmarshallingException($msg, $code);
}

if (isset($v['base']) || (array_key_exists('base', $v) && $v['base'] === null)) {
$unit = ['base' => $v['base']];
if (isset($v['base'])) {
$returnValue[$v['name']] = $this->unmarshallUnit(['base' => $v['base']]);
} elseif (isset($v['list'])) {
$returnValue[$v['name']] = $this->unmarshallList($v);
} else {
// No value found, let's go for a null value.
$unit = ['base' => null];
$returnValue[$v['name']] = $this->unmarshallUnit(['base' => null]);
}

$returnValue[$v['name']] = $this->unmarshallUnit($unit);
}

return $returnValue;
Expand Down Expand Up @@ -474,4 +437,56 @@ protected function unmarshallIdentifier(array $unit)
{
return new QtiIdentifier($unit['base']['identifier']);
}

/**
* Parse associate array, return MultipleContainer
* which contains converted to BaseType items of 'list'
*
* @param array $parsedJson
* @return MultipleContainer
* @throws FileManagerException
* @throws UnmarshallingException
*/
protected function unmarshallList(array $parsedJson)
{
$list = $parsedJson['list'];
$key = key($list);

if ($key === null) {
$msg = 'No baseType provided for list.';
throw new UnmarshallingException($msg, UnmarshallingException::NOT_PCI);
}

$baseType = BaseType::getConstantByName($key);

if ($baseType === false) {
$msg = "Unknown QTI baseType '" . $key . "'.";
$code = UnmarshallingException::NOT_PCI;
throw new UnmarshallingException($msg, $code);
}

$returnValue = new MultipleContainer($baseType);

if (!is_array($list[$key])) {
$msg = 'list is not an array';
throw new UnmarshallingException($msg, UnmarshallingException::NOT_PCI);
}

foreach ($list[$key] as $v) {
try {
if ($v === null) {
$returnValue[] = $this->unmarshallUnit(['base' => $v]);
} else {
$returnValue[] = $this->unmarshallUnit(['base' => [$key => $v]]);
}
} catch (InvalidArgumentException $e) {
$strBaseType = BaseType::getNameByConstant($baseType);
$msg = "A value is not compliant with the '${strBaseType}' baseType.";
$code = UnmarshallingException::NOT_PCI;
throw new UnmarshallingException($msg, $code);
}
}

return $returnValue;
}
}
12 changes: 9 additions & 3 deletions src/qtism/runtime/tests/AssessmentItemSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -736,8 +736,15 @@ public function beginAttempt()
$this[$endAttemptIdentifier] = new QtiBoolean(false);
}

// Increment the built-in variable 'numAttempts' by one.
$this['numAttempts']->setValue($numAttempts + 1);
if (
$this['numAttempts']->getValue() === 0
|| $this['completionStatus']->getValue() === self::COMPLETION_STATUS_COMPLETED
) {
// Increment the built-in variable 'numAttempts' by one.
$this['numAttempts']->setValue($numAttempts + 1);

$this['completionStatus']->setValue(self::COMPLETION_STATUS_UNKNOWN);
}

// The session get the INTERACTING state.
$this->setState(AssessmentItemSessionState::INTERACTING);
Expand Down Expand Up @@ -960,7 +967,6 @@ public function endCandidateSession()
$code = AssessmentItemSessionException::STATE_VIOLATION;
throw new AssessmentItemSessionException($msg, $this, $code);
} else {
$this->endAttempt(null, false);
$this->setState(AssessmentItemSessionState::SUSPENDED);
}
}
Expand Down
29 changes: 27 additions & 2 deletions test/qtismtest/runtime/pci/json/JsonUnmarshallerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public function testUnmarshallFileHash()

$json = sprintf(
'{ "base" : { "%s" : {
"mime" : "%s",
"data" : "%s",
"mime" : "%s",
"data" : "%s",
"name" : "%s",
"id" : "%s" } } }',
FileHash::FILE_HASH_KEY,
Expand Down Expand Up @@ -375,4 +375,29 @@ public function unmarshallInvalidProvider()
['{ "record" : [ { "namez" } ] '],
];
}

public function testUnmarshallListWithinRecord()
{
$unmarshaller = self::createUnmarshaller();
$json = '
{
"record": [
{ "name" : "Søyler", "list": {"string" : ["SØYLE 1: navn=1, verdi=3", "SØYLE 2: navn=1, verdi=4"] } }
]
}
';

$container = $unmarshaller->unmarshall($json);
$list = $container->getArrayCopy(true);

$key = "Søyler";

$this::assertTrue(array_key_exists($key, $list) );

$list = $list[$key];
$items = $list->getArrayCopy();

$this::assertEquals("SØYLE 1: navn=1, verdi=3", $items[0]->getValue());
$this::assertEquals("SØYLE 2: navn=1, verdi=4", $items[1]->getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public function testEvolutionBasicTimeLimitsUnderflowOverflow()

// Try again by waiting too much to respect max time at endAttempt time.
$itemSession->beginAttempt();
$this::assertEquals(0, $itemSession->getRemainingAttempts());
$this::assertEquals(1, $itemSession->getRemainingAttempts());
$itemSession->setTime(self::createDate('2014-07-14 13:00:03'));

try {
Expand All @@ -298,7 +298,7 @@ public function testEvolutionBasicTimeLimitsUnderflowOverflow()
$this::assertEquals(AssessmentItemSessionException::DURATION_OVERFLOW, $e->getCode());
}

$this::assertEquals(2, $itemSession['numAttempts']->getValue());
$this::assertEquals(1, $itemSession['numAttempts']->getValue());
$this::assertEquals(AssessmentItemSessionState::CLOSED, $itemSession->getState());
$this::assertInstanceOf(QtiFloat::class, $itemSession['SCORE']);
$this::assertEquals(0.0, $itemSession['SCORE']->getValue());
Expand Down
75 changes: 62 additions & 13 deletions test/qtismtest/runtime/tests/AssessmentTestSessionAttemptsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,83 @@
use qtism\runtime\common\ResponseVariable;
use qtism\runtime\common\State;
use qtism\runtime\tests\AssessmentItemSession;
use qtism\runtime\tests\AssessmentTestSession;
use qtism\runtime\tests\AssessmentTestSessionException;
use qtismtest\QtiSmAssessmentTestSessionTestCase;

/**
* Class AssessmentTestSessionAttemptsTest
*/
class AssessmentTestSessionAttemptsTest extends QtiSmAssessmentTestSessionTestCase
{
/** @var AssessmentTestSession */
private $session;

public function setUp(): void
{
$this->session = self::instantiate(self::samplesDir() . 'custom/runtime/attempts/max_3_attempts_nonlinear.xml');
}

public function testMultipleAttempts()
{
$session = self::instantiate(self::samplesDir() . 'custom/runtime/attempts/max_3_attempts_nonlinear.xml');
$session->beginTestSession();
$this->session->beginTestSession();

// Q01 - first attempt.
$session->beginAttempt();
$session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))]));
$this->session->beginAttempt();
$this->session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))]));

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_COMPLETED, $this->session['Q01.completionStatus']);

// Q01 - second attempt.
$this->session->beginAttempt();
$this->session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceC'))]));

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_COMPLETED, $session['Q01.completionStatus']);
$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_COMPLETED, $this->session['Q01.completionStatus']);

// Q01 - third attempt. The completion status is now completed.
$this->session->beginAttempt();
$this->session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))]));

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_COMPLETED, $this->session['Q01.completionStatus']);
}

public function testDoesNotTakeAnAttemptWhenInvokingBeginAttemptConsecutivelyWithoutEndingTheAttempt()
{
$this->session->beginTestSession();

// Q02 - second attempt.
$session->beginAttempt();
$session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceC'))]));
// Q01 - first attempt.
$this->session->beginAttempt();

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_UNKNOWN, $this->session['Q01.completionStatus']);
$this::assertEquals(1, $this->session['Q01.numAttempts']->getValue());

// Q01 - same attempt.
$this->session->beginAttempt();

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_UNKNOWN, $this->session['Q01.completionStatus']);
$this::assertEquals(1, $this->session['Q01.numAttempts']->getValue());
}

public function testThrowsWhenMaxAttemptsIsReached()
{
$this::expectException(AssessmentTestSessionException::class);
$this::expectExceptionMessage('Maximum number of attempts of Item Session \'Q01.0\' reached.');

$this->session->beginTestSession();

// Q01 - first attempt.
$this->session->beginAttempt();
$this->session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))]));

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_COMPLETED, $session['Q01.completionStatus']);
// Q01 - second attempt.
$this->session->beginAttempt();
$this->session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceC'))]));

// Q03 - third attempt. The completion status is now completed.
$session->beginAttempt();
$session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))]));
// Q01 - third attempt. The completion status is now completed.
$this->session->beginAttempt();
$this->session->endAttempt(new State([new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('ChoiceA'))]));

$this::assertEquals(AssessmentItemSession::COMPLETION_STATUS_COMPLETED, $session['Q01.completionStatus']);
// Must throw an exception
$this->session->beginAttempt();
}
}

0 comments on commit aee3872

Please sign in to comment.