feat: admin panel, Mistral client, attribute management, API key command
- Fix EasyAdmin 5 routing: #[AdminDashboard] attribute + easyadmin.routes loader
- Fix login: _username/_password field names, CSRF stateless token config,
sessions directory, Opcache reload after cache:clear
- Add MistralClient behind OllamaClientInterface — switchable via services.yaml alias
- Add Attribute CRUD with EnumType form + ChoiceField display (enum-safe rendering)
- Add Article Type CRUD with AssociationField for attribute assignments
- Add app:api-keys:create console command (bcrypt-hashed, never stored as plaintext)
- Add redis ext to Docker image + symfony/redis-messenger, start workers
- Translate all UI strings to English
- Add tests: MistralClient, ApiKey, CreateApiKeyCommand, StringArrayType,
ArticleTypeCrudController, AttributeDefinitionCrudController (82 tests total)
- Update design doc: tech stack, AI backend switching guide, ops section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:15:13 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Tests\Unit\Infrastructure\Admin;
|
|
|
|
|
|
|
|
|
|
use App\Domain\Article\AttributeDefinition;
|
|
|
|
|
use App\Domain\Article\AttributeType;
|
|
|
|
|
use App\Infrastructure\Http\Controller\Admin\AttributeDefinitionCrudController;
|
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
|
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
|
|
|
|
|
|
|
|
|
final class AttributeDefinitionCrudControllerTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
private AttributeDefinitionCrudController $controller;
|
|
|
|
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
$this->controller = new AttributeDefinitionCrudController();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetEntityFqcn(): void
|
|
|
|
|
{
|
|
|
|
|
self::assertSame(AttributeDefinition::class, AttributeDefinitionCrudController::getEntityFqcn());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testCreateEntityReturnsAttributeDefinitionWithDefaults(): void
|
|
|
|
|
{
|
|
|
|
|
$entity = $this->controller->createEntity(AttributeDefinition::class);
|
|
|
|
|
|
|
|
|
|
self::assertInstanceOf(AttributeDefinition::class, $entity);
|
|
|
|
|
self::assertSame('', $entity->getName());
|
|
|
|
|
self::assertSame(AttributeType::String, $entity->getType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testConfigureFieldsYieldsExpectedFieldNames(): void
|
|
|
|
|
{
|
|
|
|
|
$fields = iterator_to_array($this->controller->configureFields('new'));
|
2026-05-19 10:56:37 +00:00
|
|
|
$names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
|
feat: admin panel, Mistral client, attribute management, API key command
- Fix EasyAdmin 5 routing: #[AdminDashboard] attribute + easyadmin.routes loader
- Fix login: _username/_password field names, CSRF stateless token config,
sessions directory, Opcache reload after cache:clear
- Add MistralClient behind OllamaClientInterface — switchable via services.yaml alias
- Add Attribute CRUD with EnumType form + ChoiceField display (enum-safe rendering)
- Add Article Type CRUD with AssociationField for attribute assignments
- Add app:api-keys:create console command (bcrypt-hashed, never stored as plaintext)
- Add redis ext to Docker image + symfony/redis-messenger, start workers
- Translate all UI strings to English
- Add tests: MistralClient, ApiKey, CreateApiKeyCommand, StringArrayType,
ArticleTypeCrudController, AttributeDefinitionCrudController (82 tests total)
- Update design doc: tech stack, AI backend switching guide, ops section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:15:13 +00:00
|
|
|
|
|
|
|
|
self::assertContains('name', $names);
|
|
|
|
|
self::assertContains('type', $names);
|
|
|
|
|
self::assertContains('unit', $names);
|
|
|
|
|
self::assertContains('options', $names);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testTypeFieldOnIndexUsesChoiceFieldWithFormatValue(): void
|
|
|
|
|
{
|
|
|
|
|
$fields = iterator_to_array($this->controller->configureFields('index'));
|
|
|
|
|
$typeField = null;
|
|
|
|
|
foreach ($fields as $field) {
|
|
|
|
|
if ('type' === $field->getAsDto()->getProperty()) {
|
|
|
|
|
$typeField = $field;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self::assertNotNull($typeField);
|
|
|
|
|
|
|
|
|
|
// Must have formatValue so choice.html.twig can render it safely
|
|
|
|
|
$formatValue = $typeField->getAsDto()->getFormatValueCallable();
|
|
|
|
|
self::assertNotNull($formatValue, 'formatValue must be set so index page can render the enum');
|
|
|
|
|
self::assertSame('select', $formatValue(AttributeType::Select, null));
|
|
|
|
|
self::assertSame('string', $formatValue(AttributeType::String, null));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testTypeFieldOnFormUsesEnumType(): void
|
|
|
|
|
{
|
|
|
|
|
$fields = iterator_to_array($this->controller->configureFields('new'));
|
|
|
|
|
$typeField = null;
|
|
|
|
|
foreach ($fields as $field) {
|
|
|
|
|
if ('type' === $field->getAsDto()->getProperty()) {
|
|
|
|
|
$typeField = $field;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self::assertNotNull($typeField);
|
|
|
|
|
self::assertSame(EnumType::class, $typeField->getAsDto()->getFormType());
|
|
|
|
|
}
|
|
|
|
|
}
|