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