docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Search\ValueObjects;
use App\Framework\Search\ValueObjects\DocumentData;
use InvalidArgumentException;
describe('DocumentData Value Object', function () {
it('can be created empty', function () {
$data = DocumentData::empty();
expect($data->isEmpty())->toBeTrue();
expect($data->toArray())->toBe([]);
});
it('can be created from array', function () {
$fields = ['title' => 'Test', 'content' => 'Content'];
$data = DocumentData::fromArray($fields);
expect($data->toArray())->toBe($fields);
expect($data->isEmpty())->toBeFalse();
});
it('can add fields immutably', function () {
$data = DocumentData::empty();
$newData = $data->with('title', 'Test Title');
expect($data->isEmpty())->toBeTrue(); // Original unchanged
expect($newData->get('title'))->toBe('Test Title');
expect($newData->has('title'))->toBeTrue();
});
it('can remove fields immutably', function () {
$data = DocumentData::fromArray(['title' => 'Test', 'content' => 'Content']);
$newData = $data->without('title');
expect($data->has('title'))->toBeTrue(); // Original unchanged
expect($newData->has('title'))->toBeFalse();
expect($newData->has('content'))->toBeTrue();
});
it('gets field values with defaults', function () {
$data = DocumentData::fromArray(['title' => 'Test']);
expect($data->get('title'))->toBe('Test');
expect($data->get('missing'))->toBeNull();
expect($data->get('missing', 'default'))->toBe('default');
});
it('extracts text fields only', function () {
$data = DocumentData::fromArray([
'title' => 'Text Title',
'content' => 'Text Content',
'price' => 123.45,
'active' => true,
'empty_text' => '',
'whitespace' => ' ',
]);
$textFields = $data->getTextFields();
expect($textFields)->toBe([
'title' => 'Text Title',
'content' => 'Text Content',
]);
});
it('extracts facet fields only', function () {
$data = DocumentData::fromArray([
'title' => 'Text Title',
'price' => 123.45,
'active' => true,
'count' => 42,
'nested' => ['key' => 'value'],
]);
$facetFields = $data->getFacetFields();
expect($facetFields)->toBe([
'title' => 'Text Title',
'price' => 123.45,
'active' => true,
'count' => 42,
]);
});
it('validates field keys', function () {
expect(fn () => DocumentData::fromArray(['' => 'value']))->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray([' ' => 'value']))->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray([123 => 'value']))->toThrow(InvalidArgumentException::class);
});
it('validates field key length', function () {
$longKey = str_repeat('a', 256);
expect(fn () => DocumentData::fromArray([$longKey => 'value']))->toThrow(InvalidArgumentException::class);
});
it('validates field key format', function () {
expect(fn () => DocumentData::fromArray(['1invalid' => 'value']))->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray(['invalid-key' => 'value']))->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray(['invalid key' => 'value']))->toThrow(InvalidArgumentException::class);
});
it('allows valid field keys', function () {
expect(fn () => DocumentData::fromArray(['valid' => 'value']))->not->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray(['valid_key' => 'value']))->not->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray(['valid123' => 'value']))->not->toThrow(InvalidArgumentException::class);
expect(fn () => DocumentData::fromArray(['a' => 'value']))->not->toThrow(InvalidArgumentException::class);
});
it('validates field values for serializability', function () {
$resource = fopen('php://memory', 'r');
expect(fn () => DocumentData::fromArray(['resource' => $resource]))->toThrow(InvalidArgumentException::class);
fclose($resource);
});
it('allows serializable values', function () {
$data = DocumentData::fromArray([
'string' => 'text',
'int' => 123,
'float' => 12.34,
'bool' => true,
'null' => null,
'array' => [1, 2, 3],
]);
expect($data->toArray())->toHaveCount(6);
});
});

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Search\ValueObjects;
use App\Framework\Search\ValueObjects\EntityId;
use InvalidArgumentException;
describe('EntityId Value Object', function () {
it('can be created with valid ID', function () {
$id = new EntityId('user_123');
expect($id->value)->toBe('user_123');
});
it('can be created from string', function () {
$id = EntityId::fromString('product_456');
expect($id->toString())->toBe('product_456');
});
it('can generate unique ID', function () {
$id1 = EntityId::generate();
$id2 = EntityId::generate();
expect($id1->toString())->not->toBe($id2->toString());
expect($id1->toString())->toStartWith('entity_');
});
it('converts to string', function () {
$id = new EntityId('test_789');
expect($id->__toString())->toBe('test_789');
});
it('compares equality correctly', function () {
$id1 = new EntityId('same_id');
$id2 = new EntityId('same_id');
$id3 = new EntityId('different_id');
expect($id1->equals($id2))->toBeTrue();
expect($id1->equals($id3))->toBeFalse();
});
it('throws exception for empty ID', function () {
expect(fn () => new EntityId(''))->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityId(' '))->toThrow(InvalidArgumentException::class);
});
it('throws exception for too long ID', function () {
$longId = str_repeat('a', 256);
expect(fn () => new EntityId($longId))->toThrow(InvalidArgumentException::class);
});
it('throws exception for invalid characters', function () {
expect(fn () => new EntityId('invalid@id'))->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityId('invalid id'))->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityId('invalid/id'))->toThrow(InvalidArgumentException::class);
});
it('allows valid characters', function () {
expect(fn () => new EntityId('valid_id.123-test'))->not->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityId('123'))->not->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityId('a'))->not->toThrow(InvalidArgumentException::class);
});
});

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Search\ValueObjects;
use App\Framework\Search\ValueObjects\EntityType;
use InvalidArgumentException;
describe('EntityType Value Object', function () {
it('can be created with valid type', function () {
$type = new EntityType('user');
expect($type->value)->toBe('user');
});
it('provides common entity types', function () {
expect(EntityType::user()->toString())->toBe('user');
expect(EntityType::product()->toString())->toBe('product');
expect(EntityType::article()->toString())->toBe('article');
expect(EntityType::category()->toString())->toBe('category');
expect(EntityType::order()->toString())->toBe('order');
});
it('can be created from string', function () {
$type = EntityType::fromString('custom_type');
expect($type->toString())->toBe('custom_type');
});
it('converts to string', function () {
$type = new EntityType('product');
expect($type->__toString())->toBe('product');
});
it('generates index name', function () {
$type = new EntityType('user');
expect($type->getIndexName())->toBe('search_user');
});
it('compares equality correctly', function () {
$type1 = new EntityType('user');
$type2 = new EntityType('user');
$type3 = new EntityType('product');
expect($type1->equals($type2))->toBeTrue();
expect($type1->equals($type3))->toBeFalse();
});
it('throws exception for empty type', function () {
expect(fn () => new EntityType(''))->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityType(' '))->toThrow(InvalidArgumentException::class);
});
it('throws exception for too long type', function () {
$longType = str_repeat('a', 101);
expect(fn () => new EntityType($longType))->toThrow(InvalidArgumentException::class);
});
it('throws exception for invalid format', function () {
expect(fn () => new EntityType('User'))->toThrow(InvalidArgumentException::class); // uppercase
expect(fn () => new EntityType('user-type'))->toThrow(InvalidArgumentException::class); // hyphen
expect(fn () => new EntityType('user type'))->toThrow(InvalidArgumentException::class); // space
expect(fn () => new EntityType('1user'))->toThrow(InvalidArgumentException::class); // starts with number
});
it('allows valid format', function () {
expect(fn () => new EntityType('user'))->not->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityType('user_type'))->not->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityType('user123'))->not->toThrow(InvalidArgumentException::class);
expect(fn () => new EntityType('a'))->not->toThrow(InvalidArgumentException::class);
});
});