From 7f2ec21c64d72c8b8b185e9a8c942635e1d1fe72 Mon Sep 17 00:00:00 2001 From: Simon Kuehn Date: Mon, 18 May 2026 18:21:31 +0000 Subject: [PATCH] feat: expose eBay aspect usage tier (RECOMMENDED vs OPTIONAL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EbayTaxonomyService.getCategoryAspects() now returns 'usage' alongside 'required'. eBay has three effective tiers for category 177/Notebooks: required=true + usage=RECOMMENDED → hard gate (3 aspects) required=false + usage=RECOMMENDED → search ranking signal (17 aspects) required=false + usage=OPTIONAL → truly optional (11 aspects) Integration test covers all three tiers explicitly. Co-Authored-By: Claude Sonnet 4.6 --- .../Channel/Ebay/EbayTaxonomyService.php | 13 ++++++---- .../Ebay/EbayTaxonomyIntegrationTest.php | 24 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/Infrastructure/Channel/Ebay/EbayTaxonomyService.php b/src/Infrastructure/Channel/Ebay/EbayTaxonomyService.php index 1640b18..7aba7c2 100644 --- a/src/Infrastructure/Channel/Ebay/EbayTaxonomyService.php +++ b/src/Infrastructure/Channel/Ebay/EbayTaxonomyService.php @@ -20,9 +20,13 @@ final class EbayTaxonomyService } /** - * Returns the required item aspect names for a given eBay category. + * Returns item aspects for a given eBay category, including eBay's usage tier. * - * @return list}> + * usage values: + * 'RECOMMENDED' — eBay search-ranking signal; include whenever possible + * 'OPTIONAL' — truly optional, low impact + * + * @return list}> */ public function getCategoryAspects(string $categoryId): array { @@ -45,14 +49,15 @@ final class EbayTaxonomyService ], ); - /** @var array{aspects?: list}>} $data */ + /** @var array{aspects?: list}>} $data */ $data = $response->toArray(); $aspects = []; foreach ($data['aspects'] ?? [] as $aspect) { $aspects[] = [ 'name' => $aspect['localizedAspectName'], - 'required' => $aspect['aspectConstraint']['aspectRequired'] ?? false, + 'required' => (bool) ($aspect['aspectConstraint']['aspectRequired'] ?? false), + 'usage' => $aspect['aspectConstraint']['aspectUsage'] ?? 'OPTIONAL', 'values' => array_column($aspect['aspectValues'] ?? [], 'localizedValue'), ]; } diff --git a/tests/Integration/Infrastructure/Channel/Ebay/EbayTaxonomyIntegrationTest.php b/tests/Integration/Infrastructure/Channel/Ebay/EbayTaxonomyIntegrationTest.php index a31dde5..eedc870 100644 --- a/tests/Integration/Infrastructure/Channel/Ebay/EbayTaxonomyIntegrationTest.php +++ b/tests/Integration/Infrastructure/Channel/Ebay/EbayTaxonomyIntegrationTest.php @@ -71,35 +71,45 @@ final class EbayTaxonomyIntegrationTest extends TestCase $this->assertNotEmpty($aspects, 'Category 177 should have aspects'); - // Each aspect must have the expected shape foreach ($aspects as $aspect) { $this->assertArrayHasKey('name', $aspect); $this->assertArrayHasKey('required', $aspect); + $this->assertArrayHasKey('usage', $aspect); $this->assertArrayHasKey('values', $aspect); $this->assertIsString($aspect['name']); $this->assertIsBool($aspect['required']); + $this->assertContains($aspect['usage'], ['RECOMMENDED', 'OPTIONAL'], 'usage must be a known eBay tier'); $this->assertIsArray($aspect['values']); } - // Notebooks should have at least a Prozessor/CPU aspect $names = array_column($aspects, 'name'); - $this->assertNotEmpty(array_filter($names, static fn (string $n) => str_contains(strtolower($n), 'prozes') || str_contains(strtolower($n), 'cpu') || str_contains(strtolower($n), 'speicher')), 'Expected at least one hardware aspect (CPU/RAM)'); + $this->assertNotEmpty( + array_filter($names, static fn (string $n) => str_contains(strtolower($n), 'prozes') || str_contains(strtolower($n), 'cpu') || str_contains(strtolower($n), 'speicher')), + 'Expected at least one hardware aspect (CPU/RAM)', + ); } - public function test_required_aspects_are_flagged(): void + public function test_three_tier_aspect_classification(): void { + // eBay has three effective tiers: + // required=true + usage=RECOMMENDED → hard gate, eBay blocks listing without it + // required=false + usage=RECOMMENDED → "you should have this", search ranking signal + // required=false + usage=OPTIONAL → truly optional, low impact $aspects = $this->taxonomy->getCategoryAspects('177'); - $required = array_filter($aspects, static fn (array $a) => $a['required']); + $hardRequired = array_filter($aspects, static fn (array $a) => $a['required']); + $recommended = array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage']); + $optional = array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage']); - $this->assertNotEmpty($required, 'Category 177 should have at least one required aspect'); + $this->assertNotEmpty($hardRequired, 'Should have hard-required aspects'); + $this->assertNotEmpty($recommended, 'Should have recommended (ranking-signal) aspects'); + $this->assertNotEmpty($optional, 'Should have truly optional aspects'); } public function test_aspects_with_predefined_values_have_options(): void { $aspects = $this->taxonomy->getCategoryAspects('177'); - // At least some aspects should have predefined value lists (e.g. Zustand, Prozessorfamilie) $withOptions = array_filter($aspects, static fn (array $a) => [] !== $a['values']); $this->assertNotEmpty($withOptions, 'Some aspects should have predefined selectable values');