ver. 0.269: ShopPaymentMethod refactor + Apilo keepalive

This commit is contained in:
2026-02-14 15:22:02 +01:00
parent 5e5d3d068a
commit 818cd7f2c0
31 changed files with 1832 additions and 269 deletions

View File

@@ -152,19 +152,83 @@ class IntegrationsRepositoryTest extends TestCase
$this->assertNull($this->repository->apiloGetAccessToken());
}
public function testShouldRefreshAccessTokenReturnsFalseForFarFutureDate(): void
{
$reflection = new \ReflectionClass($this->repository);
$method = $reflection->getMethod('shouldRefreshAccessToken');
$method->setAccessible(true);
$future = date('Y-m-d H:i:s', time() + 3600);
$result = $method->invoke($this->repository, $future, 300);
$this->assertFalse($result);
}
public function testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate(): void
{
$reflection = new \ReflectionClass($this->repository);
$method = $reflection->getMethod('shouldRefreshAccessToken');
$method->setAccessible(true);
$near = date('Y-m-d H:i:s', time() + 120);
$result = $method->invoke($this->repository, $near, 300);
$this->assertTrue($result);
}
public function testApiloFetchListThrowsForInvalidType(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->repository->apiloFetchList('invalid');
}
public function testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing(): void
{
$stmt = $this->createMock(\PDOStatement::class);
$stmt->expects($this->once())
->method('fetchAll')
->with(\PDO::FETCH_ASSOC)
->willReturn([]);
$this->mockDb->expects($this->once())
->method('query')
->with('SELECT * FROM pp_shop_apilo_settings')
->willReturn($stmt);
$result = $this->repository->apiloFetchListResult('payment');
$this->assertIsArray($result);
$this->assertFalse((bool)($result['success'] ?? true));
$this->assertStringContainsString('Brakuje konfiguracji Apilo', (string)($result['message'] ?? ''));
}
public function testApiloIntegrationStatusReturnsMissingConfigMessage(): void
{
$stmt = $this->createMock(\PDOStatement::class);
$stmt->expects($this->once())
->method('fetchAll')
->with(\PDO::FETCH_ASSOC)
->willReturn([]);
$this->mockDb->expects($this->once())
->method('query')
->with('SELECT * FROM pp_shop_apilo_settings')
->willReturn($stmt);
$status = $this->repository->apiloIntegrationStatus();
$this->assertIsArray($status);
$this->assertFalse((bool)($status['is_valid'] ?? true));
$this->assertStringContainsString('Brakuje konfiguracji Apilo', (string)($status['message'] ?? ''));
}
public function testAllPublicMethodsExist(): void
{
$expectedMethods = [
'getSettings', 'getSetting', 'saveSetting',
'linkProduct', 'unlinkProduct',
'apiloAuthorize', 'apiloGetAccessToken',
'apiloFetchList', 'apiloProductSearch', 'apiloCreateProduct',
'apiloAuthorize', 'apiloGetAccessToken', 'apiloKeepalive', 'apiloIntegrationStatus',
'apiloFetchList', 'apiloFetchListResult', 'apiloProductSearch', 'apiloCreateProduct',
'getProductSku', 'shopproImportProduct',
];
@@ -201,4 +265,37 @@ class IntegrationsRepositoryTest extends TestCase
$settings = $this->repository->getSettings('shoppro');
$this->assertSame('test.com', $settings['domain']);
}
public function testNormalizeApiloMapListRejectsErrorPayload(): void
{
$reflection = new \ReflectionClass($this->repository);
$method = $reflection->getMethod('normalizeApiloMapList');
$method->setAccessible(true);
$result = $method->invoke($this->repository, [
'message' => 'Missing JWT token',
'code' => 401,
]);
$this->assertNull($result);
}
public function testNormalizeApiloMapListAcceptsIdNameList(): void
{
$reflection = new \ReflectionClass($this->repository);
$method = $reflection->getMethod('normalizeApiloMapList');
$method->setAccessible(true);
$payload = [
['id' => '1', 'name' => 'Przelew'],
['id' => '2', 'name' => 'Karta'],
];
$result = $method->invoke($this->repository, $payload);
$this->assertIsArray($result);
$this->assertCount(2, $result);
$this->assertSame('1', (string)$result[0]['id']);
$this->assertSame('Przelew', (string)$result[0]['name']);
}
}

View File

@@ -0,0 +1,337 @@
<?php
namespace Tests\Unit\Domain\PaymentMethod;
use PHPUnit\Framework\TestCase;
use Domain\PaymentMethod\PaymentMethodRepository;
class PaymentMethodRepositoryTest extends TestCase
{
public function testFindReturnsNullForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repository = new PaymentMethodRepository($mockDb);
$this->assertNull($repository->find(0));
}
public function testFindReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', '*', ['id' => 10])
->willReturn(null);
$repository = new PaymentMethodRepository($mockDb);
$this->assertNull($repository->find(10));
}
public function testFindNormalizesData(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', '*', ['id' => 11])
->willReturn([
'id' => '11',
'name' => ' Przelew ',
'description' => null,
'status' => '1',
'apilo_payment_type_id' => '7',
]);
$repository = new PaymentMethodRepository($mockDb);
$result = $repository->find(11);
$this->assertIsArray($result);
$this->assertSame(11, $result['id']);
$this->assertSame('Przelew', $result['name']);
$this->assertSame('', $result['description']);
$this->assertSame(1, $result['status']);
$this->assertSame(7, $result['apilo_payment_type_id']);
}
public function testSaveUpdatesRowAndReturnsId(): void
{
$mockDb = $this->createMock(\medoo::class);
$updateRow = null;
$updateWhere = null;
$mockDb->expects($this->once())
->method('update')
->willReturnCallback(function ($table, $row, $where) use (&$updateRow, &$updateWhere) {
$this->assertSame('pp_shop_payment_methods', $table);
$updateRow = $row;
$updateWhere = $where;
return true;
});
$repository = new PaymentMethodRepository($mockDb);
$id = $repository->save(3, [
'description' => ' test ',
'status' => 'on',
'apilo_payment_type_id' => '22',
]);
$this->assertSame(3, $id);
$this->assertSame('test', $updateRow['description']);
$this->assertSame(1, $updateRow['status']);
$this->assertSame(22, $updateRow['apilo_payment_type_id']);
$this->assertSame(['id' => 3], $updateWhere);
}
public function testSavePreservesNonNumericApiloPaymentTypeId(): void
{
$mockDb = $this->createMock(\medoo::class);
$updateRow = null;
$mockDb->expects($this->once())
->method('update')
->willReturnCallback(function ($table, $row) use (&$updateRow) {
$this->assertSame('pp_shop_payment_methods', $table);
$updateRow = $row;
return true;
});
$repository = new PaymentMethodRepository($mockDb);
$repository->save(4, [
'description' => 'X',
'status' => 1,
'apilo_payment_type_id' => 'CASH_ON_DELIVERY',
]);
$this->assertSame('CASH_ON_DELIVERY', $updateRow['apilo_payment_type_id']);
}
public function testSaveReturnsNullForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('update');
$repository = new PaymentMethodRepository($mockDb);
$this->assertNull($repository->save(0, ['status' => 1]));
}
public function testListForAdminWhitelistsSortAndDirection(): void
{
$mockDb = $this->createMock(\medoo::class);
$queries = [];
$mockDb->method('query')
->willReturnCallback(function ($sql, $params = []) use (&$queries) {
$queries[] = ['sql' => $sql, 'params' => $params];
if (strpos($sql, 'COUNT(0)') !== false) {
return new class {
public function fetchAll()
{
return [[1]];
}
};
}
return new class {
public function fetchAll()
{
return [[
'id' => 1,
'name' => 'Przelew',
'description' => 'Opis',
'status' => 1,
'apilo_payment_type_id' => 5,
]];
}
};
});
$repository = new PaymentMethodRepository($mockDb);
$result = $repository->listForAdmin(
[],
'name DESC; DROP TABLE pp_shop_payment_methods; --',
'DESC; DELETE FROM pp_users; --',
1,
999
);
$this->assertCount(2, $queries);
$dataSql = $queries[1]['sql'];
$this->assertMatchesRegularExpression('/ORDER BY\s+spm\.name\s+ASC,\s+spm\.id\s+ASC/i', $dataSql);
$this->assertStringNotContainsString('DROP TABLE', $dataSql);
$this->assertStringNotContainsString('DELETE FROM pp_users', $dataSql);
$this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql);
$this->assertSame(1, (int)$result['items'][0]['id']);
}
public function testAllActiveReturnsNormalizedRows(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('select')
->with('pp_shop_payment_methods', '*', [
'status' => 1,
'ORDER' => ['id' => 'ASC'],
])
->willReturn([
[
'id' => '2',
'name' => ' PayU ',
'description' => '',
'status' => '1',
'apilo_payment_type_id' => null,
],
]);
$repository = new PaymentMethodRepository($mockDb);
$rows = $repository->allActive();
$this->assertCount(1, $rows);
$this->assertSame(2, $rows[0]['id']);
$this->assertSame('PayU', $rows[0]['name']);
$this->assertSame(1, $rows[0]['status']);
$this->assertNull($rows[0]['apilo_payment_type_id']);
}
public function testAllForAdminReturnsRowsIncludingInactive(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('select')
->with('pp_shop_payment_methods', '*', [
'ORDER' => ['name' => 'ASC'],
])
->willReturn([
[
'id' => '1',
'name' => 'Przelew',
'description' => '',
'status' => '1',
'apilo_payment_type_id' => null,
],
[
'id' => '2',
'name' => 'PayPo',
'description' => '',
'status' => '0',
'apilo_payment_type_id' => null,
],
]);
$repository = new PaymentMethodRepository($mockDb);
$rows = $repository->allForAdmin();
$this->assertCount(2, $rows);
$this->assertSame(1, $rows[0]['id']);
$this->assertSame(2, $rows[1]['id']);
$this->assertSame(0, $rows[1]['status']);
}
public function testFindActiveByIdReturnsNullForNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', '*', [
'AND' => [
'id' => 4,
'status' => 1,
],
])
->willReturn(null);
$repository = new PaymentMethodRepository($mockDb);
$this->assertNull($repository->findActiveById(4));
}
public function testFindKeepsNonNumericApiloPaymentTypeId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', '*', ['id' => 12])
->willReturn([
'id' => '12',
'name' => 'PayPo',
'description' => '',
'status' => '1',
'apilo_payment_type_id' => 'PAYPO_DEFERRED',
]);
$repository = new PaymentMethodRepository($mockDb);
$result = $repository->find(12);
$this->assertIsArray($result);
$this->assertSame('PAYPO_DEFERRED', $result['apilo_payment_type_id']);
}
public function testIsActiveNormalizesStatusValue(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', 'status', ['id' => 5])
->willReturn('0');
$repository = new PaymentMethodRepository($mockDb);
$this->assertSame(0, $repository->isActive(5));
}
public function testGetApiloPaymentTypeIdHandlesNullAndInt(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(2))
->method('get')
->willReturnOnConsecutiveCalls(null, '8');
$repository = new PaymentMethodRepository($mockDb);
$this->assertNull($repository->getApiloPaymentTypeId(1));
$this->assertSame(8, $repository->getApiloPaymentTypeId(2));
}
public function testGetApiloPaymentTypeIdReturnsStringForNonNumericValue(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', 'apilo_payment_type_id', ['id' => 3])
->willReturn('BANK_TRANSFER');
$repository = new PaymentMethodRepository($mockDb);
$this->assertSame('BANK_TRANSFER', $repository->getApiloPaymentTypeId(3));
}
public function testForTransportReturnsRows(): void
{
$mockDb = $this->createMock(\medoo::class);
$capturedParams = null;
$mockDb->expects($this->once())
->method('query')
->willReturnCallback(function ($sql, $params = []) use (&$capturedParams) {
$this->assertStringContainsString('pp_shop_transport_payment_methods', $sql);
$capturedParams = $params;
return new class {
public function fetchAll()
{
return [[
'id' => '9',
'name' => 'Karta',
'description' => 'Opis',
'status' => 1,
'apilo_payment_type_id' => '4',
]];
}
};
});
$repository = new PaymentMethodRepository($mockDb);
$rows = $repository->forTransport(12);
$this->assertSame([':transport_id' => 12], $capturedParams);
$this->assertCount(1, $rows);
$this->assertSame(9, $rows[0]['id']);
$this->assertSame(4, $rows[0]['apilo_payment_type_id']);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Tests\Unit\admin\Controllers;
use PHPUnit\Framework\TestCase;
use admin\Controllers\ShopPaymentMethodController;
use Domain\PaymentMethod\PaymentMethodRepository;
class ShopPaymentMethodControllerTest extends TestCase
{
private $repository;
private $controller;
protected function setUp(): void
{
$this->repository = $this->createMock(PaymentMethodRepository::class);
$this->controller = new ShopPaymentMethodController($this->repository);
}
public function testConstructorAcceptsRepository(): void
{
$controller = new ShopPaymentMethodController($this->repository);
$this->assertInstanceOf(ShopPaymentMethodController::class, $controller);
}
public function testHasMainActionMethods(): void
{
$this->assertTrue(method_exists($this->controller, 'list'));
$this->assertTrue(method_exists($this->controller, 'edit'));
$this->assertTrue(method_exists($this->controller, 'save'));
}
public function testHasNoLegacyAliasMethods(): void
{
$this->assertFalse(method_exists($this->controller, 'view_list'));
$this->assertFalse(method_exists($this->controller, 'payment_method_edit'));
$this->assertFalse(method_exists($this->controller, 'payment_method_save'));
}
public function testActionMethodReturnTypes(): void
{
$reflection = new \ReflectionClass($this->controller);
$this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
$this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType());
$this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
}
public function testConstructorRequiresPaymentMethodRepository(): void
{
$reflection = new \ReflectionClass(ShopPaymentMethodController::class);
$constructor = $reflection->getConstructor();
$params = $constructor->getParameters();
$this->assertCount(1, $params);
$this->assertEquals('Domain\PaymentMethod\PaymentMethodRepository', $params[0]->getType()->getName());
}
}