Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
69 / 69
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
BehatContext
100.00% covered (success)
100.00%
69 / 69
100.00% covered (success)
100.00%
6 / 6
24
100.00% covered (success)
100.00%
1 / 1
 beforeScenario
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 afterScenario
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 givenTheFollowingListOfUsers
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 givenTheFollowingListOfLoans
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
5
 givenTheFollowingListOfBooks
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 initContainer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10namespace Tests\AppDemo\Acceptance;
11
12// @codeCoverageIgnoreStart
13require __DIR__.'/../../BootstrapBehat.php';
14// @codeCoverageIgnoreEnd
15
16use AppDemo\Library\Domain\Author;
17use AppDemo\Library\Domain\Book;
18use AppDemo\Library\Domain\BookRepository;
19use AppDemo\Library\Domain\BookStatus;
20use AppDemo\Library\Domain\ISBN;
21use AppDemo\Library\Domain\Title;
22use AppDemo\Loan\Domain\Loan;
23use AppDemo\Loan\Domain\LoanRepository;
24use AppDemo\Loan\Domain\LoanStatus;
25use AppDemo\User\Domain\Email;
26use AppDemo\User\Domain\HashedPassword;
27use AppDemo\User\Domain\User;
28use AppDemo\User\Domain\UserGroup;
29use AppDemo\User\Domain\UserRepository;
30use Behat\Behat\Context\Context;
31use Behat\Gherkin\Node\TableNode;
32use Behat\Hook\AfterScenario;
33use Behat\Hook\BeforeScenario;
34use Behat\Step\Given;
35use DateTimeImmutable;
36use Phexium\Plugin\IdGenerator\Port\IdGeneratorInterface;
37use Phexium\Plugin\PasswordHasher\Port\PasswordHasherInterface;
38use Tests\AppDemo\Acceptance\Trait\GetContainerTrait;
39use Tests\Phexium\Integration\Support\PdoRegistry;
40
41class BehatContext implements Context
42{
43    use GetContainerTrait;
44
45    private static ?string $dbName = null;
46
47    #[BeforeScenario]
48    public static function beforeScenario(): void
49    {
50        $_ENV['database.type'] = getenv('envRepositoryType') ?? 'Sqlite';
51
52        // Create fresh database for this scenario
53        self::$dbName = match ($_ENV['database.type']) {
54            'Mysql' => PdoRegistry::initializeMysql(true),
55            'Postgresql' => PdoRegistry::initializePostgresql(true),
56            'Sqlite' => PdoRegistry::initializeSqlite(true),
57            'InMemory' => null,
58        };
59
60        // Initialize the container with the correct type
61        self::initContainer();
62
63        if ($_ENV['database.type'] === 'InMemory') {
64            self::$container->get(BookRepository::class)->reset();
65            self::$container->get(LoanRepository::class)->reset();
66            self::$container->get(UserRepository::class)->reset();
67        }
68    }
69
70    #[AfterScenario]
71    public static function afterScenario(): void
72    {
73        // Cleanup database for this scenario
74        if ($_ENV['database.type'] !== 'InMemory' && self::$dbName !== null) {
75            match ($_ENV['database.type']) {
76                'Mysql' => PdoRegistry::cleanupMysql(self::$dbName),
77                'Postgresql' => PdoRegistry::cleanupPostgresql(self::$dbName),
78                'Sqlite' => PdoRegistry::cleanupSqlite(),
79            };
80        }
81
82        self::$dbName = null;
83        self::$container = null;
84    }
85
86    #[Given('the following list of users:')]
87    public function givenTheFollowingListOfUsers(TableNode $table): void
88    {
89        $userRepository = self::$container->get(UserRepository::class);
90        $idGenerator = self::$container->get(IdGeneratorInterface::class);
91        $passwordHasher = self::$container->get(PasswordHasherInterface::class);
92
93        foreach ($table->getHash() as $row) {
94            $userId = isset($row['ID'])
95                ? $idGenerator->from($row['ID'])
96                : $idGenerator->generate();
97            $hashedPassword = $passwordHasher->hash($row['Password'] ?? 'no password');
98
99            $user = new User(
100                id: $userId,
101                email: Email::fromString($row['Email'] ?? 'no email'),
102                hashedPassword: HashedPassword::fromHash($hashedPassword),
103                group: UserGroup::from($row['Group'] ?? 'user')
104            );
105
106            $userRepository->save($user);
107        }
108    }
109
110    #[Given('the following list of loans:')]
111    public function givenTheFollowingListOfLoans(TableNode $table): void
112    {
113        $loanRepository = self::$container->get(LoanRepository::class);
114        $idGenerator = self::$container->get(IdGeneratorInterface::class);
115
116        foreach ($table->getHash() as $row) {
117            $loanId = isset($row['ID'])
118                ? $idGenerator->from($row['ID'])
119                : $idGenerator->generate();
120
121            $returnedAt = null;
122
123            if (isset($row['ReturnedAt']) && $row['ReturnedAt'] !== '') {
124                $returnedAt = new DateTimeImmutable($row['ReturnedAt']);
125            }
126
127            $loan = new Loan(
128                id: $loanId,
129                userId: $idGenerator->from($row['UserID']),
130                bookId: $idGenerator->from($row['BookID']),
131                borrowedAt: new DateTimeImmutable($row['BorrowedAt'] ?? 'now'),
132                dueAt: new DateTimeImmutable($row['DueAt'] ?? '+1 day'),
133                returnedAt: $returnedAt,
134                status: LoanStatus::from($row['Status'] ?? 'active')
135            );
136
137            $loanRepository->save($loan);
138        }
139    }
140
141    #[Given('the following list of books:')]
142    public function givenTheFollowingListOfBooks(TableNode $table): void
143    {
144        $bookRepository = self::$container->get(BookRepository::class);
145        $idGenerator = self::$container->get(IdGeneratorInterface::class);
146
147        foreach ($table->getHash() as $row) {
148            $bookId = isset($row['ID'])
149                ? $idGenerator->from($row['ID'])
150                : $idGenerator->generate();
151
152            $book = new Book(
153                id: $bookId,
154                title: Title::fromString($row['Title'] ?? 'no title'),
155                author: Author::fromString($row['Author'] ?? 'no author'),
156                isbn: ISBN::fromString($row['ISBN'] ?? '8888888888888'),
157                status: BookStatus::from($row['Status'] ?? 'available')
158            );
159
160            $bookRepository->save($book);
161        }
162    }
163
164    private static function initContainer(): void
165    {
166        // Ensure the container is initialized with the correct type
167        self::getDiContainer($_ENV['database.type']);
168    }
169}