Files
shopPRO/tests/Unit/Domain/Client/ClientRepositoryTest.php
Jacek Pyziak d29d396197 ver. 0.289: ShopCategory + ShopClient frontend migration to Domain + Views + Controllers
ShopCategory: 9 frontend methods in CategoryRepository, front\Views\ShopCategory (3 methods),
deleted factory + view, updated 6 callers, +17 tests.

ShopClient: 13 frontend methods in ClientRepository, front\Views\ShopClient (8 methods),
front\Controllers\ShopClientController (15 methods + buildEmailBody helper),
deleted factory + view + controls, updated 7 callers, +36 tests.

Security fix: removed hardcoded password bypass 'Legia1916'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:41:40 +01:00

636 lines
22 KiB
PHP

<?php
namespace Tests\Unit\Domain\Client;
use PHPUnit\Framework\TestCase;
use Domain\Client\ClientRepository;
class ClientRepositoryTest extends TestCase
{
public function testListForAdminWhitelistsSortAndPagination(): 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 [[
'client_id' => 5,
'client_name' => 'Jan',
'client_surname' => 'Kowalski',
'client_email' => 'jan@example.com',
'client_phone' => '123',
'client_city' => 'Warszawa',
'total_orders' => 3,
'total_spent' => 199.99,
'is_registered' => 1,
]];
}
};
});
$repository = new ClientRepository($mockDb);
$result = $repository->listForAdmin(
[],
'client_name DESC; DROP TABLE pp_shop_orders; --',
'DESC; DELETE FROM pp_users; --',
1,
999
);
$this->assertCount(2, $queries);
$dataSql = $queries[1]['sql'];
$this->assertMatchesRegularExpression('/ORDER BY\s+c\.client_surname\s+ASC,\s+c\.client_surname\s+ASC,\s+c\.client_name\s+ASC/i', $dataSql);
$this->assertStringNotContainsString('DROP TABLE', $dataSql);
$this->assertMatchesRegularExpression('/LIMIT\s+100\s+OFFSET\s+0/i', $dataSql);
$this->assertSame(1, $result['total']);
$this->assertCount(1, $result['items']);
$this->assertSame('Jan', $result['items'][0]['client_name']);
}
public function testOrdersForClientReturnsEmptyOnMissingInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('query');
$repository = new ClientRepository($mockDb);
$this->assertSame([], $repository->ordersForClient('', 'Kowalski', 'jan@example.com'));
$this->assertSame([], $repository->ordersForClient('Jan', '', 'jan@example.com'));
$this->assertSame([], $repository->ordersForClient('Jan', 'Kowalski', ''));
}
public function testOrdersForClientNormalizesRows(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('query')
->willReturn(new class {
public function fetchAll() {
return [[
'id' => '10',
'date_order' => '2026-02-15 10:00:00',
'summary' => '149.50',
'payment_method' => 'Przelew',
'transport' => 'Kurier',
'message' => null,
]];
}
});
$repository = new ClientRepository($mockDb);
$rows = $repository->ordersForClient('Jan', 'Kowalski', 'jan@example.com');
$this->assertCount(1, $rows);
$this->assertSame(10, $rows[0]['id']);
$this->assertSame(149.50, $rows[0]['summary']);
$this->assertSame('Przelew', $rows[0]['payment_method']);
$this->assertSame('', $rows[0]['message']);
}
public function testTotalsForClientReturnsZeroForMissingInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('query');
$repository = new ClientRepository($mockDb);
$totals = $repository->totalsForClient('', 'Kowalski', 'jan@example.com');
$this->assertSame(0, $totals['total_orders']);
$this->assertSame(0.0, $totals['total_spent']);
}
public function testTotalsForClientReturnsAggregatedValues(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('query')
->willReturn(new class {
public function fetchAll() {
return [[
'total_orders' => '4',
'total_spent' => '456.78',
]];
}
});
$repository = new ClientRepository($mockDb);
$totals = $repository->totalsForClient('Jan', 'Kowalski', 'jan@example.com');
$this->assertSame(4, $totals['total_orders']);
$this->assertSame(456.78, $totals['total_spent']);
}
// ===== Frontend methods =====
public function testClientDetailsReturnsNullForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->clientDetails(0));
$this->assertNull($repo->clientDetails(-1));
}
public function testClientDetailsReturnsRowOnSuccess(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_clients', '*', ['id' => 5])
->willReturn(['id' => 5, 'email' => 'jan@example.com']);
$repo = new ClientRepository($mockDb);
$result = $repo->clientDetails(5);
$this->assertSame('jan@example.com', $result['email']);
}
public function testClientDetailsReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->clientDetails(999));
}
public function testClientEmailReturnsNullForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->clientEmail(0));
}
public function testClientEmailReturnsStringOnSuccess(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_clients', 'email', ['id' => 3])
->willReturn('test@example.com');
$repo = new ClientRepository($mockDb);
$this->assertSame('test@example.com', $repo->clientEmail(3));
}
public function testClientEmailReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->clientEmail(999));
}
public function testClientAddressesReturnsEmptyForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('select');
$repo = new ClientRepository($mockDb);
$this->assertSame([], $repo->clientAddresses(0));
}
public function testClientAddressesReturnsRows(): void
{
$rows = [
['id' => 1, 'client_id' => 5, 'city' => 'Warszawa'],
['id' => 2, 'client_id' => 5, 'city' => 'Kraków'],
];
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('select')
->with('pp_shop_clients_addresses', '*', ['client_id' => 5])
->willReturn($rows);
$repo = new ClientRepository($mockDb);
$this->assertCount(2, $repo->clientAddresses(5));
}
public function testClientAddressesHandlesFalseFromDb(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('select')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$this->assertSame([], $repo->clientAddresses(1));
}
public function testAddressDetailsReturnsNullForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->addressDetails(0));
}
public function testAddressDetailsReturnsRow(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_clients_addresses', '*', ['id' => 7])
->willReturn(['id' => 7, 'city' => 'Gdańsk']);
$repo = new ClientRepository($mockDb);
$result = $repo->addressDetails(7);
$this->assertSame('Gdańsk', $result['city']);
}
public function testAddressDeleteReturnsFalseForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('delete');
$repo = new ClientRepository($mockDb);
$this->assertFalse($repo->addressDelete(0));
}
public function testAddressDeleteReturnsTrueOnSuccess(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('delete')
->with('pp_shop_clients_addresses', ['id' => 3])
->willReturn(1);
$repo = new ClientRepository($mockDb);
$this->assertTrue($repo->addressDelete(3));
}
public function testAddressSaveReturnsFalseForInvalidClientId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('insert');
$mockDb->expects($this->never())->method('update');
$repo = new ClientRepository($mockDb);
$this->assertFalse($repo->addressSave(0, null, ['name' => 'Jan']));
}
public function testAddressSaveInsertsNewAddress(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('insert')
->with(
'pp_shop_clients_addresses',
$this->callback(function ($row) {
return $row['client_id'] === 5
&& $row['name'] === 'Jan'
&& $row['city'] === 'Warszawa';
})
)
->willReturn(1);
$repo = new ClientRepository($mockDb);
$result = $repo->addressSave(5, null, [
'name' => 'Jan',
'surname' => 'Kowalski',
'street' => 'Marszałkowska 1',
'postal_code' => '00-001',
'city' => 'Warszawa',
'phone' => '123456789',
]);
$this->assertTrue($result);
}
public function testAddressSaveUpdatesExistingAddress(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('update')
->with(
'pp_shop_clients_addresses',
$this->callback(function ($row) {
return $row['name'] === 'Anna' && !isset($row['client_id']);
}),
['AND' => ['client_id' => 5, 'id' => 10]]
)
->willReturn(1);
$repo = new ClientRepository($mockDb);
$result = $repo->addressSave(5, 10, [
'name' => 'Anna',
'surname' => 'Nowak',
'street' => 'Piłsudskiego 2',
'postal_code' => '30-001',
'city' => 'Kraków',
'phone' => '987654321',
]);
$this->assertTrue($result);
}
public function testMarkAddressAsCurrentReturnsFalseForInvalidIds(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('update');
$repo = new ClientRepository($mockDb);
$this->assertFalse($repo->markAddressAsCurrent(0, 1));
$this->assertFalse($repo->markAddressAsCurrent(1, 0));
}
public function testMarkAddressAsCurrentResetsAndSets(): void
{
$calls = [];
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(2))
->method('update')
->willReturnCallback(function ($table, $data, $where) use (&$calls) {
$calls[] = ['data' => $data, 'where' => $where];
return 1;
});
$repo = new ClientRepository($mockDb);
$this->assertTrue($repo->markAddressAsCurrent(5, 3));
$this->assertSame(['current' => 0], $calls[0]['data']);
$this->assertSame(['client_id' => 5], $calls[0]['where']);
$this->assertSame(['current' => 1], $calls[1]['data']);
$this->assertSame(['AND' => ['client_id' => 5, 'id' => 3]], $calls[1]['where']);
}
public function testAuthenticateReturnsErrorOnEmptyInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$result = $repo->authenticate('', 'pass');
$this->assertSame('error', $result['status']);
$result = $repo->authenticate('jan@example.com', '');
$this->assertSame('error', $result['status']);
}
public function testAuthenticateReturnsErrorWhenClientNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$result = $repo->authenticate('nobody@example.com', 'pass');
$this->assertSame('error', $result['status']);
$this->assertSame('logowanie-nieudane', $result['code']);
}
public function testAuthenticateReturnsInactiveForUnconfirmedAccount(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn([
'id' => 1,
'password' => md5('2026-01-01 00:00:00' . 'test123'),
'register_date' => '2026-01-01 00:00:00',
'hash' => 'abc123',
'status' => 0,
]);
$repo = new ClientRepository($mockDb);
$result = $repo->authenticate('jan@example.com', 'test123');
$this->assertSame('inactive', $result['status']);
$this->assertSame('abc123', $result['hash']);
}
public function testAuthenticateReturnsErrorOnWrongPassword(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn([
'id' => 1,
'password' => md5('2026-01-01 00:00:00' . 'correct'),
'register_date' => '2026-01-01 00:00:00',
'hash' => 'abc',
'status' => 1,
]);
$repo = new ClientRepository($mockDb);
$result = $repo->authenticate('jan@example.com', 'wrong');
$this->assertSame('error', $result['status']);
$this->assertSame('logowanie-blad-nieprawidlowe-haslo', $result['code']);
}
public function testAuthenticateReturnsOkOnSuccess(): void
{
$registerDate = '2026-01-01 00:00:00';
$password = 'test123';
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(2))
->method('get')
->willReturnOnConsecutiveCalls(
[
'id' => 5,
'password' => md5($registerDate . $password),
'register_date' => $registerDate,
'hash' => 'abc',
'status' => 1,
],
['id' => 5, 'email' => 'jan@example.com', 'name' => 'Jan']
);
$repo = new ClientRepository($mockDb);
$result = $repo->authenticate('jan@example.com', $password);
$this->assertSame('ok', $result['status']);
$this->assertSame(5, $result['client']['id']);
}
public function testCreateClientReturnsNullOnEmptyInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('count');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->createClient('', 'pass', false));
$this->assertNull($repo->createClient('jan@example.com', '', false));
}
public function testCreateClientReturnsNullWhenEmailTaken(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('count')
->with('pp_shop_clients', ['email' => 'jan@example.com'])
->willReturn(1);
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->createClient('jan@example.com', 'pass', false));
}
public function testCreateClientReturnsIdAndHashOnSuccess(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())->method('count')->willReturn(0);
$mockDb->expects($this->once())
->method('insert')
->with(
'pp_shop_clients',
$this->callback(function ($row) {
return $row['email'] === 'jan@example.com'
&& $row['agremment_marketing'] === 1
&& !empty($row['hash'])
&& !empty($row['password']);
})
)
->willReturn(1);
$mockDb->expects($this->once())->method('id')->willReturn(42);
$repo = new ClientRepository($mockDb);
$result = $repo->createClient('jan@example.com', 'pass', true);
$this->assertSame(42, $result['id']);
$this->assertNotEmpty($result['hash']);
}
public function testConfirmRegistrationReturnsNullOnEmptyHash(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->confirmRegistration(''));
}
public function testConfirmRegistrationReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->confirmRegistration('nonexistent'));
}
public function testConfirmRegistrationActivatesAndReturnsEmail(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->exactly(2))
->method('get')
->willReturnOnConsecutiveCalls(10, 'jan@example.com');
$mockDb->expects($this->once())
->method('update')
->with('pp_shop_clients', ['status' => 1], ['id' => 10]);
$repo = new ClientRepository($mockDb);
$this->assertSame('jan@example.com', $repo->confirmRegistration('validhash'));
}
public function testGenerateNewPasswordReturnsNullOnEmptyHash(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->generateNewPassword(''));
}
public function testGenerateNewPasswordReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->generateNewPassword('badhash'));
}
public function testGenerateNewPasswordReturnsEmailAndPassword(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn([
'id' => 5,
'email' => 'jan@example.com',
'register_date' => '2026-01-01 00:00:00',
]);
$mockDb->expects($this->once())
->method('update')
->with(
'pp_shop_clients',
$this->callback(function ($data) {
return $data['password_recovery'] === 0
&& !empty($data['password']);
}),
['id' => 5]
);
$repo = new ClientRepository($mockDb);
$result = $repo->generateNewPassword('validhash');
$this->assertSame('jan@example.com', $result['email']);
$this->assertSame(10, strlen($result['password']));
}
public function testInitiatePasswordRecoveryReturnsNullOnEmptyEmail(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->initiatePasswordRecovery(''));
}
public function testInitiatePasswordRecoveryReturnsNullWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn(false);
$repo = new ClientRepository($mockDb);
$this->assertNull($repo->initiatePasswordRecovery('nobody@example.com'));
}
public function testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->willReturn('abc123hash');
$mockDb->expects($this->once())
->method('update')
->with('pp_shop_clients', ['password_recovery' => 1], ['email' => 'jan@example.com']);
$repo = new ClientRepository($mockDb);
$this->assertSame('abc123hash', $repo->initiatePasswordRecovery('jan@example.com'));
}
public function testClientOrdersReturnsEmptyForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('select');
$repo = new ClientRepository($mockDb);
$this->assertSame([], $repo->clientOrders(0));
}
}