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)); } }