style: apply CS Fixer formatting across codebase
Some checks are pending
CI / test (push) Waiting to run

Consistent brace style, spacing, and method expansion throughout
domain, infrastructure, and test files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simon Kuehn 2026-05-19 10:56:37 +00:00
parent fc18958e0e
commit a79791a972
37 changed files with 223 additions and 167 deletions

View file

@ -57,9 +57,6 @@ final class PhotoService
} }
} }
/**
* @param list<string> $orderedPhotoIds UUIDs in the desired display order
*/
/** /**
* Attach a file that was already stored (e.g. uploaded at ingest time) to an article. * Attach a file that was already stored (e.g. uploaded at ingest time) to an article.
* *

View file

@ -200,6 +200,7 @@ class Article
foreach ($this->attributeValues as $existing) { foreach ($this->attributeValues as $existing) {
if ($existing->getAttributeDefinition()->getId()->equals($value->getAttributeDefinition()->getId())) { if ($existing->getAttributeDefinition()->getId()->equals($value->getAttributeDefinition()->getId())) {
$existing->setValue($value->getValue()); $existing->setValue($value->getValue());
return; return;
} }
} }

View file

@ -88,6 +88,7 @@ class ArticleType
$existing->setArticleFieldKey($mapping->getArticleFieldKey()); $existing->setArticleFieldKey($mapping->getArticleFieldKey());
$existing->setAttributeDefinition($mapping->getAttributeDefinition()); $existing->setAttributeDefinition($mapping->getAttributeDefinition());
$existing->setRequired($mapping->isRequired()); $existing->setRequired($mapping->isRequired());
return; return;
} }
} }
@ -105,7 +106,7 @@ class ArticleType
public function getAttributeDefinitions(): Collection public function getAttributeDefinitions(): Collection
{ {
return $this->attributeAssignments->map( return $this->attributeAssignments->map(
fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition() static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition()
); );
} }
@ -113,8 +114,8 @@ class ArticleType
public function getRequiredAttributeDefinitions(): Collection public function getRequiredAttributeDefinitions(): Collection
{ {
return $this->attributeAssignments return $this->attributeAssignments
->filter(fn (ArticleTypeAttribute $a) => $a->isRequired()) ->filter(static fn (ArticleTypeAttribute $a) => $a->isRequired())
->map(fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition()); ->map(static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -125,15 +126,15 @@ class ArticleType
public function getRequiredAttributeDefs(): Collection public function getRequiredAttributeDefs(): Collection
{ {
return $this->attributeAssignments return $this->attributeAssignments
->filter(fn (ArticleTypeAttribute $a) => $a->isRequired()) ->filter(static fn (ArticleTypeAttribute $a) => $a->isRequired())
->map(fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition()); ->map(static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
} }
/** @param iterable<AttributeDefinition> $defs */ /** @param iterable<AttributeDefinition> $defs */
public function setRequiredAttributeDefs(iterable $defs): void public function setRequiredAttributeDefs(iterable $defs): void
{ {
/** @var list<AttributeDefinition> $list */ /** @var list<AttributeDefinition> $list */
$list = $defs instanceof Collection ? $defs->toArray() : \iterator_to_array($defs, false); $list = $defs instanceof Collection ? $defs->toArray() : iterator_to_array($defs, false);
$this->pendingRequired = $list; $this->pendingRequired = $list;
} }
@ -141,15 +142,15 @@ class ArticleType
public function getOptionalAttributeDefs(): Collection public function getOptionalAttributeDefs(): Collection
{ {
return $this->attributeAssignments return $this->attributeAssignments
->filter(fn (ArticleTypeAttribute $a) => !$a->isRequired()) ->filter(static fn (ArticleTypeAttribute $a) => !$a->isRequired())
->map(fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition()); ->map(static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
} }
/** @param iterable<AttributeDefinition> $defs */ /** @param iterable<AttributeDefinition> $defs */
public function setOptionalAttributeDefs(iterable $defs): void public function setOptionalAttributeDefs(iterable $defs): void
{ {
/** @var list<AttributeDefinition> $list */ /** @var list<AttributeDefinition> $list */
$list = $defs instanceof Collection ? $defs->toArray() : \iterator_to_array($defs, false); $list = $defs instanceof Collection ? $defs->toArray() : iterator_to_array($defs, false);
$this->pendingOptional = $list; $this->pendingOptional = $list;
} }
@ -159,7 +160,7 @@ class ArticleType
*/ */
public function applyAttributeAssignments(): void public function applyAttributeAssignments(): void
{ {
if ($this->pendingRequired === null && $this->pendingOptional === null) { if (null === $this->pendingRequired && null === $this->pendingOptional) {
return; return;
} }

View file

@ -52,25 +52,55 @@ class ArticleTypeEbayMapping
$this->sourceType = $sourceType; $this->sourceType = $sourceType;
} }
public function getId(): Uuid { return $this->id; } public function getId(): Uuid
{
return $this->id;
}
public function getArticleType(): ArticleType { return $this->articleType; } public function getArticleType(): ArticleType
{
return $this->articleType;
}
public function getEbayAspectName(): string { return $this->ebayAspectName; } public function getEbayAspectName(): string
{
return $this->ebayAspectName;
}
public function getSourceType(): string { return $this->sourceType; } public function getSourceType(): string
{
return $this->sourceType;
}
public function getArticleFieldKey(): ?string { return $this->articleFieldKey; } public function getArticleFieldKey(): ?string
{
return $this->articleFieldKey;
}
public function setArticleFieldKey(?string $key): void { $this->articleFieldKey = $key; } public function setArticleFieldKey(?string $key): void
{
$this->articleFieldKey = $key;
}
public function getAttributeDefinition(): ?AttributeDefinition { return $this->attributeDefinition; } public function getAttributeDefinition(): ?AttributeDefinition
{
return $this->attributeDefinition;
}
public function setAttributeDefinition(?AttributeDefinition $def): void { $this->attributeDefinition = $def; } public function setAttributeDefinition(?AttributeDefinition $def): void
{
$this->attributeDefinition = $def;
}
public function isRequired(): bool { return $this->required; } public function isRequired(): bool
{
return $this->required;
}
public function setRequired(bool $required): void { $this->required = $required; } public function setRequired(bool $required): void
{
$this->required = $required;
}
public function getSourceLabel(): string public function getSourceLabel(): string
{ {

View file

@ -37,14 +37,48 @@ class Translation
$this->value = $value; $this->value = $value;
} }
public function getId(): Uuid { return $this->id; } public function getId(): Uuid
public function getLocale(): string { return $this->locale; } {
public function getDomain(): string { return $this->domain; } return $this->id;
public function getKey(): string { return $this->key; } }
public function getValue(): string { return $this->value; }
public function setLocale(string $locale): void { $this->locale = $locale; } public function getLocale(): string
public function setDomain(string $domain): void { $this->domain = $domain; } {
public function setKey(string $key): void { $this->key = $key; } return $this->locale;
public function setValue(string $value): void { $this->value = $value; } }
public function getDomain(): string
{
return $this->domain;
}
public function getKey(): string
{
return $this->key;
}
public function getValue(): string
{
return $this->value;
}
public function setLocale(string $locale): void
{
$this->locale = $locale;
}
public function setDomain(string $domain): void
{
$this->domain = $domain;
}
public function setKey(string $key): void
{
$this->key = $key;
}
public function setValue(string $value): void
{
$this->value = $value;
}
} }

View file

@ -45,9 +45,9 @@ final class EbayTextAgent
$modelNumber = $article->getModelNumber() ?? ''; $modelNumber = $article->getModelNumber() ?? '';
$deviceLabel = trim("{$manufacturer} {$modelName} {$modelNumber}") ?: $typeName; $deviceLabel = trim("{$manufacturer} {$modelName} {$modelNumber}") ?: $typeName;
$specsSection = $attributeText !== '' $specsSection = '' !== $attributeText
? "Known attributes:\n{$attributeText}" ? "Known attributes:\n{$attributeText}"
: ($specsText !== '' ? "Research notes:\n".mb_substr($specsText, 0, 1500) : ''); : ('' !== $specsText ? "Research notes:\n".mb_substr($specsText, 0, 1500) : '');
$titlePrompt = $this->prompts->render('ebay_title', [ $titlePrompt = $this->prompts->render('ebay_title', [
'typeName' => $typeName, 'typeName' => $typeName,

View file

@ -25,7 +25,7 @@ final class SpecsResearchAgent
*/ */
public function research(string $modelName, string $articleTypeName, string $manufacturer = '', array $attributeFields = []): array public function research(string $modelName, string $articleTypeName, string $manufacturer = '', array $attributeFields = []): array
{ {
$subject = trim(($manufacturer !== '' ? $manufacturer.' ' : '').$modelName); $subject = trim(('' !== $manufacturer ? $manufacturer.' ' : '').$modelName);
$searchResults = $this->search->search("{$subject} {$articleTypeName} specifications"); $searchResults = $this->search->search("{$subject} {$articleTypeName} specifications");
@ -36,7 +36,7 @@ final class SpecsResearchAgent
$prompt = $this->prompts->render('specs_research', [ $prompt = $this->prompts->render('specs_research', [
'articleType' => $articleTypeName, 'articleType' => $articleTypeName,
'subject' => $subject, 'subject' => $subject,
'searchResults' => $searchResults !== '' ? $searchResults : 'No web results available.', 'searchResults' => '' !== $searchResults ? $searchResults : 'No web results available.',
'fields' => $fieldsList, 'fields' => $fieldsList,
]); ]);

View file

@ -129,7 +129,7 @@ final class MistralClient implements OllamaClientInterface
private function guessMimeType(string $path): string private function guessMimeType(string $path): string
{ {
return match (strtolower(pathinfo($path, PATHINFO_EXTENSION))) { return match (strtolower(pathinfo($path, \PATHINFO_EXTENSION))) {
'jpg', 'jpeg' => 'image/jpeg', 'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png', 'png' => 'image/png',
'gif' => 'image/gif', 'gif' => 'image/gif',

View file

@ -127,7 +127,7 @@ final class FrappeErpAdapter implements ErpAdapterInterface
*/ */
private function addressMatches(array $addr, string $street, string $city, string $zip): bool private function addressMatches(array $addr, string $street, string $city, string $zip): bool
{ {
$n = static fn(string $s): string => mb_strtolower(trim($s)); $n = static fn (string $s): string => mb_strtolower(trim($s));
return $n($addr['address_line1'] ?? '') === $n($street) return $n($addr['address_line1'] ?? '') === $n($street)
&& $n($addr['city'] ?? '') === $n($city) && $n($addr['city'] ?? '') === $n($city)

View file

@ -50,7 +50,7 @@ final class CreateApiKeyCommand extends Command
$rawKey = bin2hex(random_bytes(24)); $rawKey = bin2hex(random_bytes(24));
$prefix = substr($rawKey, 0, 8); $prefix = substr($rawKey, 0, 8);
$keyHash = password_hash($rawKey, PASSWORD_BCRYPT); $keyHash = password_hash($rawKey, \PASSWORD_BCRYPT);
$apiKey = new ApiKey($user, $label, $prefix, $keyHash); $apiKey = new ApiKey($user, $label, $prefix, $keyHash);
$this->apiKeyRepository->save($apiKey); $this->apiKeyRepository->save($apiKey);

View file

@ -10,10 +10,11 @@ use App\Infrastructure\Messenger\Message\JsonCodingMessage;
use App\Infrastructure\Messenger\Message\PhotoUploadMessage; use App\Infrastructure\Messenger\Message\PhotoUploadMessage;
use App\Infrastructure\Messenger\Message\SpecsResearchMessage; use App\Infrastructure\Messenger\Message\SpecsResearchMessage;
use App\Infrastructure\Messenger\Message\ValidationMessage; use App\Infrastructure\Messenger\Message\ValidationMessage;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection; use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
@ -27,7 +28,6 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository; use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Translation\TranslatableMessage; use Symfony\Component\Translation\TranslatableMessage;

View file

@ -62,7 +62,7 @@ final class ArticleTypeCrudController extends AbstractCrudController
yield TextField::new('name', 'Name'); yield TextField::new('name', 'Name');
yield TextField::new('ebayCategoryId', 'eBay Category ID')->setRequired(false)->hideOnIndex(); yield TextField::new('ebayCategoryId', 'eBay Category ID')->setRequired(false)->hideOnIndex();
yield IntegerField::new('attributeAssignments', '# Attributes') yield IntegerField::new('attributeAssignments', '# Attributes')
->formatValue(static fn (mixed $v): int => is_countable($v) ? count($v) : 0) ->formatValue(static fn (mixed $v): int => is_countable($v) ? \count($v) : 0)
->hideOnForm() ->hideOnForm()
->setSortable(false); ->setSortable(false);
@ -77,8 +77,7 @@ final class ArticleTypeCrudController extends AbstractCrudController
'attr' => ['data-ea-widget' => 'ea-autocomplete'], 'attr' => ['data-ea-widget' => 'ea-autocomplete'],
]) ])
->hideOnIndex() ->hideOnIndex()
->formatValue(static fn (mixed $v, ArticleType $at): string => ->formatValue(static fn (mixed $v, ArticleType $at): string => implode(', ', $at->getRequiredAttributeDefs()->map(static fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
implode(', ', $at->getRequiredAttributeDefs()->map(fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
); );
yield Field::new('optionalAttributeDefs', 'Optional Attributes') yield Field::new('optionalAttributeDefs', 'Optional Attributes')
@ -92,8 +91,7 @@ final class ArticleTypeCrudController extends AbstractCrudController
'attr' => ['data-ea-widget' => 'ea-autocomplete'], 'attr' => ['data-ea-widget' => 'ea-autocomplete'],
]) ])
->hideOnIndex() ->hideOnIndex()
->formatValue(static fn (mixed $v, ArticleType $at): string => ->formatValue(static fn (mixed $v, ArticleType $at): string => implode(', ', $at->getOptionalAttributeDefs()->map(static fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
implode(', ', $at->getOptionalAttributeDefs()->map(fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
); );
} }

View file

@ -45,7 +45,7 @@ final class EbayAspectImportController extends AbstractController
return $this->json([]); return $this->json([]);
} }
return $this->json(array_slice($results, 0, 15)); return $this->json(\array_slice($results, 0, 15));
} }
/** /**
@ -109,9 +109,9 @@ final class EbayAspectImportController extends AbstractController
$allDefs = $this->em->getRepository(AttributeDefinition::class)->findBy([], ['name' => 'ASC']); $allDefs = $this->em->getRepository(AttributeDefinition::class)->findBy([], ['name' => 'ASC']);
$rows = $this->buildRows($aspects, $allDefs, $articleType); $rows = $this->buildRows($aspects, $allDefs, $articleType);
$counts = [ $counts = [
'required' => count(array_filter($aspects, static fn (array $a) => $a['required'])), 'required' => \count(array_filter($aspects, static fn (array $a) => $a['required'])),
'recommended' => count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage'])), 'recommended' => \count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage'])),
'optional' => count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage'])), 'optional' => \count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage'])),
]; ];
} }
@ -193,7 +193,7 @@ final class EbayAspectImportController extends AbstractController
} }
$rawValues = array_values(array_filter(array_map('trim', explode(',', $data['ebayValues'] ?? '')))); $rawValues = array_values(array_filter(array_map('trim', explode(',', $data['ebayValues'] ?? ''))));
$type = (count($rawValues) > 0 && count($rawValues) <= 30) $type = (\count($rawValues) > 0 && \count($rawValues) <= 30)
? AttributeType::Select ? AttributeType::Select
: AttributeType::String; : AttributeType::String;
@ -327,7 +327,7 @@ final class EbayAspectImportController extends AbstractController
$preMatchId = null; $preMatchId = null;
} }
$suggestedType = (count($aspect['values']) > 0 && count($aspect['values']) <= 30) $suggestedType = (\count($aspect['values']) > 0 && \count($aspect['values']) <= 30)
? AttributeType::Select->value ? AttributeType::Select->value
: AttributeType::String->value; : AttributeType::String->value;

View file

@ -69,7 +69,7 @@ final class PipelineStreamController extends AbstractController
$status = $job->getStatus()->value; $status = $job->getStatus()->value;
$prev = $seen[$jobId] ?? null; $prev = $seen[$jobId] ?? null;
if ($prev !== null && $prev['step'] === $step && $prev['status'] === $status) { if (null !== $prev && $prev['step'] === $step && $prev['status'] === $status) {
continue; continue;
} }
@ -105,23 +105,17 @@ final class PipelineStreamController extends AbstractController
$label = "Job #{$inv}"; $label = "Job #{$inv}";
$t = fn (string $key, array $p = []) => $this->translator->trans($key, $p, 'admin'); $t = fn (string $key, array $p = []) => $this->translator->trans($key, $p, 'admin');
$stepLabel = $step !== null $stepLabel = null !== $step
? $t(self::STEP_KEYS[$step] ?? $step) ? $t(self::STEP_KEYS[$step] ?? $step)
: ''; : '';
$message = match (true) { $message = match (true) {
$status === AIPipelineJobStatus::Queued->value $status === AIPipelineJobStatus::Queued->value => $t('pipeline.event.queued', ['%inv%' => $inv]),
=> $t('pipeline.event.queued', ['%inv%' => $inv]), $status === AIPipelineJobStatus::Processing->value && null === $step => $t('pipeline.event.processing_start', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Processing->value && $step === null $status === AIPipelineJobStatus::Processing->value => $t('pipeline.event.processing_step', ['%inv%' => $inv, '%step%' => $stepLabel]),
=> $t('pipeline.event.processing_start', ['%inv%' => $inv]), $status === AIPipelineJobStatus::Completed->value => $t('pipeline.event.completed', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Processing->value $status === AIPipelineJobStatus::Failed->value => $t('pipeline.event.failed', ['%inv%' => $inv, '%reason%' => $job->getErrorMessage() ?? '']),
=> $t('pipeline.event.processing_step', ['%inv%' => $inv, '%step%' => $stepLabel]), $status === AIPipelineJobStatus::NeedsReview->value => $t('pipeline.event.needs_review', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Completed->value
=> $t('pipeline.event.completed', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Failed->value
=> $t('pipeline.event.failed', ['%inv%' => $inv, '%reason%' => $job->getErrorMessage() ?? '']),
$status === AIPipelineJobStatus::NeedsReview->value
=> $t('pipeline.event.needs_review', ['%inv%' => $inv]),
default => "{$label}: {$status}", default => "{$label}: {$status}",
}; };

View file

@ -62,7 +62,7 @@ final class PromptTemplateCrudController extends AbstractCrudController
$lines = ['Use <code>{{variableName}}</code> as placeholders. Known keys and their variables:']; $lines = ['Use <code>{{variableName}}</code> as placeholders. Known keys and their variables:'];
$lines[] = '<ul>'; $lines[] = '<ul>';
foreach ($known as $key => $vars) { foreach ($known as $key => $vars) {
$varList = $vars !== [] ? implode(', ', array_map(static fn (string $v) => "<code>{{$v}}</code>", $vars)) : '—'; $varList = [] !== $vars ? implode(', ', array_map(static fn (string $v) => "<code>{{$v}}</code>", $vars)) : '—';
$lines[] = "<li><strong>{$key}</strong>: {$varList}</li>"; $lines[] = "<li><strong>{$key}</strong>: {$varList}</li>";
} }
$lines[] = '</ul>'; $lines[] = '</ul>';

View file

@ -39,7 +39,7 @@ final class TranslationCrudController extends AbstractCrudController
public function configureFields(string $pageName): iterable public function configureFields(string $pageName): iterable
{ {
$readonly = $pageName === Crud::PAGE_EDIT; $readonly = Crud::PAGE_EDIT === $pageName;
yield IdField::new('id')->hideOnForm()->hideOnIndex(); yield IdField::new('id')->hideOnForm()->hideOnIndex();

View file

@ -15,8 +15,8 @@ final class StringArrayType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder->addModelTransformer(new CallbackTransformer( $builder->addModelTransformer(new CallbackTransformer(
fn (?array $v) => implode("\n", $v ?? []), static fn (?array $v) => implode("\n", $v ?? []),
fn (mixed $v) => '' === ($v ?? '') ? null : array_values( static fn (mixed $v) => '' === ($v ?? '') ? null : array_values(
array_filter(array_map('trim', explode("\n", (string) $v))) array_filter(array_map('trim', explode("\n", (string) $v)))
), ),
)); ));

View file

@ -69,7 +69,7 @@ final class DraftArticleHandler
$article->setManufacturer((string) $vision['manufacturer']); $article->setManufacturer((string) $vision['manufacturer']);
} }
$correctedModelNumber = (string) ($job->getOutputData()['specs_research']['correctedModelNumber'] ?? ''); $correctedModelNumber = (string) ($job->getOutputData()['specs_research']['correctedModelNumber'] ?? '');
$modelNumber = $correctedModelNumber !== '' ? $correctedModelNumber : (string) ($vision['modelNumber'] ?? ''); $modelNumber = '' !== $correctedModelNumber ? $correctedModelNumber : (string) ($vision['modelNumber'] ?? '');
if ('' !== $modelNumber) { if ('' !== $modelNumber) {
$article->setModelNumber($modelNumber); $article->setModelNumber($modelNumber);
} }

View file

@ -80,7 +80,7 @@ final class PhotoUploadHandler
attributes: $attributes, attributes: $attributes,
condition: $inputData['condition'] ?? 'good', condition: $inputData['condition'] ?? 'good',
inventoryNumber: $inputData['inventoryNumber'] ?? null, inventoryNumber: $inputData['inventoryNumber'] ?? null,
serialNumber: $result['serial'] !== '' ? $result['serial'] : null, serialNumber: '' !== $result['serial'] ? $result['serial'] : null,
)); ));
return; return;

View file

@ -10,7 +10,9 @@ use Doctrine\ORM\EntityManagerInterface;
final class DoctrineTranslationRepository implements TranslationRepositoryInterface final class DoctrineTranslationRepository implements TranslationRepositoryInterface
{ {
public function __construct(private readonly EntityManagerInterface $em) {} public function __construct(private readonly EntityManagerInterface $em)
{
}
public function findMapByLocaleAndDomain(string $locale, string $domain): array public function findMapByLocaleAndDomain(string $locale, string $domain): array
{ {

View file

@ -28,7 +28,7 @@ final class DatabaseTranslator implements TranslatorInterface, LocaleAwareInterf
$effectiveDomain = $domain ?? 'messages'; $effectiveDomain = $domain ?? 'messages';
$cacheKey = $effectiveLocale.'.'.$effectiveDomain; $cacheKey = $effectiveLocale.'.'.$effectiveDomain;
if (!array_key_exists($cacheKey, $this->cache)) { if (!\array_key_exists($cacheKey, $this->cache)) {
try { try {
$this->cache[$cacheKey] = $this->repo->findMapByLocaleAndDomain($effectiveLocale, $effectiveDomain); $this->cache[$cacheKey] = $this->repo->findMapByLocaleAndDomain($effectiveLocale, $effectiveDomain);
} catch (\Throwable) { } catch (\Throwable) {
@ -36,10 +36,10 @@ final class DatabaseTranslator implements TranslatorInterface, LocaleAwareInterf
} }
} }
if (array_key_exists($id, $this->cache[$cacheKey])) { if (\array_key_exists($id, $this->cache[$cacheKey])) {
$value = $this->cache[$cacheKey][$id]; $value = $this->cache[$cacheKey][$id];
return $parameters !== [] ? strtr($value, $parameters) : $value; return [] !== $parameters ? strtr($value, $parameters) : $value;
} }
return $this->inner->trans($id, $parameters, $domain, $locale); return $this->inner->trans($id, $parameters, $domain, $locale);

View file

@ -56,7 +56,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
); );
} }
public function test_fetches_application_token(): void public function testFetchesApplicationToken(): void
{ {
$token = $this->oauth->getAccessToken(); $token = $this->oauth->getAccessToken();
@ -64,7 +64,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertStringStartsWith('v^1.1', $token); $this->assertStringStartsWith('v^1.1', $token);
} }
public function test_fetches_aspects_for_notebooks_category(): void public function testFetchesAspectsForNotebooksCategory(): void
{ {
// 177 = Notebooks & Netbooks in EBAY_DE // 177 = Notebooks & Netbooks in EBAY_DE
$aspects = $this->taxonomy->getCategoryAspects('177'); $aspects = $this->taxonomy->getCategoryAspects('177');
@ -89,7 +89,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
); );
} }
public function test_three_tier_aspect_classification(): void public function testThreeTierAspectClassification(): void
{ {
// eBay has three effective tiers: // eBay has three effective tiers:
// required=true + usage=RECOMMENDED → hard gate, eBay blocks listing without it // required=true + usage=RECOMMENDED → hard gate, eBay blocks listing without it
@ -106,7 +106,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertNotEmpty($optional, 'Should have truly optional aspects'); $this->assertNotEmpty($optional, 'Should have truly optional aspects');
} }
public function test_aspects_with_predefined_values_have_options(): void public function testAspectsWithPredefinedValuesHaveOptions(): void
{ {
$aspects = $this->taxonomy->getCategoryAspects('177'); $aspects = $this->taxonomy->getCategoryAspects('177');
@ -115,7 +115,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertNotEmpty($withOptions, 'Some aspects should have predefined selectable values'); $this->assertNotEmpty($withOptions, 'Some aspects should have predefined selectable values');
} }
public function test_caches_aspects_on_second_call(): void public function testCachesAspectsOnSecondCall(): void
{ {
// First call hits the API // First call hits the API
$first = $this->taxonomy->getCategoryAspects('177'); $first = $this->taxonomy->getCategoryAspects('177');
@ -125,7 +125,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertSame($first, $second); $this->assertSame($first, $second);
} }
public function test_fetches_aspects_for_ram_category(): void public function testFetchesAspectsForRamCategory(): void
{ {
// 170083 = RAM/Speicher in EBAY_DE — useful for our memory article types // 170083 = RAM/Speicher in EBAY_DE — useful for our memory article types
$aspects = $this->taxonomy->getCategoryAspects('170083'); $aspects = $this->taxonomy->getCategoryAspects('170083');
@ -135,7 +135,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertNotEmpty($names); $this->assertNotEmpty($names);
} }
public function test_category_suggestions_returns_results(): void public function testCategorySuggestionsReturnsResults(): void
{ {
$results = $this->taxonomy->getCategorySuggestions('Notebook'); $results = $this->taxonomy->getCategorySuggestions('Notebook');

View file

@ -49,7 +49,7 @@ final class FrappeCustomerIntegrationTest extends TestCase
} }
} }
public function test_create_customer(): void public function testCreateCustomer(): void
{ {
$response = $this->client->post('/api/resource/Customer', [ $response = $this->client->post('/api/resource/Customer', [
'customer_name' => 'Test Superseller Integration', 'customer_name' => 'Test Superseller Integration',
@ -64,7 +64,7 @@ final class FrappeCustomerIntegrationTest extends TestCase
$this->createdCustomerName = $response['data']['name']; $this->createdCustomerName = $response['data']['name'];
} }
public function test_find_created_customer(): void public function testFindCreatedCustomer(): void
{ {
// Create first // Create first
$created = $this->client->post('/api/resource/Customer', [ $created = $this->client->post('/api/resource/Customer', [
@ -82,14 +82,14 @@ final class FrappeCustomerIntegrationTest extends TestCase
$this->assertSame('Test Superseller Integration', $response['data']['customer_name']); $this->assertSame('Test Superseller Integration', $response['data']['customer_name']);
} }
public function test_find_nonexistent_customer_throws(): void public function testFindNonexistentCustomerThrows(): void
{ {
$this->expectException(ClientExceptionInterface::class); $this->expectException(ClientExceptionInterface::class);
$this->client->get('/api/resource/Customer/CUST-DOES-NOT-EXIST-99999'); $this->client->get('/api/resource/Customer/CUST-DOES-NOT-EXIST-99999');
} }
public function test_delete_customer(): void public function testDeleteCustomer(): void
{ {
// Create // Create
$created = $this->client->post('/api/resource/Customer', [ $created = $this->client->post('/api/resource/Customer', [

View file

@ -61,7 +61,7 @@ final class FrappeErpAdapterIntegrationTest extends TestCase
} }
} }
public function test_find_simon_kuehn_by_name_and_address(): void public function testFindSimonKuehnByNameAndAddress(): void
{ {
$customerId = $this->adapter->findExistingCustomer( $customerId = $this->adapter->findExistingCustomer(
'Simon Kühn', 'Simon Kühn',
@ -74,7 +74,7 @@ final class FrappeErpAdapterIntegrationTest extends TestCase
$this->assertStringStartsWith('Simon', $customerId); $this->assertStringStartsWith('Simon', $customerId);
} }
public function test_unknown_person_is_not_found(): void public function testUnknownPersonIsNotFound(): void
{ {
$customerId = $this->adapter->findExistingCustomer( $customerId = $this->adapter->findExistingCustomer(
'Voldemort', 'Voldemort',
@ -86,7 +86,7 @@ final class FrappeErpAdapterIntegrationTest extends TestCase
$this->assertNull($customerId); $this->assertNull($customerId);
} }
public function test_find_simon_and_create_invoice_for_1337(): void public function testFindSimonAndCreateInvoiceFor1337(): void
{ {
$frappeId = $this->adapter->findExistingCustomer( $frappeId = $this->adapter->findExistingCustomer(
'Simon Kühn', 'Simon Kühn',

View file

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Tests\Unit\Domain\Article; namespace App\Tests\Unit\Domain\Article;
use App\Domain\Article\ArticleType; use App\Domain\Article\ArticleType;
use App\Domain\Article\ArticleTypeAttribute;
use App\Domain\Article\AttributeDefinition; use App\Domain\Article\AttributeDefinition;
use App\Domain\Article\AttributeType; use App\Domain\Article\AttributeType;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;

View file

@ -35,8 +35,8 @@ final class AttributeDefinitionTest extends TestCase
public static function allAttributeTypes(): array public static function allAttributeTypes(): array
{ {
return array_combine( return array_combine(
array_map(fn (AttributeType $t) => $t->value, AttributeType::cases()), array_map(static fn (AttributeType $t) => $t->value, AttributeType::cases()),
array_map(fn (AttributeType $t) => [$t], AttributeType::cases()), array_map(static fn (AttributeType $t) => [$t], AttributeType::cases()),
); );
} }

View file

@ -87,7 +87,7 @@ final class ApiKeyTest extends TestCase
public function testRawKeyVerifiesAgainstStoredHash(): void public function testRawKeyVerifiesAgainstStoredHash(): void
{ {
$rawKey = bin2hex(random_bytes(24)); $rawKey = bin2hex(random_bytes(24));
$hash = password_hash($rawKey, PASSWORD_BCRYPT); $hash = password_hash($rawKey, \PASSWORD_BCRYPT);
$prefix = substr($rawKey, 0, 8); $prefix = substr($rawKey, 0, 8);
$key = new ApiKey($this->user, 'label', $prefix, $hash); $key = new ApiKey($this->user, 'label', $prefix, $hash);

View file

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace App\Tests\Unit\Infrastructure\AI\Agent; namespace App\Tests\Unit\Infrastructure\AI\Agent;
use App\Domain\AI\PromptTemplate;
use App\Domain\AI\Repository\PromptTemplateRepositoryInterface;
use App\Domain\Article\ArticleType; use App\Domain\Article\ArticleType;
use App\Domain\Article\AttributeDefinition; use App\Domain\Article\AttributeDefinition;
use App\Domain\Article\AttributeType; use App\Domain\Article\AttributeType;
use App\Domain\AI\PromptTemplate;
use App\Domain\AI\Repository\PromptTemplateRepositoryInterface;
use App\Infrastructure\AI\Agent\JsonCodingAgent; use App\Infrastructure\AI\Agent\JsonCodingAgent;
use App\Infrastructure\AI\OllamaClientInterface; use App\Infrastructure\AI\OllamaClientInterface;
use App\Infrastructure\AI\PromptTemplateService; use App\Infrastructure\AI\PromptTemplateService;

View file

@ -28,7 +28,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->agent = new OllamaVisionAgent($this->ollama, $prompts, 'llava'); $this->agent = new OllamaVisionAgent($this->ollama, $prompts, 'llava');
} }
public function test_parses_all_fields(): void public function testParsesAllFields(): void
{ {
$this->ollama->method('generateWithImage')->willReturn( $this->ollama->method('generateWithImage')->willReturn(
"MANUFACTURER: Lenovo\nMODEL_NAME: ThinkBook 14 G6 IRL\nMODEL_NUMBER: 21KG00NQGE\nSERIAL: PNV09SJZ" "MANUFACTURER: Lenovo\nMODEL_NAME: ThinkBook 14 G6 IRL\nMODEL_NUMBER: 21KG00NQGE\nSERIAL: PNV09SJZ"
@ -42,7 +42,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->assertSame('PNV09SJZ', $result['serial']); $this->assertSame('PNV09SJZ', $result['serial']);
} }
public function test_strips_serial_bleed_into_model_number_leading(): void public function testStripsSerialBleedIntoModelNumberLeading(): void
{ {
// LLM outputs "SERIAL: xyz" as the entire value for MODEL_NUMBER // LLM outputs "SERIAL: xyz" as the entire value for MODEL_NUMBER
$this->ollama->method('generateWithImage')->willReturn( $this->ollama->method('generateWithImage')->willReturn(
@ -55,7 +55,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->assertSame('1005NK677594', $result['serial']); $this->assertSame('1005NK677594', $result['serial']);
} }
public function test_strips_serial_bleed_into_model_name_mid_value(): void public function testStripsSerialBleedIntoModelNameMidValue(): void
{ {
// LLM appends serial after the model name // LLM appends serial after the model name
$this->ollama->method('generateWithImage')->willReturn( $this->ollama->method('generateWithImage')->willReturn(
@ -68,7 +68,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->assertSame('PNV09SJZ', $result['serial']); $this->assertSame('PNV09SJZ', $result['serial']);
} }
public function test_returns_empty_strings_when_fields_missing(): void public function testReturnsEmptyStringsWhenFieldsMissing(): void
{ {
$this->ollama->method('generateWithImage')->willReturn('I cannot read the nameplate.'); $this->ollama->method('generateWithImage')->willReturn('I cannot read the nameplate.');

View file

@ -49,7 +49,7 @@ final class MistralClientTest extends TestCase
public function testGenerateWithImageReturnsParsedContent(): void public function testGenerateWithImageReturnsParsedContent(): void
{ {
$tmpFile = tempnam(sys_get_temp_dir(), 'test_') . '.jpg'; $tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.jpg';
file_put_contents($tmpFile, 'fake-image-data'); file_put_contents($tmpFile, 'fake-image-data');
$body = json_encode([ $body = json_encode([
@ -66,7 +66,7 @@ final class MistralClientTest extends TestCase
public function testGenerateWithImageEncodesImageAsBase64(): void public function testGenerateWithImageEncodesImageAsBase64(): void
{ {
$tmpFile = tempnam(sys_get_temp_dir(), 'test_') . '.png'; $tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.png';
$imageContent = 'fake-png-bytes'; $imageContent = 'fake-png-bytes';
file_put_contents($tmpFile, $imageContent); file_put_contents($tmpFile, $imageContent);
@ -92,7 +92,7 @@ final class MistralClientTest extends TestCase
*/ */
public function testGuessMimeTypeFromExtension(string $filename, string $expectedMime): void public function testGuessMimeTypeFromExtension(string $filename, string $expectedMime): void
{ {
$tmpFile = tempnam(sys_get_temp_dir(), 'test_') . '.' . pathinfo($filename, PATHINFO_EXTENSION); $tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.'.pathinfo($filename, \PATHINFO_EXTENSION);
file_put_contents($tmpFile, 'x'); file_put_contents($tmpFile, 'x');
$body = json_encode(['choices' => [['message' => ['content' => 'ok']]]]); $body = json_encode(['choices' => [['message' => ['content' => 'ok']]]]);
@ -105,7 +105,7 @@ final class MistralClientTest extends TestCase
$requestBody = json_decode($response->getRequestOptions()['body'], true); $requestBody = json_decode($response->getRequestOptions()['body'], true);
$imageUrl = $requestBody['messages'][0]['content'][1]['image_url']['url']; $imageUrl = $requestBody['messages'][0]['content'][1]['image_url']['url'];
self::assertStringStartsWith('data:' . $expectedMime . ';base64,', $imageUrl); self::assertStringStartsWith('data:'.$expectedMime.';base64,', $imageUrl);
} }
/** @return array<string, array{string, string}> */ /** @return array<string, array{string, string}> */

View file

@ -33,7 +33,7 @@ final class ArticleTypeCrudControllerTest extends TestCase
public function testConfigureFieldsContainsExpectedProperties(): void public function testConfigureFieldsContainsExpectedProperties(): void
{ {
$fields = iterator_to_array($this->controller->configureFields('new')); $fields = iterator_to_array($this->controller->configureFields('new'));
$names = array_map(fn ($f) => $f->getAsDto()->getProperty(), $fields); $names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
self::assertContains('name', $names); self::assertContains('name', $names);
self::assertContains('requiredAttributeDefs', $names); self::assertContains('requiredAttributeDefs', $names);
@ -43,7 +43,7 @@ final class ArticleTypeCrudControllerTest extends TestCase
public function testConfigureFieldsForIndexContainsNameAndCount(): void public function testConfigureFieldsForIndexContainsNameAndCount(): void
{ {
$fields = iterator_to_array($this->controller->configureFields('index')); $fields = iterator_to_array($this->controller->configureFields('index'));
$names = array_map(fn ($f) => $f->getAsDto()->getProperty(), $fields); $names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
self::assertContains('name', $names); self::assertContains('name', $names);
self::assertContains('attributeAssignments', $names); self::assertContains('attributeAssignments', $names);

View file

@ -36,7 +36,7 @@ final class AttributeDefinitionCrudControllerTest extends TestCase
public function testConfigureFieldsYieldsExpectedFieldNames(): void public function testConfigureFieldsYieldsExpectedFieldNames(): void
{ {
$fields = iterator_to_array($this->controller->configureFields('new')); $fields = iterator_to_array($this->controller->configureFields('new'));
$names = array_map(fn ($f) => $f->getAsDto()->getProperty(), $fields); $names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
self::assertContains('name', $names); self::assertContains('name', $names);
self::assertContains('type', $names); self::assertContains('type', $names);

View file

@ -19,7 +19,7 @@ final class EbayWebhookVerifierTest extends TestCase
); );
} }
public function test_valid_signature_passes(): void public function testValidSignaturePasses(): void
{ {
$body = '{"notification":{"data":{"orderId":"123"}}}'; $body = '{"notification":{"data":{"orderId":"123"}}}';
$expected = base64_encode(hash('sha256', $body.'my-secret-tokenhttps://example.com/webhooks/ebay', binary: true)); $expected = base64_encode(hash('sha256', $body.'my-secret-tokenhttps://example.com/webhooks/ebay', binary: true));
@ -27,12 +27,12 @@ final class EbayWebhookVerifierTest extends TestCase
$this->assertTrue($this->verifier->verify($body, $expected)); $this->assertTrue($this->verifier->verify($body, $expected));
} }
public function test_invalid_signature_fails(): void public function testInvalidSignatureFails(): void
{ {
$this->assertFalse($this->verifier->verify('{"body":"x"}', 'invalidsignature')); $this->assertFalse($this->verifier->verify('{"body":"x"}', 'invalidsignature'));
} }
public function test_challenge_response_returns_correct_hash(): void public function testChallengeResponseReturnsCorrectHash(): void
{ {
$challengeCode = 'abc123'; $challengeCode = 'abc123';
$expected = hash('sha256', $challengeCode.'my-secret-tokenhttps://example.com/webhooks/ebay'); $expected = hash('sha256', $challengeCode.'my-secret-tokenhttps://example.com/webhooks/ebay');

View file

@ -26,7 +26,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->adapter = new FrappeErpAdapter($this->frappe, 'REFURB-HW'); $this->adapter = new FrappeErpAdapter($this->frappe, 'REFURB-HW');
} }
public function test_create_customer_returns_frappe_id(): void public function testCreateCustomerReturnsFrappeId(): void
{ {
$this->frappe $this->frappe
->method('post') ->method('post')
@ -41,7 +41,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertSame('CUST-00001', $result); $this->assertSame('CUST-00001', $result);
} }
public function test_create_sales_invoice_submits_and_returns_id(): void public function testCreateSalesInvoiceSubmitsAndReturnsId(): void
{ {
$this->frappe $this->frappe
->expects($this->once()) ->expects($this->once())
@ -71,7 +71,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertSame('SINV-00001', $result); $this->assertSame('SINV-00001', $result);
} }
public function test_fetch_invoice_pdf_returns_binary(): void public function testFetchInvoicePdfReturnsBinary(): void
{ {
$this->frappe $this->frappe
->method('getContent') ->method('getContent')
@ -83,7 +83,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertStringStartsWith('%PDF', $result); $this->assertStringStartsWith('%PDF', $result);
} }
public function test_find_existing_customer_returns_id_when_address_matches(): void public function testFindExistingCustomerReturnsIdWhenAddressMatches(): void
{ {
$this->frappe $this->frappe
->expects($this->exactly(2)) ->expects($this->exactly(2))
@ -98,7 +98,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertSame('CUST-99999', $result); $this->assertSame('CUST-99999', $result);
} }
public function test_find_existing_customer_returns_null_when_address_mismatch(): void public function testFindExistingCustomerReturnsNullWhenAddressMismatch(): void
{ {
$this->frappe $this->frappe
->expects($this->exactly(2)) ->expects($this->exactly(2))
@ -113,7 +113,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertNull($result); $this->assertNull($result);
} }
public function test_find_existing_customer_returns_null_when_not_in_erp(): void public function testFindExistingCustomerReturnsNullWhenNotInErp(): void
{ {
$this->frappe $this->frappe
->expects($this->once()) ->expects($this->once())
@ -125,7 +125,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertNull($result); $this->assertNull($result);
} }
public function test_find_simon_and_create_invoice_for_1337(): void public function testFindSimonAndCreateInvoiceFor1337(): void
{ {
$this->frappe $this->frappe
->expects($this->exactly(2)) ->expects($this->exactly(2))

View file

@ -69,7 +69,7 @@ final class CreateApiKeyCommandTest extends TestCase
$savedKey = null; $savedKey = null;
$this->apiKeys->expects($this->once()) $this->apiKeys->expects($this->once())
->method('save') ->method('save')
->willReturnCallback(function (ApiKey $key) use (&$savedKey): void { ->willReturnCallback(static function (ApiKey $key) use (&$savedKey): void {
$savedKey = $key; $savedKey = $key;
}); });
@ -84,7 +84,7 @@ final class CreateApiKeyCommandTest extends TestCase
self::assertNotNull($savedKey); self::assertNotNull($savedKey);
self::assertSame('dev laptop', $savedKey->getLabel()); self::assertSame('dev laptop', $savedKey->getLabel());
self::assertSame(8, strlen($savedKey->getKeyPrefix())); self::assertSame(8, \strlen($savedKey->getKeyPrefix()));
self::assertStringContainsString($savedKey->getKeyPrefix(), $display); self::assertStringContainsString($savedKey->getKeyPrefix(), $display);
} }
@ -95,7 +95,7 @@ final class CreateApiKeyCommandTest extends TestCase
$savedKey = null; $savedKey = null;
$this->apiKeys->method('save') $this->apiKeys->method('save')
->willReturnCallback(function (ApiKey $key) use (&$savedKey): void { ->willReturnCallback(static function (ApiKey $key) use (&$savedKey): void {
$savedKey = $key; $savedKey = $key;
}); });

View file

@ -24,7 +24,7 @@ final class CustomerResolverTest extends TestCase
$this->resolver = new CustomerResolver($this->customerRepo, $this->erp); $this->resolver = new CustomerResolver($this->customerRepo, $this->erp);
} }
public function test_stage_1_platform_id_match_returns_existing_customer_without_erp_call(): void public function testStage1PlatformIdMatchReturnsExistingCustomerWithoutErpCall(): void
{ {
$existing = new Customer('Max Mustermann', 'max@test.de', ['street' => 'Musterstr 1', 'city' => 'Berlin', 'zip' => '10115']); $existing = new Customer('Max Mustermann', 'max@test.de', ['street' => 'Musterstr 1', 'city' => 'Berlin', 'zip' => '10115']);
$existing->addPlatformId('ebay', 'buyer123'); $existing->addPlatformId('ebay', 'buyer123');
@ -46,7 +46,7 @@ final class CustomerResolverTest extends TestCase
$this->assertSame($existing, $result); $this->assertSame($existing, $result);
} }
public function test_stage_2_address_match_adds_platform_id_and_saves(): void public function testStage2AddressMatchAddsPlatformIdAndSaves(): void
{ {
$existing = new Customer('Max Mustermann', 'max@test.de', ['street' => 'Musterstr 1', 'city' => 'Berlin', 'zip' => '10115']); $existing = new Customer('Max Mustermann', 'max@test.de', ['street' => 'Musterstr 1', 'city' => 'Berlin', 'zip' => '10115']);
@ -69,7 +69,7 @@ final class CustomerResolverTest extends TestCase
$this->assertSame('buyer456', $result->getPlatformId('ebay')); $this->assertSame('buyer456', $result->getPlatformId('ebay'));
} }
public function test_no_match_creates_new_customer_via_erp(): void public function testNoMatchCreatesNewCustomerViaErp(): void
{ {
$this->customerRepo->method('findByPlatformId')->willReturn(null); $this->customerRepo->method('findByPlatformId')->willReturn(null);
$this->customerRepo->method('findByMatchingKey')->willReturn(null); $this->customerRepo->method('findByMatchingKey')->willReturn(null);