558 lines
20 KiB
PHP
558 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; under version 2
|
|
* of the License (non-upgradable).
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* 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) 2015 (original work) Open Assessment Technologies SA;
|
|
*
|
|
*
|
|
*/
|
|
|
|
namespace oat\taoProctoring\test\integration\monitorCache;
|
|
|
|
require_once dirname(__FILE__).'/../../../../tao/includes/raw_start.php';
|
|
|
|
use common_persistence_Persistence;
|
|
use common_persistence_SqlPersistence;
|
|
use oat\generis\persistence\PersistenceManager;
|
|
use oat\tao\test\TaoPhpUnitTestRunner;
|
|
use oat\taoProctoring\model\monitorCache\implementation\MonitoringStorage;
|
|
use oat\taoProctoring\scripts\install\db\DbSetup;
|
|
use Zend\ServiceManager\ServiceLocatorInterface;
|
|
|
|
/**
|
|
* class DeliveryMonitoringData
|
|
*
|
|
* Represents data model of delivery execution.
|
|
*
|
|
* @package oat\taoProctoring
|
|
* @author Aleh Hutnikau <hutnikau@1pt.com>
|
|
*/
|
|
class DeliveryMonitoringServiceTest extends TaoPhpUnitTestRunner
|
|
{
|
|
/**
|
|
* @var MonitoringStorage
|
|
*/
|
|
protected $service;
|
|
/**
|
|
* @var common_persistence_SqlPersistence
|
|
*/
|
|
protected $persistence;
|
|
protected $deliveryExecutionId = 'http://sample/first.rdf#i1450191587554175_test_record';
|
|
protected $fixtureData = [
|
|
[
|
|
MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554175_test_record',
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_1',
|
|
MonitoringStorage::COLUMN_STATUS => 'active_test',
|
|
'user_uri' => 'http://sample/first.rdf#i1450191587554175_test_user',
|
|
'error_code' => 1,
|
|
'session_id' => 'i1450191587554175',
|
|
],
|
|
[
|
|
MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554176_test_record',
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_2',
|
|
MonitoringStorage::COLUMN_STATUS => 'paused_test',
|
|
'user_uri' => 'http://sample/first.rdf#i1450191587554176_test_user',
|
|
'error_code' => 2,
|
|
'session_id' => 'i1450191587554176',
|
|
],
|
|
[
|
|
MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554177_test_record',
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_3',
|
|
MonitoringStorage::COLUMN_STATUS => 'finished_test',
|
|
'user_uri' => 'http://sample/first.rdf#i1450191587554177_test_user',
|
|
'error_code' => 3,
|
|
'session_id' => 'i1450191587554177',
|
|
],
|
|
[
|
|
MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554178_test_record',
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_4',
|
|
MonitoringStorage::COLUMN_STATUS => 'finished_test',
|
|
'user_uri' => 'http://sample/first.rdf#i1450191587554178_test_user',
|
|
'error_code' => 0,
|
|
'session_id' => 'i1450191587554178',
|
|
],
|
|
[
|
|
MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554179_test_record',
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_5',
|
|
MonitoringStorage::COLUMN_STATUS => 'terminated_test',
|
|
],
|
|
];
|
|
|
|
|
|
private function getService(): MonitoringStorage {
|
|
if (!$this->service) {
|
|
$this->service = new MonitoringStorage([
|
|
MonitoringStorage::OPTION_PERSISTENCE => 'test_monitoring',
|
|
MonitoringStorage::OPTION_PRIMARY_COLUMNS => array(
|
|
'delivery_execution_id',
|
|
'status',
|
|
'current_assessment_item',
|
|
'test_taker',
|
|
'authorized_by',
|
|
'start_time',
|
|
'end_time'
|
|
)
|
|
]);
|
|
}
|
|
|
|
return $this->service;
|
|
}
|
|
|
|
/**
|
|
* @return common_persistence_Persistence
|
|
*/
|
|
private function getPersistence(): common_persistence_Persistence
|
|
{
|
|
if (!$this->persistence) {
|
|
$sqlMock = $this->getSqlMock('test_monitoring');
|
|
$cacheMock = $this->getKvMock('cache');
|
|
$this->persistence = $sqlMock->getPersistenceById('test_monitoring');
|
|
DbSetup::generateTable($this->persistence);
|
|
|
|
$pmMock = $this->getMockBuilder(PersistenceManager::class)
|
|
->setMethods(['getPersistenceById'])
|
|
->getMock();
|
|
|
|
$pmMock->method('getPersistenceById')
|
|
->willReturnCallback(static function ($persistenceId) use ($sqlMock, $cacheMock) {
|
|
if ($persistenceId === 'cache') {
|
|
return $cacheMock->getPersistenceById('cache');
|
|
}
|
|
if ($persistenceId === 'test_monitoring') {
|
|
return $sqlMock->getPersistenceById('test_monitoring');
|
|
}
|
|
return false;
|
|
});
|
|
|
|
$sl = $this->prophesize(ServiceLocatorInterface::class);
|
|
$sl->get(PersistenceManager::SERVICE_ID)->willReturn($pmMock);
|
|
$this->getService()->setServiceLocator($sl->reveal());
|
|
}
|
|
return $this->persistence;
|
|
}
|
|
|
|
public function setUp(): void
|
|
{
|
|
$this->getService();
|
|
$this->getPersistence();
|
|
}
|
|
|
|
public function tearDown(): void
|
|
{
|
|
parent::tearDown();
|
|
$this->deleteTestData();
|
|
}
|
|
|
|
/**
|
|
* @after
|
|
* @before
|
|
*/
|
|
public function deleteTestData()
|
|
{
|
|
$sql = 'DELETE FROM ' . MonitoringStorage::TABLE_NAME .
|
|
' WHERE ' . MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID . " LIKE '%_test_record'";
|
|
|
|
$this->getPersistence()->exec($sql);
|
|
}
|
|
|
|
public function testPartialSave()
|
|
{
|
|
// 1. create regular cache record
|
|
$dataToUpdate = [
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_id',
|
|
MonitoringStorage::COLUMN_STATUS => 'active',
|
|
];
|
|
$secondaryDataToUpdate = [
|
|
'secondary_data_key' => 'secondary_data_val',
|
|
'secondary_data_key_2' => 'secondary_data_val_2',
|
|
];
|
|
$dataToCheck = $dataToUpdate;
|
|
$secondaryDataToCheck = $secondaryDataToUpdate;
|
|
|
|
$this->save(false, false, $dataToUpdate, $secondaryDataToUpdate, $dataToCheck, $secondaryDataToCheck);
|
|
|
|
// 2. update partially
|
|
|
|
$dataToCheck = $dataToUpdate;
|
|
$secondaryDataToCheck = $secondaryDataToUpdate;
|
|
$dataToUpdate = [
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_id_STEP_2',
|
|
];
|
|
$dataToCheck[MonitoringStorage::COLUMN_TEST_TAKER] = 'test_taker_id_STEP_2';
|
|
$secondaryDataToUpdate = [
|
|
'secondary_data_key_2' => 'secondary_data_val_2_STEP_2',
|
|
];
|
|
$secondaryDataToCheck['secondary_data_key_2'] = 'secondary_data_val_2_STEP_2';
|
|
|
|
|
|
$this->save(true, true, $dataToUpdate, $secondaryDataToUpdate, $dataToCheck, $secondaryDataToCheck);
|
|
}
|
|
|
|
public function testSaveFallbackAfterInsertFailed()
|
|
{
|
|
// 1. create regular cache record
|
|
$dataToUpdate = [
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_id',
|
|
MonitoringStorage::COLUMN_STATUS => 'active',
|
|
];
|
|
$secondaryDataToUpdate = [
|
|
'secondary_data_key' => 'secondary_data_val',
|
|
'secondary_data_key_2' => 'secondary_data_val_2',
|
|
];
|
|
$dataToCheck = $dataToUpdate;
|
|
$secondaryDataToCheck = $secondaryDataToUpdate;
|
|
|
|
$this->save(false, false, $dataToUpdate, $secondaryDataToUpdate, $dataToCheck, $secondaryDataToCheck);
|
|
|
|
// 2. check fallback of partialSave() (insert fails, update works)
|
|
|
|
$dataToCheck = $dataToUpdate;
|
|
$dataToUpdate[MonitoringStorage::COLUMN_TEST_TAKER] = 'test_taker_id_STEP_2';
|
|
$dataToCheck[MonitoringStorage::COLUMN_TEST_TAKER] = 'test_taker_id_STEP_2';
|
|
$secondaryDataToUpdate['secondary_data_key_2'] = 'secondary_data_val_2_STEP_2';
|
|
$secondaryDataToCheck['secondary_data_key_2'] = 'secondary_data_val_2_STEP_2';
|
|
|
|
$this->save(false, true, $dataToUpdate, $secondaryDataToUpdate, $dataToCheck, $secondaryDataToCheck);
|
|
}
|
|
|
|
public function testSaveFallbackAfterUpdateReturns0Rows()
|
|
{
|
|
// 1. create regular cache record
|
|
$dataToUpdate = [
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_id',
|
|
MonitoringStorage::COLUMN_STATUS => 'active',
|
|
];
|
|
$secondaryDataToUpdate = [
|
|
'secondary_data_key' => 'secondary_data_val',
|
|
'secondary_data_key_2' => 'secondary_data_val_2',
|
|
];
|
|
$dataToCheck = $dataToUpdate;
|
|
$secondaryDataToCheck = $secondaryDataToUpdate;
|
|
|
|
$this->save(false, true, $dataToUpdate, $secondaryDataToUpdate, $dataToCheck, $secondaryDataToCheck);
|
|
}
|
|
|
|
/**
|
|
* @param $partialModel
|
|
* @param $saveAsPartial
|
|
* @param array $dataToUpdate
|
|
* @param array $secondaryDataToUpdate
|
|
* @param array $dataToCheck
|
|
* @param array $secondaryDataToCheck
|
|
* @throws \common_exception_NotFound
|
|
*/
|
|
protected function save($partialModel, $saveAsPartial, array $dataToUpdate, array $secondaryDataToUpdate, array $dataToCheck, array $secondaryDataToCheck)
|
|
{
|
|
$deliveryExecution = $this->getDeliveryExecution($this->deliveryExecutionId, 'active');
|
|
if ($partialModel) {
|
|
$dataModel = $this->service->createMonitoringData($deliveryExecution);
|
|
} else {
|
|
$dataModel = $this->service->getData($deliveryExecution);
|
|
}
|
|
|
|
foreach ($dataToUpdate as $key => $val) {
|
|
$dataModel->addValue($key, $val, true);
|
|
}
|
|
foreach ($secondaryDataToUpdate as $secKey => $secVal) {
|
|
$dataModel->addValue($secKey, $secVal, true);
|
|
}
|
|
|
|
if ($saveAsPartial) {
|
|
$saveResult = $this->service->partialSave($dataModel);
|
|
} else {
|
|
$saveResult = $this->service->save($dataModel);
|
|
}
|
|
|
|
$this->assertTrue($saveResult);
|
|
$this->assertEmpty($dataModel->getErrors());
|
|
|
|
|
|
$insertedData = $this->getRecordByDeliveryExecutionId($this->deliveryExecutionId);
|
|
$this->assertNotEmpty($insertedData);
|
|
$this->assertEquals(1, count($insertedData));
|
|
$this->assertEquals('active', $insertedData[0][MonitoringStorage::COLUMN_STATUS]);
|
|
|
|
foreach ($dataToCheck as $key => $val) {
|
|
$this->assertEquals($insertedData[0][$key], $val);
|
|
}
|
|
|
|
// check key value data
|
|
$service = $this->service;
|
|
$insertedKvData = $this->getKvRecordsByParentId($insertedData[0][$service::COLUMN_ID]);
|
|
|
|
$this->assertNotEmpty($insertedKvData);
|
|
|
|
$insertedKvDataNotEmptyFieldCount = 0;
|
|
foreach ($insertedKvData as $fieldData) {
|
|
$fieldValue = $fieldData[MonitoringStorage::KV_COLUMN_VALUE];
|
|
if ($fieldValue !== null) {
|
|
$insertedKvDataNotEmptyFieldCount++;
|
|
}
|
|
}
|
|
|
|
$this->assertEquals(count($secondaryDataToCheck), $insertedKvDataNotEmptyFieldCount);
|
|
|
|
foreach ($insertedKvData as $kvData) {
|
|
$key = $kvData[MonitoringStorage::KV_COLUMN_KEY];
|
|
$val = $kvData[MonitoringStorage::KV_COLUMN_VALUE];
|
|
if ($val !== null) {
|
|
$this->assertTrue(isset($secondaryDataToCheck[$key]));
|
|
$this->assertEquals($secondaryDataToCheck[$key], $val);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testDelete()
|
|
{
|
|
$data = [
|
|
MonitoringStorage::COLUMN_TEST_TAKER => 'test_taker_id',
|
|
MonitoringStorage::COLUMN_STATUS => 'active',
|
|
'secondary_data_key' => 'secondary_data_val',
|
|
'secondary_data_key_2' => 'secondary_data_val_2',
|
|
];
|
|
|
|
$dataModel = $this->service->getData($this->getDeliveryExecution());
|
|
|
|
foreach ($data as $key => $val) {
|
|
$dataModel->addValue($key, $val);
|
|
}
|
|
|
|
$this->assertTrue($this->service->save($dataModel));
|
|
$insertedData = $this->getRecordByDeliveryExecutionId($this->deliveryExecutionId);
|
|
$this->assertNotEmpty($insertedData);
|
|
|
|
$this->assertTrue($this->service->delete($dataModel));
|
|
|
|
$insertedData = $this->getRecordByDeliveryExecutionId($this->deliveryExecutionId);
|
|
$this->assertEmpty($insertedData);
|
|
}
|
|
|
|
|
|
public function testFind()
|
|
{
|
|
$this->loadFixture();
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554175_test_record']
|
|
]);
|
|
$this->assertEquals(count($result), 1);
|
|
$this->assertEquals($result[0]->get()[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID], 'http://sample/first.rdf#i1450191587554175_test_record');
|
|
|
|
$result = $this->service->find([
|
|
['error_code' => '1'],
|
|
'OR',
|
|
['error_code' => '2'],
|
|
]);
|
|
$this->assertEquals(count($result), 2);
|
|
|
|
$result = $this->service->find([
|
|
['error_code' => '1'],
|
|
'AND',
|
|
['session_id' => 'i1450191587554175'],
|
|
]);
|
|
$this->assertEquals(count($result), 1);
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
['error_code' => '1'],
|
|
]);
|
|
$this->assertEquals(count($result), 0);
|
|
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
'AND',
|
|
[['error_code' => '0'], 'OR', ['error_code' => '1']],
|
|
]);
|
|
$this->assertEquals(count($result), 1);
|
|
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
['error_code' => '0'],
|
|
]);
|
|
$this->assertEquals(count($result), 1);
|
|
$this->assertEquals($result[0]->get()[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID], 'http://sample/first.rdf#i1450191587554178_test_record');
|
|
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
['error_code' => '0'],
|
|
], [], true);
|
|
$this->assertEquals(count($result), 1);
|
|
$this->assertEquals($result[0]->get()[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID], 'http://sample/first.rdf#i1450191587554178_test_record');
|
|
$this->assertEquals($result[0]->get()['error_code'], '0');
|
|
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
], [], true);
|
|
$this->assertEquals(count($result), 2);
|
|
|
|
foreach ($result as $resultRow) {
|
|
$this->assertTrue(isset($resultRow->get()['error_code']));
|
|
$this->assertTrue(isset($resultRow->get()['session_id']));
|
|
}
|
|
|
|
$result = $this->service->find([
|
|
['error_code' => '>=0'],
|
|
], ['order' => 'error_code ASC, session_id'], true);
|
|
|
|
$this->assertEquals(count($result), 4);
|
|
|
|
foreach ($result as $rowKey => $resultRow) {
|
|
$this->assertEquals($rowKey, $resultRow->get()['error_code']);
|
|
}
|
|
|
|
$result = $this->service->find([
|
|
[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => [
|
|
'http://sample/first.rdf#i1450191587554175_test_record',
|
|
'http://sample/first.rdf#i1450191587554176_test_record',
|
|
'http://sample/first.rdf#i1450191587554177_test_record'
|
|
]],
|
|
], ['order' => 'session_id'], true);
|
|
$this->assertEquals(count($result), 3);
|
|
$this->assertEquals($result[0]->get()[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID], 'http://sample/first.rdf#i1450191587554175_test_record');
|
|
$this->assertEquals($result[1]->get()[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID], 'http://sample/first.rdf#i1450191587554176_test_record');
|
|
$this->assertEquals($result[2]->get()[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID], 'http://sample/first.rdf#i1450191587554177_test_record');
|
|
|
|
}
|
|
|
|
public function testCount()
|
|
{
|
|
$this->loadFixture();
|
|
|
|
$result = $this->service->count();
|
|
$this->assertEquals(count($this->fixtureData), $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => 'http://sample/first.rdf#i1450191587554175_test_record']
|
|
]);
|
|
$this->assertEquals(1, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
['error_code' => '1'],
|
|
'OR',
|
|
['error_code' => '2'],
|
|
]);
|
|
$this->assertEquals(2, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
['error_code' => '1'],
|
|
'AND',
|
|
['session_id' => 'i1450191587554175'],
|
|
]);
|
|
$this->assertEquals(1, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
['error_code' => '1'],
|
|
]);
|
|
$this->assertEquals(0, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
'AND',
|
|
[['error_code' => '0'], 'OR', ['error_code' => '1']],
|
|
]);
|
|
$this->assertEquals(1, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
['error_code' => '0'],
|
|
]);
|
|
$this->assertEquals(1, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
[MonitoringStorage::COLUMN_STATUS => 'finished_test'],
|
|
]);
|
|
$this->assertEquals(2, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
['error_code' => '>=0'],
|
|
]);
|
|
$this->assertEquals(4, $result);
|
|
|
|
|
|
$result = $this->service->count([
|
|
[MonitoringStorage::COLUMN_DELIVERY_EXECUTION_ID => [
|
|
'http://sample/first.rdf#i1450191587554175_test_record',
|
|
'http://sample/first.rdf#i1450191587554176_test_record',
|
|
'http://sample/first.rdf#i1450191587554177_test_record'
|
|
]],
|
|
]);
|
|
$this->assertEquals(3, $result);
|
|
}
|
|
|
|
protected function loadFixture()
|
|
{
|
|
$this->setUp();
|
|
|
|
foreach ($this->fixtureData as $item) {
|
|
$dataModel = $this->service->getData($this->getDeliveryExecution($item[MonitoringStorage::DELIVERY_EXECUTION_ID]));
|
|
foreach ($item as $key => $val) {
|
|
$dataModel->addValue($key, $val);
|
|
}
|
|
$this->service->save($dataModel);
|
|
}
|
|
|
|
return [
|
|
[$this->fixtureData],
|
|
];
|
|
}
|
|
|
|
protected function getRecordByDeliveryExecutionId($id)
|
|
{
|
|
$service = $this->service;
|
|
$sql = 'SELECT * FROM ' . $service::TABLE_NAME .
|
|
' WHERE ' . $service::COLUMN_DELIVERY_EXECUTION_ID . '=?';
|
|
|
|
return $this->persistence->query($sql, [$id])->fetchAll();
|
|
}
|
|
|
|
protected function getKvRecordsByParentId($parentId)
|
|
{
|
|
$service = $this->service;
|
|
$sql = 'SELECT * FROM ' . $service::KV_TABLE_NAME .
|
|
' WHERE ' . $service::KV_COLUMN_PARENT_ID . '=?';
|
|
|
|
return $this->persistence->query($sql, [$parentId])->fetchAll(\PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
protected function getDeliveryExecution($id = null, $state = null)
|
|
{
|
|
if ($id === null) {
|
|
$id = $this->deliveryExecutionId;
|
|
}
|
|
$prophet = new \Prophecy\Prophet();
|
|
$deliveryExecutionProphecy = $prophet->prophesize('oat\taoDelivery\model\execution\DeliveryExecution');
|
|
$deliveryExecutionProphecy->getIdentifier()->willReturn($id);
|
|
|
|
$stateProphecy = $this->prophesize(\core_kernel_classes_Resource::class);
|
|
$stateProphecy->getUri()->willReturn($state);
|
|
|
|
$deliveryExecutionProphecy->getState()->willReturn($stateProphecy);
|
|
return $deliveryExecutionProphecy->reveal();
|
|
}
|
|
}
|