Passed
Push — master ( e8d625...c24361 )
by Sergei
02:31
created

ContainerInterfaceProxyTest   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 347
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 196
dl 0
loc 347
rs 10
c 0
b 0
f 0
wmc 10

31 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$1 ➔ has() 0 3 1
A hp$3 ➔ get() 0 3 1
A tearDown() 0 4 1
A hp$4 ➔ test1() 0 17 1
A testGetContainerItself() 0 9 1
A testGetAndHasWithNotService() 0 9 1
A testGetAndHasWithWrongId() 0 15 1
A hp$4 ➔ testProxyIsNotNeeded() 0 16 1
A hp$0 ➔ get() 0 3 1
A testImmutability() 0 9 1
A hp$4 ➔ has() 0 3 1
A hp$4 ➔ testBrokenProxyConstructor() 0 16 1
A hp$4 ➔ testHasThrowsExceptionAndErrorInCollectorIsNotEmpty() 0 40 2
A hp$1 ➔ testHasThrowsExceptionButErrorInCollectorIsAbsent() 0 40 2
A testGetAndHas() 0 7 1
A testGetAndHasWithCallableServices() 0 25 1
A testGetWithoutConfig() 0 20 1
testBrokenProxyConstructor() 0 16 ?
test1() 0 17 ?
testHasThrowsExceptionAndErrorInCollectorIsNotEmpty() 0 40 ?
testHasThrowsExceptionButErrorInCollectorIsAbsent() 0 40 ?
A testGetWithArrayConfigWithStringKeys() 0 29 1
testProxyIsNotNeeded() 0 16 ?
createServiceCollector() 0 3 ?
A hp$4 ➔ createServiceCollector() 0 3 1
A hp$4 ➔ createConfig() 0 15 1
createConfig() 0 15 ?
A hp$4 ➔ createContainer() 0 12 1
A hp$4 ➔ test2() 0 17 1
createContainer() 0 12 ?
test2() 0 17 ?
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Tests\Unit\Collector;
6
7
use PHPUnit\Framework\TestCase;
8
use Psr\Container\ContainerExceptionInterface;
9
use Psr\Container\ContainerInterface;
10
use Psr\EventDispatcher\EventDispatcherInterface;
11
use Psr\EventDispatcher\ListenerProviderInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
use stdClass;
15
use Yiisoft\Di\CompositeContainer;
16
use Yiisoft\Di\Container;
17
use Yiisoft\Di\ContainerConfig;
18
use Yiisoft\EventDispatcher\Dispatcher\Dispatcher;
19
use Yiisoft\EventDispatcher\Provider\Provider;
20
use Yiisoft\Files\FileHelper;
21
use Yiisoft\Yii\Debug\Collector\CollectorInterface;
22
use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy;
23
use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig;
24
use Yiisoft\Yii\Debug\Collector\EventCollector;
25
use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy;
26
use Yiisoft\Yii\Debug\Collector\LogCollector;
27
use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy;
28
use Yiisoft\Yii\Debug\Collector\ServiceCollector;
29
use Yiisoft\Yii\Debug\Collector\TimelineCollector;
30
use Yiisoft\Yii\Debug\Tests\Support\Stub\BrokenProxyImplementation;
31
use Yiisoft\Yii\Debug\Tests\Support\Stub\Implementation1;
32
use Yiisoft\Yii\Debug\Tests\Support\Stub\Implementation2;
33
use Yiisoft\Yii\Debug\Tests\Support\Stub\Interface1;
34
use Yiisoft\Yii\Debug\Tests\Support\Stub\Interface2;
35
36
final class ContainerInterfaceProxyTest extends TestCase
37
{
38
    private string $path = 'tests/container-proxy';
39
40
    protected function tearDown(): void
41
    {
42
        parent::tearDown();
43
        FileHelper::removeDirectory($this->path);
44
    }
45
46
    public function testImmutability(): void
47
    {
48
        $containerProxy = new ContainerInterfaceProxy(new Container(ContainerConfig::create()), new ContainerProxyConfig());
49
50
        $this->assertNotSame(
51
            $containerProxy,
52
            $containerProxy->withDecoratedServices(
53
                [
54
                    LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class],
55
                ]
56
            )
57
        );
58
    }
59
60
    public function testGetAndHas(): void
61
    {
62
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
63
64
        $this->assertTrue($containerProxy->isActive());
65
        $this->assertTrue($containerProxy->has(LoggerInterface::class));
66
        $this->assertInstanceOf(LoggerInterfaceProxy::class, $containerProxy->get(LoggerInterface::class));
67
    }
68
69
    public function testGetAndHasWithCallableServices(): void
70
    {
71
        $dispatcherMock = $this->createMock(EventDispatcherInterface::class);
72
        $config = new ContainerProxyConfig(
73
            true,
74
            [
75
                LoggerInterface::class => fn (ContainerInterface $container) => $container->get(LoggerInterfaceProxy::class),
76
                EventDispatcherInterface::class => [
77
                    EventDispatcherInterfaceProxy::class,
78
                    EventCollector::class,
79
                ],
80
            ],
81
            $dispatcherMock,
82
            $this->createServiceCollector(),
83
            $this->path,
84
            ContainerInterfaceProxy::LOG_ARGUMENTS
85
        );
86
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $config);
87
88
        $this->assertTrue($containerProxy->isActive());
89
        $this->assertTrue($containerProxy->has(LoggerInterface::class));
90
91
        $containerProxy->get(LogCollector::class)->startup();
92
        $containerProxy->get(LoggerInterface::class)->log('test', 'test message');
93
        $this->assertNotEmpty($containerProxy->get(LogCollector::class)->getCollected());
94
    }
95
96
    public function testGetWithArrayConfigWithStringKeys(): void
97
    {
98
        $dispatcherMock = $this->createMock(EventDispatcherInterface::class);
99
        $serviceCollector = $this->createServiceCollector();
100
        $serviceCollector->startup(); // activate collector
101
102
        $config = new ContainerProxyConfig(
103
            true,
104
            [
105
                LoggerInterface::class => ['logger' => LoggerInterfaceProxy::class],
106
                EventDispatcherInterface::class => [
107
                    EventDispatcherInterfaceProxy::class,
108
                    EventCollector::class,
109
                ],
110
            ],
111
            $dispatcherMock,
112
            $serviceCollector,
113
            $this->path,
114
            ContainerInterfaceProxy::LOG_ARGUMENTS
115
        );
116
        $container = $this->createContainer();
117
        $containerProxy = new ContainerInterfaceProxy($container, $config);
118
119
        $this->assertTrue($containerProxy->isActive());
120
        $this->assertInstanceOf(LoggerInterface::class, $containerProxy->get(LoggerInterface::class));
121
122
        $containerProxy->get(LoggerInterface::class)->log('test', 'test message');
123
        $this->assertInstanceOf(LoggerInterface::class, $containerProxy->get(LoggerInterface::class));
124
        $this->assertNotEmpty($config->getCollector()->getCollected());
125
    }
126
127
    public function testGetWithoutConfig(): void
128
    {
129
        $dispatcherMock = $this->createMock(EventDispatcherInterface::class);
130
        $config = new ContainerProxyConfig(
131
            true,
132
            [
133
                LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class],
134
                EventDispatcherInterface::class,
135
            ],
136
            $dispatcherMock,
137
            $this->createServiceCollector(),
138
            $this->path,
139
            ContainerInterfaceProxy::LOG_ARGUMENTS
140
        );
141
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $config);
142
143
        $this->assertInstanceOf(EventDispatcherInterface::class, $containerProxy->get(EventDispatcherInterface::class));
144
        $this->assertInstanceOf(
145
            stdClass::class,
146
            $containerProxy->get(EventDispatcherInterface::class)->dispatch(new stdClass())
147
        );
148
    }
149
150
    public function testGetAndHasWithWrongId(): void
151
    {
152
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
153
154
        $this->assertFalse($containerProxy->has(CollectorInterface::class));
155
156
        $this->expectException(ContainerExceptionInterface::class);
157
        $this->expectExceptionMessage(
158
            sprintf(
159
                'No definition or class found or resolvable for "%s" while building "%s".',
160
                CollectorInterface::class,
161
                CollectorInterface::class
162
            )
163
        );
164
        $containerProxy->get(CollectorInterface::class);
165
    }
166
167
    public function testGetContainerItself(): void
168
    {
169
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
170
171
        $this->assertTrue($containerProxy->has(ContainerInterface::class));
172
173
        $container = $containerProxy->get(ContainerInterface::class);
174
        $this->assertNotNull($container);
175
        $this->assertInstanceOf(ContainerInterface::class, $container);
176
    }
177
178
    public function testGetAndHasWithNotService(): void
179
    {
180
        $containerProxy = new ContainerInterfaceProxy($this->createContainer(), $this->createConfig());
181
182
        $this->assertTrue($containerProxy->has(ListenerProviderInterface::class));
183
        $this->assertNotNull($containerProxy->get(ListenerProviderInterface::class));
184
        $this->assertInstanceOf(
185
            ListenerProviderInterface::class,
186
            $containerProxy->get(ListenerProviderInterface::class)
187
        );
188
    }
189
190
    public function testHasThrowsExceptionButErrorInCollectorIsAbsent(): void
191
    {
192
        $container = new CompositeContainer();
193
        $container->attach(new class () implements ContainerInterface {
194
            public function get($id)
195
            {
196
                throw new class () extends \Exception implements ContainerExceptionInterface {
197
                };
198
            }
199
200
            public function has($id): bool
201
            {
202
                throw new class () extends \Exception implements ContainerExceptionInterface {
203
                };
204
            }
205
        });
206
        $container->attach($container);
207
208
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_NOTHING);
209
        $serviceCollector = $config->getCollector();
210
        $serviceCollector->startup();
211
        $containerProxy = new ContainerInterfaceProxy($container, $config);
212
213
        $thrown = null;
214
        try {
215
            $containerProxy->has('123');
216
        } catch (\Throwable $e) {
217
            $thrown = $e;
218
        }
219
220
        $this->assertNotNull($thrown);
221
        $this->assertNotNull($containerProxy->getCurrentError());
222
223
        $data = $serviceCollector->getCollected();
224
        $this->assertCount(1, $data);
225
        $this->assertSame(ContainerInterface::class, $data[0]['service']);
226
        $this->assertSame(CompositeContainer::class, $data[0]['class']);
227
        $this->assertSame('has', $data[0]['method']);
228
        $this->assertSame('failed', $data[0]['status']);
229
        $this->assertNull($data[0]['error']);
230
    }
231
232
    public function testHasThrowsExceptionAndErrorInCollectorIsNotEmpty(): void
233
    {
234
        $container = new CompositeContainer();
235
        $container->attach(new class () implements ContainerInterface {
236
            public function get($id)
237
            {
238
                throw new class () extends \Exception implements ContainerExceptionInterface {
239
                };
240
            }
241
242
            public function has($id): bool
243
            {
244
                throw new class () extends \Exception implements ContainerExceptionInterface {
245
                };
246
            }
247
        });
248
        $container->attach($container);
249
250
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
251
        $serviceCollector = $config->getCollector();
252
        $serviceCollector->startup();
253
        $containerProxy = new ContainerInterfaceProxy($container, $config);
254
255
        $thrown = null;
256
        try {
257
            $containerProxy->has('123');
258
        } catch (\Throwable $e) {
259
            $thrown = $e;
260
        }
261
262
        $this->assertNotNull($thrown);
263
        $this->assertNotNull($containerProxy->getCurrentError());
264
265
        $data = $serviceCollector->getCollected();
266
        $this->assertCount(1, $data);
267
        $this->assertSame(ContainerInterface::class, $data[0]['service']);
268
        $this->assertSame(CompositeContainer::class, $data[0]['class']);
269
        $this->assertSame('has', $data[0]['method']);
270
        $this->assertSame('failed', $data[0]['status']);
271
        $this->assertNotNull($data[0]['error']);
272
    }
273
274
    public function testProxyIsNotNeeded(): void
275
    {
276
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
277
        $config = $config->withDecoratedServices([
278
            Implementation1::class => Implementation1::class,
279
        ]);
280
        $serviceCollector = $config->getCollector();
281
        $serviceCollector->startup();
282
        $container = $this->createContainer([
283
            Implementation1::class => Implementation1::class,
284
        ]);
285
        $containerProxy = new ContainerInterfaceProxy($container, $config);
286
287
        $implementation = $containerProxy->get(Implementation1::class);
288
        $this->assertNotNull($implementation);
289
        $this->assertInstanceOf(Implementation1::class, $implementation);
290
    }
291
292
    public function testBrokenProxyConstructor(): void
293
    {
294
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
295
        $config = $config->withDecoratedServices([
296
            Interface1::class => [BrokenProxyImplementation::class, stdClass::class],
297
        ]);
298
        $serviceCollector = $config->getCollector();
299
        $serviceCollector->startup();
300
        $container = $this->createContainer([
301
            Interface1::class => Implementation1::class,
302
        ]);
303
        $containerProxy = new ContainerInterfaceProxy($container, $config);
304
305
        $implementation = $containerProxy->get(Interface1::class);
306
        $this->assertNotNull($implementation);
307
        $this->assertInstanceOf(Implementation1::class, $implementation);
308
    }
309
310
    public function test1(): void
311
    {
312
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
313
        $config = $config->withDecoratedServices([
314
            Interface2::class => ['getName' => fn() => 'from tests'],
315
        ]);
316
        $serviceCollector = $config->getCollector();
317
        $serviceCollector->startup();
318
        $container = $this->createContainer([
319
            Interface2::class => Implementation2::class,
320
        ]);
321
        $containerProxy = new ContainerInterfaceProxy($container, $config);
322
323
        $implementation = $containerProxy->get(Interface2::class);
324
        $this->assertNotNull($implementation);
325
        $this->assertInstanceOf(Interface2::class, $implementation);
326
        $this->assertSame('from tests', $implementation->getName());
327
    }
328
329
    public function test2(): void
330
    {
331
        $config = $this->createConfig(ContainerInterfaceProxy::LOG_ERROR);
332
        $config = $config->withDecoratedServices([
333
            'test-interface' => ['getName' => fn() => 'from tests'],
334
        ]);
335
        $serviceCollector = $config->getCollector();
336
        $serviceCollector->startup();
337
        $container = $this->createContainer([
338
            'test-interface' => Implementation2::class,
339
        ]);
340
        $containerProxy = new ContainerInterfaceProxy($container, $config);
341
342
        $implementation = $containerProxy->get('test-interface');
343
        $this->assertNotNull($implementation);
344
        $this->assertInstanceOf(Interface2::class, $implementation);
345
        $this->assertSame('from tests', $implementation->getName());
346
    }
347
348
    private function createConfig(int $logLevel = ContainerInterfaceProxy::LOG_ARGUMENTS): ContainerProxyConfig
349
    {
350
        return new ContainerProxyConfig(
351
            true,
352
            [
353
                LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class],
354
                EventDispatcherInterface::class => [
355
                    EventDispatcherInterfaceProxy::class,
356
                    EventCollector::class,
357
                ],
358
            ],
359
            $this->createMock(EventDispatcherInterface::class),
360
            $this->createServiceCollector(),
361
            $this->path,
362
            $logLevel
363
        );
364
    }
365
366
    private function createContainer(array $definitions = []): Container
367
    {
368
        $config = ContainerConfig::create()
369
            ->withDefinitions([
370
                EventDispatcherInterface::class => Dispatcher::class,
371
                ListenerProviderInterface::class => Provider::class,
372
                LoggerInterface::class => NullLogger::class,
373
                LogCollector::class => LogCollector::class,
374
                EventCollector::class => EventCollector::class,
375
                ...$definitions,
376
            ]);
377
        return new Container($config);
378
    }
379
380
    protected function createServiceCollector(): ServiceCollector
381
    {
382
        return new ServiceCollector(new TimelineCollector());
383
    }
384
}
385