style: apply CS Fixer formatting across codebase
Some checks are pending
CI / test (push) Waiting to run
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:
parent
fc18958e0e
commit
a79791a972
37 changed files with 223 additions and 167 deletions
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 getLocale(): string
|
||||||
public function setLocale(string $locale): void { $this->locale = $locale; }
|
{
|
||||||
public function setDomain(string $domain): void { $this->domain = $domain; }
|
return $this->locale;
|
||||||
public function setKey(string $key): void { $this->key = $key; }
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()) ?: '—'
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>';
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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', [
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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.');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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']]]]);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue