1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Http\HttplugBundle\Tests\Unit\DependencyInjection; |
4
|
|
|
|
5
|
|
|
use Http\Client\HttpClient; |
6
|
|
|
use Http\Client\Plugin\Vcr\Recorder\InMemoryRecorder; |
7
|
|
|
use Http\HttplugBundle\Collector\PluginClientFactoryListener; |
8
|
|
|
use Http\HttplugBundle\DependencyInjection\HttplugExtension; |
9
|
|
|
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; |
10
|
|
|
use Symfony\Component\DependencyInjection\Reference; |
11
|
|
|
use Symfony\Component\HttpKernel\Kernel; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* @author David Buchmann <[email protected]> |
15
|
|
|
* @author Tobias Nyholm <[email protected]> |
16
|
|
|
*/ |
17
|
|
|
class HttplugExtensionTest extends AbstractExtensionTestCase |
18
|
|
|
{ |
19
|
|
|
protected function setUp() |
20
|
|
|
{ |
21
|
|
|
parent::setUp(); |
22
|
|
|
|
23
|
|
|
$this->setParameter('kernel.debug', true); |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
protected function getContainerExtensions() |
27
|
|
|
{ |
28
|
|
|
return [ |
29
|
|
|
new HttplugExtension(), |
30
|
|
|
]; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
public function testConfigLoadDefault() |
34
|
|
|
{ |
35
|
|
|
$this->load(); |
36
|
|
|
|
37
|
|
View Code Duplication |
foreach (['client', 'message_factory', 'uri_factory', 'stream_factory'] as $type) { |
|
|
|
|
38
|
|
|
$this->assertContainerBuilderHasAlias("httplug.$type", "httplug.$type.default"); |
39
|
|
|
} |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
public function testConfigLoadClass() |
43
|
|
|
{ |
44
|
|
|
$this->load([ |
45
|
|
|
'classes' => [ |
46
|
|
|
'client' => 'Http\Adapter\Guzzle6\Client', |
47
|
|
|
], |
48
|
|
|
]); |
49
|
|
|
|
50
|
|
|
$this->assertContainerBuilderHasService('httplug.client.default', 'Http\Adapter\Guzzle6\Client'); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function testConfigLoadService() |
54
|
|
|
{ |
55
|
|
|
$this->load([ |
56
|
|
|
'main_alias' => [ |
57
|
|
|
'client' => 'my_client_service', |
58
|
|
|
'message_factory' => 'my_message_factory_service', |
59
|
|
|
'uri_factory' => 'my_uri_factory_service', |
60
|
|
|
'stream_factory' => 'my_stream_factory_service', |
61
|
|
|
], |
62
|
|
|
]); |
63
|
|
|
|
64
|
|
View Code Duplication |
foreach (['client', 'message_factory', 'uri_factory', 'stream_factory'] as $type) { |
|
|
|
|
65
|
|
|
$this->assertContainerBuilderHasAlias("httplug.$type", "my_{$type}_service"); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
public function testClientPlugins() |
70
|
|
|
{ |
71
|
|
|
$this->load([ |
72
|
|
|
'clients' => [ |
73
|
|
|
'acme' => [ |
74
|
|
|
'factory' => 'httplug.factory.curl', |
75
|
|
|
'plugins' => [ |
76
|
|
|
[ |
77
|
|
|
'decoder' => [ |
78
|
|
|
'use_content_encoding' => false, |
79
|
|
|
], |
80
|
|
|
], |
81
|
|
|
'httplug.plugin.redirect', |
82
|
|
|
[ |
83
|
|
|
'add_host' => [ |
84
|
|
|
'host' => 'http://localhost:8000', |
85
|
|
|
], |
86
|
|
|
], |
87
|
|
|
[ |
88
|
|
|
'content_type' => [ |
89
|
|
|
'skip_detection' => true, |
90
|
|
|
], |
91
|
|
|
], |
92
|
|
|
[ |
93
|
|
|
'header_append' => [ |
94
|
|
|
'headers' => ['X-FOO' => 'bar'], |
95
|
|
|
], |
96
|
|
|
], |
97
|
|
|
[ |
98
|
|
|
'header_defaults' => [ |
99
|
|
|
'headers' => ['X-FOO' => 'bar'], |
100
|
|
|
], |
101
|
|
|
], |
102
|
|
|
[ |
103
|
|
|
'header_set' => [ |
104
|
|
|
'headers' => ['X-FOO' => 'bar'], |
105
|
|
|
], |
106
|
|
|
], |
107
|
|
|
[ |
108
|
|
|
'header_remove' => [ |
109
|
|
|
'headers' => ['X-FOO'], |
110
|
|
|
], |
111
|
|
|
], |
112
|
|
|
[ |
113
|
|
|
'query_defaults' => [ |
114
|
|
|
'parameters' => ['locale' => 'en'], |
115
|
|
|
], |
116
|
|
|
], |
117
|
|
|
[ |
118
|
|
|
'authentication' => [ |
119
|
|
|
'my_basic' => [ |
120
|
|
|
'type' => 'basic', |
121
|
|
|
'username' => 'foo', |
122
|
|
|
'password' => 'bar', |
123
|
|
|
], |
124
|
|
|
], |
125
|
|
|
], |
126
|
|
|
[ |
127
|
|
|
'cache' => [ |
128
|
|
|
'cache_pool' => 'my_cache_pool', |
129
|
|
|
], |
130
|
|
|
], |
131
|
|
|
], |
132
|
|
|
], |
133
|
|
|
], |
134
|
|
|
]); |
135
|
|
|
|
136
|
|
|
$plugins = [ |
137
|
|
|
'httplug.client.acme.plugin.decoder', |
138
|
|
|
'httplug.plugin.redirect', |
139
|
|
|
'httplug.client.acme.plugin.add_host', |
140
|
|
|
'httplug.client.acme.plugin.content_type', |
141
|
|
|
'httplug.client.acme.plugin.header_append', |
142
|
|
|
'httplug.client.acme.plugin.header_defaults', |
143
|
|
|
'httplug.client.acme.plugin.header_set', |
144
|
|
|
'httplug.client.acme.plugin.header_remove', |
145
|
|
|
'httplug.client.acme.plugin.query_defaults', |
146
|
|
|
'httplug.client.acme.authentication.my_basic', |
147
|
|
|
'httplug.client.acme.plugin.cache', |
148
|
|
|
]; |
149
|
|
|
$pluginReferences = array_map(function ($id) { |
150
|
|
|
return new Reference($id); |
151
|
|
|
}, $plugins); |
152
|
|
|
|
153
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
154
|
|
|
foreach ($plugins as $id) { |
155
|
|
|
$this->assertContainerBuilderHasService($id); |
156
|
|
|
} |
157
|
|
|
$this->assertContainerBuilderHasServiceDefinitionWithArgument('httplug.client.acme', 1, $pluginReferences); |
158
|
|
|
$this->assertContainerBuilderHasService('httplug.client.mock'); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @group legacy |
163
|
|
|
*/ |
164
|
|
View Code Duplication |
public function testNoProfilingWhenToolbarIsDisabled() |
|
|
|
|
165
|
|
|
{ |
166
|
|
|
$this->load( |
167
|
|
|
[ |
168
|
|
|
'toolbar' => [ |
169
|
|
|
'enabled' => false, |
170
|
|
|
], |
171
|
|
|
'clients' => [ |
172
|
|
|
'acme' => [ |
173
|
|
|
'factory' => 'httplug.factory.curl', |
174
|
|
|
'plugins' => ['foo'], |
175
|
|
|
], |
176
|
|
|
], |
177
|
|
|
] |
178
|
|
|
); |
179
|
|
|
|
180
|
|
|
$this->verifyProfilingDisabled(); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
View Code Duplication |
public function testNoProfilingWhenNotInDebugMode() |
|
|
|
|
184
|
|
|
{ |
185
|
|
|
$this->setParameter('kernel.debug', false); |
186
|
|
|
$this->load( |
187
|
|
|
[ |
188
|
|
|
'clients' => [ |
189
|
|
|
'acme' => [ |
190
|
|
|
'factory' => 'httplug.factory.curl', |
191
|
|
|
'plugins' => ['foo'], |
192
|
|
|
], |
193
|
|
|
], |
194
|
|
|
] |
195
|
|
|
); |
196
|
|
|
|
197
|
|
|
$this->verifyProfilingDisabled(); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @group legacy |
202
|
|
|
*/ |
203
|
|
View Code Duplication |
public function testProfilingWhenToolbarIsSpecificallyOn() |
|
|
|
|
204
|
|
|
{ |
205
|
|
|
$this->setParameter('kernel.debug', false); |
206
|
|
|
$this->load( |
207
|
|
|
[ |
208
|
|
|
'toolbar' => [ |
209
|
|
|
'enabled' => true, |
210
|
|
|
], |
211
|
|
|
'clients' => [ |
212
|
|
|
'acme' => [ |
213
|
|
|
'factory' => 'httplug.factory.curl', |
214
|
|
|
'plugins' => ['foo'], |
215
|
|
|
], |
216
|
|
|
], |
217
|
|
|
] |
218
|
|
|
); |
219
|
|
|
|
220
|
|
|
$this->assertContainerBuilderHasService(PluginClientFactoryListener::class); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
public function testOverrideProfillingFormatter() |
224
|
|
|
{ |
225
|
|
|
$this->load( |
226
|
|
|
[ |
227
|
|
|
'profiling' => [ |
228
|
|
|
'formatter' => 'acme.formatter', |
229
|
|
|
], |
230
|
|
|
] |
231
|
|
|
); |
232
|
|
|
|
233
|
|
|
$def = $this->container->findDefinition('httplug.collector.formatter'); |
234
|
|
|
$this->assertEquals('acme.formatter', (string) $def->getArgument(0)); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
public function testCachePluginConfigCacheKeyGeneratorReference() |
238
|
|
|
{ |
239
|
|
|
$this->load([ |
240
|
|
|
'plugins' => [ |
241
|
|
|
'cache' => [ |
242
|
|
|
'cache_pool' => 'my_cache_pool', |
243
|
|
|
'config' => [ |
244
|
|
|
'cache_key_generator' => 'header_cache_key_generator', |
245
|
|
|
], |
246
|
|
|
], |
247
|
|
|
], |
248
|
|
|
]); |
249
|
|
|
|
250
|
|
|
$cachePlugin = $this->container->findDefinition('httplug.plugin.cache'); |
251
|
|
|
|
252
|
|
|
$config = $cachePlugin->getArgument(2); |
253
|
|
|
$this->assertArrayHasKey('cache_key_generator', $config); |
254
|
|
|
$this->assertInstanceOf(Reference::class, $config['cache_key_generator']); |
255
|
|
|
$this->assertSame('header_cache_key_generator', (string) $config['cache_key_generator']); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
public function testContentTypePluginAllowedOptions() |
259
|
|
|
{ |
260
|
|
|
$this->load([ |
261
|
|
|
'clients' => [ |
262
|
|
|
'acme' => [ |
263
|
|
|
'plugins' => [ |
264
|
|
|
[ |
265
|
|
|
'content_type' => [ |
266
|
|
|
'skip_detection' => true, |
267
|
|
|
'size_limit' => 200000, |
268
|
|
|
], |
269
|
|
|
], |
270
|
|
|
], |
271
|
|
|
], |
272
|
|
|
], |
273
|
|
|
]); |
274
|
|
|
|
275
|
|
|
$cachePlugin = $this->container->findDefinition('httplug.client.acme.plugin.content_type'); |
276
|
|
|
|
277
|
|
|
$config = $cachePlugin->getArgument(0); |
278
|
|
|
$this->assertEquals([ |
279
|
|
|
'skip_detection' => true, |
280
|
|
|
'size_limit' => 200000, |
281
|
|
|
], $config); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
public function testUsingServiceKeyForClients() |
285
|
|
|
{ |
286
|
|
|
$this->load([ |
287
|
|
|
'clients' => [ |
288
|
|
|
'acme' => [ |
289
|
|
|
'service' => 'my_custom_client', |
290
|
|
|
], |
291
|
|
|
], |
292
|
|
|
]); |
293
|
|
|
|
294
|
|
|
$client = $this->container->getAlias('httplug.client.acme.client'); |
295
|
|
|
$this->assertEquals('my_custom_client', (string) $client); |
296
|
|
|
$this->assertFalse($client->isPublic()); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
private function verifyProfilingDisabled() |
300
|
|
|
{ |
301
|
|
|
$def = $this->container->findDefinition('httplug.client'); |
302
|
|
|
$this->assertTrue(is_subclass_of($def->getClass(), HttpClient::class)); |
|
|
|
|
303
|
|
|
$arguments = $def->getArguments(); |
304
|
|
|
|
305
|
|
|
if (isset($arguments[3])) { |
306
|
|
|
$this->assertEmpty( |
307
|
|
|
$arguments[3], |
308
|
|
|
'Parameter 3 to the PluginClient must not contain any debug_plugin information when profiling is disabled' |
309
|
|
|
); |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
public function testClientShouldHaveDefaultVisibility() |
314
|
|
|
{ |
315
|
|
|
$this->load([ |
316
|
|
|
'clients' => [ |
317
|
|
|
'acme' => [], |
318
|
|
|
], |
319
|
|
|
]); |
320
|
|
|
|
321
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
322
|
|
|
|
323
|
|
|
if (version_compare(Kernel::VERSION, '3.4', '>=')) { |
324
|
|
|
// Symfony made services private by default starting from 3.4 |
325
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme')->isPublic()); |
326
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme')->isPrivate()); |
327
|
|
|
} else { |
328
|
|
|
// Legacy Symfony |
329
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme')->isPublic()); |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
|
333
|
|
View Code Duplication |
public function testFlexibleClientShouldBePrivateByDefault() |
|
|
|
|
334
|
|
|
{ |
335
|
|
|
$this->load([ |
336
|
|
|
'clients' => [ |
337
|
|
|
'acme' => [ |
338
|
|
|
'flexible_client' => true, |
339
|
|
|
], |
340
|
|
|
], |
341
|
|
|
]); |
342
|
|
|
|
343
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
344
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme.flexible')->isPublic()); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
View Code Duplication |
public function testHttpMethodsClientShouldBePrivateByDefault() |
|
|
|
|
348
|
|
|
{ |
349
|
|
|
$this->load([ |
350
|
|
|
'clients' => [ |
351
|
|
|
'acme' => [ |
352
|
|
|
'http_methods_client' => true, |
353
|
|
|
], |
354
|
|
|
], |
355
|
|
|
]); |
356
|
|
|
|
357
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
358
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme.http_methods')->isPublic()); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
View Code Duplication |
public function testBatchClientShouldBePrivateByDefault() |
|
|
|
|
362
|
|
|
{ |
363
|
|
|
$this->load([ |
364
|
|
|
'clients' => [ |
365
|
|
|
'acme' => [ |
366
|
|
|
'batch_client' => true, |
367
|
|
|
], |
368
|
|
|
], |
369
|
|
|
]); |
370
|
|
|
|
371
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
372
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme.batch_client')->isPublic()); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
View Code Duplication |
public function testClientCanBePublic() |
|
|
|
|
376
|
|
|
{ |
377
|
|
|
$this->load([ |
378
|
|
|
'clients' => [ |
379
|
|
|
'acme' => [ |
380
|
|
|
'public' => true, |
381
|
|
|
], |
382
|
|
|
], |
383
|
|
|
]); |
384
|
|
|
|
385
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
386
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme')->isPublic()); |
387
|
|
|
|
388
|
|
|
if (version_compare(Kernel::VERSION, '3.4', '>=')) { |
389
|
|
|
// Symfony made services private by default starting from 3.4 |
390
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme')->isPrivate()); |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|
394
|
|
View Code Duplication |
public function testFlexibleClientCanBePublic() |
|
|
|
|
395
|
|
|
{ |
396
|
|
|
$this->load([ |
397
|
|
|
'clients' => [ |
398
|
|
|
'acme' => [ |
399
|
|
|
'public' => true, |
400
|
|
|
'flexible_client' => true, |
401
|
|
|
], |
402
|
|
|
], |
403
|
|
|
]); |
404
|
|
|
|
405
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
406
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme.flexible')->isPublic()); |
407
|
|
|
|
408
|
|
|
if (version_compare(Kernel::VERSION, '3.4', '>=')) { |
409
|
|
|
// Symfony made services private by default starting from 3.4 |
410
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme.flexible')->isPrivate()); |
411
|
|
|
} |
412
|
|
|
} |
413
|
|
|
|
414
|
|
View Code Duplication |
public function testHttpMethodsClientCanBePublic() |
|
|
|
|
415
|
|
|
{ |
416
|
|
|
$this->load([ |
417
|
|
|
'clients' => [ |
418
|
|
|
'acme' => [ |
419
|
|
|
'public' => true, |
420
|
|
|
'http_methods_client' => true, |
421
|
|
|
], |
422
|
|
|
], |
423
|
|
|
]); |
424
|
|
|
|
425
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
426
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme.http_methods')->isPublic()); |
427
|
|
|
|
428
|
|
|
if (version_compare(Kernel::VERSION, '3.4', '>=')) { |
429
|
|
|
// Symfony made services private by default starting from 3.4 |
430
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme.http_methods')->isPrivate()); |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
434
|
|
View Code Duplication |
public function testBatchClientCanBePublic() |
|
|
|
|
435
|
|
|
{ |
436
|
|
|
$this->load([ |
437
|
|
|
'clients' => [ |
438
|
|
|
'acme' => [ |
439
|
|
|
'public' => true, |
440
|
|
|
'batch_client' => true, |
441
|
|
|
], |
442
|
|
|
], |
443
|
|
|
]); |
444
|
|
|
|
445
|
|
|
$this->assertContainerBuilderHasService('httplug.client.acme'); |
446
|
|
|
$this->assertTrue($this->container->getDefinition('httplug.client.acme.batch_client')->isPublic()); |
447
|
|
|
|
448
|
|
|
if (version_compare(Kernel::VERSION, '3.4', '>=')) { |
449
|
|
|
// Symfony made services private by default starting from 3.4 |
450
|
|
|
$this->assertFalse($this->container->getDefinition('httplug.client.acme.batch_client')->isPrivate()); |
451
|
|
|
} |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* @dataProvider provideVcrPluginConfig |
456
|
|
|
* @group vcr-plugin |
457
|
|
|
*/ |
458
|
|
|
public function testVcrPluginConfiguration(array $config, array $services, array $arguments = []) |
459
|
|
|
{ |
460
|
|
|
$prefix = 'httplug.client.acme.vcr'; |
461
|
|
|
$this->load(['clients' => ['acme' => ['plugins' => [['vcr' => $config]]]]]); |
462
|
|
|
$this->assertContainerBuilderHasService('httplug.plugin.vcr.recorder.in_memory', InMemoryRecorder::class); |
463
|
|
|
|
464
|
|
|
foreach ($services as $service) { |
465
|
|
|
$this->assertContainerBuilderHasService($prefix.'.'.$service); |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
foreach ($arguments as $id => $args) { |
469
|
|
|
foreach ($args as $index => $value) { |
470
|
|
|
$this->assertContainerBuilderHasServiceDefinitionWithArgument($prefix.'.'.$id, $index, $value); |
471
|
|
|
} |
472
|
|
|
} |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* @group vcr-plugin |
477
|
|
|
*/ |
478
|
|
|
public function testIsNotLoadedUnlessNeeded() |
479
|
|
|
{ |
480
|
|
|
$this->load(['clients' => ['acme' => ['plugins' => []]]]); |
481
|
|
|
$this->assertContainerBuilderNotHasService('httplug.plugin.vcr.recorder.in_memory'); |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
public function provideVcrPluginConfig() |
485
|
|
|
{ |
486
|
|
|
$config = [ |
487
|
|
|
'mode' => 'record', |
488
|
|
|
'recorder' => 'in_memory', |
489
|
|
|
'naming_strategy' => 'app.naming_strategy', |
490
|
|
|
]; |
491
|
|
|
yield [$config, ['record']]; |
492
|
|
|
|
493
|
|
|
$config['mode'] = 'replay'; |
494
|
|
|
yield [$config, ['replay']]; |
495
|
|
|
|
496
|
|
|
$config['mode'] = 'replay_or_record'; |
497
|
|
|
yield [$config, ['replay', 'record']]; |
498
|
|
|
|
499
|
|
|
$config['recorder'] = 'filesystem'; |
500
|
|
|
$config['fixtures_directory'] = __DIR__; |
501
|
|
|
unset($config['naming_strategy']); |
502
|
|
|
|
503
|
|
|
yield [$config, ['replay', 'record', 'recorder', 'naming_strategy'], ['replay' => [2 => false]]]; |
504
|
|
|
|
505
|
|
|
$config['naming_strategy_options'] = [ |
506
|
|
|
'hash_headers' => ['X-FOO'], |
507
|
|
|
'hash_body_methods' => ['PATCH'], |
508
|
|
|
]; |
509
|
|
|
|
510
|
|
|
yield [ |
511
|
|
|
$config, |
512
|
|
|
['replay', 'record', 'recorder', 'naming_strategy'], |
513
|
|
|
['naming_strategy' => [$config['naming_strategy_options']]], |
514
|
|
|
]; |
515
|
|
|
} |
516
|
|
|
} |
517
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.