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