Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.71% |
89 / 96 |
|
71.43% |
10 / 14 |
CRAP | |
0.00% |
0 / 1 |
LaravelTestApplication | |
92.71% |
89 / 96 |
|
71.43% |
10 / 14 |
20.16 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getConsoleApplication | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getApplicationConfig | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
defineEnvironment | |
100.00% |
38 / 38 |
|
100.00% |
1 / 1 |
1 | |||
bootApplication | |
76.92% |
10 / 13 |
|
0.00% |
0 / 1 |
2.05 | |||
getServiceContainer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
cleanApplication | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
httpRequestGet | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
handleResponse | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getPackageProviders | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
httpRequest | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
loginAs | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
logout | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getLoggedInAs | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 |
1 | <?php |
2 | namespace Apie\IntegrationTests\Applications\Laravel; |
3 | |
4 | use Apie\AiInstructor\AiClient; |
5 | use Apie\Common\Events\AddAuthenticationCookie; |
6 | use Apie\Common\IntegrationTestLogger; |
7 | use Apie\Common\ValueObjects\DecryptedAuthenticatedUser; |
8 | use Apie\Common\Wrappers\TextEncrypter; |
9 | use Apie\Core\Other\FileWriterInterface; |
10 | use Apie\Core\Other\MockFileWriter; |
11 | use Apie\IntegrationTests\Applications\MockFactory; |
12 | use Apie\IntegrationTests\Concerns\ItRunsApplications; |
13 | use Apie\IntegrationTests\Config\ApplicationConfig; |
14 | use Apie\IntegrationTests\Config\BoundedContextConfig; |
15 | use Apie\IntegrationTests\Interfaces\TestApplicationInterface; |
16 | use Apie\IntegrationTests\Requests\TestRequestInterface; |
17 | use Apie\LaravelApie\ApieServiceProvider; |
18 | use Illuminate\Contracts\Config\Repository; |
19 | use Illuminate\Contracts\Http\Kernel as HttpKernel; |
20 | use Illuminate\Http\Request; |
21 | use Illuminate\Support\Facades\Auth; |
22 | use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory; |
23 | use Orchestra\Testbench\TestCase; |
24 | use Psr\Container\ContainerInterface; |
25 | use Psr\Http\Message\ResponseInterface; |
26 | use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; |
27 | use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; |
28 | use Symfony\Component\Console\Application; |
29 | use Symfony\Component\HttpFoundation\Cookie; |
30 | use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse; |
31 | use Symfony\Component\HttpFoundation\ResponseHeaderBag; |
32 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
33 | |
34 | class LaravelTestApplication extends TestCase implements TestApplicationInterface |
35 | { |
36 | use ItRunsApplications; |
37 | |
38 | /** |
39 | * @phpstan-ignore method.parentMethodFinalByPhpDoc |
40 | */ |
41 | public function __construct( |
42 | private readonly ApplicationConfig $applicationConfig, |
43 | private readonly BoundedContextConfig $boundedContextConfig |
44 | ) { |
45 | parent::__construct('Laravel application'); |
46 | } |
47 | |
48 | public function getConsoleApplication(): Application |
49 | { |
50 | $application = new \Illuminate\Console\Application( |
51 | $this->getServiceContainer(), |
52 | $this->getServiceContainer()->get(\Illuminate\Contracts\Events\Dispatcher::class), |
53 | 'test' |
54 | ); |
55 | return $application; |
56 | } |
57 | |
58 | public function getApplicationConfig(): ApplicationConfig |
59 | { |
60 | return $this->applicationConfig; |
61 | } |
62 | |
63 | protected function defineEnvironment($app): void |
64 | { |
65 | tap($app->make('config'), function (Repository $config) { |
66 | $config->set('app.key', 'base64:/aNEFWQbsYwDslb4Xw1RKKj9oCdZdbNhvcyUpVgXPz4='); |
67 | $config->set('apie.encryption_key', 'test'); |
68 | $config->set( |
69 | 'apie.bounded_contexts', |
70 | $this->boundedContextConfig->toArray() |
71 | ); |
72 | $config->set( |
73 | 'apie.scan_bounded_contexts', |
74 | [] |
75 | ); |
76 | // this has to be explicit because of some code execution order issues |
77 | $config->set('apie.enable_ai_instructor', true); |
78 | $config->set('apie.enable_mcp_server', true); |
79 | $config->set( |
80 | 'apie.ai', |
81 | [ |
82 | 'base_url' => 'http://localbost:11434', |
83 | 'api_key' => 'test', |
84 | ] |
85 | ); |
86 | $config->set( |
87 | 'apie.datalayers', |
88 | [ |
89 | 'default_datalayer' => $this->applicationConfig->getDatalayerImplementation()->name, |
90 | ] |
91 | ); |
92 | $config->set( |
93 | 'apie.doctrine', |
94 | [ |
95 | 'build_once' => false, |
96 | 'run_migrations' => true, |
97 | 'connection_params' => [ |
98 | 'driver' => 'pdo_sqlite' |
99 | ] |
100 | ] |
101 | ); |
102 | $config->set('apie.remote_mcp_path', '/mcp'); |
103 | }); |
104 | } |
105 | |
106 | /** |
107 | * Boot application. Should be called at the start of the test. |
108 | */ |
109 | public function bootApplication(): void |
110 | { |
111 | IntegrationTestLogger::resetLoggedException(); |
112 | $this->setUp(); |
113 | $this->session([]); |
114 | if (getenv('PHPUNIT_LOG_INTEGRATION_OUTPUT')) { |
115 | $this->withoutExceptionHandling([ |
116 | NotFoundHttpException::class |
117 | ]); |
118 | } |
119 | $this->app->instance(FileWriterInterface::class, new MockFileWriter()); |
120 | $this->app->instance( |
121 | AiClient::class, |
122 | MockFactory::createMockAiClient() |
123 | ); |
124 | unset($this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME]); |
125 | } |
126 | |
127 | /** |
128 | * Gets service container of application. Should be used as little as possible. |
129 | */ |
130 | public function getServiceContainer(): ContainerInterface |
131 | { |
132 | return $this->app; |
133 | } |
134 | |
135 | /** |
136 | * Cleans up application. Should be called at the end of the test. |
137 | */ |
138 | public function cleanApplication(): void |
139 | { |
140 | $this->tearDown(); |
141 | } |
142 | |
143 | /** |
144 | * Does a HTTP request on the application and returns the response. |
145 | */ |
146 | public function httpRequestGet(string $uri): ResponseInterface |
147 | { |
148 | //workaround against spacebar search test and Laravel doing unneeded things |
149 | if (str_ends_with($uri, ' ')) { |
150 | $uri .= '&dummy=1'; |
151 | } |
152 | $testResponse = $this->get($uri); |
153 | $laravelResponse = $testResponse->baseResponse; |
154 | return $this->handleResponse($laravelResponse); |
155 | } |
156 | |
157 | private function handleResponse(HttpFoundationResponse $laravelResponse): ResponseInterface |
158 | { |
159 | $cookie = $laravelResponse->headers->getCookies( |
160 | ResponseHeaderBag::COOKIES_ARRAY |
161 | )[""]["/"][AddAuthenticationCookie::COOKIE_NAME] ?? null; |
162 | if ($cookie !== null) { |
163 | $cookie = Cookie::fromString($cookie)->getValue(); |
164 | } |
165 | if ($cookie) { |
166 | $this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME] = $cookie; |
167 | } else { |
168 | unset($this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME]); |
169 | } |
170 | |
171 | $psrFactory = new NyholmPsr17Factory(); |
172 | $factory = new PsrHttpFactory($psrFactory, $psrFactory, $psrFactory, $psrFactory); |
173 | return $factory->createResponse($laravelResponse); |
174 | } |
175 | |
176 | protected function getPackageProviders($app): array |
177 | { |
178 | return [ApieServiceProvider::class]; |
179 | } |
180 | |
181 | public function httpRequest(TestRequestInterface $testRequest): ResponseInterface |
182 | { |
183 | $psrRequest = $testRequest->getRequest(); |
184 | $factory = new HttpFoundationFactory(); |
185 | $sfRequest = $factory->createRequest($psrRequest); |
186 | $laravelRequest = Request::createFromBase($sfRequest); |
187 | if (isset($this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME])) { |
188 | $laravelRequest->cookies->set(AddAuthenticationCookie::COOKIE_NAME, $this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME]); |
189 | } |
190 | $laravelResponse = $this->app->make(HttpKernel::class)->handle($laravelRequest); |
191 | return $this->handleResponse($laravelResponse); |
192 | } |
193 | |
194 | public function loginAs(DecryptedAuthenticatedUser $user): void |
195 | { |
196 | $textEncrypter = new TextEncrypter('test'); |
197 | $this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME] = $textEncrypter->encrypt($user->toNative()); |
198 | } |
199 | |
200 | /** |
201 | * Forget that you are logged in. |
202 | */ |
203 | public function logout(): void |
204 | { |
205 | Auth::logout(); |
206 | unset($this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME]); |
207 | } |
208 | |
209 | public function getLoggedInAs(): ?DecryptedAuthenticatedUser |
210 | { |
211 | if (empty($this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME])) { |
212 | return null; |
213 | } |
214 | $textEncrypter = new TextEncrypter('test'); |
215 | return DecryptedAuthenticatedUser::fromNative( |
216 | $textEncrypter->decrypt($this->defaultCookies[AddAuthenticationCookie::COOKIE_NAME]), |
217 | ); |
218 | } |
219 | } |