php-commerce/tests/Feature/CompoundSkuTest.php
Snider a774f4e285 refactor: migrate namespace from Core\Commerce to Core\Mod\Commerce
Align commerce module with the monorepo module structure by updating
all namespaces to use the Core\Mod\Commerce convention. This change
supports the recent monorepo separation and ensures consistency with
other modules.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 16:23:12 +00:00

236 lines
8 KiB
PHP

<?php
declare(strict_types=1);
use Core\Mod\Commerce\Data\BundleItem;
use Core\Mod\Commerce\Data\ParsedItem;
use Core\Mod\Commerce\Data\SkuOption;
use Core\Mod\Commerce\Services\SkuBuilderService;
use Core\Mod\Commerce\Services\SkuParserService;
describe('Compound SKU Parser', function () {
beforeEach(function () {
$this->parser = app(SkuParserService::class);
});
test('parses simple SKU without options', function () {
$result = $this->parser->parse('LAPTOP');
expect($result->count())->toBe(1);
expect($result->items[0])->toBeInstanceOf(ParsedItem::class);
expect($result->items[0]->baseSku)->toBe('LAPTOP');
expect($result->items[0]->options)->toBeEmpty();
});
test('parses SKU with single option', function () {
$result = $this->parser->parse('LAPTOP-ram~16gb');
expect($result->count())->toBe(1);
expect($result->items[0]->baseSku)->toBe('LAPTOP');
expect($result->items[0]->options)->toHaveCount(1);
expect($result->items[0]->options[0]->code)->toBe('ram');
expect($result->items[0]->options[0]->value)->toBe('16gb');
expect($result->items[0]->options[0]->quantity)->toBe(1);
});
test('parses SKU with multiple options', function () {
$result = $this->parser->parse('LAPTOP-ram~16gb-ssd~512gb-color~silver');
expect($result->count())->toBe(1);
expect($result->items[0]->options)->toHaveCount(3);
expect($result->items[0]->getOption('ram')->value)->toBe('16gb');
expect($result->items[0]->getOption('ssd')->value)->toBe('512gb');
expect($result->items[0]->getOption('color')->value)->toBe('silver');
});
test('parses option with quantity', function () {
$result = $this->parser->parse('LAPTOP-cover~black*2');
expect($result->items[0]->options[0]->code)->toBe('cover');
expect($result->items[0]->options[0]->value)->toBe('black');
expect($result->items[0]->options[0]->quantity)->toBe(2);
});
test('parses multiple comma-separated items', function () {
$result = $this->parser->parse('LAPTOP-ram~16gb,MOUSE,PAD-size~xl');
expect($result->count())->toBe(3);
expect($result->items[0]->baseSku)->toBe('LAPTOP');
expect($result->items[1]->baseSku)->toBe('MOUSE');
expect($result->items[2]->baseSku)->toBe('PAD');
expect($result->hasBundles())->toBeFalse();
});
test('parses pipe-separated bundle', function () {
$result = $this->parser->parse('LAPTOP-ram~16gb|MOUSE|PAD');
expect($result->count())->toBe(1);
expect($result->hasBundles())->toBeTrue();
expect($result->items[0])->toBeInstanceOf(BundleItem::class);
$bundle = $result->items[0];
expect($bundle->count())->toBe(3);
expect($bundle->getBaseSkus())->toBe(['LAPTOP', 'MOUSE', 'PAD']);
});
test('bundle hash is consistent regardless of order', function () {
$result1 = $this->parser->parse('LAPTOP|MOUSE|PAD');
$result2 = $this->parser->parse('PAD|LAPTOP|MOUSE');
expect($result1->items[0]->hash)->toBe($result2->items[0]->hash);
});
test('parses mixed bundles and singles', function () {
$result = $this->parser->parse('LAPTOP|MOUSE,HDMI-length~2m');
expect($result->count())->toBe(2);
expect($result->items[0])->toBeInstanceOf(BundleItem::class);
expect($result->items[1])->toBeInstanceOf(ParsedItem::class);
});
test('preserves entity lineage in base SKU', function () {
// SKU with lineage: ORGORG-WBUTS-PROD500
$result = $this->parser->parse('ORGORG-WBUTS-PROD500-ram~16gb');
expect($result->items[0]->baseSku)->toBe('ORGORG-WBUTS-PROD500');
expect($result->items[0]->options)->toHaveCount(1);
});
test('validates SKU format', function () {
$valid = $this->parser->validate('LAPTOP-ram~16gb');
expect($valid['valid'])->toBeTrue();
$invalid = $this->parser->validate('');
expect($invalid['valid'])->toBeFalse();
});
test('round trips through parse and toString', function () {
$original = 'LAPTOP-ram~16gb-ssd~512gb';
$result = $this->parser->parse($original);
expect($result->toString())->toBe($original);
});
});
describe('Compound SKU Builder', function () {
beforeEach(function () {
$this->builder = app(SkuBuilderService::class);
});
test('builds simple item', function () {
$sku = $this->builder->build([
['base_sku' => 'laptop'],
]);
expect($sku)->toBe('LAPTOP');
});
test('builds item with options', function () {
$sku = $this->builder->build([
[
'base_sku' => 'laptop',
'options' => [
['code' => 'ram', 'value' => '16gb'],
['code' => 'ssd', 'value' => '512gb'],
],
],
]);
expect($sku)->toBe('LAPTOP-ram~16gb-ssd~512gb');
});
test('builds item with quantity on option', function () {
$sku = $this->builder->build([
[
'base_sku' => 'laptop',
'options' => [
['code' => 'cover', 'value' => 'black', 'quantity' => 2],
],
],
]);
expect($sku)->toBe('LAPTOP-cover~black*2');
});
test('builds multiple items comma-separated', function () {
$sku = $this->builder->build([
['base_sku' => 'laptop'],
['base_sku' => 'mouse'],
]);
expect($sku)->toBe('LAPTOP,MOUSE');
});
test('builds bundle with same group', function () {
$sku = $this->builder->build([
['base_sku' => 'laptop', 'bundle_group' => 'cyber'],
['base_sku' => 'mouse', 'bundle_group' => 'cyber'],
['base_sku' => 'hdmi'], // standalone
]);
expect($sku)->toBe('LAPTOP|MOUSE,HDMI');
});
test('adds entity lineage', function () {
$sku = $this->builder->addLineage('PROD500', ['ORGORG', 'WBUTS']);
expect($sku)->toBe('ORGORG-WBUTS-PROD500');
});
test('builds with lineage', function () {
$sku = $this->builder->buildWithLineage(
['ORGORG', 'WBUTS'],
[
[
'base_sku' => 'PROD500',
'options' => [['code' => 'size', 'value' => 'xl']],
],
]
);
expect($sku)->toBe('ORGORG-WBUTS-PROD500-size~xl');
});
test('generates bundle hash', function () {
$hash = $this->builder->generateBundleHash(['laptop', 'mouse', 'pad']);
// Hash is deterministic
expect($hash)->toBe($this->builder->generateBundleHash(['PAD', 'LAPTOP', 'MOUSE']));
expect(strlen($hash))->toBe(64); // SHA256
});
test('creates option and item helpers', function () {
$option = $this->builder->option('ram', '16gb', 2);
expect($option)->toBeInstanceOf(SkuOption::class);
expect($option->toString())->toBe('ram~16gb*2');
$item = $this->builder->item('LAPTOP', [$option]);
expect($item)->toBeInstanceOf(ParsedItem::class);
expect($item->toString())->toBe('LAPTOP-ram~16gb*2');
});
});
describe('SKU Parse Result', function () {
test('collects all base SKUs', function () {
$parser = app(SkuParserService::class);
$result = $parser->parse('LAPTOP|MOUSE,HDMI,PAD');
expect($result->getAllBaseSkus())->toBe(['LAPTOP', 'MOUSE', 'HDMI', 'PAD']);
});
test('counts products correctly', function () {
$parser = app(SkuParserService::class);
$result = $parser->parse('LAPTOP|MOUSE|PAD,HDMI');
expect($result->count())->toBe(2); // 1 bundle + 1 single
expect($result->productCount())->toBe(4); // 3 in bundle + 1 single
});
test('extracts bundle hashes', function () {
$parser = app(SkuParserService::class);
$result = $parser->parse('LAPTOP|MOUSE,KEYBOARD|PAD');
$hashes = $result->getBundleHashes();
expect($hashes)->toHaveCount(2);
});
});