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>
This commit is contained in:
2026-02-17 10:41:40 +01:00
parent 25348797da
commit e671142cee
34 changed files with 2049 additions and 961 deletions

View File

@@ -205,4 +205,262 @@ class CategoryRepositoryTest extends TestCase
$this->assertSame('Kategoria testowa', $repository->categoryTitle(10));
}
// ===== Frontend methods tests =====
public function testGetCategorySortReturnsZeroForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repository = new CategoryRepository($mockDb);
$this->assertSame(0, $repository->getCategorySort(0));
$this->assertSame(0, $repository->getCategorySort(-1));
}
public function testGetCategorySortReturnsSortType(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')
->with('pp_shop_categories', 'sort_type', ['id' => 5])
->willReturn('3');
$repository = new CategoryRepository($mockDb);
$this->assertSame(3, $repository->getCategorySort(5));
}
public function testCategoryNameReturnsEmptyForInvalidInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repository = new CategoryRepository($mockDb);
$this->assertSame('', $repository->categoryName(0, 'pl'));
$this->assertSame('', $repository->categoryName(5, ''));
}
public function testCategoryNameReturnsTitle(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')
->with('pp_shop_categories_langs', 'title', [
'AND' => [
'category_id' => 10,
'lang_id' => 'pl',
],
])
->willReturn('Elektronika');
$repository = new CategoryRepository($mockDb);
$this->assertSame('Elektronika', $repository->categoryName(10, 'pl'));
}
public function testCategoryNameReturnsEmptyWhenNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(null);
$repository = new CategoryRepository($mockDb);
$this->assertSame('', $repository->categoryName(10, 'pl'));
}
public function testFrontCategoryDetailsReturnsEmptyForInvalidId(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('get');
$repository = new CategoryRepository($mockDb);
$this->assertSame([], $repository->frontCategoryDetails(0, 'pl'));
$this->assertSame([], $repository->frontCategoryDetails(-1, 'en'));
}
public function testFrontCategoryDetailsReturnsCategoryWithLanguage(): void
{
$mockDb = $this->createMock(\medoo::class);
$callIndex = 0;
$mockDb->method('get')
->willReturnCallback(function ($table) use (&$callIndex) {
$callIndex++;
if ($table === 'pp_shop_categories') {
return [
'id' => 7,
'status' => 1,
'sort_type' => 2,
'parent_id' => null,
];
}
if ($table === 'pp_shop_categories_langs') {
return [
'category_id' => 7,
'lang_id' => 'pl',
'title' => 'Odzież',
'seo_link' => 'odziez',
];
}
return null;
});
$repository = new CategoryRepository($mockDb);
$result = $repository->frontCategoryDetails(7, 'pl');
$this->assertSame(7, $result['id']);
$this->assertSame('Odzież', $result['language']['title']);
$this->assertSame('odziez', $result['language']['seo_link']);
}
public function testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(null);
$repository = new CategoryRepository($mockDb);
$this->assertSame([], $repository->frontCategoryDetails(999, 'pl'));
}
public function testCategoriesTreeReturnsEmptyWhenNoCategories(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('select')->willReturn([]);
$repository = new CategoryRepository($mockDb);
$this->assertSame([], $repository->categoriesTree('pl'));
}
public function testCategoryProductsCountReturnsZeroForInvalidInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('query');
$repository = new CategoryRepository($mockDb);
$this->assertSame(0, $repository->categoryProductsCount(0, 'pl'));
$this->assertSame(0, $repository->categoryProductsCount(5, ''));
}
public function testCategoryProductsCountReturnsCount(): void
{
$mockDb = $this->createMock(\medoo::class);
$stmt = $this->createMock(\PDOStatement::class);
$stmt->method('fetchAll')->willReturn([[15]]);
$mockDb->method('query')->willReturn($stmt);
$repository = new CategoryRepository($mockDb);
$this->assertSame(15, $repository->categoryProductsCount(3, 'pl'));
}
public function testProductsIdReturnsEmptyForInvalidInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('query');
$repository = new CategoryRepository($mockDb);
$this->assertSame([], $repository->productsId(0, 0, 'pl', 12, 0));
$this->assertSame([], $repository->productsId(5, 0, '', 12, 0));
}
public function testProductsIdReturnsProductIds(): void
{
$mockDb = $this->createMock(\medoo::class);
$stmt = $this->createMock(\PDOStatement::class);
$stmt->method('fetchAll')->willReturn([
['id' => 101],
['id' => 102],
['id' => 103],
]);
$mockDb->method('query')->willReturn($stmt);
$repository = new CategoryRepository($mockDb);
$result = $repository->productsId(5, 1, 'pl', 12, 0);
$this->assertSame([101, 102, 103], $result);
}
public function testBlogCategoryProductsReturnsEmptyForInvalidInput(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('query');
$repository = new CategoryRepository($mockDb);
$this->assertSame([], $repository->blogCategoryProducts(0, 'pl', 5));
$this->assertSame([], $repository->blogCategoryProducts(5, '', 5));
$this->assertSame([], $repository->blogCategoryProducts(5, 'pl', 0));
}
public function testBlogCategoryProductsReturnsIds(): void
{
$mockDb = $this->createMock(\medoo::class);
$stmt = $this->createMock(\PDOStatement::class);
$stmt->method('fetchAll')->willReturn([
['id' => 201],
['id' => 202],
]);
$mockDb->method('query')->willReturn($stmt);
$repository = new CategoryRepository($mockDb);
$result = $repository->blogCategoryProducts(3, 'pl', 5);
$this->assertSame([201, 202], $result);
}
public function testPaginatedCategoryProductsReturnsEmptyWhenNoProducts(): void
{
$mockDb = $this->createMock(\medoo::class);
$stmt = $this->createMock(\PDOStatement::class);
$stmt->method('fetchAll')->willReturn([[0]]);
$mockDb->method('query')->willReturn($stmt);
$repository = new CategoryRepository($mockDb);
$result = $repository->paginatedCategoryProducts(5, 0, 'pl', 1);
$this->assertSame([], $result['products']);
$this->assertSame(0, $result['ls']);
}
public function testPaginatedCategoryProductsClampsPage(): void
{
$mockDb = $this->createMock(\medoo::class);
$countStmt = $this->createMock(\PDOStatement::class);
$countStmt->method('fetchAll')->willReturn([[25]]);
$productsStmt = $this->createMock(\PDOStatement::class);
$productsStmt->method('fetchAll')->willReturn([
['id' => 301],
['id' => 302],
]);
$callIndex = 0;
$mockDb->method('query')
->willReturnCallback(function () use (&$callIndex, $countStmt, $productsStmt) {
$callIndex++;
return $callIndex === 1 ? $countStmt : $productsStmt;
});
$repository = new CategoryRepository($mockDb);
// 25 products / 12 per page = 3 pages; page 99 should clamp to 3
$result = $repository->paginatedCategoryProducts(5, 0, 'pl', 99);
$this->assertSame(3, $result['ls']);
$this->assertSame([301, 302], $result['products']);
}
}

View File

@@ -131,4 +131,505 @@ class ClientRepositoryTest extends TestCase
$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));
}
}

View File

@@ -6,6 +6,9 @@ class Helpers
public static function seo($str) { return $str; }
public static function delete_dir($path) {}
public static function alert($msg) {}
public static function error($msg) {}
public static function lang($key) { return $key; }
public static function delete_session($key) { unset($_SESSION[$key]); }
public static function htacces() {}
public static function delete_cache() {}
public static function get($key) { return null; }