diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
new file mode 100644
index 0000000..507b05f
--- /dev/null
+++ b/.paul/PROJECT.md
@@ -0,0 +1,115 @@
+# shopPRO
+
+## What This Is
+
+Autorski silnik sklepu internetowego pisany od podstaw — odpowiednik WooCommerce lub PrestaShop, ale bez zależności od zewnętrznych platform. Składa się z panelu administratora (zarządzanie zamówieniami, produktami, klientami) oraz części frontowej dla klienta końcowego.
+
+## Core Value
+
+Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online — produktami, zamówieniami i klientami — w jednym spójnym systemie pisanym od podstaw, bez narzutów zewnętrznych platform.
+
+## Current State
+
+| Attribute | Value |
+|-----------|-------|
+| Version | 0.333 |
+| Status | Production |
+| Last Updated | 2026-03-12 |
+
+## Requirements
+
+### Validated (Shipped)
+
+- [x] Panel administratora — zarządzanie produktami, kategoriami, atrybutami
+- [x] Panel administratora — zarządzanie zamówieniami
+- [x] Panel administratora — zarządzanie klientami
+- [x] Część frontowa — przeglądanie i kupowanie produktów
+- [x] Koszyk i składanie zamówień
+- [x] Integracje płatności i dostaw
+- [x] REST API (ordersPRO + Ekomi)
+- [x] Redis caching
+- [x] Ochrona przed podwójnym składaniem zamówienia
+- [x] Domain-Driven Architecture (migracja z legacy zakończona)
+
+### Active (In Progress)
+
+- [ ] [Do zdefiniowania podczas planowania]
+
+### Planned (Next)
+
+- [ ] [Do zdefiniowania podczas planowania]
+
+### Out of Scope
+
+- Multitenancy (wiele sklepów w jednej instancji) — nie planowane
+
+## Target Users
+
+**Primary:** Właściciel/administrator sklepu internetowego
+- Zarządza produktami, zamówieniami, klientami przez panel admina
+- Potrzebuje niezawodnego, szybkiego narzędzia bez zbędnych zależności
+
+**Secondary:** Klient końcowy sklepu
+- Przegląda produkty, dodaje do koszyka, składa zamówienia
+
+## Context
+
+**Technical Context:**
+- PHP 7.4+ (produkcja: PHP < 8.0)
+- Medoo ORM (`$mdb`), Redis caching
+- Domain-Driven Design z Dependency Injection
+- PHPUnit 9.6, 810+ testów
+- Namespace: `\Domain\`, `\admin\`, `\front\`, `\api\`, `\Shared\`
+
+## Constraints
+
+### Technical Constraints
+- PHP < 8.0 na produkcji (brak `match`, named arguments, union types)
+- Medoo ORM — prepared statements bez wyjątków
+- Redis wymagany dla cache
+
+### Business Constraints
+- System wdrażany u klientów jako update package (ZIP)
+
+## Key Decisions
+
+| Decision | Rationale | Date | Status |
+|----------|-----------|------|--------|
+| DDD + DI zamiast legacy architektury | Testowalność, separacja odpowiedzialności | 2025 | Active |
+| PHP < 8.0 kompatybilność | Klienci na starszych serwerach | 2025 | Active |
+| Własny silnik zamiast frameworka | Pełna kontrola, brak narzutów | - | Active |
+
+## Success Metrics
+
+| Metric | Target | Current | Status |
+|--------|--------|---------|--------|
+| Testy | >800 | 810 | On track |
+| Pokrycie architektury DDD | 100% | 100% | Achieved |
+
+## Tech Stack
+
+| Layer | Technology | Notes |
+|-------|------------|-------|
+| Backend | PHP 7.4+ | < 8.0 na produkcji |
+| ORM | Medoo | `$mdb` global |
+| Cache | Redis | CacheHandler singleton |
+| Frontend | HTML/CSS/JS | Własny silnik szablonów (Tpl) |
+| Auth | Sesje PHP | CSRF, XSS protection |
+| Testy | PHPUnit 9.6 | phpunit.phar |
+
+## Specialized Flows
+
+See: .paul/SPECIAL-FLOWS.md
+
+Quick Reference:
+- /feature-dev → Nowe funkcje, większe zmiany (required)
+- /koniec-pracy → Release, update package (required)
+- /frontend-design → Komponenty UI, szablony widoków
+- /code-review → Przegląd kodu przed release
+- /simplify → Upraszczanie po implementacji
+- /claude-md-improver → Utrzymanie CLAUDE.md
+- /zapisz + /wznow → Zapis i wznowienie sesji
+
+---
+*PROJECT.md — Updated when requirements or context change*
+*Last updated: 2026-03-12*
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
new file mode 100644
index 0000000..0ac04b6
--- /dev/null
+++ b/.paul/ROADMAP.md
@@ -0,0 +1,62 @@
+# Roadmap: shopPRO
+
+## Overview
+
+shopPRO to autorski silnik sklepu internetowego rozwijany iteracyjnie. Projekt jest już na produkcji (v0.333) — roadmap obejmuje planowane funkcje i usprawnienia kolejnych wersji.
+
+## Current Milestone
+
+**Security hardening** (v0.33x)
+Status: In progress
+Phases: 3 of 4 complete
+
+## Phases
+
+| Phase | Name | Plans | Status | Completed |
+|-------|------|-------|--------|-----------|
+| 1 | Sensitive data logging fix | 1 | Done | 2026-03 |
+| 2 | Path traversal + XSS escaping | 1 | Done | 2026-03 (v0.335) |
+| 3 | Error handling w krytycznych ścieżkach | 1 | Done | 2026-03 (v0.336) |
+| 4 | CSRF protection — admin panel forms | 1 | Applied | 2026-03 (v0.337) |
+| 5 | Order bugs fix — duplicate + COD status | 1 | Applied | 2026-03 (v0.338) |
+
+## Next Milestone
+
+**Tech debt — Integrations refactoring**
+Status: Planning
+
+| Phase | Name | Plans | Status | Completed |
+|-------|------|-------|--------|-----------|
+| 6 | IntegrationsRepository split → ApiloRepository | 2 | Done | 2026-03 |
+
+## Phase Details
+
+### Phase 4 — CSRF protection
+
+**Problem:** Brak tokenów CSRF na formularzach panelu admina. State-changing POST endpointy (create/update/delete) są potencjalnie podatne na ataki CSRF.
+
+**Scope:** Dodanie CSRF tokenów do formularzy i walidacji w panelu administracyjnym.
+
+**Reference:** `.paul/codebase/concerns.md` — MEDIUM — Missing CSRF tokens
+
+### Phase 6 — IntegrationsRepository split
+
+**Problem:** `IntegrationsRepository` ma 875 linii — miesza logikę generyczną (settings, logi, product linking) z logiką specyficzną dla Apilo (~650 linii). Narusza zasadę jednej odpowiedzialności.
+
+**Scope:**
+- Plan 06-01: Utwórz `ApiloRepository` z metodami apilo* (non-breaking)
+- Plan 06-02: Zmigruj konsumentów (IntegrationsController, ShopProductController, OrderAdminService, cron.php), usuń apilo* z IntegrationsRepository
+
+---
+
+### Phase 5 — Order bugs fix
+
+**Problem 1:** Zduplikowane zamówienia — klient widzi błąd i klika złóż zamówienie ponownie. Pierwsze zamówienie trafiło do bazy mimo błędu. Powrót do `/podsumowanie` regeneruje token i pozwala złożyć drugie zamówienie.
+
+**Problem 2:** Zamówienia COD (płatność przy odbiorze) dostają status "Zamówienie złożone" zamiast "Przyjęte do realizacji". Kod sprawdza hardkodowane `payment_id == 3`, które jest inne w tej instancji sklepu.
+
+**Scope:** Guard w `summaryView()`, try-catch w `basketSave()`, kolumna `is_cod` w `pp_shop_payment_methods`, użycie flagi zamiast hardkodowanego ID.
+
+---
+*Roadmap created: 2026-03-12*
+*Last updated: 2026-03-12*
diff --git a/.paul/SPECIAL-FLOWS.md b/.paul/SPECIAL-FLOWS.md
new file mode 100644
index 0000000..bab7aa6
--- /dev/null
+++ b/.paul/SPECIAL-FLOWS.md
@@ -0,0 +1,37 @@
+# Specialized Flows: shopPRO
+
+## Project-Level Dependencies
+
+| Work Type | Skill/Command | Priority | Kiedy używać |
+|-----------|---------------|----------|--------------|
+| Komponenty UI, szablony widoków | /frontend-design | optional | Przy tworzeniu HTML/CSS |
+| Nowe funkcje, większe zmiany | /feature-dev | required | Przed implementacją fazy |
+| Przegląd kodu | /code-review | optional | Przed release / KONIEC PRACY |
+| Upraszczanie po zmianach | /simplify | optional | Po zakończeniu implementacji |
+| Utrzymanie CLAUDE.md | /claude-md-improver | optional | Co kilka faz / po dużych zmianach |
+| Release, budowanie update package | /koniec-pracy | required | Na koniec każdej sesji roboczej |
+| Zapis i wznowienie sesji | /zapisz + /wznow | optional | Na przerwę / powrót do pracy |
+
+## Phase Overrides
+
+Brak — domyślna konfiguracja obowiązuje dla wszystkich faz.
+
+## Templates & Assets
+
+| Asset Type | Location | When Used |
+|------------|----------|-----------|
+| CLAUDE.md | CLAUDE.md | Konwencje kodu, architektura, stack techniczny |
+| Struktura bazy | docs/DATABASE_STRUCTURE.md | Przy zmianach schematu DB |
+| Dokumentacja API | api-docs/api-reference.json | Przy zmianach API |
+| TODO | docs/TODO.md | Planowanie nowych funkcji |
+
+## Verification (UNIFY)
+
+Podczas UNIFY sprawdź:
+- `/feature-dev` — czy był użyty przed implementacją fazy?
+- `/koniec-pracy` — czy release został wykonany?
+
+Braki dokumentuj w STATE.md (Deferred Issues), nie blokują UNIFY.
+
+---
+*SPECIAL-FLOWS.md — Created: 2026-03-12*
diff --git a/.paul/STATE.md b/.paul/STATE.md
new file mode 100644
index 0000000..906334d
--- /dev/null
+++ b/.paul/STATE.md
@@ -0,0 +1,58 @@
+# Project State
+
+## Project Reference
+
+See: .paul/PROJECT.md (updated 2026-03-12)
+
+**Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform.
+**Current focus:** Projekt zainicjalizowany — gotowy do planowania
+
+## Current Position
+
+Milestone: Tech debt — Integrations refactoring
+Phase: 6 of ? — IntegrationsRepository split — Complete
+Plan: 06-02 complete (phase done)
+Status: UNIFY complete, phase 6 finished
+Last activity: 2026-03-12 — 06-02 UNIFY complete
+
+Note: Previous milestone (Security hardening) — fazy 4 i 5 UNIFY zakończone.
+Milestone Security hardening: COMPLETE.
+
+Progress:
+- Milestone (Security hardening): [██████████] 100% (COMPLETE)
+- Phase 6: [██████████] 100% (complete)
+
+## Loop Position
+
+Current loop state (phase 6, plan 02):
+```
+PLAN ──▶ APPLY ──▶ UNIFY
+ ✓ ✓ ✓ [Phase 6 complete]
+```
+
+Deferred (previous milestone — now closed):
+```
+Phase 4: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12]
+Phase 5: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12]
+```
+
+## Accumulated Context
+
+### Decisions
+None yet.
+
+### Deferred Issues
+None yet.
+
+### Blockers/Concerns
+None yet.
+
+## Session Continuity
+
+Last session: 2026-03-12
+Stopped at: Phase 04 UNIFY complete — wszystkie odroczone pętle zamknięte
+Next action: /paul:progress — wybierz kolejny task lub milestone
+Resume file: .paul/phases/04-csrf-protection/04-01-SUMMARY.md
+
+---
+*STATE.md — Updated after every significant action*
diff --git a/.paul/codebase/README.md b/.paul/codebase/README.md
new file mode 100644
index 0000000..b2c02f6
--- /dev/null
+++ b/.paul/codebase/README.md
@@ -0,0 +1,24 @@
+# Codebase Map — shopPRO
+
+Generated: 2026-03-12
+
+## Documents
+
+| File | Contents |
+|------|---------|
+| [overview.md](overview.md) | Project summary, size metrics, quick reference |
+| [stack.md](stack.md) | Technology stack, libraries, external integrations |
+| [architecture.md](architecture.md) | Directory structure, routing, DI, domain modules, request lifecycle |
+| [conventions.md](conventions.md) | Naming, Medoo patterns, cache patterns, security patterns |
+| [testing.md](testing.md) | PHPUnit setup, test patterns, mocking, coverage |
+| [concerns.md](concerns.md) | Security issues, technical debt, dead code, known bugs |
+| [dependencies.md](dependencies.md) | Composer, vendored libs, PHP extensions |
+
+## Quick Facts
+
+- **PHP 7.4 – <8.0** — no match, union types, str_contains etc.
+- **810 tests / 2264 assertions**
+- **29 Domain modules**, all with tests
+- **Medoo pitfall**: `delete()` takes 2 args, not 3
+- **Top concerns**: tpay.txt logging, path traversal in unlink, hardcoded payment seed
+- **Largest files**: `ProductRepository.php` (3583 lines), `IntegrationsRepository.php` (875 lines)
diff --git a/.paul/codebase/architecture.md b/.paul/codebase/architecture.md
new file mode 100644
index 0000000..6170e70
--- /dev/null
+++ b/.paul/codebase/architecture.md
@@ -0,0 +1,235 @@
+# Architecture & Structure
+
+## Directory Layout
+
+```
+shopPRO/
+├── autoload/ # Core application code (custom autoloader)
+│ ├── Domain/ # Business logic — 29 modules
+│ ├── Shared/ # Cross-cutting utilities
+│ │ ├── Cache/ # CacheHandler, RedisConnection
+│ │ ├── Email/ # Email (PHPMailer wrapper)
+│ │ ├── Helpers/ # Static utility methods
+│ │ ├── Html/ # HTML escaping/generation
+│ │ ├── Image/ # ImageManipulator
+│ │ └── Tpl/ # Template engine
+│ ├── admin/ # Admin panel layer
+│ │ ├── App.php # Router & DI factory
+│ │ ├── Controllers/ # 28 DI controllers
+│ │ ├── Support/ # Forms, TableListRequestFactory
+│ │ ├── Validation/ # FormValidator
+│ │ └── ViewModels/ # Forms/, Common/
+│ ├── front/ # Frontend layer
+│ │ ├── App.php # Router & DI factory
+│ │ ├── LayoutEngine.php # Placeholder-based layout engine
+│ │ ├── Controllers/ # 8 DI controllers
+│ │ └── Views/ # 11 static view classes
+│ └── api/ # REST API layer
+│ ├── ApiRouter.php # Auth + routing
+│ └── Controllers/ # 4 DI controllers
+├── admin/
+│ ├── index.php # Admin entry point
+│ ├── ajax.php # Admin AJAX handler
+│ ├── templates/ # Admin view templates
+│ └── layout/ # Admin CSS/JS/icons
+├── templates/ # Frontend view templates
+├── libraries/ # Third-party libraries
+├── tests/ # PHPUnit test suite
+├── docs/ # Technical documentation
+├── index.php # Frontend entry point
+├── ajax.php # Frontend AJAX handler
+├── api.php # REST API entry point
+├── cron.php # Background job processor
+└── config.php # DB/Redis config (NOT in repo)
+```
+
+## Autoloader
+
+Custom autoloader in each entry point — tries two conventions:
+1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
+2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, preferred)
+
+**Namespace → directory mapping (case-sensitive on Linux):**
+- `\Domain\` → `autoload/Domain/`
+- `\admin\` → `autoload/admin/` (**lowercase a** — never `\Admin\`)
+- `\front\` → `autoload/front/`
+- `\api\` → `autoload/api/`
+- `\Shared\` → `autoload/Shared/`
+
+## Dependency Injection
+
+Manual factory pattern in router classes. Each entry point wires dependencies once:
+
+```php
+// Example from admin\App::getControllerFactories()
+'ShopProduct' => function() {
+ global $mdb;
+ return new \admin\Controllers\ShopProductController(
+ new \Domain\Product\ProductRepository($mdb),
+ new \Domain\Integrations\IntegrationsRepository($mdb),
+ new \Domain\Languages\LanguagesRepository($mdb)
+ );
+}
+```
+
+DI wiring locations:
+- Admin: `autoload/admin/App.php` → `getControllerFactories()`
+- Frontend: `autoload/front/App.php` → `getControllerFactories()`
+- API: `autoload/api/ApiRouter.php` → `getControllerFactories()`
+
+## Routing
+
+### Admin (`\admin\App`)
+- URL: `/admin/?module=shop_product&action=view_list`
+- `module` → PascalCase (`shop_product` → `ShopProduct`) → controller lookup
+- `action` → method call on controller
+- Auth checked before routing; 2FA supported
+
+### Frontend (`\front\App`)
+- Routes stored in `pp_routes` table (regex patterns, cached in Redis as `pp_routes:all`)
+- Match URI → extract destination params → merge with `$_GET`
+- Special params: `?product=ID`, `?category=ID`, `?article=ID`
+- Controller dispatch via `getControllerFactories()`
+- Unmatched → static page content
+
+### API (`\api\ApiRouter`)
+- URL: `/api.php?endpoint=orders&action=getOrders`
+- Stateless — auth via `X-Api-Key` header (`hash_equals()`)
+- `endpoint` → controller, `action` → method
+
+## Request Lifecycle (Frontend)
+
+```
+HTTP GET /produkt/nazwa-produktu
+ → index.php (autoload, init Medoo, session, language)
+ → Fetch pp_routes from Redis (or DB)
+ → Regex match → extract ?product=123
+ → front\LayoutEngine::show()
+ → Determine layout (pp_layouts)
+ → Replace placeholders [MENU:ID], [BANER_STRONA_GLOWNA], etc.
+ → Call view classes / repositories for each placeholder
+ → Output HTML (with GTM, meta OG, WebP, lazy loading)
+```
+
+## Request Lifecycle (Admin)
+
+```
+HTTP GET /admin/?module=shop_order&action=view_list
+ → admin/index.php (IP check, session, auth cookie check)
+ → admin\App::update() (run pending DB migrations)
+ → admin\App::special_actions() (handle s-action=user-logon etc.)
+ → admin\App::render()
+ → Auth check → if not logged in, show login form
+ → admin\App::route()
+ → 'shop_order' → ShopOrder → factory()
+ → new ShopOrderController(OrderAdminService, ProductRepository)
+ → ShopOrderController::viewList()
+ → Tpl::view('shop-order/orders-list', [...])
+ → Tpl::render('site/main-layout', ['content' => $html])
+ → Output admin HTML
+```
+
+## Domain Modules (29)
+
+All in `autoload/Domain/{Module}/{Module}Repository.php`:
+
+| Module | Repository | Notes |
+|--------|-----------|-------|
+| Article | ArticleRepository | Blog/news |
+| Attribute | AttributeRepository | Product attributes (color, size) |
+| Banner | BannerRepository | Promo banners |
+| Basket | (static) | Cart calculations |
+| Cache | (utilities) | Cache key constants |
+| Category | CategoryRepository | Category tree |
+| Client | ClientRepository | Customer accounts |
+| Coupon | CouponRepository | Discount codes |
+| CronJob | CronJobRepository, CronJobProcessor | Job queue |
+| Dashboard | DashboardRepository | Admin stats |
+| Dictionaries | DictionariesRepository | Units, enums |
+| Integrations | IntegrationsRepository | Apilo, Ekomi (**875 lines — too large**) |
+| Languages | LanguagesRepository | i18n translations |
+| Layouts | LayoutsRepository | Page templates |
+| Newsletter | NewsletterRepository, NewsletterPreviewRenderer | Email campaigns |
+| Order | OrderRepository, OrderAdminService | Orders, status |
+| Pages | PagesRepository | Static pages |
+| PaymentMethod | PaymentMethodRepository | Payment gateways |
+| Producer | ProducerRepository | Brands |
+| Product | ProductRepository | Core catalog (**3583 lines — too large**) |
+| ProductSet | ProductSetRepository | Bundles |
+| Promotion | PromotionRepository | Special offers |
+| Scontainers | ScontainersRepository | Content blocks |
+| Settings | SettingsRepository | Shop config |
+| ShopStatus | ShopStatusRepository | Order statuses |
+| Transport | TransportRepository | Shipping |
+| Update | UpdateRepository | DB migrations |
+| User | UserRepository | Admin users, 2FA |
+
+## Admin Controllers (28)
+
+All in `autoload/admin/Controllers/`:
+`ArticlesController`, `ArticlesArchiveController`, `BannerController`, `DashboardController`, `DictionariesController`, `FilemanagerController`, `IntegrationsController`, `LanguagesController`, `LayoutsController`, `NewsletterController`, `PagesController`, `ProductArchiveController`, `ScontainersController`, `SettingsController`, `ShopAttributeController`, `ShopCategoryController`, `ShopClientsController`, `ShopCouponController`, `ShopOrderController`, `ShopPaymentMethodController`, `ShopProducerController`, `ShopProductController` (1199 lines), `ShopProductSetsController`, `ShopPromotionController`, `ShopStatusesController`, `ShopTransportController`, `UpdateController`, `UsersController`
+
+## Frontend Controllers (8)
+
+`autoload/front/Controllers/`: `NewsletterController`, `SearchController`, `ShopBasketController`, `ShopClientController`, `ShopCouponController`, `ShopOrderController`, `ShopProducerController`, `ShopProductController`
+
+## Frontend Views (11, static)
+
+`autoload/front/Views/`: `Articles`, `Banners`, `Languages`, `Menu`, `Newsletter`, `Scontainers`, `ShopCategory`, `ShopClient`, `ShopPaymentMethod`, `ShopProduct`, `ShopSearch`
+
+## API Controllers (4)
+
+`autoload/api/Controllers/`: `OrdersApiController`, `ProductsApiController`, `CategoriesApiController`, `DictionariesApiController`
+
+## Template System
+
+### Tpl Engine (`\Shared\Tpl\Tpl`)
+```php
+// Controller
+return \Shared\Tpl\Tpl::view('shop-category/category-edit', [
+ 'category' => $data,
+ 'languages' => $langs,
+]);
+
+// Template (templates/shop-category/category-edit.php)
+
= $this->category['name'] ?>
+```
+
+Search order: `templates_user/`, `templates/`, `../templates_user/`, `../templates/`
+
+### Frontend Layout Engine (`\front\LayoutEngine`)
+Replaces placeholders in layout HTML loaded from `pp_layouts.html`:
+- `[MENU:ID]`, `[KONTENER:ID]`, `[LANG:key]`
+- `[PROMOWANE_PRODUKTY:limit]`, `[PRODUKTY_TOP:limit]`, `[PRODUKTY_NEW:limit]`
+- `[BANER_STRONA_GLOWNA]`, `[BANERY]`, `[COPYRIGHT]`
+- `[AKTUALNOSCI:layout_id:limit]`, `[PRODUKTY_KATEGORIA:cat_id:limit]`
+
+## Admin Form System
+
+Universal form system for CRUD views. Full docs: `docs/FORM_EDIT_SYSTEM.md`.
+
+| Component | Class | Location |
+|-----------|-------|----------|
+| View model | `FormEditViewModel` | `autoload/admin/ViewModels/Forms/` |
+| Field definition | `FormField` | same |
+| Field type enum | `FormFieldType` | same |
+| Tab | `FormTab` | same |
+| Action | `FormAction` | same |
+| Validation | `FormValidator` | `autoload/admin/Validation/` |
+| POST parsing | `FormRequestHandler` | `autoload/admin/Support/Forms/` |
+| Rendering | `FormFieldRenderer` | `autoload/admin/Support/Forms/` |
+| Template | `form-edit.php` | `admin/templates/components/` |
+
+## Authentication
+
+### Admin
+- Session: `$_SESSION['user']` after successful login
+- 2FA: 6-digit code sent by email; `twofa_pending` in session during verification
+- Remember Me: 14-day HMAC-SHA256 signed cookie
+
+### API
+- Stateless; `X-Api-Key` header vs `pp_settings.api_key` via `hash_equals()`
+
+### Frontend
+- Customer session in `$_SESSION['client']`
+- IP validation on every request (`$_SESSION['ip']` vs `REMOTE_ADDR`)
diff --git a/.paul/codebase/concerns.md b/.paul/codebase/concerns.md
new file mode 100644
index 0000000..3f7dd1c
--- /dev/null
+++ b/.paul/codebase/concerns.md
@@ -0,0 +1,127 @@
+# Concerns & Technical Debt
+
+> Last updated: 2026-03-12
+
+## Security Issues
+
+### HIGH — Sensitive data logged to public file
+**File**: `autoload/front/Controllers/ShopOrderController.php:32`
+```php
+file_put_contents('tpay.txt', print_r($_POST, true) . print_r($_GET, true), FILE_APPEND);
+```
+- Logs entire POST/GET (including payment data) to `tpay.txt` likely in webroot
+- Possible information disclosure
+- **Fix**: Remove log or write to non-public path (e.g., `/logs/`)
+
+### HIGH — Hardcoded payment seed
+**File**: `autoload/front/Controllers/ShopOrderController.php:105`
+```php
+hash("sha256", "ProjectPro1916;" . round($summary_tmp, 2) ...)
+```
+- Hardcoded secret in source — should be in `config.php`
+
+### MEDIUM — SQL table name interpolated
+**File**: `autoload/Domain/Integrations/IntegrationsRepository.php:31`
+```php
+$stmt = $this->db->query("SELECT * FROM $table");
+```
+- Technically mitigated by whitelist in `settingsTable()`, but violates "no SQL string concatenation" rule
+- **Fix**: Use Medoo's native `select()` method
+
+### MEDIUM — Path traversal in unlink()
+**Files**: `autoload/Domain/Product/ProductRepository.php:1605,1617,2129,2163` and `autoload/Domain/Article/ArticleRepository.php:321,340,823,840`
+```php
+if (file_exists('../' . $row['src'])) {
+ unlink('../' . $row['src']);
+}
+```
+- Path from DB, no traversal check
+- A DB compromise could delete arbitrary files
+- **Fix**:
+```php
+$basePath = realpath('../upload/');
+$fullPath = realpath('../' . $row['src']);
+if ($fullPath && strpos($fullPath, $basePath) === 0) {
+ unlink($fullPath);
+}
+```
+
+### MEDIUM — Unsanitized output in templates
+**Files**:
+- `templates/articles/article-full.php` — article title and `$_SERVER['SERVER_NAME']` concatenated without escaping
+- `templates/articles/article-entry.php` — `$url` and article titles not escaped
+
+### MEDIUM — Missing CSRF tokens
+- No evidence of CSRF tokens on admin panel forms
+- State-changing POST endpoints (create/update/delete) are potentially CSRF-vulnerable
+
+---
+
+## Architecture Issues
+
+### IntegrationsRepository too large (875 lines)
+**File**: `autoload/Domain/Integrations/IntegrationsRepository.php`
+Does too many things: settings CRUD, logging, Apilo OAuth, product sync, webhook handling, ShopPRO import.
+**Suggested split**: `ApiloAuthManager`, `ApiloProductSyncService`, `ApiloWebhookHandler`, `IntegrationLogRepository`, `IntegrationSettingsRepository`
+
+### ProductRepository too large (3583 lines)
+**File**: `autoload/Domain/Product/ProductRepository.php`
+Candidate for extraction of: pricing logic, image handling, cache management, Google feed generation.
+
+### ShopProductController too large (1199 lines)
+**File**: `autoload/admin/Controllers/ShopProductController.php`
+
+### Helpers.php too large (1101 lines)
+**File**: `autoload/Shared/Helpers/Helpers.php`
+Static utility god class. Extract into focused service classes.
+
+### Duplicate email logic
+- `\Shared\Helpers\Helpers::send_email()` and `\Shared\Email\Email::send()` both wrap PHPMailer
+- Should be unified in `\Shared\Email\Email`
+- Documented in `docs/MEMORY.md`
+
+### 47 `global $mdb` usages remain
+- DI is complete in Controllers, but some Helpers methods still use `global $mdb`
+- Should be gradually eliminated
+
+---
+
+## Dead Code / Unused Files
+
+| File | Issue |
+|------|-------|
+| `libraries/rb.php` | RedBeanPHP — no references found in autoload, candidate for removal |
+| `cron-turstmate.php` (note: typo) | Legacy/questionable cron handler |
+| `devel.html` | Development artifact in project root |
+| `output.txt` | Artifact file |
+| `libraries/filemanager-9.14.1/` + `9.14.2/` | Duplicate versions |
+
+---
+
+## Missing Error Handling
+
+- `IntegrationsRepository.php:163-165` — DB operations after Apilo token refresh lack try-catch
+- `ShopOrderController.php:32` — `file_put_contents()` return value not checked
+- `ProductRepository.php:1605` — `unlink()` without error handling
+- `cron.php:2` — `error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED)` silences all warnings, hiding potential bugs
+
+---
+
+## Known Issues (from docs/TODO.md & docs/MEMORY.md)
+
+| Issue | Location | Status |
+|-------|----------|--------|
+| Newsletter save/unsubscribe needs testing | `Domain/Newsletter/` | Open |
+| Duplicate email sending logic | `Helpers.php` vs `Email.php` | Open |
+| `$mdb->delete()` 2-arg pitfall | Documented in MEMORY.md | Known pitfall |
+
+---
+
+## Summary by Priority
+
+| Priority | Count | Key Action |
+|----------|-------|-----------|
+| **Immediate** (security) | 5 | Remove tpay.txt logging, fix path traversal, move hardcoded secret to config |
+| **High** (architecture) | 3 | Split IntegrationsRepository, unify email logic, add CSRF |
+| **Medium** (quality) | 4 | Escape template output, add try-catch, remove dead files |
+| **Low** (maintenance) | 3 | Remove rb.php, reduce Helpers.php, document helpers usage |
diff --git a/.paul/codebase/conventions.md b/.paul/codebase/conventions.md
new file mode 100644
index 0000000..262d595
--- /dev/null
+++ b/.paul/codebase/conventions.md
@@ -0,0 +1,198 @@
+# Code Conventions
+
+## Naming
+
+| Entity | Convention | Example |
+|--------|-----------|---------|
+| Classes | PascalCase | `ProductRepository`, `ShopCategoryController` |
+| Methods | camelCase | `getQuantity()`, `categoryDetails()` |
+| Admin action methods | snake_case | `view_list()`, `category_edit()` |
+| Variables | camelCase | `$mockDb`, `$formViewModel`, `$postData` |
+| Constants | UPPER_SNAKE_CASE | `MAX_PER_PAGE`, `SORT_TYPES` |
+| DB tables | `pp_` prefix + snake_case | `pp_shop_products` |
+| DB columns | snake_case | `price_brutto`, `parent_id`, `lang_id` |
+| File (new) | `ClassName.php` | `ProductRepository.php` |
+| File (legacy) | `class.ClassName.php` | (leave, do not rename) |
+| Templates | kebab-case | `shop-category/category-edit.php` |
+
+## Medoo ORM Patterns
+
+```php
+// Get single record — returns array or null
+$product = $this->db->get('pp_shop_products', '*', ['id' => $id]);
+
+// Get single column value
+$qty = $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
+
+// Select multiple records — always guard against false return
+$rows = $this->db->select('pp_shop_categories', '*', [
+ 'parent_id' => $parentId,
+ 'ORDER' => ['o' => 'ASC'],
+]);
+if (!is_array($rows)) { return []; }
+
+// Count
+$count = $this->db->count('pp_shop_products', ['category_id' => $catId]);
+
+// Update
+$this->db->update('pp_shop_products', ['quantity' => 10], ['id' => $id]);
+
+// Delete — ALWAYS 2 arguments, never 3!
+$this->db->delete('pp_shop_categories', ['id' => $id]);
+
+// Insert, then check ID for success
+$this->db->insert('pp_shop_products', $data);
+$newId = $this->db->id();
+```
+
+**Critical pitfalls:**
+- `$mdb->delete()` takes **2 args** — passing 3 causes silent bugs
+- `$mdb->get()` returns `null` (not `false`) when no record found
+- Always check `!is_array()` on `select()` results before iterating
+
+## Redis Cache Patterns
+
+```php
+$cache = new \Shared\Cache\CacheHandler();
+
+// Read (data is serialized)
+$raw = $cache->get('shop\\product:' . $id . ':' . $lang . ':' . $hash);
+if ($raw) {
+ return unserialize($raw);
+}
+
+// Write
+$cache->set(
+ 'shop\\product:' . $id . ':' . $lang . ':' . $hash,
+ serialize($data),
+ 86400 // TTL in seconds
+);
+
+// Delete one key
+$cache->delete($key);
+
+// Delete by pattern
+$cache->deletePattern("shop\\product:$id:*");
+
+// Clear all product cache variations
+\Shared\Helpers\Helpers::clear_product_cache($productId);
+```
+
+## Template Rendering
+
+```php
+// In controller — always return string
+return \Shared\Tpl\Tpl::view('module/template-name', [
+ 'varName' => $value,
+]);
+
+// In template — variables available as $this->varName
+= $this->varName ?>
+
+// XSS escape
+= $tpl->secureHTML($this->userInput) ?>
+```
+
+## AJAX Response Format
+
+```php
+// Standard JSON response
+echo json_encode([
+ 'status' => 'ok', // or 'error'
+ 'msg' => 'Zapisano.',
+ 'id' => (int)$savedId,
+]);
+exit;
+```
+
+## Form Handling (Admin)
+
+```php
+// Define form
+$form = new FormEditViewModel('Category', 'Edit');
+$form->addField(FormField::text('name', ['label' => 'Nazwa', 'required' => true]));
+$form->addField(FormField::select('status', ['label' => 'Status', 'options' => [...]]));
+$form->addTab('General', [$field1, $field2]);
+$form->addAction(new FormAction('save', 'Zapisz', FormAction::TYPE_SUBMIT));
+
+// Validate & process POST
+$handler = new FormRequestHandler($validator);
+$result = $handler->handleSubmit($form, $_POST);
+if (!$result['success']) {
+ // return form with errors
+}
+
+// Render form
+return Tpl::view('components/form-edit', ['form' => $form]);
+```
+
+## Error Handling
+
+```php
+// Wrap risky operations — especially external API calls and file operations
+try {
+ $cache->deletePattern("shop\\product:$id:*");
+} catch (\Exception $e) {
+ error_log("Cache clear failed: " . $e->getMessage());
+}
+
+// API — always return structured error
+if (!$this->authenticate()) {
+ self::sendError('UNAUTHORIZED', 'Invalid API key', 401);
+ return;
+}
+```
+
+## Security
+
+### XSS
+```php
+// In templates — use secureHTML for user-sourced strings
+= $tpl->secureHTML($this->categoryName) ?>
+
+// Or use htmlspecialchars directly
+= htmlspecialchars($value, ENT_QUOTES, 'UTF-8') ?>
+```
+
+### SQL Injection
+- All queries via Medoo — never concatenate SQL strings
+- Use Medoo array syntax or `?` placeholders only
+
+### Session Security
+```php
+// IP-binding on every request
+if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
+ session_destroy();
+ header('Location: /');
+ exit;
+}
+```
+
+### API Auth
+```php
+// Timing-safe comparison
+return hash_equals($storedKey, $headerKey);
+```
+
+## i18n / Translations
+
+- Language stored in `$_SESSION['current-lang']`
+- Translations cached in `$_SESSION['lang-{lang_id}']`
+- DB table: `pp_langs`, keys fetched via `LanguagesRepository`
+- Helper: `\Shared\Helpers\Helpers::lang($key)` returns translation string
+
+## PHP Version Constraints (< 8.0)
+
+```php
+// ❌ FORBIDDEN
+$result = match($x) { 1 => 'a' };
+function foo(int|string $x) {}
+str_contains($s, 'needle');
+str_starts_with($s, 'pre');
+
+// ✅ USE INSTEAD
+$result = $x === 1 ? 'a' : 'b';
+function foo($x) {} // + @param int|string in docblock
+strpos($s, 'needle') !== false
+strncmp($pre, $s, strlen($pre)) === 0
+```
diff --git a/.paul/codebase/dependencies.md b/.paul/codebase/dependencies.md
new file mode 100644
index 0000000..a3e517f
--- /dev/null
+++ b/.paul/codebase/dependencies.md
@@ -0,0 +1,65 @@
+# Dependencies
+
+## Composer (PHP)
+
+**File**: `composer.json`
+**PHP requirement**: `>=7.4` (production runs <8.0)
+
+| Package | Version | Purpose |
+|---------|---------|---------|
+| `phpunit/phpunit` | ^9.5 | Testing framework |
+
+## Vendored Libraries (`libraries/`)
+
+These are NOT managed by Composer — bundled directly.
+
+| Library | Version | Status | Purpose |
+|---------|---------|--------|---------|
+| `medoo/` | 1.7.10 | Active | Database ORM |
+| `phpmailer/` | classic | Active | Email sending |
+| `rb.php` | — | **Unused** — remove | RedBeanPHP legacy ORM |
+| `ckeditor/` | 4.x | Active | Rich text editor |
+| `apexcharts/` | — | Active | Admin charts |
+| `bootstrap/` | 4.1.3 + 4.5.2 | Active | CSS framework (two versions present) |
+| `fontawesome-5.7.0/` | 5.7.0 | Active | Icons |
+| `filemanager-9.14.1/` | 9.14.1 | Active | File manager |
+| `filemanager-9.14.2/` | 9.14.2 | Duplicate? | File manager |
+| `codemirror/` | — | Active | Code editor in admin |
+| `fancyBox/` + `fancybox3/` | 2 + 3 | Active | Lightbox |
+| `plupload/` | — | Active | File uploads |
+| `grid/` | — | Active | CSS grid system |
+
+## Frontend (JS, served directly)
+
+| Library | Version | Source |
+|---------|---------|--------|
+| jQuery | 2.1.3 | `libraries/` |
+| jQuery Migrate | 1.0.0 | `libraries/` |
+| jQuery UI | — | `libraries/` |
+| jQuery Autocomplete | 1.4.11 | `libraries/` |
+| jQuery Nested Sortable | — | `libraries/` |
+| jQuery-confirm | — | `libraries/` |
+| Selectize.js | — | `libraries/` |
+| Lozad.js | — | `libraries/` |
+| Swiper | — | `libraries/` |
+| taboverride.min.js | — | `libraries/` |
+| validator.js | — | `libraries/` |
+
+## PHP Extensions Required
+
+| Extension | Purpose |
+|-----------|---------|
+| `redis` | Redis caching |
+| `curl` | External API calls (Apilo, image downloads) |
+| `pdo` + `pdo_mysql` | Medoo ORM database access |
+| `mbstring` | String handling |
+| `gd` or `imagick` | Image manipulation (ImageManipulator) |
+| `json` | JSON encode/decode |
+| `session` | Session management |
+
+## Notes
+
+- **No npm/package.json** — no JS build pipeline
+- **SCSS is pre-compiled** — CSS served as static files
+- **No Composer autoload at runtime** — custom autoloader in each entry point
+- `libraries/rb.php` (RedBeanPHP, 536 KB) — confirmed unused, safe to delete
diff --git a/.paul/codebase/overview.md b/.paul/codebase/overview.md
new file mode 100644
index 0000000..45ee390
--- /dev/null
+++ b/.paul/codebase/overview.md
@@ -0,0 +1,72 @@
+# shopPRO — Codebase Overview
+
+> Generated: 2026-03-12
+
+## What is this project?
+
+shopPRO is a PHP e-commerce platform with an admin panel, customer-facing storefront, and REST API. It uses a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete).
+
+## Size & Health
+
+| Metric | Value |
+|--------|-------|
+| PHP files (autoload/) | ~588 |
+| Lines of code (autoload/) | ~71,668 |
+| Test suite | **810 tests, 2264 assertions** |
+| Domain modules | 29 |
+| Admin controllers | 28 |
+| Frontend controllers | 8 |
+| API controllers | 4 |
+| Frontend views (static) | 11 |
+
+## Tech Snapshot
+
+| Layer | Technology |
+|-------|-----------|
+| Language | PHP 7.4–7.x (production **< 8.0**) |
+| Database ORM | Medoo 1.7.10 + MySQL |
+| Caching | Redis via `CacheHandler` |
+| Email | PHPMailer (classic) |
+| Frontend JS | jQuery 2.1.3 |
+| CSS | Bootstrap 4.x (pre-compiled SCSS) |
+| HTTP Client | Native cURL |
+| Testing | PHPUnit 9.6 via `phpunit.phar` |
+| Build tools | **None** |
+
+## Entry Points
+
+| File | Role |
+|------|------|
+| `index.php` | Frontend storefront |
+| `admin/index.php` | Admin panel |
+| `ajax.php` | Frontend AJAX |
+| `admin/ajax.php` | Admin AJAX |
+| `api.php` | REST API (ordersPRO) |
+| `cron.php` | Background job processor |
+
+## External Integrations
+
+| Integration | Purpose |
+|-------------|---------|
+| **Apilo** | ERP/WMS — order sync, inventory, pricing (OAuth 2.0) |
+| **Ekomi** | Customer review CSV export |
+| **TrustMate** | Review invitation (browser-based, separate cron) |
+| **Google XML Feed** | Google Shopping product feed |
+| **shopPRO Import** | Import products from another shopPRO instance |
+
+## Key Architecture Decisions
+
+- **DI via manual factories** in `admin\App`, `front\App`, `api\ApiRouter`
+- **Repository pattern** — all DB access in `autoload/Domain/{Module}/{Module}Repository.php`
+- **Redis caching** for products (TTL 24h), routes, and settings
+- **No Composer autoload at runtime** — custom dual-convention autoloader in each entry point
+- **Stateless REST API** — auth via `X-Api-Key` header + `hash_equals()`
+- **Job queue** — cron jobs stored in `pp_cron_jobs` table, processed by `cron.php`
+
+## Quick Reference
+
+- Full stack details: `stack.md`
+- Architecture & routing: `architecture.md`
+- Code conventions: `conventions.md`
+- Testing patterns: `testing.md`
+- Known issues & debt: `concerns.md`
diff --git a/.paul/codebase/stack.md b/.paul/codebase/stack.md
new file mode 100644
index 0000000..9b0a7b0
--- /dev/null
+++ b/.paul/codebase/stack.md
@@ -0,0 +1,141 @@
+# Technology Stack & Integrations
+
+## Languages
+
+| Language | Version | Notes |
+|----------|---------|-------|
+| PHP | 7.4 – <8.0 | Production constraint — no PHP 8.0+ syntax |
+| JavaScript | ES5 + jQuery 2.1.3 | No modern framework |
+| CSS | Bootstrap 4.x (pre-compiled SCSS) | No build pipeline |
+
+**PHP 8.0+ features explicitly forbidden:**
+- `match` expressions → use ternary / if-else
+- Named arguments
+- Union types (`int|string`) → use single type + docblock
+- `str_contains()`, `str_starts_with()`, `str_ends_with()` → use `strpos()`
+
+## Core Libraries
+
+| Library | Version | Location | Purpose |
+|---------|---------|----------|---------|
+| Medoo | 1.7.10 | `libraries/medoo/medoo.php` | Database ORM |
+| PHPMailer | classic | `libraries/phpmailer/` | Email sending |
+| RedBeanPHP | — | `libraries/rb.php` | Legacy ORM — **unused, candidate for removal** |
+
+## Frontend Libraries
+
+| Library | Location | Purpose |
+|---------|----------|---------|
+| jQuery | 2.1.3 | DOM / AJAX |
+| jQuery Migrate | 1.0.0 | Backward compat |
+| Bootstrap | 4.1.3 / 4.5.2 | `libraries/bootstrap*/` |
+| CKEditor | 4.x | `libraries/ckeditor/` | Rich text editor |
+| ApexCharts | — | `libraries/apexcharts/` | Admin charts |
+| FancyBox | 2 + 3 | `libraries/fancyBox/`, `fancybox3/` | Lightbox |
+| Plupload | — | `libraries/plupload/` | File uploads |
+| Selectize.js | — | — | Select dropdowns |
+| Lozad.js | — | — | Lazy loading |
+| Swiper | — | — | Carousel/slider |
+| CodeMirror | — | `libraries/codemirror/` | Code editor |
+| Font Awesome | 5.7.0 | `libraries/fontawesome-5.7.0/` | Icons |
+| File Manager | 9.14.1 & 9.14.2 | `libraries/filemanager-9.14.*/` | File browsing |
+
+## Database
+
+- **ORM**: Medoo 1.7.10 (custom-extended with Redis support)
+- **Engine**: MySQL
+- **Table prefix**: `pp_`
+- **Connection**: `new medoo([...])` in each entry point via credentials from `config.php`
+- **Key tables**: `pp_shop_products`, `pp_shop_orders`, `pp_shop_categories`, `pp_shop_clients`
+
+## Caching
+
+- **Technology**: Redis
+- **PHP extension**: Native `Redis` class
+- **Wrapper**: `\Shared\Cache\CacheHandler` (singleton via `RedisConnection`)
+- **Config**: `config.php` → `$config['redis']['host/port/password']`
+- **Serialization**: PHP `serialize()` / `unserialize()`
+- **Default TTL**: 86400 seconds (24h)
+- **Key patterns**:
+ - `shop\product:{id}:{lang_id}:{hash}` — product details
+ - `ProductRepository::getProductPermutationQuantityOptions:v2:{id}:*`
+ - `pp_routes:all` — URL routing patterns
+ - `pp_settings_cache` — shop settings
+
+## Email
+
+- **Library**: PHPMailer (classic, not v6)
+- **Config**: `config.php` (host, port, login, password)
+- **Helpers**:
+ - `\Shared\Helpers\Helpers::send_email($to, $subject, $text, $reply, $file)`
+ - `\Shared\Email\Email::send(...)` — newsletter / template-based
+- **Issue**: Duplicate PHPMailer logic in both classes — should be unified
+
+## HTTP Client
+
+- **Technology**: Native PHP cURL (`curl_init`, `curl_setopt`, `curl_exec`)
+- **No abstraction library** (no Guzzle, Symfony HTTP Client)
+- **Used in**: `IntegrationsRepository.php` (Apilo calls), `cron.php` (image downloads)
+
+## Dev & Build Tools
+
+| Tool | Purpose |
+|------|---------|
+| Composer | PHP dependency management |
+| PHPUnit 9.6 | Testing (`phpunit.phar`) |
+| PowerShell `test.ps1` | Recommended test runner |
+| No webpack/Vite/Gulp | SCSS pre-compiled, assets served as-is |
+
+## External Integrations
+
+### Apilo (ERP/WMS)
+- **Auth**: OAuth 2.0 Bearer token (client_id + client_secret from `pp_shop_apilo_settings`)
+- **Base URL**: `https://projectpro.apilo.com/rest/api/`
+- **Sync operations**: order sending, payment sync, status polling, product qty/price sync, pricelist sync
+- **Code**: `autoload/Domain/Integrations/IntegrationsRepository.php`
+- **Cron jobs**: `APILO_SEND_ORDER`, `APILO_SYNC_PAYMENT`, `APILO_STATUS_POLL`, `APILO_PRODUCT_SYNC`, `APILO_PRICELIST_SYNC`
+- **Logging**: `\Domain\Integrations\ApiloLogger` → `pp_log` table
+
+### Ekomi (Reviews)
+- **Type**: CSV export
+- **Code**: `api.php` → generates `/ekomi/ekomi-{date}.csv`
+
+### TrustMate (Review Invitations)
+- **Type**: Browser-based (requires JS execution)
+- **Code**: `cron.php` (line ~741), `cron-trustmate.php`
+- **Config**: `$config['trustmate']['enabled']`
+
+### Google Shopping Feed
+- **Type**: XML feed generation
+- **Cron job**: `GOOGLE_XML_FEED`
+- **Code**: `cron.php` → `ProductRepository::generateGoogleFeedXml()`
+
+### shopPRO Product Import
+- **Type**: Direct MySQL connection to remote shopPRO instance
+- **Config**: `pp_shop_shoppro_settings` (domain, db credentials)
+- **Code**: `IntegrationsRepository.php` (lines 668–850)
+- **Logs**: `/logs/shoppro-import-debug.log`
+
+### REST API (ordersPRO — outbound)
+- **Auth**: `X-Api-Key` header
+- **Endpoints**: orders (list/get/status/paid), products (list/get), dictionaries, categories
+- **Code**: `api.php` → `autoload/api/ApiRouter.php` → `autoload/api/Controllers/`
+
+## Cron Job System
+
+| Job Type | Purpose |
+|----------|---------|
+| `APILO_TOKEN_KEEPALIVE` | OAuth token refresh |
+| `APILO_SEND_ORDER` | Sync orders to Apilo (priority 40) |
+| `APILO_SYNC_PAYMENT` | Sync payment status |
+| `APILO_STATUS_POLL` | Poll order status changes |
+| `APILO_PRODUCT_SYNC` | Update product qty & prices |
+| `APILO_PRICELIST_SYNC` | Update pricelist |
+| `PRICE_HISTORY` | Record price history |
+| `ORDER_ANALYSIS` | Order/product correlation |
+| `TRUSTMATE_INVITATION` | Review invitations |
+| `GOOGLE_XML_FEED` | Google Shopping XML |
+
+- **Priority levels**: CRITICAL(10), HIGH(50), NORMAL(100), LOW(200)
+- **Backoff**: Exponential on failure (60s → 3600s max)
+- **Storage**: `pp_cron_jobs` table
diff --git a/.paul/codebase/testing.md b/.paul/codebase/testing.md
new file mode 100644
index 0000000..3783f32
--- /dev/null
+++ b/.paul/codebase/testing.md
@@ -0,0 +1,245 @@
+# Testing Patterns
+
+## Overview
+
+| Metric | Value |
+|--------|-------|
+| Total tests | **810** |
+| Total assertions | **2264** |
+| Framework | PHPUnit 9.6 (`phpunit.phar`) |
+| Bootstrap | `tests/bootstrap.php` |
+| Config | `phpunit.xml` |
+
+## Running Tests
+
+```bash
+# Full suite (PowerShell — recommended)
+./test.ps1
+
+# Specific file
+./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php
+
+# Specific test method
+./test.ps1 --filter testGetQuantityReturnsCorrectValue
+
+# Alternatives
+composer test # standard output
+./test.bat # testdox (readable list)
+./test-simple.bat # dots
+./test-debug.bat # debug output
+./test.sh # Git Bash
+```
+
+## Test Structure
+
+Tests mirror source structure:
+
+```
+tests/Unit/
+├── Domain/
+│ ├── Product/ProductRepositoryTest.php
+│ ├── Category/CategoryRepositoryTest.php
+│ ├── Order/OrderRepositoryTest.php
+│ └── ... (all 29 modules covered)
+├── admin/Controllers/
+│ ├── ShopCategoryControllerTest.php
+│ └── ...
+└── api/
+ └── ...
+```
+
+## Test Class Pattern
+
+```php
+namespace Tests\Unit\Domain\Category;
+
+use PHPUnit\Framework\TestCase;
+use Domain\Category\CategoryRepository;
+
+class CategoryRepositoryTest extends TestCase
+{
+ private $mockDb;
+ private CategoryRepository $repository;
+
+ protected function setUp(): void
+ {
+ $this->mockDb = $this->createMock(\medoo::class);
+ $this->repository = new CategoryRepository($this->mockDb);
+ }
+
+ // Tests follow below...
+}
+```
+
+## AAA Pattern (Arrange-Act-Assert)
+
+```php
+public function testGetQuantityReturnsCorrectValue(): void
+{
+ // Arrange
+ $this->mockDb->expects($this->once())
+ ->method('get')
+ ->with(
+ 'pp_shop_products',
+ 'quantity',
+ ['id' => 123]
+ )
+ ->willReturn(42);
+
+ // Act
+ $result = $this->repository->getQuantity(123);
+
+ // Assert
+ $this->assertSame(42, $result);
+}
+```
+
+## Mock Patterns
+
+### Simple return value
+```php
+$this->mockDb->method('get')->willReturn(['id' => 1, 'name' => 'Test']);
+```
+
+### Multiple calls with different return values
+```php
+$this->mockDb->method('get')
+ ->willReturnCallback(function ($table, $columns, $where) {
+ if ($table === 'pp_shop_categories') {
+ return ['id' => 15, 'status' => '1'];
+ }
+ return null;
+ });
+```
+
+### Verify exact call arguments
+```php
+$this->mockDb->expects($this->once())
+ ->method('delete')
+ ->with('pp_shop_categories', ['id' => 5]);
+```
+
+### Verify method never called
+```php
+$this->mockDb->expects($this->never())->method('update');
+```
+
+### Mock complex PDO statement (for `->query()` calls)
+```php
+$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;
+$this->mockDb->method('query')
+ ->willReturnCallback(function () use (&$callIndex, $countStmt, $productsStmt) {
+ $callIndex++;
+ return $callIndex === 1 ? $countStmt : $productsStmt;
+ });
+```
+
+## Controller Test Pattern
+
+```php
+class ShopCategoryControllerTest extends TestCase
+{
+ protected function setUp(): void
+ {
+ $this->repository = $this->createMock(CategoryRepository::class);
+ $this->languagesRepository = $this->createMock(LanguagesRepository::class);
+ $this->controller = new ShopCategoryController(
+ $this->repository,
+ $this->languagesRepository
+ );
+ }
+
+ // Verify constructor signature
+ public function testConstructorRequiresCorrectRepositories(): void
+ {
+ $reflection = new \ReflectionClass(ShopCategoryController::class);
+ $params = $reflection->getConstructor()->getParameters();
+
+ $this->assertCount(2, $params);
+ $this->assertEquals(
+ 'Domain\\Category\\CategoryRepository',
+ $params[0]->getType()->getName()
+ );
+ }
+
+ // Verify action methods return string
+ public function testViewListReturnsString(): void
+ {
+ $this->repository->method('categoriesList')->willReturn([]);
+ $result = $this->controller->view_list();
+ $this->assertIsString($result);
+ }
+
+ // Verify expected methods exist
+ public function testHasExpectedActionMethods(): void
+ {
+ $this->assertTrue(method_exists($this->controller, 'view_list'));
+ $this->assertTrue(method_exists($this->controller, 'category_edit'));
+ }
+}
+```
+
+## Test Naming Convention
+
+Pattern: `test{What}{WhenCondition}`
+
+```php
+testGetQuantityReturnsCorrectValue()
+testGetQuantityReturnsNullWhenProductNotFound()
+testCategoryDetailsReturnsDefaultForInvalidId()
+testCategoryDeleteReturnsFalseWhenHasChildren()
+testCategoryDeleteReturnsTrueWhenDeleted()
+testSaveCategoriesOrderReturnsFalseForNonArray()
+testPaginatedCategoryProductsClampsPage()
+```
+
+## Common Assertions
+
+```php
+$this->assertTrue($bool);
+$this->assertFalse($bool);
+$this->assertEquals($expected, $actual);
+$this->assertSame($expected, $actual); // type-strict
+$this->assertNull($value);
+$this->assertIsArray($value);
+$this->assertIsInt($value);
+$this->assertIsString($value);
+$this->assertEmpty($array);
+$this->assertCount(3, $array);
+$this->assertArrayHasKey('id', $array);
+$this->assertArrayNotHasKey('foo', $array);
+$this->assertGreaterThanOrEqual(3, $count);
+$this->assertInstanceOf(ClassName::class, $obj);
+```
+
+## Available Stubs (`tests/stubs/`)
+
+| Stub | Purpose |
+|------|---------|
+| `Helpers.php` | `Helpers::seo()`, `::lang()`, `::send_email()`, `::normalize_decimal()` |
+| `ShopProduct.php` | Legacy `shop\Product` class stub |
+| `RedisConnection` | Redis singleton stub (auto-loaded from bootstrap) |
+| `CacheHandler` | Cache stub (no actual Redis needed in tests) |
+
+## What's Covered
+
+- All 29 Domain repositories ✓
+- Core business logic (quantity, pricing, category tree) ✓
+- Query behavior with mocked Medoo ✓
+- Cache patterns ✓
+- Controller constructor injection ✓
+- `FormValidator` behavior ✓
+- API controllers ✓
+
+## What's Lightly Covered
+
+- Full controller action execution (template rendering)
+- Session state in tests
+- AJAX response integration
+- Frontend Views (static classes)
diff --git a/.paul/phases/04-csrf-protection/04-01-PLAN.md b/.paul/phases/04-csrf-protection/04-01-PLAN.md
new file mode 100644
index 0000000..a442ac5
--- /dev/null
+++ b/.paul/phases/04-csrf-protection/04-01-PLAN.md
@@ -0,0 +1,246 @@
+---
+phase: 04-csrf-protection
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - autoload/Shared/Security/CsrfToken.php
+ - autoload/admin/Support/Forms/FormRequestHandler.php
+ - admin/templates/components/form-edit.php
+ - admin/templates/site/unlogged-layout.php
+ - admin/templates/users/user-2fa.php
+ - autoload/admin/App.php
+ - tests/Unit/Shared/Security/CsrfTokenTest.php
+autonomous: true
+---
+
+
+## Goal
+Dodać ochronę CSRF do wszystkich state-changing POST endpointów panelu administracyjnego.
+
+## Purpose
+Brak tokenów CSRF umożliwia atakującemu wymuszenie na zalogowanym adminie wykonania akcji (zapis/usuń/aktualizuj) poprzez spreparowany link lub stronę. Jest to podatność MEDIUM wg concerns.md.
+
+## Output
+- Nowa klasa `\Shared\Security\CsrfToken` z generowaniem i walidacją tokenu
+- Integracja w `FormRequestHandler` (walidacja) + `form-edit.php` (token w formularzu)
+- Integracja w formularzach logowania i 2FA
+- Test jednostkowy dla CsrfToken
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+
+## Source Files
+@autoload/admin/Support/Forms/FormRequestHandler.php
+@admin/templates/components/form-edit.php
+@admin/templates/site/unlogged-layout.php
+@admin/templates/users/user-2fa.php
+@autoload/admin/App.php
+
+
+
+## Required Skills (from SPECIAL-FLOWS.md)
+
+| Skill | Priority | When to Invoke | Loaded? |
+|-------|----------|----------------|---------|
+| /feature-dev | required | Przed APPLY — nowe klasy, zmiany wielu plików | ○ |
+
+**BLOCKING:** /feature-dev musi być załadowany przed /paul:apply.
+
+## Skill Invocation Checklist
+- [ ] /feature-dev loaded (uruchom przed apply)
+
+
+
+
+## AC-1: Formularz edycji chroni przed CSRF
+```gherkin
+Given admin jest zalogowany i otwiera dowolny formularz edycji
+When formularz jest renderowany
+Then zawiera ukryte pole _csrf_token z aktualnym tokenem z sesji
+```
+
+## AC-2: Zapis przez formularz bez tokenu jest odrzucany
+```gherkin
+Given admin endpoint odbiera POST z FormRequestHandler
+When żądanie nie zawiera _csrf_token lub token jest nieprawidłowy
+Then handleSubmit() zwraca ['success' => false, 'errors' => ['csrf' => '...']]
+ And żadna operacja na danych nie jest wykonywana
+```
+
+## AC-3: Formularz logowania zawiera CSRF token
+```gherkin
+Given niezalogowany użytkownik otwiera stronę logowania /admin/
+When strona jest renderowana
+Then formularz logowania zawiera ukryte pole _csrf_token
+```
+
+## AC-4: special_actions waliduje CSRF dla user-logon i user-2fa-verify
+```gherkin
+Given żądanie POST trafia do special_actions()
+When s-action to 'user-logon' lub 'user-2fa-verify'
+Then token jest walidowany przed przetworzeniem danych
+ And brak tokenu kończy się przekierowaniem z komunikatem błędu
+```
+
+## AC-5: Token jest unikalny per sesja
+```gherkin
+Given sesja PHP jest aktywna
+When CsrfToken::getToken() jest wywołany wielokrotnie
+Then zwraca ten sam token w ramach jednej sesji
+ And token ma co najmniej 64 znaki hex (32 bajty)
+```
+
+
+
+
+
+
+ Task 1: Utwórz klasę CsrfToken + test jednostkowy
+ autoload/Shared/Security/CsrfToken.php, tests/Unit/Shared/Security/CsrfTokenTest.php
+
+ Utwórz `autoload/Shared/Security/CsrfToken.php` z namespace `\Shared\Security`:
+
+ ```php
+ class CsrfToken {
+ const SESSION_KEY = 'csrf_token';
+
+ public static function getToken(): string
+ // Jeśli nie ma tokenu w sesji — generuje bin2hex(random_bytes(32)) i zapisuje
+ // Zwraca istniejący lub nowy token
+
+ public static function validate(string $token): bool
+ // Pobiera token z sesji, używa hash_equals() dla bezpiecznego porównania
+ // Zwraca false jeśli sesja nie ma tokenu lub tokeny się różnią
+
+ public static function regenerate(): void
+ // Generuje nowy token i nadpisuje w sesji
+ // Używać po udanym logowaniu (session fixation prevention)
+ }
+ ```
+
+ Utwórz `tests/Unit/Shared/Security/CsrfTokenTest.php`:
+ - test getToken() zwraca string długości 64
+ - test getToken() zwraca ten sam token przy kolejnym wywołaniu (idempotency)
+ - test validate() zwraca true dla poprawnego tokenu
+ - test validate() zwraca false dla pustego stringa
+ - test validate() zwraca false dla błędnego tokenu
+ - test regenerate() zmienia token
+
+ Uwaga PHP < 8.0: brak `match`, brak named arguments, brak union types.
+ Użyj `isset($_SESSION[...])` zamiast `??` na zmiennych sesji w metodach static (sesja musi być started przed wywołaniem).
+
+ ./test.ps1 tests/Unit/Shared/Security/CsrfTokenTest.php
+ AC-5 satisfied: token unikalny, 64 znaki, idempotentny
+
+
+
+ Task 2: Integracja CSRF w formularzach edycji (form-edit.php + FormRequestHandler)
+ admin/templates/components/form-edit.php, autoload/admin/Support/Forms/FormRequestHandler.php
+
+ **1. form-edit.php** — dodaj token CSRF jako hidden field zaraz po `_form_id`:
+ ```php
+
+ ```
+ Dodaj po linii z `_form_id` (linia ~80).
+
+ **2. FormRequestHandler::handleSubmit()** — dodaj walidację CSRF jako PIERWSZĄ operację, przed walidacją pól:
+ ```php
+ $csrfToken = isset($postData['_csrf_token']) ? (string)$postData['_csrf_token'] : '';
+ if (!\Shared\Security\CsrfToken::validate($csrfToken)) {
+ return [
+ 'success' => false,
+ 'errors' => ['csrf' => 'Nieprawidłowy token bezpieczeństwa. Odśwież stronę i spróbuj ponownie.'],
+ 'data' => []
+ ];
+ }
+ ```
+
+ Unikaj: modyfikowania logiki walidacji pól — CSRF check to osobny guard przed walidacją.
+
+
+ Ręcznie: sprawdź źródło strony formularza edycji — musi zawierać input[name="_csrf_token"].
+ Testy: ./test.ps1 (suite nie powinna się zepsuć).
+
+ AC-1 i AC-2 satisfied
+
+
+
+ Task 3: CSRF w formularzach logowania i special_actions
+ admin/templates/site/unlogged-layout.php, admin/templates/users/user-2fa.php, autoload/admin/App.php
+
+ **1. unlogged-layout.php** — dodaj hidden field CSRF do formularza logowania (zaraz po `s-action`):
+ ```php
+
+ ```
+
+ **2. user-2fa.php** — sprawdź czy jest formularz POST i dodaj analogicznie token CSRF.
+
+ **3. App::special_actions()** — dodaj walidację CSRF na początku, dla akcji które mają konsekwencje:
+ - `user-logon` — waliduj token, przy błędzie: alert + redirect `/admin/`
+ - `user-2fa-verify` i `user-2fa-resend` — waliduj token
+ - Po udanym logowaniu (`user-logon` case 1) — wywołaj `\Shared\Security\CsrfToken::regenerate()` PRZED `self::finalize_admin_login()` (zapobiega session fixation)
+
+ Wzorzec walidacji w special_actions (na początku switch lub przed każdym case):
+ ```php
+ $csrfToken = isset($_POST['_csrf_token']) ? (string)$_POST['_csrf_token'] : '';
+ if (!\Shared\Security\CsrfToken::validate($csrfToken)) {
+ \Shared\Helpers\Helpers::alert('Nieprawidłowy token bezpieczeństwa. Spróbuj ponownie.');
+ header('Location: /admin/');
+ exit;
+ }
+ ```
+ Umieść ten blok PRZED switch ($sa), aby był wspólny dla wszystkich case.
+
+ Unikaj: dodawania CSRF do user-logout (to GET link, nie POST — zmiana na POST wykracza poza zakres).
+
+
+ Ręcznie: sprawdź źródło strony logowania — musi zawierać input[name="_csrf_token"].
+ ./test.ps1 (suite nie powinna się zepsuć).
+
+ AC-3 i AC-4 satisfied
+
+
+
+
+
+
+## DO NOT CHANGE
+- Logika walidacji pól w `FormValidator` — tylko dodajemy CSRF guard przed walidacją
+- Mechanizm sesji w `admin/index.php` — sesja jest już startowana przed wywołaniem kodu
+- Routing w `admin\App::route()` — nie zmieniamy routingu
+- Jakiekolwiek pliki frontendowe (front/) — CSRF dotyczy tylko admina w tej fazie
+- Pliki testów innych niż nowy CsrfTokenTest.php
+
+## SCOPE LIMITS
+- Nie zmieniać logout z GET na POST — to osobna zmiana wykraczająca poza zakres
+- Nie dodawać CSRF do admin/ajax.php (shop-category, users ajax) — to osobna iteracja
+- Nie refaktoryzować FormRequestHandler — tylko dodać CSRF check
+- Nie zmieniać struktury sesji poza `csrf_token` key
+
+
+
+
+Przed uznaniem planu za zakończony:
+- [ ] ./test.ps1 — wszystkie testy przechodzą (w tym nowe CsrfTokenTest)
+- [ ] Strona formularza edycji zawiera hidden input[name="_csrf_token"]
+- [ ] Strona logowania /admin/ zawiera hidden input[name="_csrf_token"]
+- [ ] POST bez tokenu do FormRequestHandler zwraca error 'csrf'
+- [ ] Brak regresji w istniejących testach (810 testów nadal przechodzi)
+
+
+
+- Wszystkie 3 taski wykonane
+- CsrfTokenTest przechodzi (min. 6 assertions)
+- Pełna suite testów przechodzi bez regresji
+- Wszystkie acceptance criteria AC-1 do AC-5 spełnione
+- Token regenerowany po udanym logowaniu
+
+
+
diff --git a/.paul/phases/04-csrf-protection/04-01-SUMMARY.md b/.paul/phases/04-csrf-protection/04-01-SUMMARY.md
new file mode 100644
index 0000000..c180618
--- /dev/null
+++ b/.paul/phases/04-csrf-protection/04-01-SUMMARY.md
@@ -0,0 +1,119 @@
+---
+phase: 04-csrf-protection
+plan: 01
+subsystem: auth
+tags: [csrf, security, session, admin]
+
+requires:
+ - phase: []
+ provides: []
+provides:
+ - "CsrfToken class — token generation, validation, regeneration"
+ - "CSRF protection on all admin FormRequestHandler POSTs"
+ - "CSRF protection on login and 2FA forms"
+ - "Token regeneration after successful login (session fixation prevention)"
+affects: []
+
+tech-stack:
+ added: []
+ patterns: ["CSRF guard before field validation in FormRequestHandler", "bin2hex(random_bytes(32)) per-session token"]
+
+key-files:
+ created:
+ - autoload/Shared/Security/CsrfToken.php
+ - tests/Unit/Shared/Security/CsrfTokenTest.php
+ modified:
+ - autoload/admin/Support/Forms/FormRequestHandler.php
+ - admin/templates/components/form-edit.php
+ - admin/templates/site/unlogged-layout.php
+ - admin/templates/users/user-2fa.php
+ - autoload/admin/App.php
+
+key-decisions:
+ - "Single CSRF validate() call placed before switch($sa) in special_actions() — covers all POST actions uniformly"
+ - "regenerate() called on successful login AND after 2FA verify — both session fixation points"
+
+patterns-established:
+ - "CSRF check = first operation in handleSubmit(), before field validation"
+ - "CsrfToken::getToken() in templates via htmlspecialchars() escape"
+
+duration: ~
+started: 2026-03-12T00:00:00Z
+completed: 2026-03-12T00:00:00Z
+---
+
+# Phase 4 Plan 01: CSRF Protection Summary
+
+**CSRF protection added to entire admin panel — all state-changing POST endpoints now validate a per-session token.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | single session |
+| Completed | 2026-03-12 |
+| Tasks | 3 completed |
+| Files modified | 7 |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Formularz edycji zawiera _csrf_token | Pass | form-edit.php linia 81 |
+| AC-2: POST bez tokenu odrzucany przez FormRequestHandler | Pass | FormRequestHandler.php linia 36–42 |
+| AC-3: Formularz logowania zawiera _csrf_token | Pass | unlogged-layout.php linia 46 |
+| AC-4: special_actions() waliduje CSRF dla user-logon i 2FA | Pass | App.php linia 47–51, przed switch |
+| AC-5: Token unikalny per sesja, min. 64 znaki hex | Pass | bin2hex(random_bytes(32)) = 64 znaków |
+
+## Accomplishments
+
+- Nowa klasa `\Shared\Security\CsrfToken` z `getToken()`, `validate()`, `regenerate()`
+- Guard w `FormRequestHandler::handleSubmit()` jako pierwsza operacja przed walidacją pól
+- Token w szablonach: `form-edit.php`, `unlogged-layout.php`, `user-2fa.php` (oba formularze)
+- `regenerate()` wywoływany po udanym logowaniu (linia 96) i po weryfikacji 2FA (linia 140) — zapobiega session fixation
+- 6 testów jednostkowych w `CsrfTokenTest.php`
+
+## Task Commits
+
+| Task | Commit | Type | Description |
+|------|--------|------|-------------|
+| Wszystkie 3 taski | `55988887` | security | faza 4 - ochrona CSRF panelu administracyjnego |
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `autoload/Shared/Security/CsrfToken.php` | Created | Token generation, validation, regeneration |
+| `tests/Unit/Shared/Security/CsrfTokenTest.php` | Created | 6 unit tests dla CsrfToken |
+| `autoload/admin/Support/Forms/FormRequestHandler.php` | Modified | CSRF guard w handleSubmit() |
+| `admin/templates/components/form-edit.php` | Modified | Hidden input _csrf_token |
+| `admin/templates/site/unlogged-layout.php` | Modified | Token w formularzu logowania |
+| `admin/templates/users/user-2fa.php` | Modified | Token w obu formularzach 2FA |
+| `autoload/admin/App.php` | Modified | CSRF walidacja w special_actions() + regenerate() |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Jeden blok validate() przed switch($sa) | Pokrywa wszystkie case jednym sprawdzeniem | Prostota, mniej kodu |
+| `\Exception` catch (nie `\Throwable`) | PHP 7.4 compat, wystarczy dla typowych wyjątków | Akceptowalny tradeoff |
+| Logout poza zakresem (GET link) | Zmiana na POST wykracza poza tę fazę | Zostawione do osobnej iteracji |
+
+## Deviations from Plan
+
+Brak — plan wykonany zgodnie ze specyfikacją.
+
+## Next Phase Readiness
+
+**Ready:**
+- Cały admin panel chroniony przed CSRF
+- Wzorzec do replikacji: `CsrfToken::getToken()` w szablonie + `validate()` w handlerze
+
+**Concerns:**
+- `admin/ajax.php` (shop-category, users ajax) jeszcze nie pokryty — odnotowane w planie jako out-of-scope
+
+**Blockers:** None
+
+---
+*Phase: 04-csrf-protection, Plan: 01*
+*Completed: 2026-03-12*
diff --git a/.paul/phases/05-order-bugs-fix/05-01-FIX-SUMMARY.md b/.paul/phases/05-order-bugs-fix/05-01-FIX-SUMMARY.md
new file mode 100644
index 0000000..152582a
--- /dev/null
+++ b/.paul/phases/05-order-bugs-fix/05-01-FIX-SUMMARY.md
@@ -0,0 +1,46 @@
+# FIX SUMMARY — 05-01
+
+**Phase:** 05-order-bugs-fix
+**Plan:** 05-01-FIX
+**Date:** 2026-03-12
+**Status:** COMPLETE
+
+## Tasks executed
+
+| # | Task | Status |
+|---|------|--------|
+| 1 | Guard summaryView() — redirect do istniejącego zamówienia | PASS |
+| 2 | try-catch createFromBasket w basketSave() | PASS |
+| 3 | Migracja SQL migrations/0.338.sql + DATABASE_STRUCTURE.md | PASS |
+| 4 | PaymentMethodRepository — is_cod w normalizacji i forTransport() | PASS |
+| 5 | Admin form — switch "Platnosc przy odbiorze" + save | PASS |
+| 6 | OrderRepository — is_cod zamiast hardkodowanego payment_id == 3 | PASS |
+| 7 | Checkpoint: migracja DB + ustawienie flagi w adminie | DONE |
+
+## Files modified
+
+- `autoload/front/Controllers/ShopBasketController.php`
+- `autoload/Domain/Order/OrderRepository.php`
+- `autoload/Domain/PaymentMethod/PaymentMethodRepository.php`
+- `autoload/admin/Controllers/ShopPaymentMethodController.php`
+- `migrations/0.338.sql`
+- `docs/DATABASE_STRUCTURE.md`
+
+## Deviations
+
+Brak.
+
+## Post-deploy checklist
+
+- [x] Migracja `migrations/0.338.sql` uruchomiona na produkcji
+- [x] Flaga `is_cod = 1` ustawiona na metodzie "Płatność przy odbiorze" w /admin/shop_payment_method/
+- [ ] Redis cache zflushowany (lub poczekać na wygaśnięcie 24h TTL)
+
+## AC coverage
+
+| AC | Status |
+|----|--------|
+| AC-1: Brak duplikatów przy powrocie do /podsumowanie | SATISFIED |
+| AC-2: Wyjątki z createFromBasket obsługiwane | SATISFIED |
+| AC-3: Admin może ustawić is_cod na metodzie płatności | SATISFIED |
+| AC-4: Zamówienie COD dostaje status 4 "Przyjęte do realizacji" | SATISFIED |
diff --git a/.paul/phases/05-order-bugs-fix/05-01-FIX.md b/.paul/phases/05-order-bugs-fix/05-01-FIX.md
new file mode 100644
index 0000000..94426a4
--- /dev/null
+++ b/.paul/phases/05-order-bugs-fix/05-01-FIX.md
@@ -0,0 +1,313 @@
+---
+phase: 05-order-bugs-fix
+plan: 05-01
+type: fix
+wave: 1
+depends_on: []
+files_modified:
+ - autoload/front/Controllers/ShopBasketController.php
+ - autoload/Domain/Order/OrderRepository.php
+ - autoload/Domain/PaymentMethod/PaymentMethodRepository.php
+ - autoload/admin/Controllers/ShopPaymentMethodController.php
+ - migrations/0.338.sql
+ - docs/DATABASE_STRUCTURE.md
+autonomous: true
+---
+
+
+## Goal
+Fix 2 production bugs reported by customer: (1) duplicate orders on retry after error, (2) wrong initial status for cash-on-delivery orders.
+
+## Purpose
+Production issues affecting real customers. Bug 1 causes double-billed orders. Bug 2 causes wrong order flow for COD payments.
+
+## Output
+- `summaryView()` guards against re-submission after successful order
+- `basketSave()` handles exceptions from `createFromBasket()` safely
+- `is_cod` column added to `pp_shop_payment_methods`
+- COD status promotion uses `is_cod` flag instead of hardcoded `payment_id == 3`
+- Admin form for payment methods shows `is_cod` switch
+
+
+
+@.paul/STATE.md
+@.paul/ROADMAP.md
+@autoload/front/Controllers/ShopBasketController.php
+@autoload/Domain/Order/OrderRepository.php
+@autoload/Domain/PaymentMethod/PaymentMethodRepository.php
+@autoload/admin/Controllers/ShopPaymentMethodController.php
+
+
+
+## AC-1: No duplicate order on retry
+Given a customer submits an order and it is created successfully (order_id saved in session),
+When the customer navigates back to `/podsumowanie` and tries to submit again,
+Then they are redirected to the existing order page — no new order is created.
+
+## AC-2: Exception in createFromBasket does not duplicate order
+Given `createFromBasket()` throws an uncaught exception after the INSERT succeeds (partial failure),
+When the customer retries submission with the same basket,
+Then the exception is caught, an error message is shown, basket session is preserved, and no second order is inserted via normal retry flow (AC-1 guards subsequent summary visit).
+
+## AC-3: COD flag is configurable in admin
+Given an admin opens any payment method in `/admin/shop_payment_method/edit/`,
+When they toggle "Płatność przy odbiorze" switch and save,
+Then the `is_cod` flag is persisted in `pp_shop_payment_methods.is_cod`.
+
+## AC-4: COD order gets correct initial status
+Given a customer places an order with a payment method where `is_cod = 1`,
+When the order is created,
+Then `pp_shop_order_statuses` contains status_id = 4 ("Przyjęte do realizacji") and the old status 0 entry is updated.
+
+
+
+
+
+ Fix BUG-1: Guard summaryView() against re-submission after successful order
+ autoload/front/Controllers/ShopBasketController.php
+
+In `summaryView()`, BEFORE calling `createOrderSubmitToken()`, check if `ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY` is set in session. If it is, look up that order's hash via `$this->orderRepository->findHashById($existingOrderId)`. If the hash exists, redirect to `/zamowienie/{hash}` and exit.
+
+This means the customer who navigates back to the summary page after a successful order is immediately redirected to their order instead of seeing the form again (which would regenerate a token and allow double-submission).
+
+Do NOT call `createOrderSubmitToken()` in this guard path — just redirect.
+
+Current problematic code at the top of `summaryView()`:
+```php
+$orderSubmitToken = $this->createOrderSubmitToken();
+```
+Must become:
+```php
+$existingOrderId = isset($_SESSION[self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY])
+ ? (int)$_SESSION[self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY]
+ : 0;
+if ($existingOrderId > 0) {
+ $existingOrderHash = $this->orderRepository->findHashById($existingOrderId);
+ if ($existingOrderHash) {
+ header('Location: /zamowienie/' . $existingOrderHash);
+ exit;
+ }
+}
+$orderSubmitToken = $this->createOrderSubmitToken();
+```
+
+
+ 1. Create a test order successfully
+ 2. Navigate back to /podsumowanie in the same browser session
+ 3. Confirm browser redirects to /zamowienie/{hash} without showing the summary form
+
+ AC-1 satisfied: navigating back to summary after successful order redirects, no form shown
+
+
+
+ Fix BUG-1: Wrap createFromBasket in try-catch in basketSave()
+ autoload/front/Controllers/ShopBasketController.php
+
+In `basketSave()`, wrap the call to `$this->orderRepository->createFromBasket(...)` in a try-catch block. On exception: log with `error_log()`, show user error message via `Helpers::error()`, and redirect to `/koszyk`. Do NOT clear the basket session in the catch block.
+
+Replace the current `if ($order_id = $this->orderRepository->createFromBasket(...))` pattern with:
+
+```php
+$order_id = null;
+try {
+ $order_id = $this->orderRepository->createFromBasket(
+ // ... all current args unchanged ...
+ );
+} catch (\Exception $e) {
+ error_log('[basketSave] createFromBasket exception: ' . $e->getMessage());
+ \Shared\Helpers\Helpers::error(\Shared\Helpers\Helpers::lang('zamowienie-zostalo-zlozone-komunikat-blad'));
+ header('Location: /koszyk');
+ exit;
+}
+
+if ($order_id) {
+ // ... existing success block unchanged ...
+} else {
+ // ... existing error block unchanged ...
+}
+```
+
+Use `\Exception` catch (not `\Throwable`) — the project targets PHP 7.4 which supports both, but `\Exception` covers the common cases (DB exceptions, mail exceptions). If there are any `\Error` throws in the chain they won't be caught — acceptable tradeoff for PHP 7.4 compatibility.
+
+
+ Confirm no PHP syntax errors: `php -l autoload/front/Controllers/ShopBasketController.php`
+
+ AC-2 satisfied: exceptions from createFromBasket are caught and handled gracefully
+
+
+
+ Fix BUG-2: Add is_cod column migration
+ migrations/0.338.sql, docs/DATABASE_STRUCTURE.md
+
+Create the migration file at `migrations/0.338.sql` (kolejna wersja po 0.337):
+
+```sql
+ALTER TABLE `pp_shop_payment_methods`
+ ADD COLUMN `is_cod` TINYINT(1) NOT NULL DEFAULT 0
+ COMMENT 'Platnosc przy odbiorze (cash on delivery): 1 = tak, 0 = nie';
+```
+
+Also update `docs/DATABASE_STRUCTURE.md` — in the `pp_shop_payment_methods` table section, add the new column:
+| is_cod | Płatność przy odbiorze: 1 = tak, 0 = nie (TINYINT DEFAULT 0) |
+
+The migration must be run on production DB manually (document this in the plan summary).
+
+
+ File `migrations/0.338.sql` exists and contains valid ALTER TABLE statement.
+ `docs/DATABASE_STRUCTURE.md` mentions `is_cod` in `pp_shop_payment_methods` section.
+
+ AC-3 precondition: column definition prepared for migration
+
+
+
+ Fix BUG-2: Add is_cod to PaymentMethodRepository normalization and queries
+ autoload/Domain/PaymentMethod/PaymentMethodRepository.php
+
+1. In `normalizePaymentMethod(array $row)`: add `$row['is_cod'] = (int)($row['is_cod'] ?? 0);`
+
+2. In `findActiveById()`: the method already uses `SELECT *` via Medoo `get('pp_shop_payment_methods', '*', ...)` so `is_cod` will be included automatically once the column exists.
+
+3. In `forTransport()`: the method uses explicit column list in raw SQL. Add `spm.is_cod` to the SELECT list (around line ~241, alongside `spm.apilo_payment_type_id`).
+
+4. In `paymentMethodsByTransport()` (if exists as a separate raw SQL method): similarly add `spm.is_cod` to the SELECT. Search for any other raw SQL selects in this file that list columns explicitly and add `is_cod` to them.
+
+5. In the `allActive()` / `paymentMethodsCached()` path: if `allActive()` uses raw SQL with explicit columns, add `spm.is_cod` there too. If it uses `SELECT *`, nothing needed.
+
+Cache keys that include payment method data (`payment_method{id}`, `payment_methods`) will return stale data until Redis is flushed. The post-deploy step is to flush Redis cache.
+
+
+ `php -l autoload/Domain/PaymentMethod/PaymentMethodRepository.php` — no syntax errors.
+ All explicit SQL SELECTs in this file now include `is_cod`.
+
+ AC-3 + AC-4 precondition: repository returns is_cod field
+
+
+
+ Fix BUG-2: Add is_cod switch to admin payment method form
+ autoload/admin/Controllers/ShopPaymentMethodController.php
+
+In `buildFormViewModel()`:
+
+1. Add `'is_cod' => (int)($paymentMethod['is_cod'] ?? 0)` to the `$data` array.
+
+2. Add a switch field after the `status` field:
+```php
+FormField::switch('is_cod', [
+ 'label' => 'Platnosc przy odbiorze',
+ 'tab' => 'settings',
+]),
+```
+
+In the `save()` / `update()` method of this controller: ensure `is_cod` is read from POST and included in the DB update data. Find where the other fields (description, status, apilo_payment_type_id, etc.) are read from request and add:
+```php
+'is_cod' => (int)(\Shared\Helpers\Helpers::get('is_cod') ? 1 : 0),
+```
+
+Check if there is a `FormRequestHandler` or similar save mechanism — if so, `is_cod` may need to be added to the allowed fields list. Read the save method to confirm.
+
+
+ `php -l autoload/admin/Controllers/ShopPaymentMethodController.php` — no syntax errors.
+ Check that `is_cod` appears in both the form field list and the save data array.
+
+ AC-3 satisfied: admin can set is_cod flag on any payment method
+
+
+
+ Fix BUG-2: Use is_cod flag instead of hardcoded payment_id == 3 in OrderRepository
+ autoload/Domain/Order/OrderRepository.php
+
+In `createFromBasket()`, at lines 817-820, replace the hardcoded check:
+
+```php
+// BEFORE:
+if ($payment_id == 3) {
+ $this->updateOrderStatus($order_id, 4);
+ $this->insertStatusHistory($order_id, 4, 1);
+}
+```
+
+With:
+```php
+// AFTER:
+if (!empty($payment_method['is_cod'])) {
+ $this->updateOrderStatus($order_id, 4);
+ $this->insertStatusHistory($order_id, 4, 1);
+}
+```
+
+`$payment_method` is already fetched at line 669:
+```php
+$payment_method = ( new \Domain\PaymentMethod\PaymentMethodRepository( $this->db ) )->findActiveById( (int)$payment_id );
+```
+So `$payment_method['is_cod']` is available without any additional DB query.
+
+
+ `php -l autoload/Domain/Order/OrderRepository.php` — no syntax errors.
+ Confirm the old `$payment_id == 3` no longer exists in createFromBasket().
+
+ AC-4 satisfied: COD status promotion is driven by is_cod flag, not hardcoded ID
+
+
+
+ Run the database migration on production server
+
+ Claude has prepared the migration file at `migrations/0.338.sql`.
+ The SQL is: ALTER TABLE pp_shop_payment_methods ADD COLUMN is_cod TINYINT(1) NOT NULL DEFAULT 0
+
+ You need to run this on the production database manually (via phpMyAdmin, SSH, or your DB client).
+
+ After running, go to /admin/shop_payment_method/list/ → edit the "Płatność przy odbiorze" payment method → enable the "Płatnosc przy odbiorze" switch → Save.
+
+ Also flush Redis cache (or wait for TTL expiry — payment methods cache is 24h).
+
+
+ Claude will verify the code changes are in place. The DB migration must be confirmed by you.
+
+ Type "done" when migration and admin flag set
+
+
+
+
+
+## DO NOT CHANGE
+- The CSRF token mechanism (separate from order submit token)
+- The basket session structure
+- The order submission token logic (ORDER_SUBMIT_TOKEN_SESSION_KEY) — only guard summaryView, don't change how tokens are generated/consumed
+- Email sending logic in createFromBasket
+- Any other payment method fields or behavior
+
+## SCOPE LIMITS
+- Do NOT add database-level unique constraints or idempotency key columns to pp_shop_orders (over-engineering for now)
+- Do NOT change the order status values or their meaning
+- Do NOT modify test files unless directly testing the changed methods
+- Do NOT change the frontend templates
+
+
+
+Before declaring plan complete:
+- [ ] `php -l` passes on all modified PHP files
+- [ ] summaryView() guard redirects to existing order when ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY is set
+- [ ] createFromBasket call in basketSave() is wrapped in try-catch
+- [ ] `is_cod` column exists in migration SQL
+- [ ] normalizePaymentMethod() includes is_cod normalization
+- [ ] admin form shows is_cod switch
+- [ ] admin save includes is_cod in update data
+- [ ] OrderRepository uses $payment_method['is_cod'] not $payment_id == 3
+- [ ] DATABASE_STRUCTURE.md updated
+
+
+
+- All PHP files lint-clean
+- No more duplicate orders when customer navigates back to summary after successful order
+- COD payment method (when is_cod=1) automatically promotes order to status 4
+- Admin can configure which payment method is COD
+
+
+
diff --git a/.paul/phases/06-integrations-refactoring/06-01-PLAN.md b/.paul/phases/06-integrations-refactoring/06-01-PLAN.md
new file mode 100644
index 0000000..75115a6
--- /dev/null
+++ b/.paul/phases/06-integrations-refactoring/06-01-PLAN.md
@@ -0,0 +1,188 @@
+---
+phase: 06-integrations-refactoring
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - autoload/Domain/Integrations/ApiloRepository.php
+ - tests/Unit/Domain/Integrations/ApiloRepositoryTest.php
+autonomous: true
+---
+
+
+## Goal
+Wyekstrahować wszystkie metody Apilo z `IntegrationsRepository` do nowej klasy `ApiloRepository` — non-breaking (IntegrationsRepository pozostaje bez zmian do planu 06-02).
+
+## Purpose
+`IntegrationsRepository` ma 875 linii z czego ~650 to logika Apilo (OAuth, keepalive, fetchList, produkty). Po ekstrakcji każda klasa będzie mieć jedną odpowiedzialność, zgodnie z zasadami projektu (jedna klasa = jedna odpowiedzialność, max ~50 linii na metodę).
+
+## Output
+- Nowy plik: `autoload/Domain/Integrations/ApiloRepository.php` (~650 linii)
+- Nowy plik testów: `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php`
+- `IntegrationsRepository` bez zmian (backward compatible)
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+
+## Source Files
+@autoload/Domain/Integrations/IntegrationsRepository.php
+@tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php
+
+
+
+
+## AC-1: ApiloRepository zawiera wszystkie metody Apilo
+```gherkin
+Given plik autoload/Domain/Integrations/ApiloRepository.php istnieje
+When przeglądamy jego publiczne metody
+Then klasa ma: apiloAuthorize, apiloGetAccessToken, apiloKeepalive,
+ apiloIntegrationStatus, apiloFetchList, apiloFetchListResult,
+ apiloProductSearch, apiloCreateProduct
+```
+
+## AC-2: ApiloRepository ma własny dostęp do DB (DI przez konstruktor)
+```gherkin
+Given ApiloRepository(db: $mdb) jest tworzona
+When wywoływana jest dowolna metoda apilo*
+Then używa $db do zapytań bez zależności od IntegrationsRepository
+```
+
+## AC-3: IntegrationsRepository nie zmieniona (backward compatible)
+```gherkin
+Given istniejące testy IntegrationsRepositoryTest przechodzą
+When uruchamiane jest ./test.ps1
+Then wszystkie 817+ testów green, brak nowych błędów
+```
+
+## AC-4: Testy ApiloRepository pokrywają kluczowe metody
+```gherkin
+Given nowy plik ApiloRepositoryTest.php
+When uruchamiane jest ./test.ps1
+Then testy dla: apiloGetAccessToken, apiloKeepalive, apiloIntegrationStatus,
+ apiloFetchListResult, apiloFetchList (invalid type), prywatnych helperów przechodzą
+```
+
+
+
+
+
+
+ Task 1: Utwórz ApiloRepository — ekstrakcja metod Apilo
+ autoload/Domain/Integrations/ApiloRepository.php
+
+ Utwórz nowy plik `autoload/Domain/Integrations/ApiloRepository.php`.
+
+ Namespace: `Domain\Integrations`
+
+ Klasa ma:
+ - `private $db;`
+ - `private const SETTINGS_TABLE = 'pp_shop_apilo_settings';`
+ - Konstruktor: `public function __construct($db)`
+
+ Przenieś (skopiuj) z IntegrationsRepository **bez modyfikacji logiki**:
+ - Metody publiczne: `apiloAuthorize`, `apiloGetAccessToken`, `apiloKeepalive`,
+ `apiloIntegrationStatus`, `apiloFetchList`, `apiloFetchListResult`,
+ `apiloProductSearch`, `apiloCreateProduct`
+ - Metody prywatne: `refreshApiloAccessToken`, `shouldRefreshAccessToken`,
+ `isFutureDate`, `normalizeApiloMapList`, `isMapListShape`, `extractApiloErrorMessage`
+
+ Dostosowania niezbędne po przeniesieniu:
+ - Wszędzie gdzie metody apilo* wewnętrznie wołają `$this->getSettings('apilo')` —
+ zamień na `$this->db->select(self::SETTINGS_TABLE, ['name', 'value'])` i mapuj
+ na `[$row['name'] => $row['value']]` (ta sama logika co w IntegrationsRepository::getSettings)
+ - Wszędzie gdzie wołają `$this->saveSetting('apilo', ...)` — zamień na bezpośrednie
+ `$this->db->update(self::SETTINGS_TABLE, ['value' => $value], ['name' => $name])`
+ i `$this->db->insert(self::SETTINGS_TABLE, ['name' => $name, 'value' => $value])`
+ z `count()` przed jak w saveSetting (dokładna kopia logiki)
+
+ Unikaj: dziedziczenia z IntegrationsRepository, jakichkolwiek zależności poza $db.
+ PHP < 8.0: brak match, named args, union types, str_contains.
+
+
+ php -l autoload/Domain/Integrations/ApiloRepository.php zwraca "No syntax errors"
+ Klasa ma dokładnie 8 publicznych metod apilo* + 6 prywatnych helperów.
+
+ AC-1 i AC-2 spełnione
+
+
+
+ Task 2: Utwórz ApiloRepositoryTest
+ tests/Unit/Domain/Integrations/ApiloRepositoryTest.php
+
+ Utwórz `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php`.
+
+ Namespace: `Tests\Unit\Domain\Integrations`
+ Klasa extends `PHPUnit\Framework\TestCase`
+
+ Przenieś (skopiuj) z IntegrationsRepositoryTest wszystkie testy dotyczące metod Apilo:
+ - `testApiloGetAccessTokenReturnsNullWithoutSettings`
+ - `testShouldRefreshAccessTokenReturnsFalseForFarFutureDate`
+ - `testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate`
+ - `testApiloFetchListThrowsForInvalidType`
+ - `testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing`
+ - `testApiloIntegrationStatusReturnsMissingConfigMessage`
+ - `testNormalizeApiloMapListRejectsErrorPayload`
+ - `testNormalizeApiloMapListAcceptsIdNameList`
+
+ Dostosuj w skopiowanych testach:
+ - Zmień `new IntegrationsRepository($this->mockDb)` → `new ApiloRepository($this->mockDb)`
+ - Use statement: `use Domain\Integrations\ApiloRepository;`
+ - setUp: `$this->repository = new ApiloRepository($this->mockDb);`
+
+ Uwaga: w testach mockujących `select` z `pp_shop_apilo_settings` — sprawdź czy
+ ApiloRepository używa dokładnie tej samej tabeli i struktury zapytania co IntegrationsRepository.
+ Jeśli zmieniło się wywołanie (np. bezpośrednie select zamiast przez getSettings),
+ dostosuj expect() w testach.
+
+ Nie usuwaj tych testów z IntegrationsRepositoryTest — zostają tam do planu 06-02.
+
+
+ ./test.ps1 tests/Unit/Domain/Integrations/ApiloRepositoryTest.php — wszystkie testy green
+ ./test.ps1 — pełna suite green (817+ testów, brak regresji)
+
+ AC-3 i AC-4 spełnione
+
+
+
+
+
+
+## DO NOT CHANGE
+- `autoload/Domain/Integrations/IntegrationsRepository.php` — bez żadnych zmian w tym planie
+- `tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php` — tylko dodajemy, nie usuwamy
+- Żadne kontrolery, App.php, cron.php — migracja konsumentów to plan 06-02
+- Żadne zmiany logiki biznesowej — czysta ekstrakcja, zero refaktoringu logiki
+
+## SCOPE LIMITS
+- Ten plan tworzy tylko nową klasę + testy. Konsumenci nadal używają IntegrationsRepository.
+- Nie zmieniamy nazw metod, sygnatur, zachowania.
+- Nie optymalizujemy kodu Apilo podczas przenoszenia.
+
+
+
+
+Before declaring plan complete:
+- [ ] php -l autoload/Domain/Integrations/ApiloRepository.php — no syntax errors
+- [ ] ApiloRepository ma 8 publicznych metod: apiloAuthorize, apiloGetAccessToken,
+ apiloKeepalive, apiloIntegrationStatus, apiloFetchList, apiloFetchListResult,
+ apiloProductSearch, apiloCreateProduct
+- [ ] ./test.ps1 tests/Unit/Domain/Integrations/ApiloRepositoryTest.php — all green
+- [ ] ./test.ps1 — full suite green, żadna regresja w IntegrationsRepositoryTest
+- [ ] IntegrationsRepository.php nie został zmodyfikowany
+
+
+
+- ApiloRepository.php istnieje z pełnym zestawem metod Apilo
+- ApiloRepositoryTest.php istnieje z testami dla kluczowych metod
+- Pełna suite testów green (817+ testów)
+- IntegrationsRepository niezmieniony (backward compatible)
+
+
+
diff --git a/.paul/phases/06-integrations-refactoring/06-01-SUMMARY.md b/.paul/phases/06-integrations-refactoring/06-01-SUMMARY.md
new file mode 100644
index 0000000..e4d4d9b
--- /dev/null
+++ b/.paul/phases/06-integrations-refactoring/06-01-SUMMARY.md
@@ -0,0 +1,104 @@
+---
+phase: 06-integrations-refactoring
+plan: 01
+subsystem: domain
+tags: [apilo, integrations, refactoring, repository]
+
+requires: []
+provides:
+ - "ApiloRepository — klasa z 8 pub metodami Apilo (OAuth, keepalive, fetchList, products)"
+ - "ApiloRepositoryTest — 9 testów jednostkowych"
+affects: [06-02-consumers-migration]
+
+tech-stack:
+ added: []
+ patterns:
+ - "ApiloRepository: własna stała SETTINGS_TABLE, prywatne getApiloSettings/saveApiloSetting zamiast delegacji do IntegrationsRepository"
+
+key-files:
+ created:
+ - autoload/Domain/Integrations/ApiloRepository.php
+ - tests/Unit/Domain/Integrations/ApiloRepositoryTest.php
+ modified: []
+
+key-decisions:
+ - "ApiloRepository nie dziedziczy z IntegrationsRepository — własny $db, własna const SETTINGS_TABLE"
+ - "Non-breaking: IntegrationsRepository zachowany bez zmian do planu 06-02"
+ - "saveApiloSetting/getApiloSettings jako prywatne — nie duplikują interfejsu publicznego"
+
+patterns-established:
+ - "Ekstrakcja domenowej podklasy: nowa klasa z własnym $db, prywatnym dostępem do settings swojej tabeli"
+
+duration: ~15min
+started: 2026-03-12T00:00:00Z
+completed: 2026-03-12T00:00:00Z
+---
+
+# Phase 6 Plan 01: IntegrationsRepository split — ApiloRepository Summary
+
+**Wyekstrahowano 8 metod Apilo (~330 linii) z IntegrationsRepository do nowego ApiloRepository — non-breaking, 826/826 testów green.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~15 min |
+| Completed | 2026-03-12 |
+| Tasks | 2 / 2 |
+| Files created | 2 |
+| Files modified | 0 |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: ApiloRepository zawiera wszystkie metody Apilo | Pass | 8 pub metod + 6 priv helperów |
+| AC-2: Własny DI przez konstruktor ($db) | Pass | brak zależności od IntegrationsRepository |
+| AC-3: IntegrationsRepository niezmieniony (backward compatible) | Pass | plik nie był modyfikowany |
+| AC-4: Testy ApiloRepository przechodzą | Pass | 9/9 testów, 826/826 full suite |
+
+## Accomplishments
+
+- `ApiloRepository.php` — 330 linii: OAuth (authorize, getAccessToken, keepalive, refresh), integracja status, fetchList/fetchListResult, productSearch, createProduct
+- `ApiloRepositoryTest.php` — 9 testów: getAccessToken, shouldRefreshAccessToken (×2), fetchList invalid type, fetchListResult config missing, integrationStatus missing config, normalizeApiloMapList (×2), allPublicMethodsExist
+- Full suite wzrosła z 817 do 826 testów (zero regresji)
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `autoload/Domain/Integrations/ApiloRepository.php` | Created | Klasa Apilo: OAuth, keepalive, fetchList, produkty |
+| `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php` | Created | Testy jednostkowe ApiloRepository |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Prywatne `getApiloSettings()` / `saveApiloSetting()` zamiast dziedziczenia | Unika coupling z IntegrationsRepository, czysta encapsulacja | 06-02 nie potrzebuje IntegrationsRepository w ApiloRepository |
+| Zachowanie `APILO_ENDPOINTS` i `APILO_SETTINGS_KEYS` jako class constants | Były private const w IntegrationsRepository — logicznie należą do ApiloRepository | Stałe są prywatne, nie wymuszają zmian w konsumentach |
+| Non-breaking w 06-01 | Migracja konsumentów w 06-02 — mniejsze ryzyko, łatwiejszy review | IntegrationsRepository nadal działa dla wszystkich konsumentów |
+
+## Deviations from Plan
+
+Brak — plan wykonany dokładnie jak napisano.
+
+## Issues Encountered
+
+Brak.
+
+## Next Phase Readiness
+
+**Ready:**
+- `ApiloRepository` gotowy do użycia przez konsumentów
+- Interfejs publiczny identyczny z metodami `apilo*` w IntegrationsRepository
+- Testy stanowią baseline dla weryfikacji po migracji konsumentów
+
+**Concerns:**
+- `IntegrationsController` używa zarówno metod Apilo jak i Settings/ShopPRO — po 06-02 będzie potrzebować obu repozytoriów w konstruktorze
+- `OrderAdminService` tworzy `new IntegrationsRepository($db)` lokalnie w 5 miejscach — po 06-02 trzeba zmienić na `new ApiloRepository($db)`
+
+**Blockers:** Brak
+
+---
+*Phase: 06-integrations-refactoring, Plan: 01*
+*Completed: 2026-03-12*
diff --git a/.paul/phases/06-integrations-refactoring/06-02-PLAN.md b/.paul/phases/06-integrations-refactoring/06-02-PLAN.md
new file mode 100644
index 0000000..07c5785
--- /dev/null
+++ b/.paul/phases/06-integrations-refactoring/06-02-PLAN.md
@@ -0,0 +1,296 @@
+---
+phase: 06-integrations-refactoring
+plan: 02
+type: execute
+wave: 1
+depends_on: ["06-01"]
+files_modified:
+ - autoload/admin/Controllers/IntegrationsController.php
+ - autoload/admin/App.php
+ - autoload/Domain/Order/OrderAdminService.php
+ - cron.php
+ - autoload/Domain/Integrations/IntegrationsRepository.php
+ - tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php
+autonomous: true
+---
+
+
+## Goal
+Zmigrować wszystkich konsumentów metod `apilo*` z `IntegrationsRepository` na nowy `ApiloRepository`, a następnie usunąć metody Apilo z `IntegrationsRepository` (cleanup).
+
+## Purpose
+Po tym planie `IntegrationsRepository` będzie lean (~225 linii): tylko settings, logi, product linking, ShopPRO import. `ApiloRepository` jest jedynym miejscem logiki Apilo.
+
+## Output
+- IntegrationsController: używa obu repozytoriów (IntegrationsRepository dla settings/logi, ApiloRepository dla apilo*)
+- OrderAdminService: 3 metody używają ApiloRepository dla apiloGetAccessToken
+- cron.php: apilo* wywołania przez $apiloRepository
+- IntegrationsRepository: usunięte metody apilo* (~650 linii mniej)
+- IntegrationsRepositoryTest: oczyszczony z duplikatów testów apilo*
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/STATE.md
+
+## Prior Work
+@.paul/phases/06-integrations-refactoring/06-01-SUMMARY.md
+
+## Source Files
+@autoload/admin/Controllers/IntegrationsController.php
+@autoload/admin/App.php
+@autoload/Domain/Order/OrderAdminService.php
+@autoload/Domain/Integrations/IntegrationsRepository.php
+@tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php
+
+
+
+
+## AC-1: IntegrationsController używa ApiloRepository dla apilo*
+```gherkin
+Given IntegrationsController ma dwa repozytoria: $repository i $apiloRepository
+When wywoływana jest dowolna metoda apilo* (apilo_settings, apilo_authorization, itp.)
+Then używa $this->apiloRepository->apilo*() a nie $this->repository->apilo*()
+```
+
+## AC-2: OrderAdminService i cron.php używają ApiloRepository dla apiloGetAccessToken
+```gherkin
+Given OrderAdminService::resendToApilo, syncApiloPayment, syncApiloStatus
+ oraz cron.php potrzebują access tokenu
+When wywoływana jest metoda apiloGetAccessToken()
+Then używają new ApiloRepository($db) lub $apiloRepository, nie IntegrationsRepository
+```
+
+## AC-3: IntegrationsRepository nie zawiera metod apilo*
+```gherkin
+Given plik IntegrationsRepository.php po cleanup
+When sprawdzamy publiczne metody klasy
+Then metody apilo* NIE ISTNIEJĄ, pozostają tylko:
+ getSettings, getSetting, saveSetting,
+ getLogs, deleteLog, clearLogs,
+ linkProduct, unlinkProduct, getProductSku,
+ shopproImportProduct
+```
+
+## AC-4: Pełna suite testów green
+```gherkin
+Given wszystkie zmiany wprowadzone
+When uruchamiane jest php phpunit.phar
+Then wszystkie testy green (826+ testów, zero regresji)
+```
+
+
+
+
+
+
+ Task 1: Zaktualizuj IntegrationsController i App.php
+ autoload/admin/Controllers/IntegrationsController.php, autoload/admin/App.php
+
+ **IntegrationsController.php:**
+
+ 1. Dodaj import: `use Domain\Integrations\ApiloRepository;`
+ 2. Dodaj property: `private ApiloRepository $apiloRepository;`
+ 3. Zmień konstruktor na:
+ ```php
+ public function __construct( IntegrationsRepository $repository, ApiloRepository $apiloRepository )
+ {
+ $this->repository = $repository;
+ $this->apiloRepository = $apiloRepository;
+ }
+ ```
+ 4. Zamień wszystkie wywołania `$this->repository->apilo*()` na `$this->apiloRepository->apilo*()`:
+ - linia ~128: `$this->repository->apiloIntegrationStatus()` → `$this->apiloRepository->apiloIntegrationStatus()`
+ - linia ~150: `$this->repository->apiloAuthorize(...)` → `$this->apiloRepository->apiloAuthorize(...)`
+ - linia ~159: `$this->repository->apiloIntegrationStatus()` → `$this->apiloRepository->apiloIntegrationStatus()`
+ - linia ~194: `$this->repository->apiloCreateProduct(...)` → `$this->apiloRepository->apiloCreateProduct(...)`
+ - linia ~211: `$this->repository->apiloProductSearch(...)` → `$this->apiloRepository->apiloProductSearch(...)`
+ - linia ~270: `$this->repository->apiloFetchListResult(...)` → `$this->apiloRepository->apiloFetchListResult(...)`
+
+ Pozostaw bez zmian: getLogs, clearLogs, getSettings, saveSetting, getProductSku,
+ linkProduct, unlinkProduct, getSettings('shoppro'), saveSetting('shoppro'), shopproImportProduct
+ — wszystkie przez `$this->repository`.
+
+ **App.php:**
+
+ W fabryce 'Integrations' (linia ~384) zmień:
+ ```php
+ return new \admin\Controllers\IntegrationsController(
+ new \Domain\Integrations\IntegrationsRepository( $mdb )
+ );
+ ```
+ na:
+ ```php
+ return new \admin\Controllers\IntegrationsController(
+ new \Domain\Integrations\IntegrationsRepository( $mdb ),
+ new \Domain\Integrations\ApiloRepository( $mdb )
+ );
+ ```
+
+
+ php -l autoload/admin/Controllers/IntegrationsController.php — no syntax errors
+ php -l autoload/admin/App.php — no syntax errors
+ grep "apiloRepository" autoload/admin/Controllers/IntegrationsController.php — pokazuje 6+ wystąpień
+
+ AC-1 spełnione
+
+
+
+ Task 2: Zaktualizuj OrderAdminService i cron.php
+ autoload/Domain/Order/OrderAdminService.php, cron.php
+
+ **OrderAdminService.php** — 3 metody tworzą IntegrationsRepository i wołają apiloGetAccessToken().
+ Zmień tylko te 3 miejsca (linie ~422, ~678, ~751):
+
+ ```php
+ // PRZED (w każdym z 3 miejsc):
+ $integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db);
+ // lub: new \Domain\Integrations\IntegrationsRepository( $mdb );
+ $accessToken = $integrationsRepository->apiloGetAccessToken();
+
+ // PO (w każdym z 3 miejsc):
+ $apiloRepository = new \Domain\Integrations\ApiloRepository($db);
+ // lub z $mdb gdzie używano $mdb
+ $accessToken = $apiloRepository->apiloGetAccessToken();
+ ```
+
+ POZOSTAW BEZ ZMIAN (linie ~579, ~628) — te tworzą IntegrationsRepository
+ i wołają tylko getSettings('apilo') — to metoda generyczna, zostaje w IntegrationsRepository.
+
+ **cron.php** — linia ~133:
+ Po linii `$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );`
+ dodaj:
+ ```php
+ $apiloRepository = new \Domain\Integrations\ApiloRepository( $mdb );
+ ```
+
+ Zamień wywołania apilo* przez `$integrationsRepository` na `$apiloRepository`:
+ - linia ~191: `$integrationsRepository->apiloKeepalive(300)` → `$apiloRepository->apiloKeepalive(300)`
+ - linia ~279: `$integrationsRepository->apiloGetAccessToken()` → `$apiloRepository->apiloGetAccessToken()`
+ - linia ~560: `$integrationsRepository->apiloGetAccessToken()` → `$apiloRepository->apiloGetAccessToken()`
+ - linia ~589: `$integrationsRepository->apiloGetAccessToken()` → `$apiloRepository->apiloGetAccessToken()`
+ - linia ~642: `$integrationsRepository->apiloGetAccessToken()` → `$apiloRepository->apiloGetAccessToken()`
+
+ POZOSTAW BEZ ZMIAN w cron.php:
+ - `$integrationsRepository->getSettings('apilo')` (linie ~188, ~198, ~553, ~586, ~632)
+ - `$integrationsRepository->saveSetting('apilo', ...)` (linia ~625)
+
+
+ php -l autoload/Domain/Order/OrderAdminService.php — no syntax errors
+ php -l cron.php — no syntax errors
+ grep "integrationsRepository->apilo" cron.php — brak wyników (wszystkie apilo przeniesione)
+ grep "integrationsRepository->apilo" autoload/Domain/Order/OrderAdminService.php — brak wyników
+
+ AC-2 spełnione
+
+
+
+ Task 3: Usuń metody apilo* z IntegrationsRepository + cleanup testów
+ autoload/Domain/Integrations/IntegrationsRepository.php, tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php
+
+ **IntegrationsRepository.php:**
+
+ Usuń następujące bloki (cały kod między komentarzami sekcji a kolejną sekcją):
+
+ 1. Sekcję "// ── Apilo OAuth" z metodami:
+ - `apiloAuthorize()`
+ - `apiloGetAccessToken()`
+ - `apiloKeepalive()`
+ - `refreshApiloAccessToken()` (private)
+ - `shouldRefreshAccessToken()` (private)
+ - `isFutureDate()` (private)
+
+ 2. Stałe klasy:
+ - `private const APILO_ENDPOINTS = [...]`
+ - `private const APILO_SETTINGS_KEYS = [...]`
+
+ 3. Sekcję "// ── Apilo API fetch lists" z metodami:
+ - `apiloFetchList()`
+ - `apiloFetchListResult()`
+ - `normalizeApiloMapList()` (private)
+ - `isMapListShape()` (private)
+ - `extractApiloErrorMessage()` (private)
+
+ 4. Z sekcji "// ── Apilo product operations" usuń tylko:
+ - `apiloProductSearch()`
+ - `apiloCreateProduct()`
+ (ZACHOWAJ `getProductSku()` — jest generyczna, używana też przez ShopProductController)
+
+ Po usunięciu IntegrationsRepository powinna zawierać:
+ - settings (settingsTable, getSettings, getSetting, saveSetting)
+ - logs (getLogs, deleteLog, clearLogs)
+ - product linking (linkProduct, unlinkProduct, getProductSku)
+ - ShopPRO import (shopproImportProduct, missingShopproSetting, shopproDb)
+
+ **IntegrationsRepositoryTest.php:**
+
+ Usuń następujące metody testowe (zostały już przeniesione do ApiloRepositoryTest):
+ - `testApiloGetAccessTokenReturnsNullWithoutSettings()`
+ - `testShouldRefreshAccessTokenReturnsFalseForFarFutureDate()`
+ - `testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate()`
+ - `testApiloFetchListThrowsForInvalidType()`
+ - `testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing()`
+ - `testApiloIntegrationStatusReturnsMissingConfigMessage()`
+ - `testNormalizeApiloMapListRejectsErrorPayload()`
+ - `testNormalizeApiloMapListAcceptsIdNameList()`
+
+ W metodzie `testAllPublicMethodsExist()` usuń z tablicy `$expectedMethods` wpisy apilo*:
+ - `'apiloAuthorize'`, `'apiloGetAccessToken'`, `'apiloKeepalive'`, `'apiloIntegrationStatus'`
+ - `'apiloFetchList'`, `'apiloFetchListResult'`, `'apiloProductSearch'`, `'apiloCreateProduct'`
+ (Pozostaw: `'getSettings'`, `'getSetting'`, `'saveSetting'`, `'linkProduct'`, `'unlinkProduct'`,
+ `'getProductSku'`, `'shopproImportProduct'`, `'getLogs'`, `'deleteLog'`, `'clearLogs'`)
+
+ Usuń też `testSettingsTableMapping()` i `testShopproProviderWorks()` tylko jeśli są duplikatami
+ (sprawdź przed usunięciem — jeśli nie mają odpowiedników, zostaw).
+
+
+ php -l autoload/Domain/Integrations/IntegrationsRepository.php — no syntax errors
+ grep "apilo" autoload/Domain/Integrations/IntegrationsRepository.php — brak wyników (lub tylko komentarze)
+ php phpunit.phar — wszystkie testy green (826+, zero regresji)
+ php phpunit.phar tests/Unit/Domain/Integrations/ — oba pliki testów green
+
+ AC-3 i AC-4 spełnione
+
+
+
+
+
+
+## DO NOT CHANGE
+- `autoload/Domain/Integrations/ApiloRepository.php` — gotowy, nie modyfikować
+- `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php` — gotowy, nie modyfikować
+- `autoload/admin/Controllers/ShopProductController.php` — używa tylko getSetting(), nie apilo*
+- `autoload/admin/Controllers/ShopStatusesController.php` — używa tylko getSetting(), nie apilo*
+- `autoload/admin/Controllers/ShopTransportController.php` — używa tylko getSetting(), nie apilo*
+- `autoload/admin/Controllers/ShopPaymentMethodController.php` — używa tylko getSetting(), nie apilo*
+- Logika biznesowa nie zmienia się — czysta migracja wywołań
+
+## SCOPE LIMITS
+- Nie refaktoryzujemy OrderAdminService poza zmianą 3 instancji na ApiloRepository
+- Nie zmieniamy sygnatury metod ani logiki
+- Nie przenosimy ShopPRO import do osobnej klasy (to nie ten plan)
+
+
+
+
+Before declaring plan complete:
+- [ ] php -l na wszystkich zmodyfikowanych plikach — no syntax errors
+- [ ] grep "apiloRepository->apilo" w IntegrationsController — 6 wystąpień (apilo metody)
+- [ ] grep "this->repository->apilo" w IntegrationsController — brak wyników
+- [ ] grep "integrationsRepository->apilo" w cron.php — brak wyników
+- [ ] grep "integrationsRepository->apilo" w OrderAdminService — brak wyników
+- [ ] grep "public function apilo" w IntegrationsRepository — brak wyników
+- [ ] php phpunit.phar — 826+ testów green
+
+
+
+- IntegrationsController używa ApiloRepository dla wszystkich metod apilo*
+- OrderAdminService i cron.php używają ApiloRepository dla apiloGetAccessToken
+- IntegrationsRepository nie zawiera żadnych metod apilo*
+- Pełna suite testów green bez regresji
+
+
+
diff --git a/.paul/phases/06-integrations-refactoring/06-02-SUMMARY.md b/.paul/phases/06-integrations-refactoring/06-02-SUMMARY.md
new file mode 100644
index 0000000..8321ff1
--- /dev/null
+++ b/.paul/phases/06-integrations-refactoring/06-02-SUMMARY.md
@@ -0,0 +1,99 @@
+---
+phase: 06-integrations-refactoring
+plan: 02
+subsystem: domain
+tags: [apilo, integrations, refactoring, migration]
+
+requires:
+ - phase: 06-01
+ provides: ApiloRepository class with all apilo* methods
+provides:
+ - "Wszyscy konsumenci apilo* używają ApiloRepository"
+ - "IntegrationsRepository lean (~225 linii): settings, logi, product linking, ShopPRO"
+affects: []
+
+tech-stack:
+ added: []
+ patterns:
+ - "IntegrationsController z dwoma repozytoriami: IntegrationsRepository + ApiloRepository"
+
+key-files:
+ created: []
+ modified:
+ - autoload/admin/Controllers/IntegrationsController.php
+ - autoload/admin/App.php
+ - autoload/Domain/Order/OrderAdminService.php
+ - cron.php
+ - autoload/Domain/Integrations/IntegrationsRepository.php
+ - tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php
+ - tests/Unit/admin/Controllers/IntegrationsControllerTest.php
+
+key-decisions:
+ - "IntegrationsController dostał ApiloRepository jako drugi argument konstruktora"
+ - "OrderAdminService: tylko 3 z 5 instancji zmienione na ApiloRepository (2 używają getSettings — zostają)"
+ - "cron.php: $apiloRepository obok $integrationsRepository (oba potrzebne)"
+
+patterns-established:
+ - "Kontroler używający dwóch repozytoriów: każde do swojej domeny"
+
+duration: ~20min
+started: 2026-03-12T00:00:00Z
+completed: 2026-03-12T00:00:00Z
+---
+
+# Phase 6 Plan 02: Migracja konsumentów + cleanup IntegrationsRepository
+
+**Wszyscy konsumenci apilo* zmigrowano na ApiloRepository; IntegrationsRepository oczyszczono do ~225 linii; 818/818 testów green.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~20 min |
+| Completed | 2026-03-12 |
+| Tasks | 3 / 3 |
+| Files modified | 7 |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: IntegrationsController używa ApiloRepository dla apilo* | Pass | 6 wywołań przeniesione |
+| AC-2: OrderAdminService i cron.php używają ApiloRepository | Pass | 3 metody + 5 wywołań w cron |
+| AC-3: IntegrationsRepository nie zawiera metod apilo* | Pass | 0 wystąpień apilo* |
+| AC-4: Pełna suite green | Pass | 818/818 testów |
+
+## Accomplishments
+
+- IntegrationsRepository: ~650 linii usunięte, zostały settings + logi + product linking + ShopPRO
+- IntegrationsController: nowy konstruktor `(IntegrationsRepository, ApiloRepository)`
+- OrderAdminService: 3 metody (resendToApilo, syncApiloPayment, syncApiloStatus) używają ApiloRepository
+- cron.php: `$apiloRepository` dla 5 wywołań apilo*; `$integrationsRepository` dla getSettings/saveSetting
+- IntegrationsRepositoryTest: oczyszczony z 8 duplikatów apilo testów + przywrócone 3 testy generyczne
+- IntegrationsControllerTest: zaktualizowany do nowego 2-arg konstruktora
+
+## Files Modified
+
+| File | Zmiana |
+|------|--------|
+| `autoload/admin/Controllers/IntegrationsController.php` | +ApiloRepository dependency, 6 apilo* calls rerouted |
+| `autoload/admin/App.php` | Inject ApiloRepository do IntegrationsController |
+| `autoload/Domain/Order/OrderAdminService.php` | 3× IntegrationsRepository → ApiloRepository |
+| `cron.php` | +$apiloRepository, 5 apilo* calls rerouted |
+| `autoload/Domain/Integrations/IntegrationsRepository.php` | Usunięto ~650 linii apilo* |
+| `tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php` | Cleanup + przywrócone testy generyczne |
+| `tests/Unit/admin/Controllers/IntegrationsControllerTest.php` | Zaktualizowany do 2-arg konstruktora |
+
+## Deviations from Plan
+
+- IntegrationsControllerTest wymagał aktualizacji (nie był w planie) — auto-fix podczas weryfikacji
+- 3 testy przypadkowo usunięte przez regex (testAllPublicMethodsExist, testSettingsTableMapping, testShopproProviderWorks) — przywrócone
+
+## Next Phase Readiness
+
+**Ready:** Refaktoring fazy 6 kompletny. IntegrationsRepository lean, ApiloRepository izolowany.
+**Blockers:** Brak
+
+---
+*Phase: 06-integrations-refactoring, Plan: 02*
+*Completed: 2026-03-12*
diff --git a/.phpunit.result.cache b/.phpunit.result.cache
index 2f5d95b..e8e3d8a 100644
--- a/.phpunit.result.cache
+++ b/.phpunit.result.cache
@@ -1 +1 @@
-{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":3,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenNoApiKey":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenWrongApiKey":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenStoredKeyEmpty":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingEndpoint":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingAction":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns404ForUnknownEndpoint":4,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueKeepsTaskWhenApiloOrderIdIsNull":3,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueRemovesTaskAfterMaxAttempts":3,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeRejectsGetMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeReturns400WhenNoBody":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueRejectsGetMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueReturns400WhenNoBody":4,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextUpdatesStatusToProcessing":3,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":3,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":3},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.004,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.005,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.002,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.002,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.003,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.002,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.074,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.075,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.173,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorAcceptsRepository":0.004,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorRequiresDictionariesRepository":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorRequiresLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorRequiresLayoutsRepository":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorRequiresRepositoryAndRenderer":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorAcceptsDependencies":0.001,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorRequiresRepositoryAndLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasViewListMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserEditMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasTwofaMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasLoginFormMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorRequiresUserRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserReturnsDefaultsForNull":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserCastsTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserHandlesPartialData":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderUpdatesFilesOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderSkipsEmptyValues":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPagesSummaryForArticlesBuildsLabels":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testUpdateImageAltDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testMarkFileToDeleteDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindReturnsDefaultCouponForInvalidId":0.001,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindNormalizesCouponData":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveInsertsCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveUpdatesCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingsReturnsArray":0.002,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.002,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.001,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullForNegativeId":0.001,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsStatusWithIdZero":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindNormalizesNullApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveUpdatesColorAndApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithIdZeroWorks":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithEmptyApiloStatusIdSetsNull":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveRejectsNegativeId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsValue":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsNullWhenNotSet":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdForApilo":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdReturnsNullForUnknownIntegration":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testAllStatusesReturnsOrderedList":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorRequiresShopStatusRepository":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0.001,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveUpdatesRowAndReturnsId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePreservesNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllForAdminReturnsRowsIncludingInactive":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindKeepsNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveNormalizesStatusValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdHandlesNullAndInt":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsStringForNonNumericValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindNormalizesDataAndIncludesPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindHandlesNullMaxWpAndApiloId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNewId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveUpdateReturnsExistingId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveResetsDefaultWhenSettingNew":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveSwitchValuesNormalization":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testListForAdminWhitelistsSortColumn":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsIntOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetTransportCostReturnsFloatOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllForAdminReturnsAllTransports":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorRequiresPaymentMethodRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFindAttributeReturnsDefaultAttributeForInvalidId":0.003,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForAdminWhitelistsSortDirectionAndPerPage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesRemovesObsoleteRowsAndSetsDefault":0.001,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesDeletesTranslationWhenNameIsEmpty":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testGetAttributeValueByIdUsesDefaultLanguageWhenNotProvided":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSortTypesReturnsExpectedKeys":0.002,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsReturnsDefaultForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsLoadsTranslations":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderReturnsFalseForNonArray":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderUpdatesOrderAndParent":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderReturnsFalseForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderUpdatesCategoryProductOrder":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsFalseWhenHasChildren":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsFirstAvailableTitle":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testListForAdminWhitelistsSortAndPagination":0.002,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientReturnsEmptyOnMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientNormalizesRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsZeroForMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsAggregatedValues":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testHasAllPublicMethods":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testSalesGridReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testLastOrdersReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testMostViewedProductsReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testBestSalesProductsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsFallbackWhenEmpty":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsList":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyArrayWhenNone":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsDefaultsToPl":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsForDifferentLanguage":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionUpdatesStatus":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailReturnsFalseForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSignupReturnsFalseForExistingEmail":0.001,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConstructorAcceptsOptionalDependencies":0.003,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testNextAndPrevOrderIdReturnNullForInvalidInput":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindReturnsDefaultProducerForInvalidId":0.001,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindNormalizesProducerData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveInsertsNewProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveUpdatesExistingProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllProducersReturnsFormattedList":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testProducerProductsReturnsPaginatedResults":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindReturnsDefaultSetForInvalidId":0.001,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindNormalizesSetData":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveInsertsNewSetAndSyncsProducts":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveUpdatesExistingSet":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testAllSetsReturnsFormattedList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditReturnsMap":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsEmptyArray":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsNullForInvalidProduct":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsCorrectPrices":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentZeroPercentNullsPromo":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsAssociativeArray":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenNoSettings":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsHandlesNullFromDb":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsCorrectParam":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueUsesParamNotHardcoded":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsEmptyStringWhenNotFound":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testConstructorAcceptsDb":0.002,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasUpdateMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testUpdateReturnsArray":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasRunPendingMigrationsMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testRunPendingMigrationsWithNoResults":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasPrivateHelperMethods":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsErrorsForMissingDefaultLanguageAndDefaultSelection":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsEmptyArrayForValidRows":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorRequiresCategoryAndLanguagesRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorRequiresClientRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsService":0.002,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderAdminService":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorAcceptsRepositories":0.003,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasMassEditActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasViewListMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasEditAndSaveMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasOperationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasCombinationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasImageAndFileMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditReturnsString":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditSaveReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testGetProductsByCategoryReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasFormBuildingHelpers":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testSaveMethodReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorRequiresProductSetRepository":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsSortedIds":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsNullForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsInt":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsZeroForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesPagination":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsBool":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsFalseForNonNoindex":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsReturnsArticlesArray":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsActiveBannersWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsNullWhenNoBanners":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsActiveBannerWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsNullWhenNoBanner":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsNullWhenNone":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutFallsBackToDefault":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutReturnsNullWhenNothingFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsPageWithLanguage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdReturnsStartPage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdFallsBackToFirstActive":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageSortReturnsValue":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsMenuWithPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsNullForInvalidMenu":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuPagesReturnsEmptyForNoPages":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsAttributeWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsValueWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpCalculatesTotal":0.001,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsSumsQuantities":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextSingular":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural2to4":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural5Plus":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextCastsToInt":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsSortType":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsTitle":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsCategoryWithLanguage":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoriesTreeReturnsEmptyWhenNoCategories":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsZeroForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsCount":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsProductIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsClampsPage":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsRowOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsStringOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesHandlesFalseFromDb":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsRow":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveReturnsFalseForInvalidClientId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveInsertsNewAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveUpdatesExistingAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentReturnsFalseForInvalidIds":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentResetsAndSets":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorWhenClientNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsInactiveForUnconfirmedAccount":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnWrongPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsOkOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullWhenEmailTaken":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsIdAndHashOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationActivatesAndReturnsEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsEmailAndPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullOnEmptyEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientOrdersReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsObjectWhenFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullForEmptyName":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsTrueForActiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForUsedCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForInactiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForNullCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableWorksWithArray":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedSkipsInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountSkipsInvalidId":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsIdWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullForEmptyHash":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsHashWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByIdReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByHashReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberFormatsCorrectly":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberStartsAt001":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsFullData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsProducerWithLanguage":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorRequiresProducerRepository":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsOneForActivePayment":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNormalizedData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsSku":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackReturnsEan":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsOneForActive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInactive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsCategories":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontUsesParentId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testTopProductIdsReturnsActiveProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsProductIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsEmptyWhenNone":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeWholeBasketAppliesDiscountToAll":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesOrAppliesDiscountToMatchingCategories":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionAppliesWhenConditionMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionNoDiscountWhenConditionNotMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesAndAppliesWhenBothConditionsMet":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportCostCachedReturnsCost":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsTransport":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsNullForInvalid":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsTransports":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnNullBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsEmptyArrayOnNull":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsBasketArrayAsIs":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsOnlyOrderRepository":0.005,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsAllDependencies":0.012,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsEmptyForEmptyQuery":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsEmptyWithoutProductRepo":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsFormattedResults":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsReturnsFalseForInvalidOrderId":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsDeletesRemovedProducts":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsUpdatesQuantityAndAdjustsStock":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsAddsNewProductAndDecreasesStock":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testGetFreeDeliveryThresholdReturnsValue":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGetOrderProductReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGetOrderProductReturnsArray":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testAddOrderProductReturnsNullForInvalidOrderId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testAddOrderProductInsertsAndReturnsId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductUpdatesFields":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductReturnsFalseForEmptyData":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testDeleteOrderProductReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testDeleteOrderProductCallsDelete":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateTransportCostDoesNothingForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateTransportCostUpdatesOrder":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenNoApiKey":0.001,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenWrongApiKey":0.001,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenStoredKeyEmpty":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingEndpoint":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingAction":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns404ForUnknownEndpoint":0,"Tests\\Unit\\api\\ApiRouterTest::testSendSuccessOutputsCorrectJson":0,"Tests\\Unit\\api\\ApiRouterTest::testSendErrorOutputsCorrectJson":0,"Tests\\Unit\\api\\ApiRouterTest::testRequireMethodReturnsTrueForMatchingMethod":0,"Tests\\Unit\\api\\ApiRouterTest::testRequireMethodReturnsFalseAndSendsErrorForMismatch":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesReturnsFormattedList":0.001,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsReturnsFormattedList":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsReturnsFormattedList":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testListReturnsOrders":0.001,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testListRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testListPassesFiltersToRepository":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testGetReturnsOrder":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testGetReturns404WhenOrderNotFound":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testGetReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testChangeStatusUpdatesOrder":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testChangeStatusReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testChangeStatusRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetPaidReturns404WhenOrderNotFound":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetPaidReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetPaidCallsServiceWhenOrderExists":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetUnpaidReturns404WhenOrderNotFound":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetUnpaidCallsServiceWhenOrderExists":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListReturnsProducts":0.001,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListPassesFiltersToRepository":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListDefaultPagination":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListClampsPerPageTo100":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetReturnsProduct":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetReturns404WhenProductNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateReturns404WhenProductNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataConvertsStatusToCheckbox":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsLanguages":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsNumericFields":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsCategories":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataPartialUpdatePreservesExisting":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsForeignKeys":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testVerifyChecksumValidFormat":0.014,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testVerifyChecksumInvalidHash":0.011,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testVerifyChecksumInvalidFormat":0.001,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testCreateBackupWithEmptyManifest":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testDownloadManifestReturnsNullForInvalidUrl":0.045,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForApiReturnsActiveAttributesWithValues":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForApiReturnsEmptyWhenNoAttributes":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantsForApiReturnsVariants":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantsForApiReturnsEmptyWhenNoVariants":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantForApiReturnsVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantForApiReturnsNullForNonVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantForApiReturnsNullForNonexistent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiSuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullForArchivedParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullWhenParentIsVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullForEmptyAttributes":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullForDuplicateHash":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiSuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiReturnsFalseForNonVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiReturnsFalseForNonexistent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiFiltersUnallowedFields":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testDeleteVariantForApiSuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testDeleteVariantForApiReturnsFalseForNonVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testDeleteVariantForApiReturnsFalseForNonexistent":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesReturnsFormattedList":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturnsVariantsList":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturns404WhenProductNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturns400ForVariantProduct":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateVariantRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateVariantReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateVariantReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateVariantRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateVariantReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateVariantReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantReturns404WhenNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantSuccess":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListPassesAttributeFilters":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiCastsTypes":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePersistsMinMaxOrderAmount":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveConvertsEmptyMinMaxToNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesMinMaxOrderAmount":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesNullMinMaxOrderAmount":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontHidesTransportWhenAllPaymentsExceedMaxAmount":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontHidesTransportWhenAllPaymentsBelowMinAmount":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontHidesTransportWithNoPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontKeepsBothTransportsWhenPaymentsAvailable":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontKeepsTransportIfAtLeastOnePaymentAvailable":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusDataReturnsBothNamesAndColors":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusDataFiltersInvalidHexColors":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusDataReturnsEmptyOnDbFailure":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorReturnsBlackForLightColor":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorReturnsWhiteForDarkColor":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorHandlesShortHex":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorDefaultsToWhiteForInvalidHex":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlStripsDisallowedTags":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlStripsAttributesFromAllowedTags":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlPreservesCleanTags":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlHandlesPlainText":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetLogsReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetLogsReturnsEmptyWhenNoResults":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetLogsHandlesNullFromSelect":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testDeleteLogCallsDelete":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testClearLogsDeletesAll":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasLogsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testLogsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testLogsClearReturnsVoid":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueKeepsTaskWhenApiloOrderIdIsNull":0.023,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueRemovesTaskAfterMaxAttempts":0.014,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataPreservesZeroBasePriceForSaveProduct":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueReturns400WhenNoBody":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testRegisterHandlerAndProcessJob":0.004,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueReturnsEmptyStatsWhenNoJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueHandlerReturnsFalse":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueHandlerThrowsException":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueNoHandlerRegistered":0.001,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueHandlerReturnsArray":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueuePassesPayloadToHandler":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueMultipleJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsFromDueSchedules":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsSkipsDuplicates":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsWithPayload":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsReturnsZeroWhenNoSchedules":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testRunExecutesFullPipeline":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testRunReturnsScheduledCount":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueInsertsJobAndReturnsId":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueWithPayloadEncodesJson":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueWithoutPayloadDoesNotSetPayloadKey":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueWithScheduledAt":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextReturnsEmptyArrayWhenNoJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextUpdatesStatusToProcessing":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextDecodesPayloadJson":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkCompletedUpdatesStatus":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkCompletedWithResult":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkFailedWithRetriesLeft":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkFailedWhenMaxAttemptsReached":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkFailedTruncatesErrorTo500Chars":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testHasPendingJobReturnsTrueWhenExists":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testHasPendingJobReturnsFalseWhenNone":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testHasPendingJobWithPayloadMatch":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testCleanupDeletesOldCompletedJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testRecoverStuckResetsProcessingJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testGetDueSchedulesReturnsEnabledSchedules":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testTouchScheduleUpdatesTimestamps":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testAllTypesReturnsAllJobTypes":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testAllStatusesReturnsAllStatuses":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testPriorityConstants":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testCalculateBackoffExponential":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testCalculateBackoffCapsAtMax":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testJobTypeConstantsMatchStrings":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testStatusConstantsMatchStrings":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsCronJobRepo":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasBulkDeletePermanentMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testBulkDeletePermanentMethodReturnType":0,"Tests\\Unit\\front\\Controllers\\ShopBasketControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\front\\Controllers\\ShopBasketControllerTest::testHasCheckoutMethods":0,"Tests\\Unit\\front\\Controllers\\ShopBasketControllerTest::testConstructorRequiresDependencies":0}}
\ No newline at end of file
+{"version":1,"defects":{"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":4,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":3,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":4,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":3,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenNoApiKey":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenWrongApiKey":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenStoredKeyEmpty":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingEndpoint":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingAction":4,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns404ForUnknownEndpoint":4,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueKeepsTaskWhenApiloOrderIdIsNull":3,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueRemovesTaskAfterMaxAttempts":3,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesReturnsFormattedList":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesRejectsPostMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeRejectsGetMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeReturns400WhenNoBody":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueRejectsGetMethod":4,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueReturns400WhenNoBody":4,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextUpdatesStatusToProcessing":3,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":4,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":4,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":3,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":3,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingsReturnsArray":4,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":3,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":3,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasLogsMethods":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testLogsReturnsString":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testLogsClearReturnsVoid":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":4,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.006,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.005,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedFilesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeleteNonassignedImagesDeletesDbRows":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveCreatesNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveReturnsZeroWhenInsertFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveUpdatesExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsInsertsForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveTranslationsUpsertsForExistingArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSavePagesForNewArticle":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveDeletesMarkedImagesOnUpdate":0.001,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveSetsStatusToMinusOne":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArchiveReturnsFalseWhenUpdateFails":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderUpdatesImageOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveGalleryOrderSkipsEmptyValues":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasBrowseListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasGalleryOrderSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testBrowseListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testGalleryOrderSaveMethodReturnType":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListForAdminUsesBoundParamsForTitleFilter":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveWithLegacyFormat":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveUpdatesExistingTranslationsByBannerAndLang":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testListForAdminIncludesThumbnailSrc":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testRestoreSetsStatusToZero":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testDeletePermanentlyRemovesArticleAndRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testListArchivedForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsUnitWithTranslations":0.001,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testFindReturnsNullWhenUnitNotFound":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testSaveInsertsNewUnitAndTranslationsForStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testDeleteRemovesUnitAndTranslations":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdReturnsTextFromDatabase":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testGetUnitNameByIdSupportsStringLanguageId":0,"Tests\\Unit\\Domain\\Dictionaries\\DictionariesRepositoryTest::testAllUnitsReturnsArrayIndexedById":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsArrayOrNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveLanguageRejectsInvalidLanguageId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testSaveTranslationInsertsNewTranslationAndReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDeleteTranslationReturnsBoolean":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdReturnsLanguageWithStartFlag":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageIdFallsBackToFirstLanguageOrPl":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsLayoutWithRelations":0.002,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testDeleteReturnsFalseWhenOnlyOneLayoutExists":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testFindReturnsDefaultLayoutWhenRecordDoesNotExist":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testSaveInsertsNewLayoutAndReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testListAllReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsNullForInvalidId":0.003,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateDetailsReturnsArray":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSaveSettingsUpdatesHeaderAndFooter":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testDeleteTemplateReturnsFalseForAdminTemplate":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testTemplateByNameReturnsText":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsDefaultContainerForInvalidId":0.001,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFindReturnsContainerWithTranslations":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testDetailsForLanguageReturnsNullForInvalidData":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserWhenExists":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsErrorWhenLoginIsTaken":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testCheckLoginReturnsOkWhenAvailable":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnCreate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveCreatesUserWithNormalizedSwitches":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveUpdatesExistingUserWithoutPassword":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForTooShortPasswordOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorForMismatchedPasswordsOnUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrue":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsFalseOnFailure":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsUserByLogin":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsSuccessForValidCredentials":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsNegativeOneForBlockedUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForNonexistentUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseAfterMaxAttempts":0.1,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseForExpiredCode":0.101,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueForValidCode":0.201,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseWhen2FADisabled":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSendTwofaCodeReturnsFalseForInvalidEmail":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testUpdateByIdCallsDbUpdate":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesArchiveControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorAcceptsRepository":0.003,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testHasDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\DictionariesControllerTest::testConstructorRequiresDictionariesRepository":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LanguagesControllerTest::testConstructorRequiresLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\LayoutsControllerTest::testConstructorRequiresLayoutsRepository":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\NewsletterControllerTest::testConstructorRequiresRepositoryAndRenderer":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ScontainersControllerTest::testConstructorRequiresRepositoryAndLanguagesRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasViewListMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserEditMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasUserDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasTwofaMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testHasLoginFormMethod":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testConstructorRequiresUserRepository":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserReturnsDefaultsForNull":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserCastsTypes":0,"Tests\\Unit\\admin\\Controllers\\UsersControllerTest::testNormalizeUserHandlesPartialData":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderUpdatesFilesOrder":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testSaveFilesOrderSkipsEmptyValues":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPagesSummaryForArticlesBuildsLabels":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testUpdateImageAltDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testMarkFileToDeleteDelegatesToDatabase":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindReturnsDefaultCouponForInvalidId":0.001,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindNormalizesCouponData":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveInsertsCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testSaveUpdatesCouponAndReturnsId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingsReturnsArray":0.001,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetSettingReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingUpdatesExistingValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSaveSettingInsertsNewValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testInvalidProviderThrowsException":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testLinkProductUpdatesDatabase":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testUnlinkProductClearsFields":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsValue":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetProductSkuReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testAllPublicMethodsExist":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testSettingsTableMapping":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShopproProviderWorks":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenusListReturnsArray":0.002,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testMenuDeleteReturnsFalseWhenMenuHasPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testGenerateSeoLinkAddsSuffixWhenBaseSlugExists":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testPageUrlPreviewBuildsLanguagePrefixedUrlForNonDefaultLanguage":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testFindReturnsDefaultPromotionForInvalidId":0.002,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testSaveInsertsPromotionAndReturnsId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testDeleteReturnsTrueWhenDatabaseDeleteSucceeds":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testCategoriesTreeReturnsHierarchy":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageAltChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileNameChangeMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasImageDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasFileDeleteMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageAltChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileNameChangeMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testImageDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testFileDeleteMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloSettingsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloDataFetchMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllApiloProductMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasAllShopproMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testApiloSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testShopproSettingsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testVoidReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveSellasistMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testDoesNotHaveBaselinkerMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\PagesControllerTest::testConstructorRequiresPagesLanguagesAndLayoutsRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPromotionControllerTest::testConstructorRequiresPromotionRepository":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullForNegativeId":0.001,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindReturnsStatusWithIdZero":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testFindNormalizesNullApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveUpdatesColorAndApiloStatusId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithIdZeroWorks":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveWithEmptyApiloStatusIdSetsNull":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testSaveRejectsNegativeId":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsValue":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetApiloStatusIdReturnsNullWhenNotSet":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdForApilo":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testGetByIntegrationStatusIdReturnsNullForUnknownIntegration":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testAllStatusesReturnsOrderedList":0,"Tests\\Unit\\Domain\\ShopStatus\\ShopStatusRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopStatusesControllerTest::testConstructorRequiresShopStatusRepository":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullForInvalidId":0.001,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveUpdatesRowAndReturnsId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePreservesNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testListForAdminWhitelistsSortAndDirection":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllForAdminReturnsRowsIncludingInactive":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForNotFound":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindKeepsNonNumericApiloPaymentTypeId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveNormalizesStatusValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdHandlesNullAndInt":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsStringForNonNumericValue":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindNormalizesDataAndIncludesPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindHandlesNullMaxWpAndApiloId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNewId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveUpdateReturnsExistingId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveInsertReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveResetsDefaultWhenSettingNew":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testSaveSwitchValuesNormalization":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testListForAdminWhitelistsSortColumn":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllActiveReturnsNormalizedRows":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetApiloCarrierAccountIdReturnsIntOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testGetTransportCostReturnsFloatOrNull":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testAllForAdminReturnsAllTransports":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopPaymentMethodControllerTest::testConstructorRequiresPaymentMethodRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorAcceptsRepositories":0.001,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopTransportControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFindAttributeReturnsDefaultAttributeForInvalidId":0.002,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForAdminWhitelistsSortDirectionAndPerPage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesRemovesObsoleteRowsAndSetsDefault":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testSaveValuesDeletesTranslationWhenNameIsEmpty":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testGetAttributeValueByIdUsesDefaultLanguageWhenNotProvided":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSortTypesReturnsExpectedKeys":0.002,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsReturnsDefaultForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDetailsLoadsTranslations":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderReturnsFalseForNonArray":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveCategoriesOrderUpdatesOrderAndParent":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderReturnsFalseForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testSaveProductOrderUpdatesCategoryProductOrder":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsFalseWhenHasChildren":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryDeleteReturnsTrueWhenDeleted":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryTitleReturnsFirstAvailableTitle":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testListForAdminWhitelistsSortAndPagination":0.001,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientReturnsEmptyOnMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testOrdersForClientNormalizesRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsZeroForMissingInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testTotalsForClientReturnsAggregatedValues":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testConstructorAcceptsDb":0.001,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testHasAllPublicMethods":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testSalesGridReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testLastOrdersReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testMostViewedProductsReturnsArray":0,"Tests\\Unit\\Domain\\Dashboard\\DashboardRepositoryTest::testBestSalesProductsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsId":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testDefaultLanguageReturnsFallbackWhenEmpty":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsList":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyArrayWhenNone":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsReturnsArray":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsDefaultsToPl":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationsForDifferentLanguage":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testUnsubscribeDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionReturnsFalseForInvalidHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConfirmSubscriptionUpdatesStatus":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsHash":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testGetHashByEmailReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailDeletesSubscriber":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testRemoveByEmailReturnsFalseForMissing":0,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testSignupReturnsFalseForExistingEmail":0.001,"Tests\\Unit\\Domain\\Newsletter\\NewsletterRepositoryTest::testConstructorAcceptsOptionalDependencies":0.003,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusesReturnsMappedArray":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testNextAndPrevOrderIdReturnNullForInvalidInput":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testListForAdminReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindReturnsDefaultProducerForInvalidId":0.001,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindNormalizesProducerData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveInsertsNewProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testSaveUpdatesExistingProducer":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllProducersReturnsFormattedList":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testProducerProductsReturnsPaginatedResults":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindReturnsDefaultSetForInvalidId":0.001,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testFindNormalizesSetData":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveInsertsNewSetAndSyncsProducts":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testSaveUpdatesExistingSet":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testListForAdminWhitelistsSortAndPagination":0,"Tests\\Unit\\Domain\\ProductSet\\ProductSetRepositoryTest::testAllSetsReturnsFormattedList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditReturnsMap":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testAllProductsForMassEditEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsList":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetProductsByCategoryReturnsEmptyArray":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsNullForInvalidProduct":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentReturnsCorrectPrices":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testApplyDiscountPercentZeroPercentNullsPromo":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsAssociativeArray":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenNoSettings":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsHandlesNullFromDb":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsCorrectParam":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueUsesParamNotHardcoded":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testGetSingleValueReturnsEmptyStringWhenNotFound":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testConstructorAcceptsDb":0.002,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasUpdateMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testUpdateReturnsArray":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasRunPendingMigrationsMethod":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testRunPendingMigrationsWithNoResults":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testHasPrivateHelperMethods":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\DashboardControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorAcceptsRepositories":0.003,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testHasNoLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsErrorsForMissingDefaultLanguageAndDefaultSelection":0,"Tests\\Unit\\admin\\Controllers\\ShopAttributeControllerTest::testValidateValuesRowsReturnsEmptyArrayForValidRows":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorAcceptsDependencies":0.002,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopCategoryControllerTest::testConstructorRequiresCategoryAndLanguagesRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorAcceptsRepository":0.002,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopClientsControllerTest::testConstructorRequiresClientRepository":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsService":0.003,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testHasExpectedActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testViewActionsReturnString":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testMutationActionsReturnVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderAdminService":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProducerControllerTest::testConstructorRequiresBothRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorAcceptsRepositories":0.003,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasMassEditActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasViewListMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasEditAndSaveMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasOperationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasCombinationMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasImageAndFileMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditReturnsString":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testMassEditSaveReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testGetProductsByCategoryReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testConstructorRequiresRepositories":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testHasFormBuildingHelpers":0,"Tests\\Unit\\admin\\Controllers\\ShopProductControllerTest::testSaveMethodReturnsVoid":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testHasLegacyAliasMethods":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ShopProductSetsControllerTest::testConstructorRequiresProductSetRepository":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasMainViewMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testMainViewReturnsString":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testHasUpdateAllMethod":0,"Tests\\Unit\\admin\\Controllers\\UpdateControllerTest::testConstructorRequiresRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsArticleWithRelations":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendReturnsNullForMissing":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleDetailsFrontendCopyFromFallback":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsSortedIds":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticlesIdsReturnsNullForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsInt":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesCountReturnsZeroForEmpty":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testPageArticlesPagination":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsBool":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testArticleNoindexReturnsFalseForNonNoindex":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsReturnsArticlesArray":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testTopArticlesOrderByViews":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testNewsListArticlesOrderByDateDesc":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsActiveBannersWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testBannersReturnsNullWhenNoBanners":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsActiveBannerWithFlatLanguages":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testMainBannerReturnsNullWhenNoBanner":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsId":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testCategoryDefaultLayoutIdReturnsNullWhenNone":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetDefaultLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsLayoutFromDb":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetArticleLayoutReturnsNullWhenNoLayout":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutFallsBackToDefault":0,"Tests\\Unit\\Domain\\Layouts\\LayoutsRepositoryTest::testGetActiveLayoutReturnsNullWhenNothingFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsPageWithLanguage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdReturnsStartPage":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMainPageIdFallsBackToFirstActive":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontPageSortReturnsValue":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsMenuWithPages":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuDetailsReturnsNullForInvalidMenu":0,"Tests\\Unit\\Domain\\Pages\\PagesRepositoryTest::testFrontMenuPagesReturnsEmptyForNoPages":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsContainerWithLanguage":0,"Tests\\Unit\\Domain\\Scontainers\\ScontainersRepositoryTest::testFrontScontainerDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsAttributeWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontAttributeDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsValueWithLanguage":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testFrontValueDetailsReturnsFallbackForNotFound":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpCalculatesTotal":0.001,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testSummaryWpReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsSumsQuantities":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsReturnsZeroForEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextSingular":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural2to4":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextPlural5Plus":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCountProductsTextCastsToInt":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testGetCategorySortReturnsSortType":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsTitle":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryNameReturnsEmptyWhenNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsCategoryWithLanguage":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testFrontCategoryDetailsReturnsEmptyWhenCategoryNotFound":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoriesTreeReturnsEmptyWhenNoCategories":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsZeroForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testCategoryProductsCountReturnsCount":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testProductsIdReturnsProductIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsEmptyForInvalidInput":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testBlogCategoryProductsReturnsIds":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Category\\CategoryRepositoryTest::testPaginatedCategoryProductsClampsPage":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsRowOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientDetailsReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsStringOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientEmailReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesReturnsRows":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientAddressesHandlesFalseFromDb":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDetailsReturnsRow":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveReturnsFalseForInvalidClientId":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveInsertsNewAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAddressSaveUpdatesExistingAddress":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentReturnsFalseForInvalidIds":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testMarkAddressAsCurrentResetsAndSets":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorWhenClientNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsInactiveForUnconfirmedAccount":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsErrorOnWrongPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testAuthenticateReturnsOkOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullOnEmptyInput":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsNullWhenEmailTaken":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testCreateClientReturnsIdAndHashOnSuccess":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testConfirmRegistrationActivatesAndReturnsEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullOnEmptyHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testGenerateNewPasswordReturnsEmailAndPassword":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullOnEmptyEmail":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoveryReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testInitiatePasswordRecoverySetsRecoveryFlagAndReturnsHash":0,"Tests\\Unit\\Domain\\Client\\ClientRepositoryTest::testClientOrdersReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsObjectWhenFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testFindByNameReturnsNullForEmptyName":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsTrueForActiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForUsedCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForInactiveCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableReturnsFalseForNullCoupon":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIsAvailableWorksWithArray":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testMarkAsUsedSkipsInvalidId":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountCallsUpdate":0,"Tests\\Unit\\Domain\\Coupon\\CouponRepositoryTest::testIncrementUsedCountSkipsInvalidId":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopCouponControllerTest::testConstructorRequiresCouponRepository":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsIdWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindIdByHashReturnsNullForEmptyHash":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsHashWhenFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testFindHashByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByIdReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendByHashReturnsArrayWithProducts":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderDetailsFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberFormatsCorrectly":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGenerateOrderNumberStartsAt001":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsFullData":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testAllActiveProducersReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Producer\\ProducerRepositoryTest::testFindForFrontendReturnsProducerWithLanguage":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopOrderControllerTest::testConstructorRequiresOrderRepository":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testHasMainActionMethods":0,"Tests\\Unit\\front\\Controllers\\ShopProducerControllerTest::testConstructorRequiresProducerRepository":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsOneForActivePayment":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testIsActiveReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNormalizedData":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindActiveByIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testAllActiveReturnsEmptyOnNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testGetApiloPaymentTypeIdReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testForTransportReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsSku":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetSkuWithFallbackReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackReturnsEan":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetEanWithFallbackFromParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsOneForActive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInactive":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testIsProductActiveCachedReturnsZeroForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsCategories":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontUsesParentId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testProductCategoriesFrontReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageZeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsMessage":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetWarehouseMessageNonzeroReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testTopProductIdsReturnsActiveProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsProductIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testNewProductIdsReturnsEmptyWhenNoProducts":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsIds":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testPromotedProductIdsCachedReturnsEmptyWhenNone":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeWholeBasketAppliesDiscountToAll":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesOrAppliesDiscountToMatchingCategories":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionAppliesWhenConditionMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoryConditionNoDiscountWhenConditionNotMet":0,"Tests\\Unit\\Domain\\Promotion\\PromotionRepositoryTest::testApplyTypeCategoriesAndAppliesWhenBothConditionsMet":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportCostCachedReturnsCost":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsTransport":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testFindActiveByIdCachedReturnsNullForInvalid":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsTransports":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testForPaymentMethodReturnsEmptyForInvalidId":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnNullBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testCheckProductQuantityInStockReturnsFalseOnEmptyBasket":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsEmptyArrayOnNull":0,"Tests\\Unit\\Domain\\Basket\\BasketCalculatorTest::testValidateBasketReturnsBasketArrayAsIs":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsOnlyOrderRepository":0.006,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsAllDependencies":0.014,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsEmptyForEmptyQuery":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsEmptyWithoutProductRepo":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSearchProductsReturnsFormattedResults":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsReturnsFalseForInvalidOrderId":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsDeletesRemovedProducts":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsUpdatesQuantityAndAdjustsStock":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testSaveOrderProductsAddsNewProductAndDecreasesStock":0.001,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testGetFreeDeliveryThresholdReturnsValue":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGetOrderProductReturnsNullForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testGetOrderProductReturnsArray":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testAddOrderProductReturnsNullForInvalidOrderId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testAddOrderProductInsertsAndReturnsId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductUpdatesFields":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateOrderProductReturnsFalseForEmptyData":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testDeleteOrderProductReturnsFalseForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testDeleteOrderProductCallsDelete":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateTransportCostDoesNothingForInvalidId":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testUpdateTransportCostUpdatesOrder":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenNoApiKey":0.001,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenWrongApiKey":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns401WhenStoredKeyEmpty":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingEndpoint":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns400WhenMissingAction":0,"Tests\\Unit\\api\\ApiRouterTest::testHandleReturns404ForUnknownEndpoint":0,"Tests\\Unit\\api\\ApiRouterTest::testSendSuccessOutputsCorrectJson":0,"Tests\\Unit\\api\\ApiRouterTest::testSendErrorOutputsCorrectJson":0,"Tests\\Unit\\api\\ApiRouterTest::testRequireMethodReturnsTrueForMatchingMethod":0,"Tests\\Unit\\api\\ApiRouterTest::testRequireMethodReturnsFalseAndSendsErrorForMismatch":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesReturnsFormattedList":0.001,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testStatusesRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsReturnsFormattedList":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testTransportsRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsReturnsFormattedList":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testPaymentMethodsRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testListReturnsOrders":0.001,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testListRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testListPassesFiltersToRepository":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testGetReturnsOrder":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testGetReturns404WhenOrderNotFound":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testGetReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testChangeStatusUpdatesOrder":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testChangeStatusReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testChangeStatusRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetPaidReturns404WhenOrderNotFound":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetPaidReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetPaidCallsServiceWhenOrderExists":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetUnpaidReturns404WhenOrderNotFound":0,"Tests\\Unit\\api\\Controllers\\OrdersApiControllerTest::testSetUnpaidCallsServiceWhenOrderExists":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListReturnsProducts":0.002,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListPassesFiltersToRepository":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListDefaultPagination":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListClampsPerPageTo100":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetReturnsProduct":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetReturns404WhenProductNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testGetRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateReturns404WhenProductNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataConvertsStatusToCheckbox":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsLanguages":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsNumericFields":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsCategories":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataPartialUpdatePreservesExisting":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataMapsForeignKeys":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testVerifyChecksumValidFormat":0.005,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testVerifyChecksumInvalidHash":0.004,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testVerifyChecksumInvalidFormat":0.001,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testCreateBackupWithEmptyManifest":0,"Tests\\Unit\\Domain\\Update\\UpdateRepositoryTest::testDownloadManifestReturnsNullForInvalidUrl":0.016,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForApiReturnsActiveAttributesWithValues":0,"Tests\\Unit\\Domain\\Attribute\\AttributeRepositoryTest::testListForApiReturnsEmptyWhenNoAttributes":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantsForApiReturnsVariants":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantsForApiReturnsEmptyWhenNoVariants":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantForApiReturnsVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantForApiReturnsNullForNonVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindVariantForApiReturnsNullForNonexistent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiSuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullForArchivedParent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullWhenParentIsVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullForEmptyAttributes":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testCreateVariantForApiReturnsNullForDuplicateHash":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiSuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiReturnsFalseForNonVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiReturnsFalseForNonexistent":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiFiltersUnallowedFields":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testDeleteVariantForApiSuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testDeleteVariantForApiReturnsFalseForNonVariant":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testDeleteVariantForApiReturnsFalseForNonexistent":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesReturnsFormattedList":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testAttributesRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturnsVariantsList":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturns404WhenProductNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsReturns400ForVariantProduct":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testVariantsRejectsPostMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateVariantRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateVariantReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testCreateVariantReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateVariantRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateVariantReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testUpdateVariantReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantReturns400WhenMissingId":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantReturns404WhenNotFound":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testDeleteVariantSuccess":0,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testListPassesAttributeFilters":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateVariantForApiCastsTypes":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSavePersistsMinMaxOrderAmount":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testSaveConvertsEmptyMinMaxToNull":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesMinMaxOrderAmount":0,"Tests\\Unit\\Domain\\PaymentMethod\\PaymentMethodRepositoryTest::testFindNormalizesNullMinMaxOrderAmount":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontHidesTransportWhenAllPaymentsExceedMaxAmount":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontHidesTransportWhenAllPaymentsBelowMinAmount":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontHidesTransportWithNoPaymentMethods":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontKeepsBothTransportsWhenPaymentsAvailable":0,"Tests\\Unit\\Domain\\Transport\\TransportRepositoryTest::testTransportMethodsFrontKeepsTransportIfAtLeastOnePaymentAvailable":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusDataReturnsBothNamesAndColors":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusDataFiltersInvalidHexColors":0,"Tests\\Unit\\Domain\\Order\\OrderRepositoryTest::testOrderStatusDataReturnsEmptyOnDbFailure":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorReturnsBlackForLightColor":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorReturnsWhiteForDarkColor":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorHandlesShortHex":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testContrastTextColorDefaultsToWhiteForInvalidHex":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlStripsDisallowedTags":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlStripsAttributesFromAllowedTags":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlPreservesCleanTags":0,"Tests\\Unit\\admin\\Controllers\\ShopOrderControllerTest::testSanitizeInlineHtmlHandlesPlainText":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetLogsReturnsItemsAndTotal":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetLogsReturnsEmptyWhenNoResults":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testGetLogsHandlesNullFromSelect":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testDeleteLogCallsDelete":0,"Tests\\Unit\\Domain\\Integrations\\IntegrationsRepositoryTest::testClearLogsDeletesAll":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testHasLogsMethods":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testLogsReturnsString":0,"Tests\\Unit\\admin\\Controllers\\IntegrationsControllerTest::testLogsClearReturnsVoid":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueKeepsTaskWhenApiloOrderIdIsNull":0.023,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testProcessApiloSyncQueueRemovesTaskAfterMaxAttempts":0.014,"Tests\\Unit\\api\\Controllers\\ProductsApiControllerTest::testMapApiToFormDataPreservesZeroBasePriceForSaveProduct":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeReturns400WhenNoBody":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueRejectsGetMethod":0,"Tests\\Unit\\api\\Controllers\\DictionariesApiControllerTest::testEnsureAttributeValueReturns400WhenNoBody":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testRegisterHandlerAndProcessJob":0.003,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueReturnsEmptyStatsWhenNoJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueHandlerReturnsFalse":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueHandlerThrowsException":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueNoHandlerRegistered":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueHandlerReturnsArray":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueuePassesPayloadToHandler":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testProcessQueueMultipleJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsFromDueSchedules":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsSkipsDuplicates":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsWithPayload":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testCreateScheduledJobsReturnsZeroWhenNoSchedules":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testRunExecutesFullPipeline":0,"Tests\\Unit\\Domain\\CronJob\\CronJobProcessorTest::testRunReturnsScheduledCount":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueInsertsJobAndReturnsId":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueWithPayloadEncodesJson":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueWithoutPayloadDoesNotSetPayloadKey":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueWithScheduledAt":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testEnqueueReturnsNullOnFailure":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextReturnsEmptyArrayWhenNoJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextUpdatesStatusToProcessing":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testFetchNextDecodesPayloadJson":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkCompletedUpdatesStatus":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkCompletedWithResult":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkFailedWithRetriesLeft":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkFailedWhenMaxAttemptsReached":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testMarkFailedTruncatesErrorTo500Chars":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testHasPendingJobReturnsTrueWhenExists":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testHasPendingJobReturnsFalseWhenNone":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testHasPendingJobWithPayloadMatch":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testCleanupDeletesOldCompletedJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testRecoverStuckResetsProcessingJobs":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testGetDueSchedulesReturnsEnabledSchedules":0,"Tests\\Unit\\Domain\\CronJob\\CronJobRepositoryTest::testTouchScheduleUpdatesTimestamps":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testAllTypesReturnsAllJobTypes":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testAllStatusesReturnsAllStatuses":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testPriorityConstants":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testCalculateBackoffExponential":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testCalculateBackoffCapsAtMax":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testJobTypeConstantsMatchStrings":0,"Tests\\Unit\\Domain\\CronJob\\CronJobTypeTest::testStatusConstantsMatchStrings":0,"Tests\\Unit\\Domain\\Order\\OrderAdminServiceTest::testConstructorAcceptsCronJobRepo":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasBulkDeletePermanentMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testBulkDeletePermanentMethodReturnType":0,"Tests\\Unit\\front\\Controllers\\ShopBasketControllerTest::testConstructorAcceptsRepositories":0.002,"Tests\\Unit\\front\\Controllers\\ShopBasketControllerTest::testHasCheckoutMethods":0,"Tests\\Unit\\front\\Controllers\\ShopBasketControllerTest::testConstructorRequiresDependencies":0,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testGetTokenReturns64CharHexString":0.001,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testGetTokenIsIdempotent":0,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testValidateReturnsTrueForCorrectToken":0,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testValidateReturnsFalseForEmptyString":0,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testValidateReturnsFalseForWrongToken":0,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testValidateReturnsFalseWhenNoSessionToken":0,"Tests\\Unit\\Shared\\Security\\CsrfTokenTest::testRegenerateChangesToken":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testApiloGetAccessTokenReturnsNullWithoutSettings":0.002,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testShouldRefreshAccessTokenReturnsFalseForFarFutureDate":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testShouldRefreshAccessTokenReturnsTrueForNearExpiryDate":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testApiloFetchListThrowsForInvalidType":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testApiloFetchListResultReturnsDetailedErrorWhenConfigMissing":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testApiloIntegrationStatusReturnsMissingConfigMessage":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testNormalizeApiloMapListRejectsErrorPayload":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testNormalizeApiloMapListAcceptsIdNameList":0,"Tests\\Unit\\Domain\\Integrations\\ApiloRepositoryTest::testAllPublicMethodsExist":0}}
\ No newline at end of file
diff --git a/.vscode/ftp-kr.diff.ver_0.338.2.zip b/.vscode/ftp-kr.diff.ver_0.338.2.zip
new file mode 100644
index 0000000..b459f43
--- /dev/null
+++ b/.vscode/ftp-kr.diff.ver_0.338.2.zip
@@ -0,0 +1 @@
+c:\visual studio code\projekty\shopPRO\updates\0.30\ver_0.338.zip
\ No newline at end of file
diff --git a/.vscode/ftp-kr.diff.ver_0.338.zip b/.vscode/ftp-kr.diff.ver_0.338.zip
new file mode 100644
index 0000000..b459f43
--- /dev/null
+++ b/.vscode/ftp-kr.diff.ver_0.338.zip
@@ -0,0 +1 @@
+c:\visual studio code\projekty\shopPRO\updates\0.30\ver_0.338.zip
\ No newline at end of file
diff --git a/.vscode/ftp-kr.json b/.vscode/ftp-kr.json
index 80959a7..4df2b00 100644
--- a/.vscode/ftp-kr.json
+++ b/.vscode/ftp-kr.json
@@ -18,6 +18,7 @@
"/.serena",
"/.claude",
"/docs",
- "/tests"
+ "/tests",
+ "/.paul"
]
}
diff --git a/docs/PAUL_WORKFLOW.md b/docs/PAUL_WORKFLOW.md
new file mode 100644
index 0000000..bacf360
--- /dev/null
+++ b/docs/PAUL_WORKFLOW.md
@@ -0,0 +1,150 @@
+# PAUL — Workflow dla shopPRO
+
+[PAUL](https://github.com/ChristopherKahler/paul) to zestaw komend dla Claude Code, który strukturyzuje pracę nad projektem: planowanie → implementacja → weryfikacja. Działa przez slash-komendy w czacie z Claude.
+
+---
+
+## Jednorazowa inicjalizacja
+
+```
+/paul:init
+```
+
+Uruchom raz — tworzy plik `.paul/` z konfiguracją projektu (milestones, fazy). Jeśli już zainicjalizowany, pomijasz ten krok.
+
+---
+
+## Nowa funkcja (feature)
+
+### 1. Omów wizję
+
+```
+/paul:discuss
+```
+
+Claude zadaje pytania, żeby doprecyzować, co dokładnie chcesz zbudować — wynik to jasna definicja przed planowaniem.
+
+### 2. Zbadaj opcje techniczne (opcjonalnie)
+
+```
+/paul:discover
+```
+
+Przydatne gdy nie jesteś pewny jak zaimplementować coś technicznie — Claude przegląda kod i proponuje podejścia.
+
+### 3. Zaplanuj implementację
+
+```
+/paul:plan
+```
+
+Tworzy szczegółowy plan z krokami, plikami do zmiany, kolejnością. Zatwierdź plan zanim ruszysz dalej.
+
+### 4. Wykonaj plan
+
+```
+/paul:apply
+```
+
+Claude implementuje plan krok po kroku, z commitami na każdą fazę.
+
+### 5. Zweryfikuj (UAT)
+
+```
+/paul:verify
+```
+
+Claude generuje checklistę testów manualnych — punkt po punkcie sprawdzasz czy funkcja działa.
+
+---
+
+## Poprawka błędu (bug fix)
+
+```
+/paul:plan-fix
+```
+
+Dedykowany flow dla bugów: opisujesz problem → Claude analizuje kod → tworzy plan naprawy → `/paul:apply` wykonuje.
+
+Krótszy wariant dla prostych bugów — możesz też po prostu opisać błąd i użyć `/paul:plan` → `/paul:apply`.
+
+---
+
+## Zarządzanie sesjami
+
+| Komenda | Kiedy |
+|---------|-------|
+| `/paul:progress` | Sprawdź gdzie jesteś, co dalej |
+| `/paul:pause` | Kończysz sesję — tworzy plik handoff |
+| `/paul:resume` | Zaczynasz nową sesję — wczytuje kontekst |
+| `/paul:handoff` | Generuje dokument przekazania pracy |
+
+---
+
+## Milestones (większe wdrożenia)
+
+Gdy planujesz większą wersję (np. nowy moduł, refactor):
+
+```
+/paul:milestone # Tworzysz nowy milestone
+/paul:add-phase # Dodajesz fazę do milestone
+/paul:complete-milestone # Zamykasz po wdrożeniu
+```
+
+---
+
+## Czy PAUL może przejrzeć projekt pod kątem błędów?
+
+Tak, częściowo. Najlepsze podejście:
+
+### Mapowanie kodu
+
+```
+/paul:map-codebase
+```
+
+Generuje analizę architektury — wyłapuje niespójności, martwy kod, problematyczne zależności.
+
+### Research konkretnego obszaru
+
+```
+/paul:research
+```
+
+Np. _"Sprawdź czy walidacja zamówień w OrderRepository jest kompletna"_ — Claude przegląda kod i raportuje problemy.
+
+### Ograniczenia
+
+PAUL nie jest narzędziem do statycznej analizy kodu (jak PHPStan czy psalm). Do systematycznego przeglądu błędów lepiej połączyć:
+
+- **PAUL** (`/paul:map-codebase` + `/paul:research`) — problemy architektoniczne, logika biznesowa
+- **Testy** (`./test.ps1`) — regresje, złe zachowanie metod
+- **PHPStan** (jeśli dodany do projektu) — typy, niezdefiniowane zmienne
+
+---
+
+## Typowy dzień pracy w shopPRO
+
+```
+# Rano — wznów kontekst
+/paul:resume
+
+# W trakcie — sprawdź co dalej
+/paul:progress
+
+# Nowa funkcja lub bug
+/paul:discuss → /paul:plan → /paul:apply → /paul:verify
+
+# Na koniec dnia
+/paul:pause
+```
+
+---
+
+## Dobre praktyki
+
+- Zawsze zatwierdzaj plan (`/paul:plan`) zanim uruchomisz `/paul:apply` — łatwiej zmienić plan niż cofnąć kod
+- Po każdym `/paul:apply` odpal testy: `./test.ps1`
+- Używaj `/paul:verify` przed mergem — generuje checklistę zamiast zgadywać co przetestować
+- Przy bugach najpierw opisz symptom dokładnie, Claude lepiej planuje naprawę mając konkretny przypadek
+