Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
BehatContext
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
6 / 6
24
100.00% covered (success)
100.00%
1 / 1
 beforeScenario
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 afterScenario
100.00% covered (success)
100.00%
6 / 6
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\Component\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            // @codeCoverageIgnoreStart
55            // disabled for CI resource optimization
56            'Mysql' => PdoRegistry::initializeMysql(true),
57            'Postgresql' => PdoRegistry::initializePostgresql(true),
58            // @codeCoverageIgnoreEnd
59            'Sqlite' => PdoRegistry::initializeSqlite(true),
60            'InMemory' => null,
61        };
62
63        // Initialize the container with the correct type
64        self::initContainer();
65
66        if ($_ENV['database.type'] === 'InMemory') {
67            self::$container->get(BookRepository::class)->reset();
68            self::$container->get(LoanRepository::class)->reset();
69            self::$container->get(UserRepository::class)->reset();
70        }
71    }
72
73    #[AfterScenario]
74    public static function afterScenario(): void
75    {
76        // Cleanup database for this scenario
77        if ($_ENV['database.type'] !== 'InMemory' && self::$dbName !== null) {
78            match ($_ENV['database.type']) {
79                // @codeCoverageIgnoreStart
80                // disabled for CI resource optimization
81                'Mysql' => PdoRegistry::cleanupMysql(self::$dbName),
82                'Postgresql' => PdoRegistry::cleanupPostgresql(self::$dbName),
83                // @codeCoverageIgnoreEnd
84                'Sqlite' => PdoRegistry::cleanupSqlite(),
85            };
86        }
87
88        self::$dbName = null;
89        self::$container = null;
90    }
91
92    #[Given('the following list of users:')]
93    public function givenTheFollowingListOfUsers(TableNode $table): void
94    {
95        $userRepository = self::$container->get(UserRepository::class);
96        $idGenerator = self::$container->get(IdGeneratorInterface::class);
97        $passwordHasher = self::$container->get(PasswordHasherInterface::class);
98
99        foreach ($table->getHash() as $row) {
100            $userId = isset($row['ID'])
101                ? $idGenerator->from($row['ID'])
102                : $idGenerator->generate();
103            $hashedPassword = $passwordHasher->hash($row['Password'] ?? 'no password');
104
105            $user = new User(
106                id: $userId,
107                email: Email::fromString($row['Email'] ?? 'no email'),
108                hashedPassword: HashedPassword::fromHash($hashedPassword),
109                group: UserGroup::from($row['Group'] ?? 'user')
110            );
111
112            $userRepository->save($user);
113        }
114    }
115
116    #[Given('the following list of loans:')]
117    public function givenTheFollowingListOfLoans(TableNode $table): void
118    {
119        $loanRepository = self::$container->get(LoanRepository::class);
120        $idGenerator = self::$container->get(IdGeneratorInterface::class);
121
122        foreach ($table->getHash() as $row) {
123            $loanId = isset($row['ID'])
124                ? $idGenerator->from($row['ID'])
125                : $idGenerator->generate();
126
127            $returnedAt = null;
128
129            if (isset($row['ReturnedAt']) && $row['ReturnedAt'] !== '') {
130                $returnedAt = new DateTimeImmutable($row['ReturnedAt']);
131            }
132
133            $loan = new Loan(
134                id: $loanId,
135                userId: $idGenerator->from($row['UserID']),
136                bookId: $idGenerator->from($row['BookID']),
137                borrowedAt: new DateTimeImmutable($row['BorrowedAt'] ?? 'now'),
138                dueAt: new DateTimeImmutable($row['DueAt'] ?? '+1 day'),
139                returnedAt: $returnedAt,
140                status: LoanStatus::from($row['Status'] ?? 'active')
141            );
142
143            $loanRepository->save($loan);
144        }
145    }
146
147    #[Given('the following list of books:')]
148    public function givenTheFollowingListOfBooks(TableNode $table): void
149    {
150        $bookRepository = self::$container->get(BookRepository::class);
151        $idGenerator = self::$container->get(IdGeneratorInterface::class);
152
153        foreach ($table->getHash() as $row) {
154            $bookId = isset($row['ID'])
155                ? $idGenerator->from($row['ID'])
156                : $idGenerator->generate();
157
158            $book = new Book(
159                id: $bookId,
160                title: Title::fromString($row['Title'] ?? 'no title'),
161                author: Author::fromString($row['Author'] ?? 'no author'),
162                isbn: ISBN::fromString($row['ISBN'] ?? '8888888888888'),
163                status: BookStatus::from($row['Status'] ?? 'available')
164            );
165
166            $bookRepository->save($book);
167        }
168    }
169
170    private static function initContainer(): void
171    {
172        // Ensure the container is initialized with the correct type
173        self::getDiContainer($_ENV['database.type']);
174    }
175}