diff --git a/README.md b/README.md index 87c4e2a..293d689 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,6 @@ composer require pobo-builder/php-sdk ```php use Pobo\Sdk\PoboClient; -use Pobo\Sdk\DTO\Product; -use Pobo\Sdk\DTO\LocalizedString; $client = new PoboClient( apiToken: 'your-api-token', @@ -34,32 +32,48 @@ $client = new PoboClient( ); ``` -### Import Parameters +## Import -Import parameters first (no dependencies). +### Import Order + +``` +1. Parameters (no dependencies) +2. Categories (no dependencies) +3. Products (depends on categories and parameters) +4. Blogs (no dependencies) +``` + +### Import Parameters ```php use Pobo\Sdk\DTO\Parameter; use Pobo\Sdk\DTO\ParameterValue; -$parameter = new Parameter( - id: 1, - name: 'Color', - values: [ - new ParameterValue(id: 1, value: 'Red'), - new ParameterValue(id: 2, value: 'Blue'), - new ParameterValue(id: 3, value: 'Green'), - ], -); +$parameters = [ + new Parameter( + id: 1, + name: 'Color', + values: [ + new ParameterValue(id: 1, value: 'Red'), + new ParameterValue(id: 2, value: 'Blue'), + ], + ), + new Parameter( + id: 2, + name: 'Size', + values: [ + new ParameterValue(id: 3, value: 'S'), + new ParameterValue(id: 4, value: 'M'), + ], + ), +]; -$result = $client->importParameters([$parameter]); -echo sprintf('Values imported: %d', $result->valuesImported); +$result = $client->importParameters($parameters); +echo sprintf('Imported: %d, Values: %d', $result->imported, $result->valuesImported); ``` ### Import Categories -Import categories second (no dependencies). - ```php use Pobo\Sdk\DTO\Category; use Pobo\Sdk\DTO\LocalizedString; @@ -75,7 +89,9 @@ $categories = [ url: LocalizedString::create('https://example.com/electronics') ->withTranslation(Language::CS, 'https://example.com/cs/elektronika') ->withTranslation(Language::SK, 'https://example.com/sk/elektronika'), - description: LocalizedString::create('

All electronics

'), + description: LocalizedString::create('

All electronics

') + ->withTranslation(Language::CS, '

Veškerá elektronika

') + ->withTranslation(Language::SK, '

Všetka elektronika

'), images: ['https://example.com/images/electronics.jpg'], ), new Category( @@ -96,8 +112,6 @@ echo sprintf('Imported: %d, Updated: %d', $result->imported, $result->updated); ### Import Products -Import products last (depends on categories and parameters IDs). - ```php use Pobo\Sdk\DTO\Product; use Pobo\Sdk\DTO\LocalizedString; @@ -116,10 +130,10 @@ $products = [ shortDescription: LocalizedString::create('Latest iPhone model') ->withTranslation(Language::CS, 'Nejnovější model iPhone') ->withTranslation(Language::SK, 'Najnovší model iPhone'), - images: [ - 'https://example.com/images/iphone-1.jpg', - 'https://example.com/images/iphone-2.jpg', - ], + description: LocalizedString::create('

The best iPhone ever.

') + ->withTranslation(Language::CS, '

Nejlepší iPhone vůbec.

') + ->withTranslation(Language::SK, '

Najlepší iPhone vôbec.

'), + images: ['https://example.com/images/iphone-1.jpg'], categoriesIds: ['CAT-001', 'CAT-002'], parametersIds: [1, 2], ), @@ -132,13 +146,7 @@ $products = [ url: LocalizedString::create('https://example.com/samsung-s24') ->withTranslation(Language::CS, 'https://example.com/cs/samsung-s24') ->withTranslation(Language::SK, 'https://example.com/sk/samsung-s24'), - shortDescription: LocalizedString::create('Flagship Android phone') - ->withTranslation(Language::CS, 'Vlajková loď Android') - ->withTranslation(Language::SK, 'Vlajková loď Android'), - images: [ - 'https://example.com/images/samsung-1.jpg', - ], - categoriesIds: ['CAT-001', 'CAT-002'], + categoriesIds: ['CAT-001'], parametersIds: [1, 3], ), ]; @@ -147,17 +155,56 @@ $result = $client->importProducts($products); if ($result->hasErrors() === true) { foreach ($result->errors as $error) { - echo sprintf('Error at index %d: %s', $error['index'], implode(', ', $error['errors'])); + echo sprintf('Error: %s', implode(', ', $error['errors'])); } } +``` + +### Import Blogs + +```php +use Pobo\Sdk\DTO\Blog; +use Pobo\Sdk\DTO\LocalizedString; +use Pobo\Sdk\Enum\Language; +$blogs = [ + new Blog( + guid: '550e8400-e29b-41d4-a716-446655440000', + category: 'news', + isVisible: true, + name: LocalizedString::create('New Product Launch') + ->withTranslation(Language::CS, 'Uvedení nového produktu') + ->withTranslation(Language::SK, 'Uvedenie nového produktu'), + url: LocalizedString::create('https://example.com/blog/new-product') + ->withTranslation(Language::CS, 'https://example.com/cs/blog/novy-produkt') + ->withTranslation(Language::SK, 'https://example.com/sk/blog/novy-produkt'), + description: LocalizedString::create('

We are excited to announce...

') + ->withTranslation(Language::CS, '

S radostí oznamujeme...

') + ->withTranslation(Language::SK, '

S radosťou oznamujeme...

'), + images: ['https://example.com/images/blog-1.jpg'], + ), + new Blog( + guid: '550e8400-e29b-41d4-a716-446655440001', + category: 'tips', + isVisible: true, + name: LocalizedString::create('How to Choose') + ->withTranslation(Language::CS, 'Jak vybrat') + ->withTranslation(Language::SK, 'Ako vybrať'), + url: LocalizedString::create('https://example.com/blog/how-to-choose') + ->withTranslation(Language::CS, 'https://example.com/cs/blog/jak-vybrat') + ->withTranslation(Language::SK, 'https://example.com/sk/blog/ako-vybrat'), + ), +]; + +$result = $client->importBlogs($blogs); echo sprintf('Imported: %d, Updated: %d', $result->imported, $result->updated); ``` +## Export + ### Export Products ```php -// Get single page $response = $client->getProducts(page: 1, perPage: 50); foreach ($response->data as $product) { @@ -182,7 +229,6 @@ $response = $client->getProducts(isEdited: true); ### Export Categories ```php -// Get all categories $response = $client->getCategories(); foreach ($response->data as $category) { @@ -191,7 +237,61 @@ foreach ($response->data as $category) { // Iterate through all categories foreach ($client->iterateCategories() as $category) { - // Process category + processCategory($category); +} +``` + +### Export Blogs + +```php +$response = $client->getBlogs(); + +foreach ($response->data as $blog) { + echo sprintf("%d: %s\n", $blog->id, $blog->name->getDefault()); +} + +// Iterate through all blogs +foreach ($client->iterateBlogs() as $blog) { + processBlog($blog); +} +``` + +## Content (HTML/Marketplace) + +Products, categories, and blogs include a `content` field with generated HTML content for web and marketplace: + +```php +use Pobo\Sdk\Enum\Language; + +foreach ($client->iterateProducts() as $product) { + if ($product->content !== null) { + // Get HTML content for web + $htmlCs = $product->content->getHtml(Language::CS); + $htmlSk = $product->content->getHtml(Language::SK); + $htmlEn = $product->content->getHtml(Language::EN); + + // Get content for marketplace + $marketplaceCs = $product->content->getMarketplace(Language::CS); + $marketplaceSk = $product->content->getMarketplace(Language::SK); + + // Get default content + $htmlDefault = $product->content->getHtmlDefault(); + $marketplaceDefault = $product->content->getMarketplaceDefault(); + } +} + +// Same for categories +foreach ($client->iterateCategories() as $category) { + if ($category->content !== null) { + echo $category->content->getHtml(Language::CS); + } +} + +// Same for blogs +foreach ($client->iterateBlogs() as $blog) { + if ($blog->content !== null) { + echo $blog->content->getHtml(Language::CS); + } } ``` @@ -207,15 +307,8 @@ use Pobo\Sdk\Exception\WebhookException; $handler = new WebhookHandler(webhookSecret: 'your-webhook-secret'); try { - // Handle from global PHP variables $payload = $handler->handleFromGlobals(); - // Or handle manually - $payload = $handler->handle( - payload: file_get_contents('php://input'), - signature: $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '' - ); - match ($payload->event) { WebhookEvent::PRODUCTS_UPDATE => syncProducts($client), WebhookEvent::CATEGORIES_UPDATE => syncCategories($client), @@ -230,6 +323,15 @@ try { } ``` +### Manual Handling + +```php +$payload = $handler->handle( + payload: file_get_contents('php://input'), + signature: $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '' +); +``` + ### Webhook Payload ```php @@ -248,11 +350,9 @@ use Pobo\Sdk\Exception\WebhookException; try { $result = $client->importProducts($products); } catch (ValidationException $e) { - // Local validation error (e.g., too many items) echo sprintf('Validation error: %s', $e->getMessage()); print_r($e->errors); } catch (ApiException $e) { - // API error (4xx, 5xx) echo sprintf('API error (%d): %s', $e->httpCode, $e->getMessage()); print_r($e->responseBody); } @@ -260,8 +360,6 @@ try { ## Localized Strings -The SDK uses `LocalizedString` for multi-language support: - ```php use Pobo\Sdk\DTO\LocalizedString; use Pobo\Sdk\Enum\Language; @@ -293,6 +391,21 @@ $name->toArray(); // ['default' => '...', 'cs' => '...', ...] | `pl` | Polish | | `hu` | Hungarian | +## API Methods + +| Method | Description | +|----------------------------------------------------------------------------------------|----------------------------------| +| `importProducts(array $products)` | Bulk import products (max 100) | +| `importCategories(array $categories)` | Bulk import categories (max 100) | +| `importParameters(array $parameters)` | Bulk import parameters (max 100) | +| `importBlogs(array $blogs)` | Bulk import blogs (max 100) | +| `getProducts(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get products page | +| `getCategories(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get categories page | +| `getBlogs(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get blogs page | +| `iterateProducts(?DateTime $lastUpdateFrom, ?bool $isEdited)` | Iterate all products | +| `iterateCategories(?DateTime $lastUpdateFrom, ?bool $isEdited)` | Iterate all categories | +| `iterateBlogs(?DateTime $lastUpdateFrom, ?bool $isEdited)` | Iterate all blogs | + ## Limits | Limit | Value | diff --git a/src/DTO/Blog.php b/src/DTO/Blog.php new file mode 100644 index 0000000..b82251e --- /dev/null +++ b/src/DTO/Blog.php @@ -0,0 +1,96 @@ + $images + */ + public function __construct( + public readonly ?int $id = null, + public readonly ?string $guid = null, + public readonly ?string $category = null, + public readonly bool $isVisible = true, + public readonly ?LocalizedString $name = null, + public readonly ?LocalizedString $url = null, + public readonly ?LocalizedString $description = null, + public readonly ?LocalizedString $seoTitle = null, + public readonly ?LocalizedString $seoDescription = null, + public readonly ?Content $content = null, + public readonly array $images = [], + public readonly ?bool $isLoaded = null, + public readonly ?\DateTimeInterface $createdAt = null, + public readonly ?\DateTimeInterface $updatedAt = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $data = [ + 'is_visible' => $this->isVisible, + ]; + + if ($this->guid !== null) { + $data['guid'] = $this->guid; + } + + if ($this->category !== null) { + $data['category'] = $this->category; + } + + if ($this->name !== null) { + $data['name'] = $this->name->toArray(); + } + + if ($this->url !== null) { + $data['url'] = $this->url->toArray(); + } + + if ($this->description !== null) { + $data['description'] = $this->description->toArray(); + } + + if ($this->seoTitle !== null) { + $data['seo_title'] = $this->seoTitle->toArray(); + } + + if ($this->seoDescription !== null) { + $data['seo_description'] = $this->seoDescription->toArray(); + } + + if ($this->images !== []) { + $data['images'] = $this->images; + } + + return $data; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + return new self( + id: $data['id'] ?? null, + guid: $data['guid'] ?? null, + category: $data['category'] ?? null, + isVisible: $data['is_visible'] ?? true, + name: isset($data['name']) ? LocalizedString::fromArray($data['name']) : null, + url: isset($data['url']) ? LocalizedString::fromArray($data['url']) : null, + description: isset($data['description']) ? LocalizedString::fromArray($data['description']) : null, + seoTitle: isset($data['seo_title']) ? LocalizedString::fromArray($data['seo_title']) : null, + seoDescription: isset($data['seo_description']) ? LocalizedString::fromArray($data['seo_description']) : null, + content: isset($data['content']) ? Content::fromArray($data['content']) : null, + images: $data['images'] ?? [], + isLoaded: $data['is_loaded'] ?? null, + createdAt: isset($data['created_at']) ? new \DateTimeImmutable($data['created_at']) : null, + updatedAt: isset($data['updated_at']) ? new \DateTimeImmutable($data['updated_at']) : null, + ); + } +} diff --git a/src/DTO/Category.php b/src/DTO/Category.php index a1a0e57..07b3395 100644 --- a/src/DTO/Category.php +++ b/src/DTO/Category.php @@ -17,8 +17,10 @@ public function __construct( public readonly ?LocalizedString $description = null, public readonly ?LocalizedString $seoTitle = null, public readonly ?LocalizedString $seoDescription = null, + public readonly ?Content $content = null, public readonly array $images = [], public readonly ?string $guid = null, + public readonly ?bool $isLoaded = null, public readonly ?\DateTimeInterface $createdAt = null, public readonly ?\DateTimeInterface $updatedAt = null, ) { @@ -68,8 +70,10 @@ public static function fromArray(array $data): self description: isset($data['description']) ? LocalizedString::fromArray($data['description']) : null, seoTitle: isset($data['seo_title']) ? LocalizedString::fromArray($data['seo_title']) : null, seoDescription: isset($data['seo_description']) ? LocalizedString::fromArray($data['seo_description']) : null, + content: isset($data['content']) ? Content::fromArray($data['content']) : null, images: $data['images'] ?? [], guid: $data['guid'] ?? null, + isLoaded: $data['is_loaded'] ?? null, createdAt: isset($data['created_at']) ? new \DateTimeImmutable($data['created_at']) : null, updatedAt: isset($data['updated_at']) ? new \DateTimeImmutable($data['updated_at']) : null, ); diff --git a/src/DTO/Content.php b/src/DTO/Content.php new file mode 100644 index 0000000..bfec4d5 --- /dev/null +++ b/src/DTO/Content.php @@ -0,0 +1,62 @@ + $html + * @param array $marketplace + */ + public function __construct( + public readonly array $html = [], + public readonly array $marketplace = [], + ) { + } + + public function getHtml(Language $language): ?string + { + return $this->html[$language->value] ?? null; + } + + public function getMarketplace(Language $language): ?string + { + return $this->marketplace[$language->value] ?? null; + } + + public function getHtmlDefault(): ?string + { + return $this->html['default'] ?? $this->html['cs'] ?? null; + } + + public function getMarketplaceDefault(): ?string + { + return $this->marketplace['default'] ?? $this->marketplace['cs'] ?? null; + } + + /** + * @return array> + */ + public function toArray(): array + { + return [ + 'html' => $this->html, + 'marketplace' => $this->marketplace, + ]; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + return new self( + html: $data['html'] ?? [], + marketplace: $data['marketplace'] ?? [], + ); + } +} diff --git a/src/DTO/Product.php b/src/DTO/Product.php index 3060aa7..c3f3da3 100644 --- a/src/DTO/Product.php +++ b/src/DTO/Product.php @@ -21,6 +21,7 @@ public function __construct( public readonly ?LocalizedString $description = null, public readonly ?LocalizedString $seoTitle = null, public readonly ?LocalizedString $seoDescription = null, + public readonly ?Content $content = null, public readonly array $images = [], public readonly array $categoriesIds = [], public readonly array $parametersIds = [], @@ -89,6 +90,7 @@ public static function fromArray(array $data): self description: isset($data['description']) ? LocalizedString::fromArray($data['description']) : null, seoTitle: isset($data['seo_title']) ? LocalizedString::fromArray($data['seo_title']) : null, seoDescription: isset($data['seo_description']) ? LocalizedString::fromArray($data['seo_description']) : null, + content: isset($data['content']) ? Content::fromArray($data['content']) : null, images: $data['images'] ?? [], categoriesIds: $data['categories_ids'] ?? [], parametersIds: $data['parameters_ids'] ?? [], diff --git a/src/PoboClient.php b/src/PoboClient.php index d206d7f..0e0819f 100644 --- a/src/PoboClient.php +++ b/src/PoboClient.php @@ -4,6 +4,7 @@ namespace Pobo\Sdk; +use Pobo\Sdk\DTO\Blog; use Pobo\Sdk\DTO\Category; use Pobo\Sdk\DTO\ImportResult; use Pobo\Sdk\DTO\PaginatedResponse; @@ -26,8 +27,6 @@ public function __construct( } /** - * Import products in bulk (max 100 items per request) - * * @param array> $products * @throws ValidationException * @throws ApiException @@ -46,8 +45,6 @@ public function importProducts(array $products): ImportResult } /** - * Import categories in bulk (max 100 items per request) - * * @param array> $categories * @throws ValidationException * @throws ApiException @@ -66,8 +63,6 @@ public function importCategories(array $categories): ImportResult } /** - * Import parameters in bulk (max 100 items per request) - * * @param array> $parameters * @throws ValidationException * @throws ApiException @@ -86,8 +81,24 @@ public function importParameters(array $parameters): ImportResult } /** - * Get products with pagination - * + * @param array> $blogs + * @throws ValidationException + * @throws ApiException + */ + public function importBlogs(array $blogs): ImportResult + { + $this->validateBulkSize($blogs); + + $payload = array_map( + fn($blog) => $blog instanceof Blog ? $blog->toArray() : $blog, + $blogs + ); + + $response = $this->request('POST', '/api/v2/rest/blogs', $payload); + return ImportResult::fromArray($response); + } + + /** * @throws ApiException */ public function getProducts( @@ -102,8 +113,6 @@ public function getProducts( } /** - * Get categories with pagination - * * @throws ApiException */ public function getCategories( @@ -118,8 +127,20 @@ public function getCategories( } /** - * Iterate through all products (handles pagination automatically) - * + * @throws ApiException + */ + public function getBlogs( + ?int $page = null, + ?int $perPage = null, + ?\DateTimeInterface $lastUpdateFrom = null, + ?bool $isEdited = null, + ): PaginatedResponse { + $query = $this->buildQueryParams($page, $perPage, $lastUpdateFrom, $isEdited); + $response = $this->request('GET', '/api/v2/rest/blogs' . $query); + return PaginatedResponse::fromArray($response, Blog::class); + } + + /** * @return \Generator * @throws ApiException */ @@ -141,8 +162,6 @@ public function iterateProducts( } /** - * Iterate through all categories (handles pagination automatically) - * * @return \Generator * @throws ApiException */ @@ -163,6 +182,27 @@ public function iterateCategories( } while ($response->hasMorePages()); } + /** + * @return \Generator + * @throws ApiException + */ + public function iterateBlogs( + ?\DateTimeInterface $lastUpdateFrom = null, + ?bool $isEdited = null, + ): \Generator { + $page = 1; + + do { + $response = $this->getBlogs($page, self::MAX_BULK_ITEMS, $lastUpdateFrom, $isEdited); + + foreach ($response->data as $blog) { + yield $blog; + } + + $page++; + } while ($response->hasMorePages()); + } + /** * @throws ValidationException */ diff --git a/tests/BlogTest.php b/tests/BlogTest.php new file mode 100644 index 0000000..195abec --- /dev/null +++ b/tests/BlogTest.php @@ -0,0 +1,188 @@ +withTranslation(Language::CS, 'Název blogu'), + url: LocalizedString::create('https://example.com/blog') + ->withTranslation(Language::CS, 'https://example.com/cs/blog'), + description: LocalizedString::create('

Description

'), + seoTitle: LocalizedString::create('SEO Title'), + seoDescription: LocalizedString::create('SEO Description'), + images: ['https://example.com/image.jpg'], + ); + + $this->assertSame(123, $blog->id); + $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $blog->guid); + $this->assertSame('news', $blog->category); + $this->assertTrue($blog->isVisible); + $this->assertSame('Blog Title', $blog->name->getDefault()); + $this->assertSame('Název blogu', $blog->name->get(Language::CS)); + } + + public function testBlogToArray(): void + { + $blog = new Blog( + guid: '550e8400-e29b-41d4-a716-446655440000', + category: 'tips', + isVisible: true, + name: LocalizedString::create('Blog Title'), + url: LocalizedString::create('https://example.com/blog'), + description: LocalizedString::create('

Description

'), + images: ['https://example.com/image.jpg'], + ); + + $array = $blog->toArray(); + + $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $array['guid']); + $this->assertSame('tips', $array['category']); + $this->assertTrue($array['is_visible']); + $this->assertArrayHasKey('name', $array); + $this->assertArrayHasKey('url', $array); + $this->assertArrayHasKey('description', $array); + $this->assertArrayHasKey('images', $array); + } + + public function testBlogToArrayExcludesNullFields(): void + { + $blog = new Blog( + isVisible: true, + name: LocalizedString::create('Blog Title'), + url: LocalizedString::create('https://example.com/blog'), + ); + + $array = $blog->toArray(); + + $this->assertArrayNotHasKey('guid', $array); + $this->assertArrayNotHasKey('category', $array); + $this->assertArrayNotHasKey('description', $array); + $this->assertArrayNotHasKey('seo_title', $array); + $this->assertArrayNotHasKey('seo_description', $array); + $this->assertArrayNotHasKey('images', $array); + } + + public function testBlogFromArray(): void + { + $data = [ + 'id' => 456, + 'guid' => '550e8400-e29b-41d4-a716-446655440001', + 'category' => 'news', + 'is_visible' => true, + 'name' => ['default' => 'Blog Title', 'cs' => 'Název blogu'], + 'url' => ['default' => 'https://example.com/blog'], + 'description' => ['default' => '

Description

'], + 'seo_title' => ['default' => 'SEO Title'], + 'seo_description' => ['default' => 'SEO Description'], + 'images' => ['https://example.com/image.jpg'], + 'is_loaded' => false, + 'created_at' => '2024-01-15T10:30:00.000000Z', + 'updated_at' => '2024-01-16T14:20:00.000000Z', + ]; + + $blog = Blog::fromArray($data); + + $this->assertSame(456, $blog->id); + $this->assertSame('550e8400-e29b-41d4-a716-446655440001', $blog->guid); + $this->assertSame('news', $blog->category); + $this->assertTrue($blog->isVisible); + $this->assertSame('Blog Title', $blog->name->getDefault()); + $this->assertSame('Název blogu', $blog->name->get(Language::CS)); + $this->assertFalse($blog->isLoaded); + $this->assertInstanceOf(\DateTimeInterface::class, $blog->createdAt); + $this->assertInstanceOf(\DateTimeInterface::class, $blog->updatedAt); + } + + public function testBlogFromArrayWithContent(): void + { + $data = [ + 'id' => 789, + 'is_visible' => true, + 'name' => ['default' => 'Blog'], + 'url' => ['default' => 'https://example.com'], + 'content' => [ + 'html' => [ + 'cs' => '
Czech HTML
', + 'sk' => '
Slovak HTML
', + ], + 'marketplace' => [ + 'cs' => '
Czech Marketplace
', + ], + ], + ]; + + $blog = Blog::fromArray($data); + + $this->assertInstanceOf(Content::class, $blog->content); + $this->assertSame('
Czech HTML
', $blog->content->getHtml(Language::CS)); + $this->assertSame('
Slovak HTML
', $blog->content->getHtml(Language::SK)); + $this->assertSame('
Czech Marketplace
', $blog->content->getMarketplace(Language::CS)); + } + + public function testBlogFromArrayWithMinimalData(): void + { + $data = [ + 'id' => 1, + 'is_visible' => false, + 'name' => ['default' => 'Minimal Blog'], + 'url' => ['default' => 'https://example.com/minimal'], + ]; + + $blog = Blog::fromArray($data); + + $this->assertSame(1, $blog->id); + $this->assertFalse($blog->isVisible); + $this->assertNull($blog->guid); + $this->assertNull($blog->category); + $this->assertNull($blog->description); + $this->assertNull($blog->seoTitle); + $this->assertNull($blog->seoDescription); + $this->assertNull($blog->content); + $this->assertSame([], $blog->images); + } + + public function testBlogDefaultIsVisible(): void + { + $blog = new Blog(); + + $this->assertTrue($blog->isVisible); + } + + public function testBlogImagesArray(): void + { + $images = [ + 'https://example.com/image1.jpg', + 'https://example.com/image2.jpg', + 'https://example.com/image3.jpg', + ]; + + $blog = new Blog( + isVisible: true, + name: LocalizedString::create('Blog'), + url: LocalizedString::create('https://example.com'), + images: $images, + ); + + $this->assertCount(3, $blog->images); + $this->assertSame($images, $blog->images); + + $array = $blog->toArray(); + $this->assertSame($images, $array['images']); + } +} diff --git a/tests/CategoryTest.php b/tests/CategoryTest.php new file mode 100644 index 0000000..fd86feb --- /dev/null +++ b/tests/CategoryTest.php @@ -0,0 +1,168 @@ + 'CAT-001', + 'is_visible' => true, + 'name' => ['default' => 'Category Name', 'cs' => 'Název kategorie'], + 'url' => ['default' => 'https://example.com/category'], + 'description' => ['default' => '

Category description

'], + 'content' => [ + 'html' => [ + 'cs' => '
Czech HTML
', + 'sk' => '
Slovak HTML
', + ], + 'marketplace' => [ + 'cs' => '
Czech Marketplace
', + ], + ], + 'images' => ['https://example.com/image.jpg'], + 'guid' => '550e8400-e29b-41d4-a716-446655440000', + 'is_loaded' => false, + 'created_at' => '2024-01-15T10:30:00.000000Z', + 'updated_at' => '2024-01-16T14:20:00.000000Z', + ]; + + $category = Category::fromArray($data); + + $this->assertSame('CAT-001', $category->id); + $this->assertTrue($category->isVisible); + $this->assertSame('Category Name', $category->name->getDefault()); + $this->assertSame('Název kategorie', $category->name->get(Language::CS)); + + $this->assertInstanceOf(Content::class, $category->content); + $this->assertSame('
Czech HTML
', $category->content->getHtml(Language::CS)); + $this->assertSame('
Slovak HTML
', $category->content->getHtml(Language::SK)); + $this->assertSame('
Czech Marketplace
', $category->content->getMarketplace(Language::CS)); + + $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $category->guid); + $this->assertFalse($category->isLoaded); + } + + public function testCategoryFromArrayWithoutContent(): void + { + $data = [ + 'id' => 'CAT-002', + 'is_visible' => true, + 'name' => ['default' => 'Category'], + 'url' => ['default' => 'https://example.com'], + ]; + + $category = Category::fromArray($data); + + $this->assertNull($category->content); + } + + public function testCategoryToArrayDoesNotIncludeContent(): void + { + $category = new Category( + id: 'CAT-001', + isVisible: true, + name: LocalizedString::create('Category'), + url: LocalizedString::create('https://example.com'), + content: new Content( + html: ['cs' => '
Test
'], + marketplace: [], + ), + ); + + $array = $category->toArray(); + + $this->assertArrayNotHasKey('content', $array); + } + + public function testCategoryWithAllLocalizedFields(): void + { + $category = new Category( + id: 'CAT-001', + isVisible: true, + name: LocalizedString::create('Category') + ->withTranslation(Language::CS, 'Kategorie') + ->withTranslation(Language::SK, 'Kategória'), + url: LocalizedString::create('https://example.com/category') + ->withTranslation(Language::CS, 'https://example.com/cs/kategorie') + ->withTranslation(Language::SK, 'https://example.com/sk/kategoria'), + description: LocalizedString::create('

Description

') + ->withTranslation(Language::CS, '

Popis

'), + seoTitle: LocalizedString::create('SEO Title') + ->withTranslation(Language::CS, 'SEO Titulek'), + seoDescription: LocalizedString::create('SEO Description') + ->withTranslation(Language::CS, 'SEO Popis'), + ); + + $array = $category->toArray(); + + $this->assertArrayHasKey('description', $array); + $this->assertArrayHasKey('seo_title', $array); + $this->assertArrayHasKey('seo_description', $array); + } + + public function testCategoryWithImages(): void + { + $images = [ + 'https://example.com/image1.jpg', + 'https://example.com/image2.jpg', + ]; + + $category = new Category( + id: 'CAT-001', + isVisible: true, + name: LocalizedString::create('Category'), + url: LocalizedString::create('https://example.com'), + images: $images, + ); + + $array = $category->toArray(); + + $this->assertSame($images, $array['images']); + } + + public function testCategoryToArrayExcludesNullFields(): void + { + $category = new Category( + id: 'CAT-001', + isVisible: true, + name: LocalizedString::create('Category'), + url: LocalizedString::create('https://example.com'), + ); + + $array = $category->toArray(); + + $this->assertArrayNotHasKey('description', $array); + $this->assertArrayNotHasKey('seo_title', $array); + $this->assertArrayNotHasKey('seo_description', $array); + $this->assertArrayNotHasKey('images', $array); + } + + public function testCategoryTimestamps(): void + { + $data = [ + 'id' => 'CAT-001', + 'is_visible' => true, + 'name' => ['default' => 'Category'], + 'url' => ['default' => 'https://example.com'], + 'created_at' => '2024-01-15T10:30:00.000000Z', + 'updated_at' => '2024-01-16T14:20:00.000000Z', + ]; + + $category = Category::fromArray($data); + + $this->assertInstanceOf(\DateTimeInterface::class, $category->createdAt); + $this->assertInstanceOf(\DateTimeInterface::class, $category->updatedAt); + $this->assertSame('2024-01-15', $category->createdAt->format('Y-m-d')); + $this->assertSame('2024-01-16', $category->updatedAt->format('Y-m-d')); + } +} diff --git a/tests/ContentTest.php b/tests/ContentTest.php new file mode 100644 index 0000000..f1ddb23 --- /dev/null +++ b/tests/ContentTest.php @@ -0,0 +1,144 @@ + [ + 'cs' => '
Czech HTML
', + 'sk' => '
Slovak HTML
', + 'en' => '
English HTML
', + ], + 'marketplace' => [ + 'cs' => '
Czech Marketplace
', + 'sk' => '
Slovak Marketplace
', + ], + ]; + + $content = Content::fromArray($data); + + $this->assertSame('
Czech HTML
', $content->getHtml(Language::CS)); + $this->assertSame('
Slovak HTML
', $content->getHtml(Language::SK)); + $this->assertSame('
English HTML
', $content->getHtml(Language::EN)); + $this->assertSame('
Czech Marketplace
', $content->getMarketplace(Language::CS)); + $this->assertSame('
Slovak Marketplace
', $content->getMarketplace(Language::SK)); + } + + public function testGetHtmlReturnsNullForMissingLanguage(): void + { + $content = Content::fromArray([ + 'html' => ['cs' => '
Czech
'], + 'marketplace' => [], + ]); + + $this->assertNull($content->getHtml(Language::DE)); + $this->assertNull($content->getHtml(Language::PL)); + } + + public function testGetMarketplaceReturnsNullForMissingLanguage(): void + { + $content = Content::fromArray([ + 'html' => [], + 'marketplace' => ['cs' => '
Czech
'], + ]); + + $this->assertNull($content->getMarketplace(Language::EN)); + $this->assertNull($content->getMarketplace(Language::HU)); + } + + public function testGetHtmlDefault(): void + { + $content = Content::fromArray([ + 'html' => [ + 'default' => '
Default HTML
', + 'cs' => '
Czech HTML
', + ], + 'marketplace' => [], + ]); + + $this->assertSame('
Default HTML
', $content->getHtmlDefault()); + } + + public function testGetHtmlDefaultFallsBackToCs(): void + { + $content = Content::fromArray([ + 'html' => [ + 'cs' => '
Czech HTML
', + ], + 'marketplace' => [], + ]); + + $this->assertSame('
Czech HTML
', $content->getHtmlDefault()); + } + + public function testGetMarketplaceDefault(): void + { + $content = Content::fromArray([ + 'html' => [], + 'marketplace' => [ + 'default' => '
Default Marketplace
', + 'cs' => '
Czech Marketplace
', + ], + ]); + + $this->assertSame('
Default Marketplace
', $content->getMarketplaceDefault()); + } + + public function testGetMarketplaceDefaultFallsBackToCs(): void + { + $content = Content::fromArray([ + 'html' => [], + 'marketplace' => [ + 'cs' => '
Czech Marketplace
', + ], + ]); + + $this->assertSame('
Czech Marketplace
', $content->getMarketplaceDefault()); + } + + public function testToArray(): void + { + $content = new Content( + html: ['cs' => '
Czech
', 'en' => '
English
'], + marketplace: ['cs' => '
Czech MP
'], + ); + + $array = $content->toArray(); + + $this->assertArrayHasKey('html', $array); + $this->assertArrayHasKey('marketplace', $array); + $this->assertSame('
Czech
', $array['html']['cs']); + $this->assertSame('
English
', $array['html']['en']); + $this->assertSame('
Czech MP
', $array['marketplace']['cs']); + } + + public function testEmptyContent(): void + { + $content = Content::fromArray([]); + + $this->assertSame([], $content->html); + $this->assertSame([], $content->marketplace); + $this->assertNull($content->getHtmlDefault()); + $this->assertNull($content->getMarketplaceDefault()); + } + + public function testContentIsReadonly(): void + { + $content = new Content( + html: ['cs' => '
Test
'], + marketplace: ['cs' => '
Test MP
'], + ); + + $this->assertSame(['cs' => '
Test
'], $content->html); + $this->assertSame(['cs' => '
Test MP
'], $content->marketplace); + } +} diff --git a/tests/PoboClientTest.php b/tests/PoboClientTest.php index 2445e6f..68dae3f 100644 --- a/tests/PoboClientTest.php +++ b/tests/PoboClientTest.php @@ -5,6 +5,7 @@ namespace Pobo\Sdk\Tests; use PHPUnit\Framework\TestCase; +use Pobo\Sdk\DTO\Blog; use Pobo\Sdk\DTO\Category; use Pobo\Sdk\DTO\LocalizedString; use Pobo\Sdk\DTO\Parameter; @@ -202,4 +203,47 @@ public function testMaxItemsValidationExceptionContainsErrors(): void $this->assertStringContainsString('Maximum 100 items', $e->errors['bulk'][0]); } } + + public function testImportBlogsThrowsExceptionForEmptyArray(): void + { + $this->expectException(ValidationException::class); + $this->expectExceptionMessage('Payload cannot be empty'); + + $this->client->importBlogs([]); + } + + public function testImportBlogsThrowsExceptionForTooManyItems(): void + { + $blogs = []; + for ($i = 0; $i < 101; $i++) { + $blogs[] = [ + 'guid' => sprintf('550e8400-e29b-41d4-a716-4466554400%02d', $i), + 'is_visible' => true, + 'name' => ['default' => sprintf('Blog %d', $i)], + 'url' => ['default' => sprintf('https://example.com/blog/%d', $i)], + ]; + } + + $this->expectException(ValidationException::class); + $this->expectExceptionMessage('Too many items: 101 provided, maximum is 100'); + + $this->client->importBlogs($blogs); + } + + public function testBlogDtoIsConvertedToArray(): void + { + $blog = new Blog( + guid: '550e8400-e29b-41d4-a716-446655440000', + category: 'news', + isVisible: true, + name: LocalizedString::create('Test Blog'), + url: LocalizedString::create('https://example.com/blog'), + ); + + $array = $blog->toArray(); + + $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $array['guid']); + $this->assertSame('news', $array['category']); + $this->assertTrue($array['is_visible']); + } } diff --git a/tests/ProductTest.php b/tests/ProductTest.php new file mode 100644 index 0000000..0112a85 --- /dev/null +++ b/tests/ProductTest.php @@ -0,0 +1,171 @@ + 'PROD-001', + 'is_visible' => true, + 'name' => ['default' => 'Product Name', 'cs' => 'Název produktu'], + 'url' => ['default' => 'https://example.com/product'], + 'short_description' => ['default' => 'Short description'], + 'description' => ['default' => '

Full description

'], + 'content' => [ + 'html' => [ + 'cs' => '
Czech HTML
', + 'sk' => '
Slovak HTML
', + 'en' => '
English HTML
', + ], + 'marketplace' => [ + 'cs' => '
Czech Marketplace
', + 'sk' => '
Slovak Marketplace
', + ], + ], + 'images' => ['https://example.com/image.jpg'], + 'categories' => [ + ['id' => 'CAT-001', 'name' => ['default' => 'Category 1']], + ], + 'created_at' => '2024-01-15T10:30:00.000000Z', + 'updated_at' => '2024-01-16T14:20:00.000000Z', + ]; + + $product = Product::fromArray($data); + + $this->assertSame('PROD-001', $product->id); + $this->assertTrue($product->isVisible); + $this->assertSame('Product Name', $product->name->getDefault()); + $this->assertSame('Název produktu', $product->name->get(Language::CS)); + + $this->assertInstanceOf(Content::class, $product->content); + $this->assertSame('
Czech HTML
', $product->content->getHtml(Language::CS)); + $this->assertSame('
Slovak HTML
', $product->content->getHtml(Language::SK)); + $this->assertSame('
English HTML
', $product->content->getHtml(Language::EN)); + $this->assertSame('
Czech Marketplace
', $product->content->getMarketplace(Language::CS)); + } + + public function testProductFromArrayWithoutContent(): void + { + $data = [ + 'id' => 'PROD-002', + 'is_visible' => true, + 'name' => ['default' => 'Product'], + 'url' => ['default' => 'https://example.com'], + ]; + + $product = Product::fromArray($data); + + $this->assertNull($product->content); + } + + public function testProductToArrayDoesNotIncludeContent(): void + { + $product = new Product( + id: 'PROD-001', + isVisible: true, + name: LocalizedString::create('Product'), + url: LocalizedString::create('https://example.com'), + content: new Content( + html: ['cs' => '
Test
'], + marketplace: [], + ), + ); + + $array = $product->toArray(); + + $this->assertArrayNotHasKey('content', $array); + } + + public function testProductWithAllLocalizedFields(): void + { + $product = new Product( + id: 'PROD-001', + isVisible: true, + name: LocalizedString::create('Product') + ->withTranslation(Language::CS, 'Produkt') + ->withTranslation(Language::SK, 'Produkt'), + url: LocalizedString::create('https://example.com/product') + ->withTranslation(Language::CS, 'https://example.com/cs/produkt') + ->withTranslation(Language::SK, 'https://example.com/sk/produkt'), + shortDescription: LocalizedString::create('Short') + ->withTranslation(Language::CS, 'Krátký'), + description: LocalizedString::create('

Full

') + ->withTranslation(Language::CS, '

Plný

'), + seoTitle: LocalizedString::create('SEO Title') + ->withTranslation(Language::CS, 'SEO Titulek'), + seoDescription: LocalizedString::create('SEO Description') + ->withTranslation(Language::CS, 'SEO Popis'), + ); + + $array = $product->toArray(); + + $this->assertArrayHasKey('short_description', $array); + $this->assertArrayHasKey('description', $array); + $this->assertArrayHasKey('seo_title', $array); + $this->assertArrayHasKey('seo_description', $array); + } + + public function testProductWithCategoriesAndParameters(): void + { + $product = new Product( + id: 'PROD-001', + isVisible: true, + name: LocalizedString::create('Product'), + url: LocalizedString::create('https://example.com'), + categoriesIds: ['CAT-001', 'CAT-002'], + parametersIds: [1, 2, 3], + ); + + $array = $product->toArray(); + + $this->assertSame(['CAT-001', 'CAT-002'], $array['categories_ids']); + $this->assertSame([1, 2, 3], $array['parameters_ids']); + } + + public function testProductWithImages(): void + { + $images = [ + 'https://example.com/image1.jpg', + 'https://example.com/image2.jpg', + ]; + + $product = new Product( + id: 'PROD-001', + isVisible: true, + name: LocalizedString::create('Product'), + url: LocalizedString::create('https://example.com'), + images: $images, + ); + + $array = $product->toArray(); + + $this->assertSame($images, $array['images']); + } + + public function testProductWithGuidAndIsLoaded(): void + { + $data = [ + 'id' => 'PROD-001', + 'guid' => '550e8400-e29b-41d4-a716-446655440000', + 'is_visible' => true, + 'is_loaded' => true, + 'name' => ['default' => 'Product'], + 'url' => ['default' => 'https://example.com'], + ]; + + $product = Product::fromArray($data); + + $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $product->guid); + $this->assertTrue($product->isLoaded); + } +}