Merge branch 'feature/tanach-seeder'
This commit is contained in:
commit
8218de16f5
2 changed files with 433 additions and 0 deletions
249
backend/database/seeders/TanachSeeder.php
Normal file
249
backend/database/seeders/TanachSeeder.php
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Element\CreateElementDto;
|
||||||
|
use App\Element\Element;
|
||||||
|
use App\Element\ElementRepository;
|
||||||
|
use App\Set\CreateSetDto;
|
||||||
|
use App\Set\CreateSetLevelDto;
|
||||||
|
use App\Set\Set;
|
||||||
|
use App\Set\SetLevel;
|
||||||
|
use App\Set\SetLevelRepository;
|
||||||
|
use App\Set\SetRepository;
|
||||||
|
use App\Shared\ValueObject\EmailAddress;
|
||||||
|
use App\User\UserRepository;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class TanachSeeder extends Seeder
|
||||||
|
{
|
||||||
|
private const string SET_NAME = 'Tanach';
|
||||||
|
|
||||||
|
private const array LEVEL_KINDS = [
|
||||||
|
'chelek',
|
||||||
|
'sefer',
|
||||||
|
'perek',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const array SEFARIM_BY_CHELEK = [
|
||||||
|
'Torah' => [
|
||||||
|
'Bereishis' => 50,
|
||||||
|
'Shemos' => 40,
|
||||||
|
'Vayikra' => 27,
|
||||||
|
'Bamidbar' => 36,
|
||||||
|
'Devarim' => 34,
|
||||||
|
],
|
||||||
|
"Nevi'im" => [
|
||||||
|
'Yehoshua' => 24,
|
||||||
|
'Shoftim' => 21,
|
||||||
|
'Shmuel Alef' => 31,
|
||||||
|
'Shmuel Beis' => 24,
|
||||||
|
'Melachim Alef' => 22,
|
||||||
|
'Melachim Beis' => 25,
|
||||||
|
'Yeshayahu' => 66,
|
||||||
|
'Yirmiyahu' => 52,
|
||||||
|
'Yechezkel' => 48,
|
||||||
|
'Hoshea' => 14,
|
||||||
|
'Yoel' => 4,
|
||||||
|
'Amos' => 9,
|
||||||
|
'Ovadiah' => 1,
|
||||||
|
'Yonah' => 4,
|
||||||
|
'Michah' => 7,
|
||||||
|
'Nachum' => 3,
|
||||||
|
'Chavakuk' => 3,
|
||||||
|
'Tzefaniah' => 3,
|
||||||
|
'Chaggai' => 2,
|
||||||
|
'Zechariah' => 14,
|
||||||
|
'Malachi' => 3,
|
||||||
|
],
|
||||||
|
'Kesuvim' => [
|
||||||
|
'Tehillim' => 150,
|
||||||
|
'Mishlei' => 31,
|
||||||
|
'Iyov' => 42,
|
||||||
|
'Shir HaShirim' => 8,
|
||||||
|
'Rus' => 4,
|
||||||
|
'Eichah' => 5,
|
||||||
|
'Koheles' => 12,
|
||||||
|
'Esther' => 10,
|
||||||
|
'Daniel' => 12,
|
||||||
|
'Ezra' => 10,
|
||||||
|
'Nechemiah' => 13,
|
||||||
|
'Divrei HaYamim Alef' => 29,
|
||||||
|
'Divrei HaYamim Beis' => 36,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$this->call(UserSeeder::class);
|
||||||
|
|
||||||
|
$user = app(UserRepository::class)->findByEmail(
|
||||||
|
new EmailAddress(UserSeeder::EMAIL),
|
||||||
|
);
|
||||||
|
if ($user === null) {
|
||||||
|
throw new RuntimeException('seeded user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$setRepository = app(SetRepository::class);
|
||||||
|
$tanach = $this->findTanach($setRepository);
|
||||||
|
if ($tanach === null) {
|
||||||
|
$tanach = $setRepository->create(new CreateSetDto(
|
||||||
|
name: self::SET_NAME,
|
||||||
|
creator: $user,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$levels = $this->seedLevels(
|
||||||
|
repository: app(SetLevelRepository::class),
|
||||||
|
tanach: $tanach,
|
||||||
|
);
|
||||||
|
$this->seedElements(
|
||||||
|
repository: app(ElementRepository::class),
|
||||||
|
tanach: $tanach,
|
||||||
|
levels: $levels,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findTanach(SetRepository $repository): ?Set
|
||||||
|
{
|
||||||
|
foreach ($repository->all() as $set) {
|
||||||
|
if ($set->getName() === self::SET_NAME) {
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{
|
||||||
|
* chelek: SetLevel,
|
||||||
|
* sefer: SetLevel,
|
||||||
|
* perek: SetLevel
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
private function seedLevels(
|
||||||
|
SetLevelRepository $repository,
|
||||||
|
Set $tanach,
|
||||||
|
): array {
|
||||||
|
$levels = $repository->findBySet($tanach);
|
||||||
|
|
||||||
|
foreach ($levels as $depth => $level) {
|
||||||
|
$expectedKind = self::LEVEL_KINDS[$depth] ?? null;
|
||||||
|
if ($level->getKind() !== $expectedKind) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
'Tanach levels do not match the expected hierarchy',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingKinds = array_slice(self::LEVEL_KINDS, count($levels));
|
||||||
|
foreach ($missingKinds as $kind) {
|
||||||
|
$levels[] = $repository->create(new CreateSetLevelDto(
|
||||||
|
set: $tanach,
|
||||||
|
kind: $kind,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'chelek' => $this->findLevel($levels, 'chelek'),
|
||||||
|
'sefer' => $this->findLevel($levels, 'sefer'),
|
||||||
|
'perek' => $this->findLevel($levels, 'perek'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<SetLevel> $levels
|
||||||
|
*/
|
||||||
|
private function findLevel(array $levels, string $kind): SetLevel
|
||||||
|
{
|
||||||
|
foreach ($levels as $level) {
|
||||||
|
if ($level->getKind() === $kind) {
|
||||||
|
return $level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Tanach {$kind} level not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{
|
||||||
|
* chelek: SetLevel,
|
||||||
|
* sefer: SetLevel,
|
||||||
|
* perek: SetLevel
|
||||||
|
* } $levels
|
||||||
|
*/
|
||||||
|
private function seedElements(
|
||||||
|
ElementRepository $repository,
|
||||||
|
Set $tanach,
|
||||||
|
array $levels,
|
||||||
|
): void {
|
||||||
|
$chelakim = $repository->findTopLevelBySet($tanach);
|
||||||
|
|
||||||
|
foreach (self::SEFARIM_BY_CHELEK as $chelekName => $sefarim) {
|
||||||
|
$chelek = $this->findOrCreateElement(
|
||||||
|
repository: $repository,
|
||||||
|
siblings: $chelakim,
|
||||||
|
name: $chelekName,
|
||||||
|
level: $levels['chelek'],
|
||||||
|
parentElement: null,
|
||||||
|
);
|
||||||
|
$seededSefarim = $repository->findByParentElement($chelek);
|
||||||
|
|
||||||
|
foreach ($sefarim as $seferName => $perekCount) {
|
||||||
|
$sefer = $this->findOrCreateElement(
|
||||||
|
repository: $repository,
|
||||||
|
siblings: $seededSefarim,
|
||||||
|
name: $seferName,
|
||||||
|
level: $levels['sefer'],
|
||||||
|
parentElement: $chelek,
|
||||||
|
);
|
||||||
|
$perakim = $repository->findByParentElement($sefer);
|
||||||
|
|
||||||
|
for (
|
||||||
|
$perekNumber = 1;
|
||||||
|
$perekNumber <= $perekCount;
|
||||||
|
$perekNumber++
|
||||||
|
) {
|
||||||
|
$this->findOrCreateElement(
|
||||||
|
repository: $repository,
|
||||||
|
siblings: $perakim,
|
||||||
|
name: "Perek {$perekNumber}",
|
||||||
|
level: $levels['perek'],
|
||||||
|
parentElement: $sefer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Element> $siblings
|
||||||
|
*/
|
||||||
|
private function findOrCreateElement(
|
||||||
|
ElementRepository $repository,
|
||||||
|
array &$siblings,
|
||||||
|
string $name,
|
||||||
|
SetLevel $level,
|
||||||
|
?Element $parentElement,
|
||||||
|
): Element {
|
||||||
|
foreach ($siblings as $sibling) {
|
||||||
|
if (
|
||||||
|
$sibling->getName() === $name
|
||||||
|
&& $sibling->getLevel()->getId() === $level->getId()
|
||||||
|
) {
|
||||||
|
return $sibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$element = $repository->create(new CreateElementDto(
|
||||||
|
name: $name,
|
||||||
|
level: $level,
|
||||||
|
parentElement: $parentElement,
|
||||||
|
));
|
||||||
|
$siblings[] = $element;
|
||||||
|
|
||||||
|
return $element;
|
||||||
|
}
|
||||||
|
}
|
||||||
184
backend/tests/Feature/Database/TanachSeederTest.php
Normal file
184
backend/tests/Feature/Database/TanachSeederTest.php
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Database;
|
||||||
|
|
||||||
|
use App\Element\Element;
|
||||||
|
use App\Element\ElementRepository;
|
||||||
|
use App\Set\Set;
|
||||||
|
use App\Set\SetLevelRepository;
|
||||||
|
use App\Set\SetRepository;
|
||||||
|
use App\Shared\ValueObject\EmailAddress;
|
||||||
|
use App\User\UserRepository;
|
||||||
|
use Database\Seeders\TanachSeeder;
|
||||||
|
use Database\Seeders\UserSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class TanachSeederTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private const array SEFARIM_BY_CHELEK = [
|
||||||
|
'Torah' => [
|
||||||
|
'Bereishis' => 50,
|
||||||
|
'Shemos' => 40,
|
||||||
|
'Vayikra' => 27,
|
||||||
|
'Bamidbar' => 36,
|
||||||
|
'Devarim' => 34,
|
||||||
|
],
|
||||||
|
"Nevi'im" => [
|
||||||
|
'Yehoshua' => 24,
|
||||||
|
'Shoftim' => 21,
|
||||||
|
'Shmuel Alef' => 31,
|
||||||
|
'Shmuel Beis' => 24,
|
||||||
|
'Melachim Alef' => 22,
|
||||||
|
'Melachim Beis' => 25,
|
||||||
|
'Yeshayahu' => 66,
|
||||||
|
'Yirmiyahu' => 52,
|
||||||
|
'Yechezkel' => 48,
|
||||||
|
'Hoshea' => 14,
|
||||||
|
'Yoel' => 4,
|
||||||
|
'Amos' => 9,
|
||||||
|
'Ovadiah' => 1,
|
||||||
|
'Yonah' => 4,
|
||||||
|
'Michah' => 7,
|
||||||
|
'Nachum' => 3,
|
||||||
|
'Chavakuk' => 3,
|
||||||
|
'Tzefaniah' => 3,
|
||||||
|
'Chaggai' => 2,
|
||||||
|
'Zechariah' => 14,
|
||||||
|
'Malachi' => 3,
|
||||||
|
],
|
||||||
|
'Kesuvim' => [
|
||||||
|
'Tehillim' => 150,
|
||||||
|
'Mishlei' => 31,
|
||||||
|
'Iyov' => 42,
|
||||||
|
'Shir HaShirim' => 8,
|
||||||
|
'Rus' => 4,
|
||||||
|
'Eichah' => 5,
|
||||||
|
'Koheles' => 12,
|
||||||
|
'Esther' => 10,
|
||||||
|
'Daniel' => 12,
|
||||||
|
'Ezra' => 10,
|
||||||
|
'Nechemiah' => 13,
|
||||||
|
'Divrei HaYamim Alef' => 29,
|
||||||
|
'Divrei HaYamim Beis' => 36,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function test_it_seeds_the_complete_tanach_idempotently(): void
|
||||||
|
{
|
||||||
|
$this->seed(TanachSeeder::class);
|
||||||
|
$this->seed(TanachSeeder::class);
|
||||||
|
|
||||||
|
$this->assertDatabaseCount('users', 1);
|
||||||
|
$this->assertDatabaseCount('sets', 1);
|
||||||
|
$this->assertDatabaseCount('set_levels', 3);
|
||||||
|
$this->assertDatabaseCount('elements', 971);
|
||||||
|
|
||||||
|
$user = app(UserRepository::class)->findByEmail(
|
||||||
|
new EmailAddress(UserSeeder::EMAIL),
|
||||||
|
);
|
||||||
|
$this->assertNotNull($user);
|
||||||
|
|
||||||
|
$tanach = $this->findTanach();
|
||||||
|
$this->assertSame(
|
||||||
|
UserSeeder::EMAIL,
|
||||||
|
$tanach->getCreator()->getEmail()->value(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$levels = app(SetLevelRepository::class)->findBySet($tanach);
|
||||||
|
$this->assertSame(
|
||||||
|
['chelek', 'sefer', 'perek'],
|
||||||
|
array_map(function ($level): string {
|
||||||
|
return $level->getKind();
|
||||||
|
}, $levels),
|
||||||
|
);
|
||||||
|
|
||||||
|
$elementRepository = app(ElementRepository::class);
|
||||||
|
$chelakim = $elementRepository->findTopLevelBySet($tanach);
|
||||||
|
$this->assertElements(
|
||||||
|
elements: $chelakim,
|
||||||
|
expectedNames: array_keys(self::SEFARIM_BY_CHELEK),
|
||||||
|
expectedKind: 'chelek',
|
||||||
|
);
|
||||||
|
|
||||||
|
$seferCount = 0;
|
||||||
|
$perekCount = 0;
|
||||||
|
|
||||||
|
foreach ($chelakim as $chelek) {
|
||||||
|
$expectedSefarim = self::SEFARIM_BY_CHELEK[
|
||||||
|
$chelek->getName()
|
||||||
|
] ?? null;
|
||||||
|
$this->assertNotNull($expectedSefarim);
|
||||||
|
|
||||||
|
$sefarim = $elementRepository->findByParentElement($chelek);
|
||||||
|
$this->assertElements(
|
||||||
|
elements: $sefarim,
|
||||||
|
expectedNames: array_keys($expectedSefarim),
|
||||||
|
expectedKind: 'sefer',
|
||||||
|
);
|
||||||
|
$seferCount += count($sefarim);
|
||||||
|
|
||||||
|
foreach ($sefarim as $sefer) {
|
||||||
|
$expectedPerekCount = $expectedSefarim[
|
||||||
|
$sefer->getName()
|
||||||
|
] ?? null;
|
||||||
|
$this->assertNotNull($expectedPerekCount);
|
||||||
|
|
||||||
|
$expectedPerekNames = [];
|
||||||
|
for (
|
||||||
|
$perekNumber = 1;
|
||||||
|
$perekNumber <= $expectedPerekCount;
|
||||||
|
$perekNumber++
|
||||||
|
) {
|
||||||
|
$expectedPerekNames[] = "Perek {$perekNumber}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$perakim = $elementRepository->findByParentElement($sefer);
|
||||||
|
$this->assertElements(
|
||||||
|
elements: $perakim,
|
||||||
|
expectedNames: $expectedPerekNames,
|
||||||
|
expectedKind: 'perek',
|
||||||
|
);
|
||||||
|
$perekCount += count($perakim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame(39, $seferCount);
|
||||||
|
$this->assertSame(929, $perekCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findTanach(): Set
|
||||||
|
{
|
||||||
|
foreach (app(SetRepository::class)->all() as $set) {
|
||||||
|
if ($set->getName() === 'Tanach') {
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fail('Tanach set was not seeded');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Element> $elements
|
||||||
|
* @param list<string> $expectedNames
|
||||||
|
*/
|
||||||
|
private function assertElements(
|
||||||
|
array $elements,
|
||||||
|
array $expectedNames,
|
||||||
|
string $expectedKind,
|
||||||
|
): void {
|
||||||
|
$this->assertSame(
|
||||||
|
$expectedNames,
|
||||||
|
array_map(function (Element $element): string {
|
||||||
|
return $element->getName();
|
||||||
|
}, $elements),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($elements as $index => $element) {
|
||||||
|
$this->assertSame($expectedKind, $element->getKind());
|
||||||
|
$this->assertSame($index + 1, $element->getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue