Add new version 0.238 zip file containing updated ProductRepository and Product class files
This commit is contained in:
@@ -2,7 +2,12 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.234.zip'' -Force\")",
|
||||
"Bash(powershell -Command:*)"
|
||||
"Bash(powershell -Command:*)",
|
||||
"Bash(ls -la \"c:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\updates\\\\0.20\"\" | grep \"ver_ \")",
|
||||
"Bash(C:/xampp/php/php.exe:*)",
|
||||
"Bash(where:*)",
|
||||
"Bash(composer:*)",
|
||||
"Bash(curl:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
.phpunit.result.cache
Normal file
1
.phpunit.result.cache
Normal file
@@ -0,0 +1 @@
|
||||
{"version":1,"defects":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.003,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0}}
|
||||
239
PROJECT_STRUCTURE.md
Normal file
239
PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Struktura Projektu shopPRO
|
||||
|
||||
Dokumentacja struktury projektu shopPRO do szybkiego odniesienia.
|
||||
|
||||
## System Cache (Redis)
|
||||
|
||||
### Klasy odpowiedzialne za cache
|
||||
|
||||
#### RedisConnection
|
||||
- **Plik:** `autoload/class.RedisConnection.php`
|
||||
- **Opis:** Singleton zarządzający połączeniem z Redis
|
||||
- **Metody:**
|
||||
- `getInstance()` - pobiera instancję połączenia
|
||||
- `getConnection()` - zwraca obiekt Redis
|
||||
|
||||
#### CacheHandler
|
||||
- **Plik:** `autoload/class.CacheHandler.php`
|
||||
- **Opis:** Handler do obsługi cache Redis
|
||||
- **Metody:**
|
||||
- `get($key)` - pobiera wartość z cache
|
||||
- `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache
|
||||
- `exists($key)` - sprawdza czy klucz istnieje
|
||||
- `delete($key)` - usuwa pojedynczy klucz
|
||||
- `deletePattern($pattern)` - usuwa klucze według wzorca
|
||||
|
||||
#### Klasa S (pomocnicza)
|
||||
- **Plik:** `autoload/class.S.php`
|
||||
- **Metody cache:**
|
||||
- `clear_redis_cache()` - czyści cały cache Redis (flushAll)
|
||||
- `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu
|
||||
|
||||
### Wzorce kluczy Redis
|
||||
|
||||
#### Produkty
|
||||
```
|
||||
shop\product:{product_id}:{lang_id}:{permutation_hash}
|
||||
```
|
||||
- Przechowuje zserializowany obiekt produktu
|
||||
- TTL: 24 godziny (86400 sekund)
|
||||
- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121`
|
||||
|
||||
#### Opcje ilościowe produktu
|
||||
```
|
||||
\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation}
|
||||
```
|
||||
- Przechowuje informacje o ilości i komunikatach magazynowych
|
||||
- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549`
|
||||
|
||||
#### Zestawy produktów
|
||||
```
|
||||
\shop\Product::product_sets_when_add_to_basket:{product_id}
|
||||
```
|
||||
- Przechowuje produkty często kupowane razem
|
||||
- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316`
|
||||
|
||||
## Integracje z systemami zewnętrznymi (CRON)
|
||||
|
||||
### Plik: `cron.php`
|
||||
|
||||
#### Sellasist
|
||||
- **Aktualizacja produktów:** Linia 111-149
|
||||
- **Funkcje:** Aktualizacja cen i stanów magazynowych
|
||||
- **Częstotliwość:** Co 10 minut
|
||||
- **Czyszczenie cache:** Linia 146
|
||||
|
||||
#### Apilo
|
||||
- **Aktualizacja pojedynczego produktu:** Linia 152-176
|
||||
- Częstotliwość: Co 10 minut
|
||||
- Czyszczenie cache: Linia 173
|
||||
|
||||
- **Synchronizacja cennika:** Linia 179-218
|
||||
- Częstotliwość: Co 1 godzinę
|
||||
- Czyszczenie cache: Linia 212
|
||||
|
||||
#### Baselinker
|
||||
- **Aktualizacja produktów:** Linia 220-289
|
||||
- **Funkcje:** Ceny, stany magazynowe, wagi
|
||||
- **Częstotliwość:** Co 24 godziny (1440 minut)
|
||||
- **Czyszczenie cache:** Linia 278
|
||||
|
||||
## Panel Administratora
|
||||
|
||||
### Routing
|
||||
- Główny katalog: `admin/`
|
||||
- Template główny: `admin/templates/site/main-layout.php`
|
||||
- Kontrolery: `autoload/admin/controls/`
|
||||
|
||||
### Przycisk "Wyczyść cache"
|
||||
- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172`
|
||||
- **JavaScript:** `admin/templates/site/main-layout.php:235-274`
|
||||
- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/`
|
||||
- **Kontroler:** `autoload/admin/controls/class.Settings.php:20-42`
|
||||
- **Działanie:**
|
||||
1. Pokazuje spinner "Czyszczę cache..."
|
||||
2. Czyści katalogi: `temp/`, `thumbs/`
|
||||
3. Wykonuje `flushAll()` na Redis
|
||||
4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy
|
||||
5. Przywraca stan początkowy
|
||||
|
||||
## Struktura katalogów
|
||||
|
||||
```
|
||||
shopPRO/
|
||||
├── admin/ # Panel administratora
|
||||
│ ├── templates/ # Szablony widoków
|
||||
│ └── layout/ # Zasoby CSS/JS/ikony
|
||||
├── autoload/ # Klasy autoloadowane
|
||||
│ ├── admin/ # Klasy panelu admin
|
||||
│ │ ├── controls/ # Kontrolery
|
||||
│ │ └── factory/ # Fabryki/helpery
|
||||
│ ├── front/ # Klasy frontendu
|
||||
│ │ └── factory/ # Fabryki/helpery
|
||||
│ └── shop/ # Klasy sklepu
|
||||
├── libraries/ # Biblioteki zewnętrzne
|
||||
├── temp/ # Cache tymczasowy
|
||||
├── thumbs/ # Miniatury zdjęć
|
||||
└── cron.php # Zadania CRON
|
||||
|
||||
```
|
||||
|
||||
## Baza danych
|
||||
|
||||
### Główne tabele produktów
|
||||
- `pp_shop_products` - produkty główne
|
||||
- `pp_shop_products_langs` - tłumaczenia produktów
|
||||
- `pp_shop_products_images` - zdjęcia produktów
|
||||
- `pp_shop_products_categories` - kategorie produktów
|
||||
- `pp_shop_products_custom_fields` - pola własne produktów
|
||||
|
||||
### Tabele integracji
|
||||
- Kolumny w `pp_shop_products`:
|
||||
- `sellasist_product_id`, `sellasist_get_data_date`
|
||||
- `apilo_product_id`, `apilo_get_data_date`
|
||||
- `baselinker_product_id`, `baselinker_get_data_date`
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
### Redis
|
||||
- Konfiguracja: `config.php` (zmienna `$config['redis']`)
|
||||
- Parametry: host, port, password
|
||||
|
||||
### Autoload
|
||||
- Funkcja: `__autoload_my_classes()` w `cron.php:6`
|
||||
- Wzorzec: `autoload/{namespace}/class.{ClassName}.php`
|
||||
|
||||
## Klasy pomocnicze
|
||||
|
||||
### \S (autoload/class.S.php)
|
||||
Główna klasa helper z metodami:
|
||||
- `seo($val)` - generowanie URL SEO
|
||||
- `normalize_decimal($val, $precision)` - normalizacja liczb
|
||||
- `send_email()` - wysyłanie emaili
|
||||
- `delete_dir($dir)` - usuwanie katalogów
|
||||
- `htacces()` - generowanie .htaccess i sitemap.xml
|
||||
|
||||
### Medoo
|
||||
- Plik: `libraries/medoo/medoo.php`
|
||||
- Zmienna: `$mdb`
|
||||
- ORM do operacji na bazie danych
|
||||
|
||||
## Najważniejsze wzorce
|
||||
|
||||
### Namespace'y
|
||||
- `\admin\controls\` - kontrolery panelu admin
|
||||
- `\admin\factory\` - helpery/fabryki admin
|
||||
- `\front\factory\` - helpery/fabryki frontend
|
||||
- `\shop\` - klasy sklepu (Product, Order, itp.)
|
||||
|
||||
### Cachowanie produktów
|
||||
```php
|
||||
// Pobranie produktu z cache
|
||||
$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash);
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache($product_id);
|
||||
|
||||
// Czyszczenie całego cache
|
||||
\S::clear_redis_cache();
|
||||
```
|
||||
|
||||
## Refaktoryzacja do Domain-Driven Architecture
|
||||
|
||||
### Nowa struktura (w trakcie migracji)
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Nowa warstwa biznesowa
|
||||
│ └── Product/
|
||||
│ └── ProductRepository.php
|
||||
├── shop/ # Legacy - fasady do nowych klas
|
||||
├── admin/factory/ # Legacy - stopniowo migrowane
|
||||
└── front/factory/ # Legacy - stopniowo migrowane
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
||||
```php
|
||||
// STARE
|
||||
global $mdb;
|
||||
$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
|
||||
// NOWE
|
||||
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||
$quantity = $repository->getQuantity($id);
|
||||
```
|
||||
|
||||
## Testowanie (tylko dla deweloperów)
|
||||
|
||||
**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów!
|
||||
|
||||
### Narzędzia
|
||||
- **PHPUnit 9.6.34** - framework testowy
|
||||
- **test.bat** - uruchamianie testów
|
||||
- **composer.json** - autoloading PSR-4
|
||||
|
||||
### Struktura
|
||||
```
|
||||
tests/
|
||||
├── Unit/ # Testy jednostkowe
|
||||
│ └── Domain/Product/ProductRepositoryTest.php
|
||||
└── Integration/ # Testy integracyjne
|
||||
```
|
||||
|
||||
## Ostatnie modyfikacje
|
||||
|
||||
### 2025-02-05: Refaktoryzacja - Product Repository (ver. 0.238)
|
||||
- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
||||
- **NOWE:** Dependency Injection zamiast `global $mdb`
|
||||
- **NOWE:** Testy jednostkowe (5 testów, 100% pokrycie)
|
||||
- Zmigrowano: `get_product_quantity()` → `ProductRepository::getQuantity()`
|
||||
- Kompatybilność: Stara klasa `shop\Product` działa jako fasada
|
||||
|
||||
### 2025-02-05: System cache produktów (ver. 0.237)
|
||||
- Automatyczne czyszczenie cache produktu po aktualizacji przez CRON
|
||||
- AJAX dla przycisku "Wyczyść cache" w panelu admin
|
||||
- Metody `delete()` i `deletePattern()` w CacheHandler
|
||||
- Metoda `clear_product_cache()` w klasie S
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2025-02-05*
|
||||
254
REFACTORING_PLAN.md
Normal file
254
REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture
|
||||
|
||||
## Cel
|
||||
Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
|
||||
- **Domain/** - logika biznesowa (core)
|
||||
- **Admin/** - warstwa administratora
|
||||
- **Frontend/** - warstwa użytkownika
|
||||
- **Shared/** - współdzielone narzędzia
|
||||
|
||||
## Docelowa struktura
|
||||
|
||||
```
|
||||
autoload/
|
||||
├── Domain/ # Logika biznesowa (CORE)
|
||||
│ ├── Product/
|
||||
│ │ ├── Product.php # Entity
|
||||
│ │ ├── ProductRepository.php # Dostęp do bazy
|
||||
│ │ ├── ProductService.php # Logika biznesowa
|
||||
│ │ └── ProductCacheService.php # Cache produktu
|
||||
│ ├── Order/
|
||||
│ ├── Category/
|
||||
│ └── ...
|
||||
│
|
||||
├── Admin/ # Warstwa administratora
|
||||
│ ├── Controllers/
|
||||
│ └── Services/
|
||||
│
|
||||
├── Frontend/ # Warstwa użytkownika
|
||||
│ ├── Controllers/
|
||||
│ └── Services/
|
||||
│
|
||||
├── Shared/ # Współdzielone narzędzia
|
||||
│ ├── Cache/
|
||||
│ │ ├── CacheHandler.php
|
||||
│ │ └── RedisConnection.php
|
||||
│ └── Helpers/
|
||||
│ └── S.php
|
||||
│
|
||||
└── [LEGACY] # Stare klasy (stopniowo deprecated)
|
||||
├── shop/
|
||||
├── admin/factory/
|
||||
└── front/factory/
|
||||
```
|
||||
|
||||
## Zasady migracji
|
||||
|
||||
### 1. Stopniowość
|
||||
- Przenosimy **jedną funkcję na raz**
|
||||
- Zachowujemy kompatybilność wsteczną
|
||||
- Stare klasy działają jako fasady do nowych
|
||||
|
||||
### 2. Dependency Injection zamiast statycznych metod
|
||||
```php
|
||||
// ❌ STARE - statyczne
|
||||
class Product {
|
||||
public static function getQuantity($id) {
|
||||
global $mdb;
|
||||
return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NOWE - instancje z DI
|
||||
class ProductRepository {
|
||||
private $db;
|
||||
|
||||
public function __construct($db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function getQuantity($id) {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Fasady dla kompatybilności
|
||||
```php
|
||||
// Stara klasa wywołuje nową
|
||||
namespace shop;
|
||||
|
||||
class Product {
|
||||
public static function getQuantity($id) {
|
||||
global $mdb;
|
||||
$repo = new \Domain\Product\ProductRepository($mdb);
|
||||
return $repo->getQuantity($id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Proces migracji funkcji
|
||||
|
||||
### Krok 1: Wybór funkcji
|
||||
- Wybierz prostą funkcję statyczną
|
||||
- Sprawdź jej zależności
|
||||
- Przeanalizuj gdzie jest używana
|
||||
|
||||
### Krok 2: Stworzenie nowej struktury
|
||||
- Utwórz folder `Domain/{Module}/`
|
||||
- Stwórz odpowiednią klasę (Repository/Service/Entity)
|
||||
- Przenieś logikę
|
||||
|
||||
### Krok 3: Znalezienie użyć
|
||||
```bash
|
||||
grep -r "Product::getQuantity" .
|
||||
```
|
||||
|
||||
### Krok 4: Aktualizacja wywołań
|
||||
- Opcja A: Bezpośrednie wywołanie nowej klasy
|
||||
- Opcja B: Fasada w starej klasie (zalecane na początek)
|
||||
|
||||
### Krok 5: Testy
|
||||
- Napisz test jednostkowy dla nowej funkcji
|
||||
- Sprawdź czy stare wywołania działają
|
||||
|
||||
## Status migracji
|
||||
|
||||
### ✅ Zmigrowane moduły
|
||||
- **Cache** (częściowo)
|
||||
- ✅ CacheHandler - ma delete/deletePattern
|
||||
- ✅ RedisConnection - singleton
|
||||
- ✅ S::clear_product_cache() - nowa metoda
|
||||
|
||||
### 🔄 W trakcie
|
||||
- **Product**
|
||||
- ✅ get_product_quantity() - **ZMIGROWANE** (2025-02-05) 🎉
|
||||
- Nowa klasa: `Domain\Product\ProductRepository::getQuantity()`
|
||||
- Fasada w: `shop\Product::get_product_quantity()`
|
||||
- Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php`
|
||||
- Testy: ✅ 5/5 przechodzą (11 asercji)
|
||||
- Aktualizacja: ver. 0.238
|
||||
- Użycie DI: ✅ Konstruktor przyjmuje `$db`
|
||||
- [ ] get_product_price() - NASTĘPNA 👉
|
||||
- [ ] get_product_name()
|
||||
|
||||
### 📋 Do zrobienia
|
||||
- Order
|
||||
- Category
|
||||
- ShopAttribute
|
||||
- ShopProduct (factory)
|
||||
|
||||
## Testowanie
|
||||
|
||||
### Framework: PHPUnit
|
||||
Instalacja:
|
||||
```bash
|
||||
composer require --dev phpunit/phpunit
|
||||
```
|
||||
|
||||
### Struktura testów
|
||||
```
|
||||
tests/
|
||||
├── Unit/
|
||||
│ ├── Domain/
|
||||
│ │ └── Product/
|
||||
│ │ ├── ProductRepositoryTest.php
|
||||
│ │ └── ProductServiceTest.php
|
||||
│ └── Shared/
|
||||
│ └── Cache/
|
||||
│ └── CacheHandlerTest.php
|
||||
└── Integration/
|
||||
└── Domain/
|
||||
└── Product/
|
||||
```
|
||||
|
||||
### Przykład testu
|
||||
```php
|
||||
// tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\Product\ProductRepository;
|
||||
|
||||
class ProductRepositoryTest extends TestCase {
|
||||
public function testGetQuantityReturnsCorrectValue() {
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('get')->willReturn(10);
|
||||
|
||||
$repo = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$quantity = $repo->getQuantity(123);
|
||||
|
||||
// Assert
|
||||
$this->assertEquals(10, $quantity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Zasady kodu
|
||||
|
||||
### 1. SOLID Principles
|
||||
- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność
|
||||
- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje
|
||||
- **L**iskov Substitution - podklasy mogą zastąpić nadklasy
|
||||
- **I**nterface Segregation - wiele małych interfejsów
|
||||
- **D**ependency Inversion - zależności od abstrakcji
|
||||
|
||||
### 2. Nazewnictwo
|
||||
- **Entity** - `Product.php` (reprezentuje obiekt domenowy)
|
||||
- **Repository** - `ProductRepository.php` (dostęp do danych)
|
||||
- **Service** - `ProductService.php` (logika biznesowa)
|
||||
- **Controller** - `ProductController.php` (obsługa requestów)
|
||||
|
||||
### 3. Type Hinting
|
||||
```php
|
||||
// ✅ DOBRE
|
||||
public function getQuantity(int $id): ?int {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
|
||||
// ❌ ZŁE
|
||||
public function getQuantity($id) {
|
||||
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
}
|
||||
```
|
||||
|
||||
## Narzędzia pomocnicze
|
||||
|
||||
### Composer autoloader
|
||||
Dodaj do `composer.json`:
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Domain\\": "autoload/Domain/",
|
||||
"Admin\\": "autoload/Admin/",
|
||||
"Frontend\\": "autoload/Frontend/",
|
||||
"Shared\\": "autoload/Shared/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Static Analysis
|
||||
```bash
|
||||
composer require --dev phpstan/phpstan
|
||||
vendor/bin/phpstan analyse autoload/Domain
|
||||
```
|
||||
|
||||
## Kolejność refaktoryzacji (priorytet)
|
||||
|
||||
1. **Cache** (już w trakcie) ✅
|
||||
2. **Product** (rozpoczynamy)
|
||||
- getQuantity
|
||||
- getPrice
|
||||
- getName
|
||||
- getFromCache
|
||||
3. **ProductRepository** (dostęp do bazy)
|
||||
4. **ProductService** (logika biznesowa)
|
||||
5. **Order**
|
||||
6. **Category**
|
||||
|
||||
---
|
||||
*Rozpoczęto: 2025-02-05*
|
||||
*Ostatnia aktualizacja: 2025-02-05*
|
||||
134
TESTING.md
Normal file
134
TESTING.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# 🧪 Testowanie shopPRO
|
||||
|
||||
## Szybki start
|
||||
|
||||
### Uruchom wszystkie testy
|
||||
```bash
|
||||
./test.bat # Windows CMD (z nazwami testów)
|
||||
./test-simple.bat # Tylko kropki (szybki)
|
||||
./test-debug.bat # Pełne szczegóły (debug)
|
||||
./test.sh # Git Bash
|
||||
```
|
||||
|
||||
### Konkretny plik
|
||||
```bash
|
||||
./test.bat tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||
```
|
||||
|
||||
## Tryby wyświetlania
|
||||
|
||||
### 1. TestDox (domyślny) - Czytelna lista ✅
|
||||
```bash
|
||||
./test.bat
|
||||
```
|
||||
Wynik:
|
||||
```
|
||||
Product Repository
|
||||
✔ Get quantity returns correct value [2.78 ms]
|
||||
✔ Get quantity returns null when product not found
|
||||
✔ Find returns product data
|
||||
```
|
||||
|
||||
### 2. Simple - Tylko kropki 📊
|
||||
```bash
|
||||
./test-simple.bat
|
||||
```
|
||||
Wynik:
|
||||
```
|
||||
..... 5 / 5 (100%)
|
||||
OK (5 tests, 11 assertions)
|
||||
```
|
||||
|
||||
### 3. Debug - Wszystkie szczegóły 🔬
|
||||
```bash
|
||||
./test-debug.bat
|
||||
```
|
||||
Wynik:
|
||||
```
|
||||
Test 'testGetQuantity' started
|
||||
Test 'testGetQuantity' ended
|
||||
...
|
||||
```
|
||||
|
||||
## Interpretacja wyników
|
||||
|
||||
### ✅ Sukces
|
||||
```
|
||||
..... 5 / 5 (100%)
|
||||
|
||||
OK (5 tests, 11 assertions)
|
||||
```
|
||||
- `.` = test przeszedł
|
||||
- Wszystko działa!
|
||||
|
||||
### ❌ Błąd
|
||||
```
|
||||
..E.. 5 / 5 (100%)
|
||||
|
||||
ERRORS!
|
||||
Tests: 5, Assertions: 8, Errors: 1.
|
||||
```
|
||||
- `E` = Error - błąd w kodzie
|
||||
- Sprawdź szczegóły powyżej
|
||||
|
||||
### ❌ Niepowodzenie
|
||||
```
|
||||
..F.. 5 / 5 (100%)
|
||||
|
||||
FAILURES!
|
||||
Tests: 5, Assertions: 11, Failures: 1.
|
||||
```
|
||||
- `F` = Failure - asercja się nie powiodła
|
||||
- Oczekiwano innej wartości
|
||||
|
||||
## Przykładowy test
|
||||
|
||||
```php
|
||||
public function testGetQuantityReturnsCorrectValue()
|
||||
{
|
||||
// Arrange - Przygotuj
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('get')->willReturn(42);
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act - Wykonaj
|
||||
$quantity = $repository->getQuantity(123);
|
||||
|
||||
// Assert - Sprawdź
|
||||
$this->assertEquals(42, $quantity);
|
||||
}
|
||||
```
|
||||
|
||||
## Dodawanie nowych testów
|
||||
|
||||
1. Utwórz plik w `tests/Unit/Domain/{Module}/{Class}Test.php`
|
||||
2. Rozszerz `TestCase`
|
||||
3. Metody testowe zaczynaj od `test`
|
||||
4. Użyj pattern **AAA** (Arrange, Act, Assert)
|
||||
|
||||
## Asercje
|
||||
|
||||
```php
|
||||
$this->assertEquals(expected, actual); // Równość
|
||||
$this->assertIsInt($value); // Typ
|
||||
$this->assertNull($value); // Null
|
||||
$this->assertTrue($condition); // Prawda
|
||||
$this->assertCount(3, $array); // Rozmiar
|
||||
```
|
||||
|
||||
## Mockowanie
|
||||
|
||||
```php
|
||||
// Prosty mock
|
||||
$mock = $this->createMock(\medoo::class);
|
||||
$mock->method('get')->willReturn('wartość');
|
||||
|
||||
// Z weryfikacją
|
||||
$mock->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->equalTo('tabela'))
|
||||
->willReturn(42);
|
||||
```
|
||||
|
||||
---
|
||||
📚 Więcej: [tests/README.md](tests/README.md)
|
||||
@@ -169,7 +169,7 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3 col-lg-2">
|
||||
<a href="/admin/settings/clear_cache/" class="btn btn-danger mt-3">Wyczyść cache</a>
|
||||
<button id="clear-cache-btn" class="btn btn-danger mt-3">Wyczyść cache</button>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-10 top-user">
|
||||
<div class="dropdown">
|
||||
@@ -229,6 +229,48 @@
|
||||
$( '#mobile-menu-btn i' ).addClass( 'fa-times' ).removeClass( 'fa-bars' );
|
||||
}
|
||||
});
|
||||
|
||||
// Obsługa przycisku czyszczenia cache
|
||||
$('#clear-cache-btn').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $btn = $(this);
|
||||
var originalText = $btn.text();
|
||||
|
||||
// Wyświetl komunikat o czyszczeniu
|
||||
$btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Czyszczę cache...');
|
||||
|
||||
// Wyślij żądanie AJAX
|
||||
$.ajax({
|
||||
url: '/admin/settings/clear_cache_ajax/',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
// Zmień komunikat na "wyczyszczono"
|
||||
$btn.html('<i class="fa fa-check"></i> Cache wyczyszczony!').removeClass('btn-danger').addClass('btn-success');
|
||||
|
||||
// Po 2 sekundach przywróć pierwotny stan
|
||||
setTimeout(function() {
|
||||
$btn.prop('disabled', false).html(originalText).removeClass('btn-success').addClass('btn-danger');
|
||||
}, 2000);
|
||||
} else {
|
||||
// Obsługa błędu
|
||||
$btn.html('<i class="fa fa-exclamation-triangle"></i> Błąd!').removeClass('btn-danger').addClass('btn-warning');
|
||||
setTimeout(function() {
|
||||
$btn.prop('disabled', false).html(originalText).removeClass('btn-warning').addClass('btn-danger');
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
// Obsługa błędu połączenia
|
||||
$btn.html('<i class="fa fa-exclamation-triangle"></i> Błąd połączenia!').removeClass('btn-danger').addClass('btn-warning');
|
||||
setTimeout(function() {
|
||||
$btn.prop('disabled', false).html(originalText).removeClass('btn-warning').addClass('btn-danger');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
70
autoload/Domain/Product/ProductRepository.php
Normal file
70
autoload/Domain/Product/ProductRepository.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace Domain\Product;
|
||||
|
||||
/**
|
||||
* Repository odpowiedzialny za dostęp do danych produktów
|
||||
*
|
||||
* Zgodnie z wzorcem Repository Pattern, ta klasa enkapsuluje
|
||||
* logikę dostępu do bazy danych dla produktów.
|
||||
*/
|
||||
class ProductRepository
|
||||
{
|
||||
/**
|
||||
* @var \medoo Instancja Medoo ORM
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Konstruktor - przyjmuje instancję bazy danych
|
||||
*
|
||||
* @param \medoo $db Instancja Medoo ORM
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera stan magazynowy produktu
|
||||
*
|
||||
* @param int $productId ID produktu
|
||||
* @return int|null Ilość produktu lub null jeśli nie znaleziono
|
||||
*/
|
||||
public function getQuantity(int $productId): ?int
|
||||
{
|
||||
$quantity = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]);
|
||||
|
||||
// Medoo zwraca false jeśli nie znaleziono
|
||||
return $quantity !== false ? (int)$quantity : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera produkt po ID
|
||||
*
|
||||
* @param int $productId ID produktu
|
||||
* @return array|null Dane produktu lub null
|
||||
*/
|
||||
public function find(int $productId): ?array
|
||||
{
|
||||
$product = $this->db->get('pp_shop_products', '*', ['id' => $productId]);
|
||||
return $product ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizuje ilość produktu
|
||||
*
|
||||
* @param int $productId ID produktu
|
||||
* @param int $quantity Nowa ilość
|
||||
* @return bool Czy aktualizacja się powiodła
|
||||
*/
|
||||
public function updateQuantity(int $productId, int $quantity): bool
|
||||
{
|
||||
$result = $this->db->update(
|
||||
'pp_shop_products',
|
||||
['quantity' => $quantity],
|
||||
['id' => $productId]
|
||||
);
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,30 @@ class Settings
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function clear_cache_ajax()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Czyszczenie katalogów cache
|
||||
\S::delete_dir( '../temp/' );
|
||||
\S::delete_dir( '../thumbs/' );
|
||||
|
||||
// Czyszczenie Redis cache
|
||||
$redis = \RedisConnection::getInstance() -> getConnection();
|
||||
if ( $redis )
|
||||
$redis -> flushAll();
|
||||
|
||||
// Zwróć odpowiedź JSON
|
||||
echo json_encode( [ 'status' => 'success', 'message' => 'Cache został wyczyszczony.' ] );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
// W przypadku błędu
|
||||
echo json_encode( [ 'status' => 'error', 'message' => 'Błąd podczas czyszczenia cache: ' . $e->getMessage() ] );
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function settings_save()
|
||||
{
|
||||
$values = json_decode( \S::get( 'values' ), true );
|
||||
|
||||
@@ -36,4 +36,23 @@ class CacheHandler
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
if ($this->redis) {
|
||||
return $this->redis->del($key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deletePattern($pattern)
|
||||
{
|
||||
if ($this->redis) {
|
||||
$keys = $this->redis->keys($pattern);
|
||||
if (!empty($keys)) {
|
||||
return $this->redis->del($keys);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,27 @@ class S
|
||||
$redis -> flushAll();
|
||||
}
|
||||
|
||||
static public function clear_product_cache( int $product_id )
|
||||
{
|
||||
if ( class_exists('Redis') )
|
||||
{
|
||||
try
|
||||
{
|
||||
$cacheHandler = new \CacheHandler();
|
||||
// Wyczyść cache produktu dla wszystkich języków i permutacji
|
||||
$cacheHandler -> deletePattern( "shop\\product:$product_id:*" );
|
||||
// Wyczyść cache związane z opcjami ilościowymi
|
||||
$cacheHandler -> deletePattern( "\\shop\\Product::get_product_permutation_quantity_options:$product_id:*" );
|
||||
// Wyczyść cache zestawów produktów
|
||||
$cacheHandler -> deletePattern( "\\shop\\Product::product_sets_when_add_to_basket:$product_id" );
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
error_log("Błąd podczas czyszczenia cache produktu: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public function remove_special_chars( $string ) {
|
||||
return str_ireplace( array( '\'', '"', ',' , ';', '<', '>' ), ' ', $string );
|
||||
}
|
||||
|
||||
@@ -608,10 +608,12 @@ class Product implements \ArrayAccess
|
||||
}
|
||||
|
||||
// pobierz stan magazynowy produktu
|
||||
// FASADA - wywołuje nową klasę Domain\Product\ProductRepository
|
||||
static public function get_product_quantity( int $product_id )
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
|
||||
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||
return $repository->getQuantity($product_id);
|
||||
}
|
||||
|
||||
public static function product_categories( int $product_id )
|
||||
|
||||
28
composer.json
Normal file
28
composer.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "shoppro/shoppro",
|
||||
"description": "shopPRO - System sklepu internetowego",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Domain\\": "autoload/Domain/",
|
||||
"Admin\\": "autoload/Admin/",
|
||||
"Frontend\\": "autoload/Frontend/",
|
||||
"Shared\\": "autoload/Shared/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"test-coverage": "phpunit --coverage-html coverage"
|
||||
}
|
||||
}
|
||||
1818
composer.lock
generated
Normal file
1818
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
cron.php
14
cron.php
@@ -141,6 +141,10 @@ if ( $sellasist_settings['enabled'] and $sellasist_settings['sync_products'] and
|
||||
$mdb -> update( 'pp_shop_products', [ 'quantity' => $responseData['storages'][0]['quantity'] ], [ 'sellasist_product_id' => $result['sellasist_product_id'] ] );
|
||||
|
||||
$mdb -> update( 'pp_shop_products', [ 'sellasist_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'sellasist_product_id' => $result['sellasist_product_id'] ] );
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache( (int)$result['id'] );
|
||||
|
||||
echo '<p>Zaktualizowałem dane produktu <b>' . $result['sellasist_product_name'] . ' #' . $result['id'] . '</b></p>';
|
||||
}
|
||||
}
|
||||
@@ -169,6 +173,9 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_products'] and $apilo_
|
||||
|
||||
$mdb -> update( 'pp_shop_products', [ 'apilo_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'apilo_product_id' => $result['apilo_product_id'] ] );
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache( (int)$result['id'] );
|
||||
|
||||
echo '<p>Zaktualizowałem dane produktu (APILO) <b>' . $result['apilo_product_name'] . ' #' . $result['id'] . '</b></p>';
|
||||
}
|
||||
}
|
||||
@@ -207,6 +214,9 @@ if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and ( !$apil
|
||||
$product_id = $mdb -> get( 'pp_shop_products', 'id', [ 'apilo_product_id' => $product_price['product'] ] );
|
||||
|
||||
\admin\factory\ShopProduct::update_product_combinations_prices( (int)$product_id, $price_brutto, $vat, null );
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache( (int)$product_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,9 +284,11 @@ if ( $baselinker_settings['enabled'] and $baselinker_settings['sync_products'] a
|
||||
|
||||
$mdb -> update( 'pp_shop_products', [ 'baselinker_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'baselinker_product_id' => $baselinker_product_id ] );
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache( (int)$result['id'] );
|
||||
|
||||
echo '<p>Zaktualizowałem dane produktu <b>' . $baselinker_product['text_fields']['name'] . ' #' . $result['id'] . '</b></p>';
|
||||
}
|
||||
\S::clear_redis_cache();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
113333
phpunit.phar
Normal file
113333
phpunit.phar
Normal file
File diff suppressed because one or more lines are too long
25
phpunit.xml
Normal file
25
phpunit.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
verbose="true">
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Integration">
|
||||
<directory>tests/Integration</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">autoload/Domain</directory>
|
||||
<directory suffix=".php">autoload/Shared</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory>vendor</directory>
|
||||
<directory>tests</directory>
|
||||
</exclude>
|
||||
</coverage>
|
||||
</phpunit>
|
||||
13
test-debug.bat
Normal file
13
test-debug.bat
Normal file
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
REM Skrypt do uruchamiania testów z PEŁNYMI szczegółami
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo Testy DEBUG - pełne szczegóły
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
C:\xampp\php\php.exe phpunit.phar --debug %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
13
test-simple.bat
Normal file
13
test-simple.bat
Normal file
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
REM Skrypt do uruchamiania testów - tylko kropki
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo Testy jednostkowe shopPRO
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
C:\xampp\php\php.exe phpunit.phar %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
13
test.bat
Normal file
13
test.bat
Normal file
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
REM Skrypt do uruchamiania testów PHPUnit
|
||||
|
||||
echo.
|
||||
echo ================================
|
||||
echo Testy jednostkowe shopPRO
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
C:\xampp\php\php.exe phpunit.phar --testdox %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
10
test.sh
Normal file
10
test.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Skrypt do uruchamiania testów PHPUnit
|
||||
|
||||
echo ""
|
||||
echo "================================"
|
||||
echo " Testy jednostkowe shopPRO"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
/c/xampp/php/php.exe phpunit.phar "$@"
|
||||
1
tests/Integration/.gitkeep
Normal file
1
tests/Integration/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
.gitkeep
|
||||
63
tests/README.md
Normal file
63
tests/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Testy shopPRO
|
||||
|
||||
## Instalacja PHPUnit
|
||||
|
||||
### Opcja 1: Przez Composer (zalecane)
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
### Opcja 2: Ręcznie (jeśli nie masz Composera)
|
||||
```bash
|
||||
wget https://phar.phpunit.de/phpunit-9.phar
|
||||
php phpunit-9.phar --version
|
||||
```
|
||||
|
||||
## Uruchamianie testów
|
||||
|
||||
### Wszystkie testy
|
||||
```bash
|
||||
composer test
|
||||
# lub
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
### Konkretny plik
|
||||
```bash
|
||||
vendor/bin/phpunit tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||
```
|
||||
|
||||
### Z pokryciem kodu
|
||||
```bash
|
||||
composer test-coverage
|
||||
```
|
||||
|
||||
## Anatomia testu (AAA Pattern)
|
||||
|
||||
```php
|
||||
public function testGetQuantityReturnsCorrectValue()
|
||||
{
|
||||
// Arrange - Przygotowanie
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('get')->willReturn(42);
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act - Wykonanie akcji
|
||||
$quantity = $repository->getQuantity(123);
|
||||
|
||||
// Assert - Sprawdzenie wyniku
|
||||
$this->assertEquals(42, $quantity);
|
||||
}
|
||||
```
|
||||
|
||||
## Najważniejsze asercje
|
||||
|
||||
```php
|
||||
$this->assertEquals(expected, actual); // Równość wartości
|
||||
$this->assertIsInt($value); // Typ
|
||||
$this->assertNull($value); // Czy null
|
||||
$this->assertTrue($condition); // Czy prawda
|
||||
```
|
||||
|
||||
---
|
||||
*Więcej: https://phpunit.de/documentation.html*
|
||||
136
tests/Unit/Domain/Product/ProductRepositoryTest.php
Normal file
136
tests/Unit/Domain/Product/ProductRepositoryTest.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
namespace Tests\Unit\Domain\Product;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\Product\ProductRepository;
|
||||
|
||||
/**
|
||||
* Testy jednostkowe dla ProductRepository
|
||||
*
|
||||
* Testujemy izolowaną logikę repozytorium z mockami bazy danych
|
||||
*/
|
||||
class ProductRepositoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test pobierania ilości produktu - przypadek sukcesu
|
||||
*/
|
||||
public function testGetQuantityReturnsCorrectValue()
|
||||
{
|
||||
// Arrange (Przygotowanie)
|
||||
// Tworzymy mock bazy danych
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
// Konfigurujemy mock - metoda get() zwróci 42
|
||||
$mockDb->expects($this->once())
|
||||
->method('get')
|
||||
->with(
|
||||
$this->equalTo('pp_shop_products'),
|
||||
$this->equalTo('quantity'),
|
||||
$this->equalTo(['id' => 123])
|
||||
)
|
||||
->willReturn(42);
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act (Działanie)
|
||||
$quantity = $repository->getQuantity(123);
|
||||
|
||||
// Assert (Asercja)
|
||||
$this->assertEquals(42, $quantity);
|
||||
$this->assertIsInt($quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pobierania ilości - produkt nie istnieje
|
||||
*/
|
||||
public function testGetQuantityReturnsNullWhenProductNotFound()
|
||||
{
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
// Medoo zwraca false gdy nie znajdzie rekordu
|
||||
$mockDb->method('get')->willReturn(false);
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$quantity = $repository->getQuantity(999);
|
||||
|
||||
// Assert
|
||||
$this->assertNull($quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pobierania produktu po ID
|
||||
*/
|
||||
public function testFindReturnsProductData()
|
||||
{
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$expectedProduct = [
|
||||
'id' => 123,
|
||||
'name' => 'Test Product',
|
||||
'quantity' => 10,
|
||||
'price_brutto' => '99.99'
|
||||
];
|
||||
|
||||
$mockDb->method('get')->willReturn($expectedProduct);
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$product = $repository->find(123);
|
||||
|
||||
// Assert
|
||||
$this->assertEquals($expectedProduct, $product);
|
||||
$this->assertIsArray($product);
|
||||
$this->assertEquals(123, $product['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test aktualizacji ilości produktu
|
||||
*/
|
||||
public function testUpdateQuantitySuccess()
|
||||
{
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
// Medoo zwraca PDOStatement w przypadku sukcesu
|
||||
$mockDb->expects($this->once())
|
||||
->method('update')
|
||||
->with(
|
||||
$this->equalTo('pp_shop_products'),
|
||||
$this->equalTo(['quantity' => 50]),
|
||||
$this->equalTo(['id' => 123])
|
||||
)
|
||||
->willReturn($this->createMock(\PDOStatement::class));
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$result = $repository->updateQuantity(123, 50);
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test typu zwracanej wartości
|
||||
*/
|
||||
public function testGetQuantityReturnsInteger()
|
||||
{
|
||||
// Arrange
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('get')->willReturn('25'); // Baza może zwrócić string
|
||||
|
||||
$repository = new ProductRepository($mockDb);
|
||||
|
||||
// Act
|
||||
$quantity = $repository->getQuantity(123);
|
||||
|
||||
// Assert
|
||||
$this->assertIsInt($quantity); // Sprawdzamy czy konwersja na int zadziałała
|
||||
$this->assertEquals(25, $quantity);
|
||||
}
|
||||
}
|
||||
33
tests/bootstrap.php
Normal file
33
tests/bootstrap.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Bootstrap dla testów PHPUnit
|
||||
*/
|
||||
|
||||
// Załaduj Composer autoloader (jeśli istnieje)
|
||||
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
} else {
|
||||
// Ręczny autoloader dla Domain
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'Domain\\';
|
||||
$baseDir = __DIR__ . '/../autoload/Domain/';
|
||||
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relativeClass = substr($class, $len);
|
||||
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Załaduj Medoo
|
||||
require_once __DIR__ . '/../libraries/medoo/medoo.php';
|
||||
|
||||
// Ustaw timezone
|
||||
date_default_timezone_set('Europe/Warsaw');
|
||||
BIN
updates/0.20/ver_0.237.zip
Normal file
BIN
updates/0.20/ver_0.237.zip
Normal file
Binary file not shown.
BIN
updates/0.20/ver_0.238.zip
Normal file
BIN
updates/0.20/ver_0.238.zip
Normal file
Binary file not shown.
@@ -1,3 +1,12 @@
|
||||
<b>ver. 0.238</b><br />
|
||||
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
|
||||
- NEW - Dependency Injection zamiast global variables
|
||||
- UPDATE - shop\Product::get_product_quantity() używa teraz nowego repozytorium (kompatybilność zachowana)
|
||||
<hr>
|
||||
<b>ver. 0.237</b><br />
|
||||
- NEW - automatyczne czyszczenie cache produktu po aktualizacji przez CRON (Sellasist, Apilo, Baselinker)
|
||||
- UPDATE - przycisk "Wyczyść cache" w panelu administratora z obsługą AJAX i komunikatami o postępie
|
||||
<hr>
|
||||
<b>ver. 0.236</b><br />
|
||||
- FIX - zabezpieczenie przed duplikatami zamówień w Apilo - automatyczne pobieranie ID zamówienia przy błędzie "idExternal już wykorzystywany"
|
||||
<hr>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 236;
|
||||
$current_ver = 238;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user