Commit b665ec68 authored by Karamel's avatar Karamel
Browse files

Add options, routes and tests.

parent 51d46068
......@@ -65,6 +65,7 @@ include('../routes/discount.php');
include('../routes/discountprofile.php');
include('../routes/image.php');
include('../routes/login.php');
include('../routes/option.php');
include('../routes/paymentmode.php');
include('../routes/place.php');
include('../routes/product.php');
......
<?php
use \Pasteque\Server\Model\Option;
use \Pasteque\Server\System\API\APICaller;
use \Pasteque\Server\System\API\APIResult;
$app->GET('/api/option/getAll', function ($request, $response, $args) {
$ptApp = $this->get('settings')['ptApp'];
$optCall = APICaller::run($ptApp, 'option', 'getAll');
return $response->withAPIResult($optCall);
});
$app->GET('/api/option/{name}', function ($request, $response, $args) {
$ptApp = $this->get('settings')['ptApp'];
$optCall = APICaller::run($ptApp, 'option', 'get', $args['name']);
return $response->withAPIResult($optCall);
});
$app->POST('/api/option', function ($request, $response, $args) {
$ptApp = $this->get('settings')['ptApp'];
$tab = $request->getParsedBody();
$opt = Option::loadFromId($tab['name'], $ptApp->getDao());
if ($opt == null) {
$opt = new Option();
}
try {
$opt->merge($tab, $ptApp->getDao());
} catch (InvalidFieldException $e) {
return $response->reject($e);
}
return $response->withApiResult(APICaller::run($ptApp, 'option', 'write',
$opt));
});
$app->DELETE('/api/option/{name}', function ($request, $response, $args) {
$ptApp = $this->get('settings')['ptApp'];
$optCall = APICaller::run($ptApp, 'option', 'delete', $args['name']);
return $response->withAPIResult($optCall);
});
......@@ -20,6 +20,7 @@
namespace Pasteque\Server\API;
use \Pasteque\Server\CommonAPI\OptionAPI;
use \Pasteque\Server\Exception\RecordNotFoundException;
use \Pasteque\Server\Model\GenericModel;
......@@ -40,6 +41,7 @@ class SyncAPI implements API {
protected $customerAPI;
protected $discountProfileAPI;
protected $resourceAPI;
protected $optionAPI;
public function __construct($app) {
$this->cashRegisterAPI = CashregisterAPI::fromApp($app);
......@@ -57,6 +59,7 @@ class SyncAPI implements API {
$this->customerAPI = CustomerAPI::fromApp($app);
$this->discountProfileAPI = DiscountprofileAPI::fromApp($app);
$this->resourceAPI = ResourceAPI::fromApp($app);
$this->optionAPI = OptionAPI::fromApp($app);
}
public static function fromApp($app) {
......@@ -81,6 +84,7 @@ class SyncAPI implements API {
$result->set('customers', $this->customerAPI->getAll());
$result->set('discountprofiles', $this->discountProfileAPI->getAll());
$result->set('resources', $this->resourceAPI->getAll());
$result->set('options', $this->optionAPI->getAll());
return $result;
}
......@@ -117,6 +121,7 @@ class SyncAPI implements API {
$result->set('topcustomers', $topCust);
$result->set('discountprofiles', $this->discountProfileAPI->getAll());
$result->set('resources', $this->resourceAPI->getAll());
$result->set('options', $this->optionAPI->getAll());
return $result;
}
......
<?php
// Pastèque API
//
// Copyright (C)
// 2012 Scil (http://scil.coop)
// 2017 Karamel, Association Pastèque (karamel@creativekara.fr, https://pasteque.org)
//
// This file is part of Pastèque.
//
// Pastèque 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, either version 3 of the License, or
// (at your option) any later version.
//
// Pastèque 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 Pastèque. If not, see <http://www.gnu.org/licenses/>.
namespace Pasteque\Server\CommonAPI;
use \Pasteque\Server\API\API;
use \Pasteque\Server\Exception\InvalidRecordException;
use \Pasteque\Server\System\DAO\DAOCondition;
class OptionAPI implements API
{
const MODEL_NAME = 'Pasteque\Server\Model\Option';
public function __construct($dao) {
$this->dao = $dao;
}
public static function fromApp($app) {
return new OptionAPI($app->getDao());
}
/** Use default order if no order is given. */
protected function getOrder($order) {
if ($order === null) {
return 'name';
}
return $order;
}
/** Get all non-system options. */
public function getAll($order = null) {
$order = $this->getOrder($order);
return $this->dao->search(static::MODEL_NAME,
new DAOCondition('system', '=', false), null, null, $order);
}
/** Get a single entry from it's name. It can be a system option or not. */
public function get($name) {
return $this->dao->read(static::MODEL_NAME, $name);
}
/** Check if record(s) can be handled by this helper.
* @param $data Single or array of records to check.
* @throws \InvalidArgumentException when the class of at least
* one record doesn't match the one handled by this helper.
* @return True. Exception otherwise. */
protected function supportOrDie($data) {
if (!is_array($data)) {
if (get_class($data) != static::MODEL_NAME) {
throw new \InvalidArgumentException(sprintf('Incompatible class %s expecting %s', get_class($data), static::MODEL_NAME));
}
} else {
foreach ($data as $d) {
if (get_class($d) != static::MODEL_NAME) {
throw new \InvalidArgumentException(sprintf('Incompatible class %s expecting %s', get_class($d), static::MODEL_NAME));
}
}
}
return true;
}
/** Write a single or an array of entries. If the ID is set, it's an update,
* otherwise it's a create. Sets the ID in $data.
* System options cannot be written, use writeSystem instead.
* $data Entities to write/update.
* @return The single entry or the array of entries.
* @throw InvalidArgumentException when at least one entry is not of the
* managed class.
* @throw InvalidRecordException when at least one entry
* is a system option. */
public function write($data) {
$this->supportOrDie($data);
$arrayArgs = is_array($data);
$data = ($arrayArgs) ? $data : array($data);
foreach ($data as $d) {
if ($d->isSystem()) {
throw new InvalidRecordException(InvalidRecordException::CSTR_READ_ONLY,
static::MODEL_NAME, $d->getName());
}
$this->dao->write($d);
}
$this->dao->commit();
if ($arrayArgs) {
return $data;
} else {
return $data[0];
}
}
/** Write a single or an array of entries. If the ID is set, it's an update,
* otherwise it's a create. Sets the ID in $data.
* Can write either system on non-system options.
* $data Entities to write/update.
* @return The single entry or the array of entries.
* @throw InvalidArgumentException when at least one entry is not of the
* managed class. */
public function writeSystem($data) {
$this->supportOrDie($data);
$arrayArgs = is_array($data);
$data = ($arrayArgs) ? $data : array($data);
foreach ($data as $d) {
$this->dao->write($d);
}
$this->dao->commit();
if ($arrayArgs) {
return $data;
} else {
return $data[0];
}
}
/** Delete a single or an array of entries by their ID.
* If an ID is not mapped to any record, it is ignored.
* System options cannot be deleted.
* @return the number of elements deleted.
* @throw InvalidRecordException when at least one option
* is a system one. */
public function delete($id) {
$id = (is_array($id)) ? $id : array($id);
$references = array();
foreach ($id as $i) {
$model = $this->dao->read(static::MODEL_NAME, $i);
if ($model != null) {
$references[] = $model;
if ($model->isSystem()) {
throw new InvalidRecordException(InvalidRecordException::CSTR_READ_ONLY,
static::MODEL_NAME, $model->getName());
}
}
}
foreach ($references as $ref) {
$this->dao->delete($ref);
}
$this->dao->commit();
return count($references);
}
}
......@@ -35,7 +35,7 @@ class Option extends DoctrineMainModel
{
protected static function getDirectFieldNames() {
// 'key' and 'value' are keywords in mysql and crash Doctrine
return ['name', 'system', 'mandatory', 'content'];
return ['name', 'system', 'content'];
}
protected static function getAssociationFields() {
return [];
......@@ -65,7 +65,7 @@ class Option extends DoctrineMainModel
* @SWG\Property()
* @Column(type="boolean")
*/
protected $system = true;
protected $system = false;
public function getSystem() { return $this->system; }
/** Alias for getSystem (the latter is required for Doctrine) */
public function isSystem() { return $this->getSystem(); }
......
<?php
// Pasteque server testing
//
// Copyright (C)
// 2012 Scil (http://scil.coop)
// 2017 Karamel, Association Pastèque (karamel@creativekara.fr, https://pasteque.org)
//
// This file is part of Pasteque.
//
// Pasteque 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, either version 3 of the License, or
// (at your option) any later version.
//
// Pasteque 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 Pasteque. If not, see <http://www.gnu.org/licenses/>.
namespace Pasteque\Server;
use \Pasteque\Server\CommonAPI\OptionAPI;
use \Pasteque\Server\Exception\InvalidRecordException;
use \Pasteque\Server\Model\Option;
use \Pasteque\Server\System\DAO\DAOFactory;
use \PHPUnit\Framework\TestCase;
require_once(dirname(dirname(__FILE__)) . "/common_load.php");
class OptionAPITest extends TestCase
{
private $dao;
private $api;
protected function setUp(): void {
global $dbInfo;
$this->dao = DAOFactory::getDAO($dbInfo, ['debug' => true]);
$this->api = new OptionAPI($this->dao);
}
protected function tearDown(): void {
$all = $this->dao->search(Option::class);
foreach($all as $record) {
$this->dao->delete($record);
}
$this->dao->commit();
$this->dao->close();
}
public function testGetAll() {
$opt1 = new Option();
$opt1->setName('opt1');
$opt1->setContent('val1');
$this->dao->write($opt1);
$opt2 = new Option();
$opt2->setName('opt2');
$opt2->setContent('val2');
$this->dao->write($opt2);
$optSys = new Option();
$optSys->setName('system');
$optSys->setContent('sys');
$optSys->setSystem(true);
$this->dao->write($optSys);
$this->dao->commit();
$opts = $this->api->getAll();
$this->assertEquals(2, count($opts));
$this->assertEquals('opt1', $opts[0]->getName());
$this->assertEquals('val1', $opts[0]->getContent());
$this->assertFalse($opts[0]->isSystem());
$this->assertEquals('opt2', $opts[1]->getName());
$this->assertEquals('val2', $opts[1]->getContent());
$this->assertFalse($opts[1]->isSystem());
}
public function testWrite() {
$opt1 = new Option();
$opt1->setName('opt1');
$opt1->setContent('val1');
$this->api->write($opt1);
$snap = $this->dao->readSnapshot(Option::class, $opt1->getName());
$this->assertNotNull($snap);
$this->assertEquals($opt1->getName(), $snap->getName());
$this->assertEquals($opt1->getContent(), $snap->getContent());
$this->assertEquals($opt1->isSystem(), $snap->isSystem());
}
public function testWriteWithSys() {
$opt1 = new Option();
$opt1->setName('opt1');
$opt1->setContent('val1');
$opt1->setSystem(true);
$exceptionThrown = false;
try {
$this->api->write($opt1);
} catch (InvalidRecordException $e) {
$exceptionThrown = true;
$this->assertEquals(InvalidRecordException::CSTR_READ_ONLY,
$e->getConstraint());
$this->assertEquals(Option::class, $e->getClass());
$this->assertEquals($opt1->getName(), $e->getId());
}
$this->assertTrue($exceptionThrown);
}
public function testWriteSys() {
$opt1 = new Option();
$opt1->setName('opt1');
$opt1->setContent('val1');
$opt1->setSystem(true);
$this->api->writeSystem($opt1);
$snap = $this->dao->readSnapshot(Option::class, $opt1->getName());
$this->assertNotNull($snap);
$this->assertEquals($opt1->getName(), $snap->getName());
$this->assertEquals($opt1->getContent(), $snap->getContent());
$this->assertEquals($opt1->isSystem(), $snap->isSystem());
}
public function testDelete() {
$opt1 = new Option();
$opt1->setName('opt1');
$opt1->setContent('val1');
$this->dao->write($opt1);
$this->dao->commit();
$this->api->delete('opt1');
$this->assertEquals(0, $this->dao->count(Option::class));
}
public function testDeleteSys() {
$opt1 = new Option();
$opt1->setName('opt1');
$opt1->setContent('val1');
$opt1->setSystem(true);
$this->dao->write($opt1);
$this->dao->commit();
$exceptionThrown = false;
try {
$this->api->delete('opt1');
} catch (InvalidRecordException $e) {
$exceptionThrown = true;
$this->assertEquals(InvalidRecordException::CSTR_READ_ONLY,
$e->getConstraint());
$this->assertEquals(Option::class, $e->getClass());
$this->assertEquals($opt1->getName(), $e->getId());
}
$this->assertTrue($exceptionThrown);
}
}
......@@ -46,6 +46,7 @@ class TestData
private $curr;
private $role;
private $user;
private $option;
public function __construct() {
}
......@@ -95,6 +96,10 @@ class TestData
$this->version->setName('dblevel');
$this->version->setSystem(true);
$dao->write($this->version);
$this->option = new Option();
$this->option->setName('option');
$this->option->setContent('test');
$dao->write($this->option);
$dao->commit();
}
......@@ -110,7 +115,8 @@ class TestData
$dao->delete($this->pm);
$dao->delete($this->curr);
$dao->delete($this->version);
$dao->delete($this->option);
$dao->commit();
}
}
\ No newline at end of file
}
<?php
// Pasteque server testing
//
// Copyright (C)
// 2012 Scil (http://scil.coop)
// 2017 Karamel, Association Pastèque (karamel@creativekara.fr, https://pasteque.org)
//
// This file is part of Pasteque.
//
// Pasteque 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, either version 3 of the License, or
// (at your option) any later version.
//
// Pasteque 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 Pasteque. If not, see <http://www.gnu.org/licenses/>.
namespace Pasteque\Server;
use \Pasteque\Server\CommonAPI\OptionAPI;
use \Pasteque\Server\Model\Option;
use \Pasteque\Server\System\Login;
use \Pasteque\Server\System\DAO\DAOFactory;
use \PHPUnit\Framework\TestCase;
require_once(dirname(dirname(__FILE__)) . "/common_load.php");
class HttpOptionTest extends TestCase
{
private $curl;
private static $token;
private $dao;
private $sysOpt;
private $opt;
public static function setUpBeforeClass(): void {
static::$token = obtainToken();
}
public static function tearDownAfterClass(): void {
}
protected function setUp(): void {
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->curl, CURLOPT_HTTPHEADER,
[Login::TOKEN_HEADER . ': ' . static::$token]);
global $dbInfo;
$this->dao = DAOFactory::getDAO($dbInfo, ['debug' => true]);
$opt = new Option();
$opt->setName('opt');
$opt->setContent('val');
$sysOpt = new Option();
$sysOpt->setName('sys');
$sysOpt->setContent('sysVal');
$sysOpt->setSystem(true);
$this->dao->write($opt);
$this->dao->write($sysOpt);
$this->dao->commit();
}
protected function tearDown(): void {
curl_close($this->curl);
$all = $this->dao->search(Option::class);
foreach($all as $record) {
$this->dao->delete($record);
}
$this->dao->commit();
$this->dao->close();
}
public function testGetAll() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/option/getAll'));
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$data = json_decode($resp, true);
$this->assertNotNull($data);
$this->assertEquals(1, count($data));
$this->assertEquals('opt', $data[0]['name']);
$this->assertEquals('val', $data[0]['content']);
$this->assertFalse($data[0]['system']);
}
public function testGet() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/option/opt'));
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$data = json_decode($resp, true);
$this->assertNotNull($data);
$this->assertEquals('opt', $data['name']);
$this->assertEquals('val', $data['content']);
$this->assertFalse($data['system']);
}
public function testGetSys() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/option/sys'));
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$data = json_decode($resp, true);
$this->assertNotNull($data);
$this->assertEquals('sys', $data['name']);
$this->assertEquals('sysVal', $data['content']);
$this->assertTrue($data['system']);
}
public function testGetNone() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/option/oops'));
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$this->assertEquals('null', $resp);
}
public function testPostCreate() {
$opt = new Option();
$opt->setName('new');
$opt->setContent('newVal');
$postData = json_encode($opt->toStruct());
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/option'));
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postData);
curl_setopt($this->curl, CURLOPT_HTTPHEADER,
[Login::TOKEN_HEADER . ': ' . static::$token,
'Content-Type: application/json']);
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$newOpt = $this->dao->readSnapshot(Option::class, 'new');
$this->assertNotNull($newOpt);
$this->assertEquals('new', $newOpt->getName());
$this->assertEquals('newVal', $newOpt->getContent());
$this->assertFalse($newOpt->isSystem());
}
public function testPostCreateSys() {
$opt = new Option();
$opt->setName('new');
$opt->setContent('newVal');
$opt->setSystem(true);
$postData = json_encode($opt->toStruct());
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/option'));
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postData);
curl_setopt($this->curl, CURLOPT_HTTPHEADER,
[Login::TOKEN_HEADER . ': ' . static::$token,
'Content-Type: application/json']);
$resp = curl_exec($this->curl);
$this->assertEquals(400, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$jsResp = json_decode($resp, true);
$this->assertNotEquals(false, $jsResp);
$this->assertEquals('InvalidRecord', $jsResp['error']);
$this->assertEquals('ReadOnly', $jsResp['constraint']);
$this->assertEquals(Option::class, $jsResp['class']);
$this->assertEquals('new', $jsResp['key']);
$newOpt = $this->dao->readSnapshot(Option::class, 'new');
$this->assertNull($newOpt);
}
public function testPostUpdate() {
$opt = new Option();