Commit f4bc8b64 authored by Karamel's avatar Karamel
Browse files

3in1 commit. Sync, http tests and postgresql compatibility.

- Add sync without cash register API and route
- Update getTop customers for postgresql compatibility
- Add curl tests to test routes, Login, Version and Sync tests
parent 1664266d
......@@ -7,3 +7,10 @@ database/port = 3306
database/name = "pasteque"
database/user = "user"
database/password = "password"
; Http tests target a valid local test instance
; that is tore down after each calls, thus must be this one.
; The test user must use the same database than the one
; defined above.
http/host = "http://localhost"
http/user = "test"
http/password = "pasteque"
......@@ -11,5 +11,12 @@ use \Pasteque\Server\System\API\APIResult;
*/
$app->GET('/api/sync/{cashregister}', function ($request, $response, $args) {
$ptApp = $this->get('settings')['ptApp'];
return $response->withApiResult(APICaller::run($ptApp, 'sync', 'sync', $args['cashregister']));
return $response->withApiResult(APICaller::run($ptApp,
'sync', 'syncCashRegister', $args['cashregister']));
});
$app->GET('/api/sync', function ($request, $response, $args) {
$ptApp = $this->get('settings')['ptApp'];
return $response->withApiResult(APICaller::run($ptApp,
'sync', 'sync'));
});
\ No newline at end of file
......@@ -27,28 +27,37 @@ class CustomerAPI extends APIHelper implements API
{
const MODEL_NAME = 'Pasteque\Server\Model\Customer';
public function getTop($limit = 10) {
public function getTopIds($limit = 10) {
// This is a report, but there are no reports.
// So it's uglily hardcoded until then.
$em = $this->dao->getEntityManager();
$now = new \DateTime();
$q = $em->createQuery('select t, count(t.id) '
$q = $em->createQuery('select c.id, count(c.id) as num '
. 'from \Pasteque\Server\Model\Ticket t '
. 'join t.customer c '
. 'where c.visible = true '
. 'and (c.expireDate is null or c.expireDate > :now) '
. 'group by c.id');
. 'group by c.id order by num desc');
$q->setParameter('now', $now);
$q->setMaxResults($limit);
$top = $q->getResult();
$result = [];
foreach ($top as $ticket) {
$customer = $ticket[0]->getCustomer();
$result[] = $customer;
foreach ($top as $cust) {
$result[] = $cust['id'];
}
return $result;
}
public function getTop($limit = 10) {
$ids = $this->getTopIds($limit);
$customers = [];
foreach ($ids as $id) {
$customer = $this->dao->read(static::MODEL_NAME, $id);
$customers[] = $customer;
}
return $customers;
}
/** Write one or multiple customers. Balance cannot be updated this way
* to prevent unexpected changes. It is updated with tickets or with the
* dedicated function. */
......
......@@ -62,7 +62,27 @@ class SyncAPI implements API {
return new static($app);
}
public function sync($cashRegisterName) {
public function sync() {
$result = new GenericModel();
// Send everything
$result->set('cashRegisters', $this->cashRegisterAPI->getAll());
$result->set('paymentmodes', $this->paymentModeAPI->getAll());
$result->set('currencies', $this->currencyAPI->getAll());
$result->set('floors', $this->placeAPI->getAll());
$result->set('users', $this->userAPI->getAll());
$result->set('roles', $this->roleAPI->getAll());
$result->set('taxes', $this->taxAPI->getAll());
$result->set('categories', $this->categoryAPI->getAll());
$result->set('products', $this->productAPI->getAll());
$result->set('tariffareas', $this->tariffAreaAPI->getAll());
$result->set('discounts', $this->discountAPI->getAll());
$result->set('customers', $this->customerAPI->getAll());
$result->set('discountprofiles', $this->discountProfileAPI->getAll());
$result->set('resources', $this->resourceAPI->getAll());
return $result;
}
public function syncCashRegister($cashRegisterName) {
$result = new GenericModel();
// Look for the cash register, reject if not found.
$cashRegister = $this->cashRegisterAPI->getByName($cashRegisterName);
......@@ -84,12 +104,8 @@ class SyncAPI implements API {
$result->set('discounts', $this->discountAPI->getAll());
$result->set('customers', $this->customerAPI->getAll());
// Set top customers as id only not to duplicate data
$topCust = $this->customerAPI->getTop();
$top = [];
foreach ($topCust as $cust) {
$top[] = $cust->getId();
}
$result->set('topcustomers', $top);
$topCust = $this->customerAPI->getTopIds();
$result->set('topcustomers', $topCust);
$result->set('discountprofiles', $this->discountProfileAPI->getAll());
$result->set('resources', $this->resourceAPI->getAll());
return $result;
......
......@@ -21,11 +21,13 @@
// along with Pasteque. If not, see <http://www.gnu.org/licenses/>.
namespace Pasteque\Server;
use \Pasteque\Server\API\CustomerAPI;
use \Pasteque\Server\API\TicketAPI;
use \Pasteque\Server\Model\Category;
use \Pasteque\Server\Model\CashRegister;
use \Pasteque\Server\Model\CashSession;
use \Pasteque\Server\Model\Currency;
use \Pasteque\Server\Model\Customer;
use \Pasteque\Server\Model\FiscalTicket;
use \Pasteque\Server\Model\PaymentMode;
use \Pasteque\Server\Model\Product;
......@@ -120,14 +122,13 @@ class TicketAPITest extends TestCase
$this->dao->delete($record);
}
foreach ([TicketPayment::class, TicketTax::class, TicketLine::class,
Ticket::class]
Ticket::class, Customer::class]
as $class) {
$all = $this->dao->search($class);
foreach($all as $record) {
$this->dao->delete($record);
}
}
// Directly deleting $this->session doesn't work
// and f**k it, you won't delete sessions anyway.
$sessions = $this->dao->search(CashSession::class);
$this->dao->delete($sessions[0]);
......@@ -423,4 +424,55 @@ class TicketAPITest extends TestCase
public function testSaveTicketDiscontinuous() {
}
/** @depends testSaveTicket */
public function testTopCustomers() {
$cTop = new Customer();
$cTop->setDispName('Top');
$this->dao->write($cTop);
$cTop2 = new Customer();
$cTop2->setDispName('Top2');
$this->dao->write($cTop2);
$cTopNull = new Customer();
$cTopNull->setDispName('Not customer');
$this->dao->write($cTopNull);
$this->dao->commit();
// Add tickets to Top
$i = 1;
for (; $i < 3; $i++) {
$tkt = new Ticket();
$tkt->setCashRegister($this->session->getCashRegister());
$tkt->setSequence($this->session->getSequence());
$tkt->setNumber($i);
$tkt->setDate(new \DateTime('2018-01-01 8:05'));
$tkt->setUser($this->user);
$tkt->setTaxedPrice(11.0);
$tkt->setFinalPrice(10.0); $tkt->setFinalTaxedPrice(11.0);
$tkt->setCustomer($cTop);
$this->api->write($tkt);
}
// Add tickets to Top2
for (; $i < 4; $i++) {
$tkt = new Ticket();
$tkt->setCashRegister($this->session->getCashRegister());
$tkt->setSequence($this->session->getSequence());
$tkt->setNumber($i);
$tkt->setDate(new \DateTime('2018-01-01 8:05'));
$tkt->setUser($this->user);
$tkt->setTaxedPrice(11.0);
$tkt->setFinalPrice(10.0); $tkt->setFinalTaxedPrice(11.0);
$tkt->setCustomer($cTop2);
$this->api->write($tkt);
}
// Check that Top and Top2 are returned and not TopNull
$custAPI = new CustomerAPI($this->dao);
$topIds = $custAPI->getTopIds(3);
$this->assertEquals(2, count($topIds));
$this->assertEquals($cTop->getId(), $topIds[0]);
$this->assertEquals($cTop2->getId(), $topIds[1]);
$top = $custAPI->getTop(3);
$this->assertEquals(2, count($top));
$this->assertEquals($cTop->getId(), $top[0]->getId());
$this->assertEquals($cTop2->getId(), $top[1]->getId());
}
}
<?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;
use \Pasteque\Server\Model\Category;
use \Pasteque\Server\Model\CashRegister;
use \Pasteque\Server\Model\CashSession;
use \Pasteque\Server\Model\Currency;
use \Pasteque\Server\Model\Option;
use \Pasteque\Server\Model\PaymentMode;
use \Pasteque\Server\Model\Product;
use \Pasteque\Server\Model\Role;
use \Pasteque\Server\Model\Tax;
use \Pasteque\Server\Model\TariffArea;
use \Pasteque\Server\Model\User;
/** Sample data to init/destroy test data. */
class TestData
{
private $tax;
private $cat;
private $prd;
private $cash;
private $session;
private $pm;
private $curr;
private $role;
private $user;
public function __construct() {
}
public function install($dao) {
$this->cat = new Category();
$this->cat->setReference('category');
$this->cat->setLabel('Category');
$dao->write($this->cat);
$this->tax= new Tax();
$this->tax->setLabel('VAT');
$this->tax->setRate(0.1);
$dao->write($this->tax);
$this->prd = new Product();
$this->prd->setReference('product');
$this->prd->setLabel('Product');
$this->prd->setTax($this->tax);
$this->prd->setCategory($this->cat);
$this->prd->setPriceSell(1.0);
$dao->write($this->prd);
$this->pm = new PaymentMode();
$this->pm->setReference('pm');
$this->pm->setLabel('Payment mode');
$dao->write($this->pm);
$this->curr = new Currency();
$this->curr->setReference('curr');
$this->curr->setLabel('Currency');
$this->curr->setMain(true);
$dao->write($this->curr);
$this->cash = new CashRegister();
$this->cash->setReference('cash');
$this->cash->setLabel('Cash');
$dao->write($this->cash);
$this->role = new Role();
$this->role->setName('role');
$dao->write($this->role);
$this->user = new User();
$this->user->setName('user');
$this->user->setRole($this->role);
$dao->write($this->user);
$this->session = new CashSession();
$this->session->setCashRegister($this->cash);
$this->session->setSequence(1);
$this->session->setOpenDate(new \DateTime('2018-01-01 8:00'));
$dao->write($this->session);
$this->version = new Option();
$this->version->setName('dblevel');
$this->version->setSystem(true);
$dao->write($this->version);
$dao->commit();
}
public function delete($dao) {
$sessions = $dao->search(CashSession::class);
$dao->delete($sessions[0]);
$dao->delete($this->cash);
$dao->delete($this->user);
$dao->delete($this->role);
$dao->delete($this->prd);
$dao->delete($this->cat);
$dao->delete($this->tax);
$dao->delete($this->pm);
$dao->delete($this->curr);
$dao->delete($this->version);
$dao->commit();
}
}
\ No newline at end of file
......@@ -33,6 +33,29 @@ $dbInfo = ['type' => $cfg['database/type'], 'host' => $cfg['database/host'],
'port' => $cfg['database/port'], 'name' => $cfg['database/name'],
'user' => $cfg['database/user'], 'password' => $cfg['database/password']];
function apiUrl($target) {
global $cfg;
if (substr($cfg['http/host'], -1) == '/') {
return $cfg['http/host'] . $target;
} else {
return $cfg['http/host'] . '/' . $target;
}
}
function obtainToken() {
global $cfg;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, apiUrl('api/login'));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POSTFIELDS,
['user' => $cfg['http/user'],
'password' => $cfg['http/password']]);
$resp = curl_exec($curl);
curl_close($curl);
return str_replace('"', '', $resp);
}
function dropDatabase() {
global $config;
global $dbModule;
......
<?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\System\Login;
use \PHPUnit\Framework\TestCase;
require_once(dirname(dirname(__FILE__)) . "/common_load.php");
class HttpLoginTest extends TestCase
{
private $curl;
protected function setUp() {
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
}
protected function tearDown() {
curl_close($this->curl);
}
/** Send a call without login. Expect it to be rejected. */
public function testReject() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/version'));
curl_exec($this->curl);
$this->assertEquals(403, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
}
public function testRejectGet() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/login'));
curl_exec($this->curl);
$this->assertEquals(405, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
}
public function testNoCredentials() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/login'));
curl_setopt($this->curl, CURLOPT_POST, true);
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$this->assertEquals('null', $resp);
}
public function testWrongCredentials() {
global $cfg;
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/login'));
curl_setopt($this->curl, CURLOPT_POST, true);
curl_setopt($this->curl, CURLOPT_POSTFIELDS,
['user' => $cfg['http/user'], 'password' => 'notme']);
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$this->assertEquals('null', $resp);
}
public function testGoodCredentials() {
global $cfg;
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/login'));
curl_setopt($this->curl, CURLOPT_POST, true);
curl_setopt($this->curl, CURLOPT_POSTFIELDS,
['user' => $cfg['http/user'],
'password' => $cfg['http/password']]);
$resp = curl_exec($this->curl);
$this->assertEquals(200, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
$this->assertNotEquals('null', $resp);
// TODO: check the Token header in response
}
}
<?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\API\VersionAPI;
use \Pasteque\Server\System\Login;
use \Pasteque\Server\System\DAO\DAOFactory;
use \PHPUnit\Framework\TestCase;
require_once(dirname(dirname(__FILE__)) . "/common_load.php");
require_once(dirname(dirname(__FILE__)) . "/TestData.php");
class HttpSyncTest extends TestCase
{
private $curl;
private static $token;
private static $dao;
private static $testData;
public static function setUpBeforeClass() {
global $dbInfo;
static::$dao = DAOFactory::getDAO($dbInfo, ['debug' => true]);
static::$testData = new TestData();
static::$testData->install(static::$dao);
static::$token = obtainToken();
}
public static function tearDownAfterClass() {
static::$testData->delete(static::$dao);
static::$dao->close();
}
protected function setUp() {
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->curl, CURLOPT_HTTPHEADER,
[Login::TOKEN_HEADER . ': ' . static::$token]);
}
protected function tearDown() {
curl_close($this->curl);
}
public function testSync() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/sync'));
$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['cashRegisters']));
$this->assertEquals(1, count($data['paymentmodes']));
$this->assertEquals(1, count($data['currencies']));
$this->assertEquals(1, count($data['users']));
$this->assertEquals(1, count($data['products']));
$this->markTestIncomplete('Much more to check.');
}
public function testSyncWrongCashRegister() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/sync/NotTheCash'));
$resp = curl_exec($this->curl);
$this->assertEquals(400, curl_getinfo($this->curl, CURLINFO_HTTP_CODE));
}
public function testSyncCashRegister() {
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/sync/Cash'));
$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['paymentmodes']));
$this->assertEquals(1, count($data['currencies']));
$this->assertEquals(1, count($data['users']));
$this->assertEquals(1, count($data['products']));
$this->markTestIncomplete('Much more to check.');
}
}
<?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\API\VersionAPI;
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 HttpVersionTest extends TestCase
{
private $curl;
private static $token;
private static $dao;
private static $version;
public static function setUpBeforeClass() {
static::$token = obtainToken();
static::$version = new Option();
static::$version->setName('dblevel');
static::$version->setContent(VersionAPI::LEVEL);
static::$version->setSystem(true);
global $dbInfo;
static::$dao = DAOFactory::getDAO($dbInfo, ['debug' => true]);
static::$dao->write(static::$version);
static::$dao->commit();
}
public static function tearDownAfterClass() {
static::$dao->delete(static::$version);
static::$dao->commit();
static::$dao->close();
}
protected function setUp() {
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_URL, apiUrl('api/version'));
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->curl, CURLOPT_HTTPHEADER,
[Login::TOKEN_HEADER . ': ' . static::$token]);
}
protected function tearDown() {
curl_close($this->curl);
}
public function testVersion() {
$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(VersionAPI::VERSION, $data['version']);