From 851360efd962f7c01e90f8f649e047a6788729af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Mon, 15 Jan 2024 16:21:36 +0100 Subject: [PATCH 01/18] feat: Adjust server timers when resuming an execution --- model/Service/ConcurringSessionService.php | 263 +++++++++++++++++- models/classes/runner/session/TestSession.php | 8 +- 2 files changed, 267 insertions(+), 4 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index fb0c8d71b..dd55d2066 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -15,7 +15,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright (c) 2023 (original work) Open Assessment Technologies SA; + * Copyright (c) 2023-2024 (original work) Open Assessment Technologies SA; */ declare(strict_types=1); @@ -23,6 +23,10 @@ namespace oat\taoQtiTest\model\Service; use common_Exception; +use core_kernel_classes_Resource; +use DateTime; +use oat\generis\model\data\Ontology; +use oat\oatbox\service\ServiceManager; use oat\tao\model\featureFlag\FeatureFlagCheckerInterface; use oat\taoDelivery\model\execution\DeliveryExecution; use oat\taoDelivery\model\execution\DeliveryExecutionInterface; @@ -31,8 +35,15 @@ use oat\taoQtiTest\models\container\QtiTestDeliveryContainer; use oat\taoQtiTest\models\runner\QtiRunnerService; use oat\taoQtiTest\models\runner\QtiRunnerServiceContext; +use oat\taoQtiTest\models\runner\session\TestSession; +use oat\taoQtiTest\models\runner\time\QtiTimer; +use oat\taoQtiTest\models\runner\time\QtiTimerFactory; +use oat\taoQtiTest\models\runner\time\TimerAdjustmentService; +use oat\taoQtiTest\models\SessionStateService; +use oat\taoQtiTest\models\TestSessionService; use PHPSession; use Psr\Log\LoggerInterface; +use qtism\runtime\tests\RouteItem; use Throwable; class ConcurringSessionService @@ -42,6 +53,7 @@ class ConcurringSessionService private LoggerInterface $logger; private QtiRunnerService $qtiRunnerService; private RuntimeService $runtimeService; + private Ontology $ontology; private DeliveryExecutionService $deliveryExecutionService; private FeatureFlagCheckerInterface $featureFlagChecker; private ?PHPSession $currentSession; @@ -50,6 +62,7 @@ public function __construct( LoggerInterface $logger, QtiRunnerService $qtiRunnerService, RuntimeService $runtimeService, + Ontology $ontology, DeliveryExecutionService $deliveryExecutionService, FeatureFlagCheckerInterface $featureFlagChecker, PHPSession $currentSession = null @@ -57,6 +70,7 @@ public function __construct( $this->logger = $logger; $this->qtiRunnerService = $qtiRunnerService; $this->runtimeService = $runtimeService; + $this->ontology = $ontology; $this->deliveryExecutionService = $deliveryExecutionService; $this->featureFlagChecker = $featureFlagChecker; $this->currentSession = $currentSession ?? PHPSession::singleton(); @@ -68,6 +82,7 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi return; } + $now = $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true); $userIdentifier = $activeExecution->getUserIdentifier(); if (empty($userIdentifier) || $userIdentifier === 'anonymous') { @@ -99,6 +114,17 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi ) ); } + /*$this->pauseSingleExecution($execution, $now); + } + } catch (Throwable $e) { + $this->logger->warning( + sprintf( + '%s: Unable to pause delivery execution %s: %s', + self::class, + $executionId, + $e->getMessage() + ) + );*/ } } } @@ -124,9 +150,219 @@ public function setConcurringSession(string $executionId): void ); } + public function adjustTimers(DeliveryExecution $execution): void + { + $this->logger->info( + sprintf("Adjusting timers on test restart, current ts is %f", microtime(true)) + ); + + $testSession = $this->getTestSessionService()->getTestSession($execution); + + if ($testSession instanceof TestSession) { + $timer = $testSession->getTimer(); + } else { + $timer = $this->getQtiTimerFactory()->getTimer( + $execution->getIdentifier(), + $execution->getUserIdentifier() + ); + } + + $executionId = $execution->getIdentifier(); + + if ($this->currentSession->hasAttribute("pausedAt-{$executionId}")) { + $last = $this->currentSession->getAttribute("pausedAt-{$executionId}"); + $this->currentSession->removeAttribute("pausedAt-{$executionId}"); + + $this->logger->info( + sprintf("Adjusting timers based on timestamp stored in session: %f", $last) + ); + } + + if (!isset($last) && $testSession instanceof TestSession) { + $last = $this->getHighestItemTimestamp($testSession, $timer); + + $this->logger->info( + sprintf("Adjusting timers based on highest item timestamp: %f", $last) + ); + } + + if (isset($last) && $last > 0) { + $increaseSeconds = (new DateTime('now'))->format('U') - $last; + + $this->logger->info( + sprintf("Adding %.2f s increase to adj map", $increaseSeconds) + ); + + $this->getTimerAdjustmentService()->increase( + $testSession, + (int) $increaseSeconds + ); + + $testSession->suspend(); + $this->getTestSessionService()->persist($testSession); + } + } + + private function getHighestItemTimestamp(TestSession $testSession, QtiTimer $timer): ?float + { + $timestamps = []; + + foreach ($testSession->getRoute()->getAllRouteItems() as $item) { + /* @var $item RouteItem */ + $timestamp = $timer->getLastTimestamp( + $testSession->getItemTags($item) + ); + + if($timestamp > 0) { + $timestamps[] = $timestamp; + } + } + + if (empty($timestamps)) { + return null; + } + + return max($timestamps); + } + + private function getDeliveryIdByExecutionId(string $executionId): ?string + { + $executionClass = $this->ontology->getClass(DeliveryExecutionInterface::CLASS_URI); + $deliveryProperty = $this->ontology->getProperty(DeliveryExecutionInterface::PROPERTY_DELIVERY); + + $executionInstance = $executionClass->getResource($executionId); + $deliveryUri = $executionInstance->getUniquePropertyValue($deliveryProperty); + + if ($deliveryUri instanceof core_kernel_classes_Resource) { + $deliveryUri = $deliveryUri->getUri(); + } + + if ($deliveryUri) { + return (string)$deliveryUri; + } + + return null; + } + + /** + * @return string[] + */ + private function getExecutionIdsForOtherDeliveries(string $userUri, string $currentExecutionId): array + { + $currentDeliveryUri = (string)$this->getDeliveryIdByExecutionId($currentExecutionId); + $executions = $this->getActiveDeliveryExecutionsByUser($userUri); + + $this->logger->debug( + sprintf( + '%s: userUri=%s currentExecutionId=%s currentDeliveryUri=%s', + __FUNCTION__, + $userUri, + $currentExecutionId, + $currentDeliveryUri + ) + ); + + $executionIdsForOtherDeliveries = []; + + foreach ($executions as $execution) { + if ( + $execution->getIdentifier() !== $currentExecutionId + && $execution->getDelivery()->getUri() !== $currentDeliveryUri + ) { + $executionIdsForOtherDeliveries[] = $execution->getIdentifier(); + + $this->logger->debug( + sprintf( + '%s: execution %s belongs to other delivery "%s" != "%s"', + __FUNCTION__, + $execution->getIdentifier(), + $execution->getDelivery()->getUri(), + $currentDeliveryUri + ) + ); + } + } + + return $executionIdsForOtherDeliveries; + } + + /** + * @return DeliveryExecutionInterface[] + */ + private function getActiveDeliveryExecutionsByUser(string $userUri): array + { + $executionClass = $this->ontology->getClass(DeliveryExecutionInterface::CLASS_URI); + $executionInstances = $executionClass->searchInstances([ + DeliveryExecutionInterface::PROPERTY_SUBJECT => $userUri, + DeliveryExecutionInterface::PROPERTY_STATUS => DeliveryExecutionInterface::STATE_ACTIVE, + ], [ + 'like' => false + ]); + + $executions = []; + + foreach ($executionInstances as $executionInstance) { + $executions[] = $this->deliveryExecutionService->getDeliveryExecution( + $executionInstance->getUri() + ); + } + + return $executions; + } + + private function pauseSingleExecution(DeliveryExecution $execution, float $timestamp): void + { + $executionId = $execution->getIdentifier(); + + if ($execution->getState()->getUri() !== DeliveryExecutionInterface::STATE_ACTIVE) { + $this->logger->debug(sprintf('%s is not active, not pausing', $executionId)); + + return; + } + + $this->logger->info(sprintf('Pausing execution %s', $executionId)); + + $this->setConcurringSession($executionId); + + $context = $this->getContextByDeliveryExecution($execution); + $this->qtiRunnerService->endTimer($context); + $this->qtiRunnerService->pause($context); + + $this->pauseTimers($execution, $timestamp); + + $this->currentSession->setAttribute("pausedAt-{$executionId}", $timestamp); + } + + private function pauseTimers(DeliveryExecution $execution, float $timestamp): void + { + $assessmentTestSession = $this->getTestSessionService()->getTestSession($execution); + + if (!$assessmentTestSession instanceof TestSession) { + $this->logger->warning( + sprintf( + 'Test session for %s is not a TestSession instance (%s)', + $execution->getIdentifier(), + get_class($assessmentTestSession) + ) + ); + + return; + } + + $tags = $assessmentTestSession->getItemTags( + $assessmentTestSession->getRoute()->current() + ); + + $assessmentTestSession->getTimer()->end($tags, $timestamp)->save(); + $this->logger->debug( + sprintf('Pushed the current time on test suspension: %f', $timestamp) + ); + } + private function getContextByDeliveryExecution(DeliveryExecutionInterface $execution): QtiRunnerServiceContext { - $container = $this->runtimeService->getDeliveryContainer($execution->getDelivery()->getUri()); + $delivery = $execution->getDelivery(); + $container = $this->runtimeService->getDeliveryContainer($delivery->getUri()); if (!$container instanceof QtiTestDeliveryContainer) { throw new common_Exception( @@ -150,4 +386,27 @@ private function getContextByDeliveryExecution(DeliveryExecutionInterface $execu $execution->getOriginalIdentifier() ); } + + private function getQtiTimerFactory(): QtiTimerFactory + { + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return $this->getServiceManager()->get(QtiTimerFactory::SERVICE_ID); + } + + private function getTimerAdjustmentService(): TimerAdjustmentService + { + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return $this->getServiceManager()->get(TimerAdjustmentService::SERVICE_ID); + } + + private function getTestSessionService(): TestSessionService + { + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return $this->getServiceManager()->get(TestSessionService::SERVICE_ID); + } + + private function getServiceManager() + { + return ServiceManager::getServiceManager(); + } } diff --git a/models/classes/runner/session/TestSession.php b/models/classes/runner/session/TestSession.php index 0dfa46fa5..72f59799d 100644 --- a/models/classes/runner/session/TestSession.php +++ b/models/classes/runner/session/TestSession.php @@ -336,13 +336,17 @@ protected function updateCurrentDurationCache() { $target = $this->getTimerTarget(); $routeItem = $this->getCurrentRouteItem(); + $sources = [ - $routeItem->getAssessmentTest(), + $this->getAssessmentTest(), $this->getCurrentTestPart(), $this->getCurrentAssessmentSection(), - $routeItem->getAssessmentItemRef(), ]; + if ($routeItem instanceof RouteItem) { + array_unshift($sources, $routeItem->getAssessmentItemRef()); + } + foreach ($sources as $source) { $this->updateDurationCache($source->getIdentifier(), $target); } From e9bc6361234c321a2729d8fb45a86bf03a1b9e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Mon, 15 Jan 2024 16:29:24 +0100 Subject: [PATCH 02/18] chore: Cleanup --- model/Service/ConcurringSessionService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index dd55d2066..90b084abb 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -39,7 +39,6 @@ use oat\taoQtiTest\models\runner\time\QtiTimer; use oat\taoQtiTest\models\runner\time\QtiTimerFactory; use oat\taoQtiTest\models\runner\time\TimerAdjustmentService; -use oat\taoQtiTest\models\SessionStateService; use oat\taoQtiTest\models\TestSessionService; use PHPSession; use Psr\Log\LoggerInterface; From 83dd3f82ee0266896b5636202b6684fc0162c317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Mon, 15 Jan 2024 16:34:35 +0100 Subject: [PATCH 03/18] chore: Cleanup --- model/Service/ConcurringSessionService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index 90b084abb..d8a31c446 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -24,7 +24,6 @@ use common_Exception; use core_kernel_classes_Resource; -use DateTime; use oat\generis\model\data\Ontology; use oat\oatbox\service\ServiceManager; use oat\tao\model\featureFlag\FeatureFlagCheckerInterface; @@ -43,6 +42,7 @@ use PHPSession; use Psr\Log\LoggerInterface; use qtism\runtime\tests\RouteItem; +use DateTime; use Throwable; class ConcurringSessionService From a5b8f753981be5ed37c596fbb16b21f9d29328e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 10:25:52 +0100 Subject: [PATCH 04/18] chore: Update year in license header --- models/classes/runner/session/TestSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/classes/runner/session/TestSession.php b/models/classes/runner/session/TestSession.php index 72f59799d..03cf47654 100644 --- a/models/classes/runner/session/TestSession.php +++ b/models/classes/runner/session/TestSession.php @@ -15,7 +15,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright (c) 2016-2017 (original work) Open Assessment Technologies SA + * Copyright (c) 2016-2024 (original work) Open Assessment Technologies SA * */ From 8b5b6f29fa0b5c801841e2abcec586edab7f3468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 10:44:15 +0100 Subject: [PATCH 05/18] chore: Get the former order for duration cache updates --- models/classes/runner/session/TestSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/classes/runner/session/TestSession.php b/models/classes/runner/session/TestSession.php index 03cf47654..632287734 100644 --- a/models/classes/runner/session/TestSession.php +++ b/models/classes/runner/session/TestSession.php @@ -344,7 +344,7 @@ protected function updateCurrentDurationCache() ]; if ($routeItem instanceof RouteItem) { - array_unshift($sources, $routeItem->getAssessmentItemRef()); + $sources[] = $routeItem->getAssessmentItemRef(); } foreach ($sources as $source) { From 229d5a3d385f7cc0db29ab80bc9a3209ca2095dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 11:56:49 +0100 Subject: [PATCH 06/18] fix: Explicitly provide the component to apply the adjustment in --- model/Service/ConcurringSessionService.php | 49 +++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index d8a31c446..fdd135235 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -38,9 +38,11 @@ use oat\taoQtiTest\models\runner\time\QtiTimer; use oat\taoQtiTest\models\runner\time\QtiTimerFactory; use oat\taoQtiTest\models\runner\time\TimerAdjustmentService; +use oat\taoQtiTest\models\runner\time\TimerAdjustmentServiceInterface; use oat\taoQtiTest\models\TestSessionService; use PHPSession; use Psr\Log\LoggerInterface; +use qtism\data\QtiIdentifiable; use qtism\runtime\tests\RouteItem; use DateTime; use Throwable; @@ -186,15 +188,14 @@ public function adjustTimers(DeliveryExecution $execution): void } if (isset($last) && $last > 0) { - $increaseSeconds = (new DateTime('now'))->format('U') - $last; - - $this->logger->info( - sprintf("Adding %.2f s increase to adj map", $increaseSeconds) - ); + $delta = (new DateTime('now'))->format('U') - $last; + $this->logger->info(sprintf("Adjusting by %.2f s", $delta)); $this->getTimerAdjustmentService()->increase( $testSession, - (int) $increaseSeconds + (int) $delta, + TimerAdjustmentServiceInterface::TYPE_TIME_ADJUSTMENT, + $this->getAdjustmentPlace($testSession) ); $testSession->suspend(); @@ -202,6 +203,42 @@ public function adjustTimers(DeliveryExecution $execution): void } } + private function getAdjustmentPlace(TestSession $testSession): ?QtiIdentifiable + { + $test = $testSession->getAssessmentTest(); + $testPart = $testSession->getCurrentTestPart(); + $section = $testSession->getCurrentAssessmentSection(); + $itemRef = $testSession->getCurrentAssessmentItemRef(); + + if ($itemRef && $itemRef->hasTimeLimits()) { + $this->logger->info('Adjusting at the item level'); + + return $itemRef; + } + + if ($testPart && $testPart->hasTimeLimits()) { + $this->logger->info('Adjusting at the test part level'); + + return $testPart; + } + + if ($section && $section->hasTimeLimits()) { + $this->logger->info('Adjusting at the section level'); + + return $section; + } + + if ($test && $test->hasTimeLimits()) { + $this->logger->info('Adjusting at the test level'); + + return $section; + } + + $this->logger->info('Adjusting at the test session level'); + + return null; + } + private function getHighestItemTimestamp(TestSession $testSession, QtiTimer $timer): ?float { $timestamps = []; From fb85c06d7dddfa8a993d7935b1f173ef5045c6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 13:12:48 +0100 Subject: [PATCH 07/18] chore: Revert to the simpler approach applying the adjustment tot he test session as a whole --- model/Service/ConcurringSessionService.php | 40 +--------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index fdd135235..fbe3d79fb 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -193,9 +193,7 @@ public function adjustTimers(DeliveryExecution $execution): void $this->getTimerAdjustmentService()->increase( $testSession, - (int) $delta, - TimerAdjustmentServiceInterface::TYPE_TIME_ADJUSTMENT, - $this->getAdjustmentPlace($testSession) + (int) $delta ); $testSession->suspend(); @@ -203,42 +201,6 @@ public function adjustTimers(DeliveryExecution $execution): void } } - private function getAdjustmentPlace(TestSession $testSession): ?QtiIdentifiable - { - $test = $testSession->getAssessmentTest(); - $testPart = $testSession->getCurrentTestPart(); - $section = $testSession->getCurrentAssessmentSection(); - $itemRef = $testSession->getCurrentAssessmentItemRef(); - - if ($itemRef && $itemRef->hasTimeLimits()) { - $this->logger->info('Adjusting at the item level'); - - return $itemRef; - } - - if ($testPart && $testPart->hasTimeLimits()) { - $this->logger->info('Adjusting at the test part level'); - - return $testPart; - } - - if ($section && $section->hasTimeLimits()) { - $this->logger->info('Adjusting at the section level'); - - return $section; - } - - if ($test && $test->hasTimeLimits()) { - $this->logger->info('Adjusting at the test level'); - - return $section; - } - - $this->logger->info('Adjusting at the test session level'); - - return null; - } - private function getHighestItemTimestamp(TestSession $testSession, QtiTimer $timer): ?float { $timestamps = []; From c5d9648bd54b3c200f115badda27a7db80221448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 13:16:33 +0100 Subject: [PATCH 08/18] chore: Change log msg --- model/Service/ConcurringSessionService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index fbe3d79fb..24a7dd972 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -189,7 +189,7 @@ public function adjustTimers(DeliveryExecution $execution): void if (isset($last) && $last > 0) { $delta = (new DateTime('now'))->format('U') - $last; - $this->logger->info(sprintf("Adjusting by %.2f s", $delta)); + $this->logger->info(sprintf("Adjusting timers by %.2f s", $delta)); $this->getTimerAdjustmentService()->increase( $testSession, From 8da35ae7fae00e6455064b155c3590532aa0ec77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 15:12:43 +0100 Subject: [PATCH 09/18] fix: Store the pause timestamp associated to all execution IDs --- model/Service/ConcurringSessionService.php | 172 ++------------------- 1 file changed, 16 insertions(+), 156 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index 24a7dd972..dada0e03b 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -38,11 +38,9 @@ use oat\taoQtiTest\models\runner\time\QtiTimer; use oat\taoQtiTest\models\runner\time\QtiTimerFactory; use oat\taoQtiTest\models\runner\time\TimerAdjustmentService; -use oat\taoQtiTest\models\runner\time\TimerAdjustmentServiceInterface; use oat\taoQtiTest\models\TestSessionService; use PHPSession; use Psr\Log\LoggerInterface; -use qtism\data\QtiIdentifiable; use qtism\runtime\tests\RouteItem; use DateTime; use Throwable; @@ -97,14 +95,16 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi ); foreach ($userExecutions as $execution) { - if ($execution->getOriginalIdentifier() !== $activeExecution->getOriginalIdentifier()) { + $executionId = $execution->getOriginalIdentifier(); + if ($executionId !== $activeExecution->getOriginalIdentifier()) { try { $this->setConcurringSession($execution->getOriginalIdentifier()); $context = $this->getContextByDeliveryExecution($execution); - $this->qtiRunnerService->endTimer($context); $this->qtiRunnerService->pause($context); + + $this->currentSession->setAttribute("pausedAt-{$executionId}", $now); } catch (Throwable $e) { $this->logger->warning( sprintf( @@ -115,17 +115,6 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi ) ); } - /*$this->pauseSingleExecution($execution, $now); - } - } catch (Throwable $e) { - $this->logger->warning( - sprintf( - '%s: Unable to pause delivery execution %s: %s', - self::class, - $executionId, - $e->getMessage() - ) - );*/ } } } @@ -168,15 +157,20 @@ public function adjustTimers(DeliveryExecution $execution): void ); } - $executionId = $execution->getIdentifier(); + $ids = [ + $execution->getIdentifier(), + $execution->getOriginalIdentifier() + ]; - if ($this->currentSession->hasAttribute("pausedAt-{$executionId}")) { - $last = $this->currentSession->getAttribute("pausedAt-{$executionId}"); - $this->currentSession->removeAttribute("pausedAt-{$executionId}"); + foreach ($ids as $executionId) { + if ($this->currentSession->hasAttribute("pausedAt-{$executionId}")) { + $last = $this->currentSession->getAttribute("pausedAt-{$executionId}"); + $this->currentSession->removeAttribute("pausedAt-{$executionId}"); - $this->logger->info( - sprintf("Adjusting timers based on timestamp stored in session: %f", $last) - ); + $this->logger->info( + sprintf("Adjusting timers based on timestamp stored in session: %f", $last) + ); + } } if (!isset($last) && $testSession instanceof TestSession) { @@ -223,140 +217,6 @@ private function getHighestItemTimestamp(TestSession $testSession, QtiTimer $tim return max($timestamps); } - private function getDeliveryIdByExecutionId(string $executionId): ?string - { - $executionClass = $this->ontology->getClass(DeliveryExecutionInterface::CLASS_URI); - $deliveryProperty = $this->ontology->getProperty(DeliveryExecutionInterface::PROPERTY_DELIVERY); - - $executionInstance = $executionClass->getResource($executionId); - $deliveryUri = $executionInstance->getUniquePropertyValue($deliveryProperty); - - if ($deliveryUri instanceof core_kernel_classes_Resource) { - $deliveryUri = $deliveryUri->getUri(); - } - - if ($deliveryUri) { - return (string)$deliveryUri; - } - - return null; - } - - /** - * @return string[] - */ - private function getExecutionIdsForOtherDeliveries(string $userUri, string $currentExecutionId): array - { - $currentDeliveryUri = (string)$this->getDeliveryIdByExecutionId($currentExecutionId); - $executions = $this->getActiveDeliveryExecutionsByUser($userUri); - - $this->logger->debug( - sprintf( - '%s: userUri=%s currentExecutionId=%s currentDeliveryUri=%s', - __FUNCTION__, - $userUri, - $currentExecutionId, - $currentDeliveryUri - ) - ); - - $executionIdsForOtherDeliveries = []; - - foreach ($executions as $execution) { - if ( - $execution->getIdentifier() !== $currentExecutionId - && $execution->getDelivery()->getUri() !== $currentDeliveryUri - ) { - $executionIdsForOtherDeliveries[] = $execution->getIdentifier(); - - $this->logger->debug( - sprintf( - '%s: execution %s belongs to other delivery "%s" != "%s"', - __FUNCTION__, - $execution->getIdentifier(), - $execution->getDelivery()->getUri(), - $currentDeliveryUri - ) - ); - } - } - - return $executionIdsForOtherDeliveries; - } - - /** - * @return DeliveryExecutionInterface[] - */ - private function getActiveDeliveryExecutionsByUser(string $userUri): array - { - $executionClass = $this->ontology->getClass(DeliveryExecutionInterface::CLASS_URI); - $executionInstances = $executionClass->searchInstances([ - DeliveryExecutionInterface::PROPERTY_SUBJECT => $userUri, - DeliveryExecutionInterface::PROPERTY_STATUS => DeliveryExecutionInterface::STATE_ACTIVE, - ], [ - 'like' => false - ]); - - $executions = []; - - foreach ($executionInstances as $executionInstance) { - $executions[] = $this->deliveryExecutionService->getDeliveryExecution( - $executionInstance->getUri() - ); - } - - return $executions; - } - - private function pauseSingleExecution(DeliveryExecution $execution, float $timestamp): void - { - $executionId = $execution->getIdentifier(); - - if ($execution->getState()->getUri() !== DeliveryExecutionInterface::STATE_ACTIVE) { - $this->logger->debug(sprintf('%s is not active, not pausing', $executionId)); - - return; - } - - $this->logger->info(sprintf('Pausing execution %s', $executionId)); - - $this->setConcurringSession($executionId); - - $context = $this->getContextByDeliveryExecution($execution); - $this->qtiRunnerService->endTimer($context); - $this->qtiRunnerService->pause($context); - - $this->pauseTimers($execution, $timestamp); - - $this->currentSession->setAttribute("pausedAt-{$executionId}", $timestamp); - } - - private function pauseTimers(DeliveryExecution $execution, float $timestamp): void - { - $assessmentTestSession = $this->getTestSessionService()->getTestSession($execution); - - if (!$assessmentTestSession instanceof TestSession) { - $this->logger->warning( - sprintf( - 'Test session for %s is not a TestSession instance (%s)', - $execution->getIdentifier(), - get_class($assessmentTestSession) - ) - ); - - return; - } - - $tags = $assessmentTestSession->getItemTags( - $assessmentTestSession->getRoute()->current() - ); - - $assessmentTestSession->getTimer()->end($tags, $timestamp)->save(); - $this->logger->debug( - sprintf('Pushed the current time on test suspension: %f', $timestamp) - ); - } - private function getContextByDeliveryExecution(DeliveryExecutionInterface $execution): QtiRunnerServiceContext { $delivery = $execution->getDelivery(); From 4393a42f51e73edeb6317fa557884f0d330d3a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 15:14:24 +0100 Subject: [PATCH 10/18] chore: Cleanup --- model/Service/ConcurringSessionService.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index dada0e03b..b6554442a 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -96,9 +96,10 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi foreach ($userExecutions as $execution) { $executionId = $execution->getOriginalIdentifier(); + if ($executionId !== $activeExecution->getOriginalIdentifier()) { try { - $this->setConcurringSession($execution->getOriginalIdentifier()); + $this->setConcurringSession($executionId); $context = $this->getContextByDeliveryExecution($execution); $this->qtiRunnerService->endTimer($context); @@ -110,7 +111,7 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi sprintf( '%s: Unable to pause delivery execution %s: %s', self::class, - $execution->getOriginalIdentifier(), + $executionId, $e->getMessage() ) ); From 0519afb7b4e5ea9e95026d6c61f04998bedd253d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 15:23:15 +0100 Subject: [PATCH 11/18] chore: Cleanup --- model/Service/ConcurringSessionService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index b6554442a..4419ef5c9 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -23,7 +23,6 @@ namespace oat\taoQtiTest\model\Service; use common_Exception; -use core_kernel_classes_Resource; use oat\generis\model\data\Ontology; use oat\oatbox\service\ServiceManager; use oat\tao\model\featureFlag\FeatureFlagCheckerInterface; From b4e1bd2c5d6135b517060df8c983e18d6a393272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Tue, 16 Jan 2024 16:01:26 +0100 Subject: [PATCH 12/18] chore: Cleanup --- model/Service/ConcurringSessionService.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index 4419ef5c9..78434c187 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -23,7 +23,6 @@ namespace oat\taoQtiTest\model\Service; use common_Exception; -use oat\generis\model\data\Ontology; use oat\oatbox\service\ServiceManager; use oat\tao\model\featureFlag\FeatureFlagCheckerInterface; use oat\taoDelivery\model\execution\DeliveryExecution; @@ -51,7 +50,6 @@ class ConcurringSessionService private LoggerInterface $logger; private QtiRunnerService $qtiRunnerService; private RuntimeService $runtimeService; - private Ontology $ontology; private DeliveryExecutionService $deliveryExecutionService; private FeatureFlagCheckerInterface $featureFlagChecker; private ?PHPSession $currentSession; @@ -60,7 +58,6 @@ public function __construct( LoggerInterface $logger, QtiRunnerService $qtiRunnerService, RuntimeService $runtimeService, - Ontology $ontology, DeliveryExecutionService $deliveryExecutionService, FeatureFlagCheckerInterface $featureFlagChecker, PHPSession $currentSession = null @@ -68,7 +65,6 @@ public function __construct( $this->logger = $logger; $this->qtiRunnerService = $qtiRunnerService; $this->runtimeService = $runtimeService; - $this->ontology = $ontology; $this->deliveryExecutionService = $deliveryExecutionService; $this->featureFlagChecker = $featureFlagChecker; $this->currentSession = $currentSession ?? PHPSession::singleton(); From 4c1ca83e7a0a19ac51c1c976922a82076ad2648b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Wed, 17 Jan 2024 12:10:48 +0100 Subject: [PATCH 13/18] chore: Coding style improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrei Shapiro <59471572+shpran@users.noreply.github.com> Signed-off-by: Héctor Arroyo --- model/Service/ConcurringSessionService.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index 78434c187..dc55919bc 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -139,7 +139,7 @@ public function setConcurringSession(string $executionId): void public function adjustTimers(DeliveryExecution $execution): void { $this->logger->info( - sprintf("Adjusting timers on test restart, current ts is %f", microtime(true)) + sprintf('Adjusting timers on test restart, current ts is %f', microtime(true)) ); $testSession = $this->getTestSessionService()->getTestSession($execution); @@ -164,7 +164,7 @@ public function adjustTimers(DeliveryExecution $execution): void $this->currentSession->removeAttribute("pausedAt-{$executionId}"); $this->logger->info( - sprintf("Adjusting timers based on timestamp stored in session: %f", $last) + sprintf('Adjusting timers based on timestamp stored in session: %f', $last) ); } } @@ -173,13 +173,13 @@ public function adjustTimers(DeliveryExecution $execution): void $last = $this->getHighestItemTimestamp($testSession, $timer); $this->logger->info( - sprintf("Adjusting timers based on highest item timestamp: %f", $last) + sprintf('Adjusting timers based on highest item timestamp: %f', $last) ); } if (isset($last) && $last > 0) { $delta = (new DateTime('now'))->format('U') - $last; - $this->logger->info(sprintf("Adjusting timers by %.2f s", $delta)); + $this->logger->info(sprintf('Adjusting timers by %.2f s', $delta)); $this->getTimerAdjustmentService()->increase( $testSession, @@ -201,7 +201,7 @@ private function getHighestItemTimestamp(TestSession $testSession, QtiTimer $tim $testSession->getItemTags($item) ); - if($timestamp > 0) { + if ($timestamp > 0) { $timestamps[] = $timestamp; } } From 46d1793a6d2bea937e0b8513656f24c4935bc6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Wed, 17 Jan 2024 12:15:17 +0100 Subject: [PATCH 14/18] chore: Use a lower log level for debug messages in ConcurringSessionService --- model/Service/ConcurringSessionService.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index dc55919bc..760aa4ce3 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -138,7 +138,7 @@ public function setConcurringSession(string $executionId): void public function adjustTimers(DeliveryExecution $execution): void { - $this->logger->info( + $this->logger->debug( sprintf('Adjusting timers on test restart, current ts is %f', microtime(true)) ); @@ -163,7 +163,7 @@ public function adjustTimers(DeliveryExecution $execution): void $last = $this->currentSession->getAttribute("pausedAt-{$executionId}"); $this->currentSession->removeAttribute("pausedAt-{$executionId}"); - $this->logger->info( + $this->logger->debug( sprintf('Adjusting timers based on timestamp stored in session: %f', $last) ); } @@ -172,14 +172,14 @@ public function adjustTimers(DeliveryExecution $execution): void if (!isset($last) && $testSession instanceof TestSession) { $last = $this->getHighestItemTimestamp($testSession, $timer); - $this->logger->info( + $this->logger->debug( sprintf('Adjusting timers based on highest item timestamp: %f', $last) ); } if (isset($last) && $last > 0) { $delta = (new DateTime('now'))->format('U') - $last; - $this->logger->info(sprintf('Adjusting timers by %.2f s', $delta)); + $this->logger->debug(sprintf('Adjusting timers by %.2f s', $delta)); $this->getTimerAdjustmentService()->increase( $testSession, From 46991ef269d714f513a0cec907743c2879aa1f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Thu, 18 Jan 2024 14:42:03 +0100 Subject: [PATCH 15/18] fix: Don't try to update the duration cache for non-available sources --- models/classes/runner/session/TestSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/classes/runner/session/TestSession.php b/models/classes/runner/session/TestSession.php index 632287734..f05950363 100644 --- a/models/classes/runner/session/TestSession.php +++ b/models/classes/runner/session/TestSession.php @@ -347,7 +347,7 @@ protected function updateCurrentDurationCache() $sources[] = $routeItem->getAssessmentItemRef(); } - foreach ($sources as $source) { + foreach (array_filter($sources) as $source) { $this->updateDurationCache($source->getIdentifier(), $target); } } From 75fb5c8754bd293fa55f3594d55467c1a9c429a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Mon, 22 Jan 2024 13:28:29 +0100 Subject: [PATCH 16/18] fix: Pass the execution user ID when fetching the service context --- model/Service/ConcurringSessionService.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index 760aa4ce3..ca0cb4a74 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -178,13 +178,10 @@ public function adjustTimers(DeliveryExecution $execution): void } if (isset($last) && $last > 0) { - $delta = (new DateTime('now'))->format('U') - $last; - $this->logger->debug(sprintf('Adjusting timers by %.2f s', $delta)); + $delta = (int) round((new DateTime('now'))->format('U') - $last); + $this->logger->debug(sprintf('Adjusting timers by %d s', $delta)); - $this->getTimerAdjustmentService()->increase( - $testSession, - (int) $delta - ); + $this->getTimerAdjustmentService()->increase($testSession, $delta); $testSession->suspend(); $this->getTestSessionService()->persist($testSession); @@ -227,7 +224,8 @@ private function getContextByDeliveryExecution(DeliveryExecutionInterface $execu ); } - $testDefinition = $container->getSourceTest($execution); + $sessionId = $execution->getIdentifier(); + $testDefinitionUri = $container->getSourceTest($execution); $testCompilation = sprintf( '%s|%s', $container->getPrivateDirId($execution), @@ -235,9 +233,10 @@ private function getContextByDeliveryExecution(DeliveryExecutionInterface $execu ); return $this->qtiRunnerService->getServiceContext( - $testDefinition, + $testDefinitionUri, $testCompilation, - $execution->getOriginalIdentifier() + $sessionId, + $execution->getUserIdentifier() ); } From f84131469e651b0cad50644270d6b38160cc5435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Thu, 25 Jan 2024 10:44:09 +0100 Subject: [PATCH 17/18] fix: Store the item duration instead of the current timestamp on test suspension --- model/Service/ConcurringSessionService.php | 131 +++++++++++---------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/model/Service/ConcurringSessionService.php b/model/Service/ConcurringSessionService.php index ca0cb4a74..6000da453 100644 --- a/model/Service/ConcurringSessionService.php +++ b/model/Service/ConcurringSessionService.php @@ -32,15 +32,12 @@ use oat\taoQtiTest\models\container\QtiTestDeliveryContainer; use oat\taoQtiTest\models\runner\QtiRunnerService; use oat\taoQtiTest\models\runner\QtiRunnerServiceContext; -use oat\taoQtiTest\models\runner\session\TestSession; -use oat\taoQtiTest\models\runner\time\QtiTimer; -use oat\taoQtiTest\models\runner\time\QtiTimerFactory; use oat\taoQtiTest\models\runner\time\TimerAdjustmentService; use oat\taoQtiTest\models\TestSessionService; use PHPSession; use Psr\Log\LoggerInterface; -use qtism\runtime\tests\RouteItem; -use DateTime; +use qtism\common\datatypes\QtiDuration; +use qtism\data\AssessmentItemRef; use Throwable; class ConcurringSessionService @@ -76,7 +73,6 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi return; } - $now = $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true); $userIdentifier = $activeExecution->getUserIdentifier(); if (empty($userIdentifier) || $userIdentifier === 'anonymous') { @@ -97,10 +93,10 @@ public function pauseConcurrentSessions(DeliveryExecution $activeExecution): voi $this->setConcurringSession($executionId); $context = $this->getContextByDeliveryExecution($execution); + + $this->storeItemDuration($context, $executionId); $this->qtiRunnerService->endTimer($context); $this->qtiRunnerService->pause($context); - - $this->currentSession->setAttribute("pausedAt-{$executionId}", $now); } catch (Throwable $e) { $this->logger->warning( sprintf( @@ -139,75 +135,92 @@ public function setConcurringSession(string $executionId): void public function adjustTimers(DeliveryExecution $execution): void { $this->logger->debug( - sprintf('Adjusting timers on test restart, current ts is %f', microtime(true)) + sprintf( + 'Adjusting timers on execution %s restart', + $execution->getIdentifier() + ) ); $testSession = $this->getTestSessionService()->getTestSession($execution); - if ($testSession instanceof TestSession) { - $timer = $testSession->getTimer(); - } else { - $timer = $this->getQtiTimerFactory()->getTimer( - $execution->getIdentifier(), - $execution->getUserIdentifier() + if ($testSession->getCurrentAssessmentItemRef()) { + $duration = $testSession->getTimerDuration( + $testSession->getCurrentAssessmentItemRef()->getIdentifier(), + $testSession->getTimerTarget() + ); + + $this->logger->debug( + sprintf( + 'Timer duration on execution %s timer adjustment = %f', + $execution->getIdentifier(), + $duration->getSeconds(true) + ) ); - } - $ids = [ - $execution->getIdentifier(), - $execution->getOriginalIdentifier() - ]; + $ids = [ + $execution->getIdentifier(), + $execution->getOriginalIdentifier() + ]; + + foreach ($ids as $executionId) { + $key = "itemDuration-{$executionId}"; - foreach ($ids as $executionId) { - if ($this->currentSession->hasAttribute("pausedAt-{$executionId}")) { - $last = $this->currentSession->getAttribute("pausedAt-{$executionId}"); - $this->currentSession->removeAttribute("pausedAt-{$executionId}"); + if (!$this->currentSession->hasAttribute($key)) { + continue; + } + + $oldDuration = $this->currentSession->getAttribute($key); + $this->currentSession->removeAttribute($key); $this->logger->debug( - sprintf('Adjusting timers based on timestamp stored in session: %f', $last) + sprintf( + 'Timer duration on execution %s pause was %f', + $execution->getIdentifier(), + $oldDuration + ) ); - } - } - if (!isset($last) && $testSession instanceof TestSession) { - $last = $this->getHighestItemTimestamp($testSession, $timer); + $delta = (int) ceil($duration->getSeconds(true) - $oldDuration); - $this->logger->debug( - sprintf('Adjusting timers based on highest item timestamp: %f', $last) - ); - } + if ($delta > 0) { + $this->logger->debug(sprintf('Adjusting timers by %d s', $delta)); - if (isset($last) && $last > 0) { - $delta = (int) round((new DateTime('now'))->format('U') - $last); - $this->logger->debug(sprintf('Adjusting timers by %d s', $delta)); + $this->getTimerAdjustmentService()->increase($testSession, $delta); - $this->getTimerAdjustmentService()->increase($testSession, $delta); - - $testSession->suspend(); - $this->getTestSessionService()->persist($testSession); + $testSession->suspend(); + $this->getTestSessionService()->persist($testSession); + } + } } } - private function getHighestItemTimestamp(TestSession $testSession, QtiTimer $timer): ?float - { - $timestamps = []; - - foreach ($testSession->getRoute()->getAllRouteItems() as $item) { - /* @var $item RouteItem */ - $timestamp = $timer->getLastTimestamp( - $testSession->getItemTags($item) + private function storeItemDuration( + QtiRunnerServiceContext $context, + string $executionId + ): void { + $testSession = $context->getTestSession(); + $itemRef = $testSession->getCurrentAssessmentItemRef(); + + if ($itemRef instanceof AssessmentItemRef) { + /** @var QtiDuration $duration */ + $duration = $context->getTestSession()->getTimerDuration( + $itemRef->getIdentifier(), + $testSession->getTimerTarget() ); - if ($timestamp > 0) { - $timestamps[] = $timestamp; - } - } + $this->logger->debug( + sprintf( + 'duration when execution %s was paused = %f', + $executionId, + $duration->getSeconds(true) + ) + ); - if (empty($timestamps)) { - return null; + $this->currentSession->setAttribute( + "itemDuration-{$executionId}", + $duration->getSeconds(true) + ); } - - return max($timestamps); } private function getContextByDeliveryExecution(DeliveryExecutionInterface $execution): QtiRunnerServiceContext @@ -240,12 +253,6 @@ private function getContextByDeliveryExecution(DeliveryExecutionInterface $execu ); } - private function getQtiTimerFactory(): QtiTimerFactory - { - /** @noinspection PhpIncompatibleReturnTypeInspection */ - return $this->getServiceManager()->get(QtiTimerFactory::SERVICE_ID); - } - private function getTimerAdjustmentService(): TimerAdjustmentService { /** @noinspection PhpIncompatibleReturnTypeInspection */ From c6b4f7ba917eee33cb2664f445dfd52e8358f48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Arroyo?= Date: Thu, 25 Jan 2024 11:12:54 +0100 Subject: [PATCH 18/18] test: Update unit tests --- .../Service/ConcurringSessionServiceTest.php | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/test/unit/model/Service/ConcurringSessionServiceTest.php b/test/unit/model/Service/ConcurringSessionServiceTest.php index ccc1f4150..1b0c921e6 100644 --- a/test/unit/model/Service/ConcurringSessionServiceTest.php +++ b/test/unit/model/Service/ConcurringSessionServiceTest.php @@ -31,9 +31,13 @@ use oat\taoQtiTest\models\container\QtiTestDeliveryContainer; use oat\taoQtiTest\models\runner\QtiRunnerService; use oat\taoQtiTest\models\runner\QtiRunnerServiceContext; +use oat\taoQtiTest\models\runner\session\TestSession; +use oat\taoTests\models\runner\time\TimePoint; use PHPSession; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use qtism\common\datatypes\QtiDuration; +use qtism\data\AssessmentItemRef; class ConcurringSessionServiceTest extends TestCase { @@ -129,7 +133,6 @@ public function testPausesExecutionsForOtherDeliveries(): void ->with('FEATURE_FLAG_PAUSE_CONCURRENT_SESSIONS') ->willReturn(true); - $deliveryResource = $this->createMock(core_kernel_classes_Resource::class); $otherDeliveryResource = $this->createMock(core_kernel_classes_Resource::class); $executionDomainObject = $this->createMock(DeliveryExecution::class); $otherExecutionDomainObject = $this->createMock(DeliveryExecution::class); @@ -147,6 +150,10 @@ public function testPausesExecutionsForOtherDeliveries(): void ->expects($this->atLeastOnce()) ->method('getOriginalIdentifier') ->willReturn('https://example.com/execution/2'); + $otherExecutionDomainObject + ->expects($this->atLeastOnce()) + ->method('getIdentifier') + ->willReturn('https://example.com/execution/2'); $this->deliveryExecutionService ->expects($this->atLeastOnce()) @@ -191,7 +198,39 @@ public function testPausesExecutionsForOtherDeliveries(): void ->with('https://example.com/delivery/2') ->willReturn($qtiTestDeliveryContainer); + $itemRef = $this->createMock(AssessmentItemRef::class); + $itemRef + ->expects($this->once()) + ->method('getIdentifier') + ->willReturn('itemRef'); + + $duration = $this->createMock(QtiDuration::class); + $duration + ->expects($this->exactly(2)) + ->method('getSeconds') + ->with(true) + ->willReturn(123); + + $testSession = $this->createMock(TestSession::class); + $testSession + ->expects($this->once()) + ->method('getCurrentAssessmentItemRef') + ->willReturn($itemRef); + $testSession + ->expects($this->once()) + ->method('getTimerTarget') + ->willReturn(TimePoint::TARGET_SERVER); + $testSession + ->expects($this->once()) + ->method('getTimerDuration') + ->with('itemRef', TimePoint::TARGET_SERVER) + ->willReturn($duration); + $context = $this->createMock(QtiRunnerServiceContext::class); + $context + ->expects($this->exactly(2)) + ->method('getTestSession') + ->willReturn($testSession); $this->qtiRunnerService ->expects($this->once()) @@ -202,14 +241,18 @@ public function testPausesExecutionsForOtherDeliveries(): void 'https://example.com/execution/2' )->willReturn($context); - // Expectations - // $this->currentSession - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('setAttribute') - ->with( - 'pauseReason-https://example.com/execution/2', - 'PAUSE_REASON_CONCURRENT_TEST' + ->withConsecutive( + [ + 'pauseReason-https://example.com/execution/2', + 'PAUSE_REASON_CONCURRENT_TEST' + ], + [ + 'itemDuration-https://example.com/execution/2', + 123 + ] )->willReturn(null); $this->qtiRunnerService