Passed
Pull Request — 8.x-2.x (#58)
by Frédéric G.
03:34
created

ControllerTest::verifyReports()   C

Complexity

Conditions 9
Paths 96

Size

Total Lines 92

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 96
nop 1
dl 0
loc 92
rs 6.6189
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Drupal\Tests\mongodb_watchdog\Functional;
6
7
use Drupal\Core\Logger\RfcLogLevel;
8
use Drupal\Core\Site\Settings;
9
use Drupal\Core\StringTranslation\StringTranslationTrait;
10
use Drupal\mongodb\MongoDb;
11
use Drupal\mongodb_watchdog\Logger;
12
use Drupal\Tests\BrowserTestBase;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LogLevel;
15
use Symfony\Component\HttpFoundation\Response;
16
17
/**
18
 * Class ControllerTest.
19
 *
20
 * @group MongoDB
21
 */
22
class ControllerTest extends BrowserTestBase {
23
  use StringTranslationTrait;
24
25
  const DEFAULT_URI = 'mongodb://localhost:27017';
26
  const CLIENT_TEST_ALIAS = 'test';
27
28
  const DB_DEFAULT_ALIAS = 'default';
29
30
  const PATH_DENIED = '/admin/reports/mongodb/watchdog/access-denied';
31
  const PATH_EVENT_BASE = "/admin/reports/mongodb/watchdog/";
32
  const PATH_NOT_FOUND = '/admin/reports/mongodb/watchdog/page-not-found';
33
  const PATH_OVERVIEW = 'admin/reports/mongodb/watchdog';
34
35
  /**
36
   * Map of PSR3 log constants to RFC 5424 log constants.
37
   *
38
   * @var array
39
   */
40
  const LEVEL_TRANSLATION = [
41
    LogLevel::EMERGENCY => RfcLogLevel::EMERGENCY,
42
    LogLevel::ALERT => RfcLogLevel::ALERT,
43
    LogLevel::CRITICAL => RfcLogLevel::CRITICAL,
44
    LogLevel::ERROR => RfcLogLevel::ERROR,
45
    LogLevel::WARNING => RfcLogLevel::WARNING,
46
    LogLevel::NOTICE => RfcLogLevel::NOTICE,
47
    LogLevel::INFO => RfcLogLevel::INFO,
48
    LogLevel::DEBUG => RfcLogLevel::DEBUG,
49
  ];
50
51
  /**
52
   * These modules need to be enabled.
53
   *
54
   * @var array
55
   */
56
  protected static $modules = [
57
    // Needed to check admin/help/mongodb.
58
    'help',
59
    MongoDb::MODULE,
60
    Logger::MODULE,
61
  ];
62
63
  /**
64
   * An administrator account.
65
   *
66
   * @var \Drupal\user\Entity\User
67
   */
68
  protected $adminUser;
69
70
  /**
71
   * A basic authenticated user account.
72
   *
73
   * @var \Drupal\user\Entity\User
74
   */
75
  protected $anyUser;
76
77
  /**
78
   * An administrator-type user account, but not an administrator.
79
   *
80
   * @var \Drupal\user\Entity\User
81
   */
82
  protected $bigUser;
83
84
  /**
85
   * The event templates collection.
86
   *
87
   * @var \MongoDB\Collection
88
   */
89
  protected $collection;
90
91
  /**
92
   * The default theme, needed after 8.8.0.
93
   *
94
   * @var string
95
   *
96
   * @see https://www.drupal.org/node/3083055
97
   */
98
  protected $defaultTheme = 'stark';
99
100
  /**
101
   * The time the test started, simulating a request time.
102
   *
103
   * @var int
104
   */
105
  protected $requestTime;
106
107
  /**
108
   * The site base URI.
109
   *
110
   * @var string
111
   */
112
  protected $uri;
113
114
  /**
115
   * Remove all Drupal markup placeholders.
116
   *
117
   * @param string $message
118
   *   The raw message.
119
   *
120
   * @return string
121
   *   The replacement message.
122
   */
123
  protected static function neuter(string $message): string {
124
    return str_replace(['{', '}', '@', '%', ':'], '', $message);
125
  }
126
127
  /**
128
   * {@inheritdoc}
129
   *
130
   * Configure settings and create users with specific permissions.
131
   *
132
   * @see \Drupal\Tests\mongodb_watchdog\Functional\ControllerTest::writeSettings()
133
   */
134
  public function setUp() {
135
    // $_ENV if it comes from phpunit.xml <env>
136
    // $_SERVER if it comes from the phpunit command line environment.
137
    $this->uri = $_ENV['MONGODB_URI']
138
      ?? $_SERVER['MONGODB_URI']
139
      ?? static::DEFAULT_URI;
140
141
    // This line customizes the parent site; ::writeSettings the child site.
142
    $this->settings = new Settings([
143
      MongoDb::MODULE => $this->getSettingsArray(),
144
    ]);
145
146
    parent::setUp();
147
148
    // Create users.
149
    $this->adminUser = $this->drupalCreateUser([], 'test_admin', TRUE);
150
    $this->bigUser = $this->drupalCreateUser([
151
      'administer site configuration',
152
      'access administration pages',
153
      'access site reports',
154
      'administer users',
155
    ], 'test_honcho');
156
    $this->anyUser = $this->drupalCreateUser([
157
      'access content',
158
    ], 'test_lambda');
159
160
    $this->requestTime = $this->container
161
      ->get('datetime.time')
162
      ->getCurrentTime();
163
164
    try {
165
      $this->collection = $this->container
166
        ->get(MongoDb::SERVICE_DB_FACTORY)
167
        ->get(Logger::DB_LOGGER)
168
        ->selectCollection(Logger::TEMPLATE_COLLECTION);
169
    }
170
    catch (\Exception $e) {
171
      $this->collection = NULL;
172
    }
173
    $this->assertNotNull($this->collection, (string) $this->t('Access MongoDB watchdog collection'));
174
  }
175
176
  /**
177
   * {@inheritdoc}
178
   */
179
  public function tearDown() {
180
    // Get the database before container is torn down.
181
    $database = $this->container
182
      ->get(MongoDb::SERVICE_DB_FACTORY)
183
      ->get(Logger::DB_LOGGER);
184
185
    // Might trigger some more log insertions, so do not drop yet.
186
    parent::tearDown();
187
188
    $database->drop();
189
  }
190
191
  /**
192
   * Rewrites the settings.php file of the test site.
193
   *
194
   * @param array $settings
195
   *   An array of settings to write out, in the format expected by
196
   *   drupal_rewrite_settings().
197
   *
198
   * @throws \Exception
199
   *
200
   * @see \Drupal\Core\Test\FunctionalTestSetupTrait::writeSettings()
201
   */
202
  protected function writeSettings(array $settings) {
203
    // Taken from trait.
204
    include_once DRUPAL_ROOT . '/core/includes/install.inc';
205
    $filename = $this->siteDirectory . '/settings.php';
206
207
    // Customizations.
208
    $settings['settings'] += [
209
      MongoDb::MODULE => (object) [
210
        'value' => $this->getSettingsArray(),
211
        'required' => TRUE,
212
      ],
213
    ];
214
215
    // End of code taken from trait again.
216
    // system_requirements() removes write permissions from settings.php
217
    // whenever it is invoked.
218
    // Not using File API; a potential error must trigger a PHP warning.
219
    chmod($filename, 0666);
220
    drupal_rewrite_settings($settings, $filename);
221
  }
222
223
  /**
224
   * Prepare the Settings from a base set of MongoDB settings.
225
   *
226
   * @return array
227
   *   A settings array only containing MongoDB-related settings.
228
   */
229
  protected function getSettingsArray() : array {
230
    return [
231
      'clients' => [
232
        static::CLIENT_TEST_ALIAS => [
233
          'uri' => $this->uri,
234
          'uriOptions' => [],
235
          'driverOptions' => [],
236
        ],
237
      ],
238
      'databases' => [
239
        static::DB_DEFAULT_ALIAS => [static::CLIENT_TEST_ALIAS, $this->getDatabasePrefix()],
240
        Logger::DB_LOGGER => [static::CLIENT_TEST_ALIAS, $this->getDatabasePrefix()],
241
      ],
242
    ];
243
  }
244
245
  /**
246
   * Getter for the test database prefix.
247
   *
248
   * @return string
249
   *   The prefix.
250
   *
251
   * @see \Drupal\KernelTests\KernelTestBase::getDatabasePrefix()
252
   */
253
  protected function getDatabasePrefix() : string {
254
    return $this->databasePrefix ?? '';
255
  }
256
257
  /**
258
   * Get the log entry information form the page.
259
   *
260
   * @return array
261
   *   List of entries and their information.
262
   */
263
  protected function getLogEntries() : array {
264
    $entries = [];
265
    if ($table = $this->getLogsEntriesTable()) {
266
      /** @var \Behat\Mink\Element\NodeElement $row */
267
      foreach ($table as $row) {
268
        /** @var \Behat\Mink\Element\NodeElement[] $cells */
269
        $cells = $row->findAll('css', 'td');
270
        $entries[] = [
271
          'severity' => $this->getSeverityConstant($cells[2]->getAttribute('class')),
272
          'type' => $cells[3]->getText(),
273
          'message' => $cells[4]->getText(),
274
        ];
275
      }
276
    }
277
    return $entries;
278
  }
279
280
  /**
281
   * Gets the watchdog severity constant corresponding to the CSS class.
282
   *
283
   * @param string $class
284
   *   CSS class attribute.
285
   *
286
   * @return int|null
287
   *   The watchdog severity constant or NULL if not found.
288
   */
289
  protected function getSeverityConstant(string $class) : int {
290
    // Class: "mongodb-watchdog__severity--(level)", prefix length = 28.
291
    $level = substr($class, 28);
292
    return static::LEVEL_TRANSLATION[$level];
293
  }
294
295
  /**
296
   * Find the Logs table in the DOM.
297
   *
298
   * @return \Behat\Mink\Element\NodeElement[]
299
   *   The return value of a xpath search.
300
   */
301
  protected function getLogsEntriesTable() : array {
302
    return $this->xpath('.//table/tbody/tr');
303
  }
304
305
  /**
306
   * Asserts that the counts for displayed entries match the expected counts.
307
   *
308
   * @param array $types
309
   *   The type information to compare against.
310
   */
311
  protected function assertTypeCount(array $types) {
312
    $entries = $this->getLogEntries();
313
    $reducer = function ($accu, $curr) {
314
      $accu[$curr['type'] . '-' . $curr['severity']] = [$curr['type'], $curr['severity']];
315
      return $accu;
316
    };
317
    $actual = array_reduce($entries, $reducer, []);
318
    $expected = array_reduce($types, $reducer, []);
319
    $this->assertEquals($expected, $actual, "Inserted events are found on page");
320
  }
321
322
  /**
323
   * Generate dblog entries.
324
   *
325
   * @param \Psr\Log\LoggerInterface $logger
326
   *   The mongodb.logger service.
327
   * @param int $count
328
   *   Number of log entries to generate.
329
   * @param string $type
330
   *   The type of watchdog entry.
331
   * @param int $severity
332
   *   The severity of the watchdog entry.
333
   */
334
  private function insertLogEntries(
335
    LoggerInterface $logger,
336
    int $count,
337
    string $type = 'custom',
338
    int $severity = RfcLogLevel::EMERGENCY
339
  ) {
340
    $ip = '::1';
341
    $context = [
342
      'channel'     => $type,
343
      'link'        => NULL,
344
      'user'        => ['uid' => $this->bigUser->id()],
345
      'request_uri' => "http://[$ip]/",
346
      'referer'     => $_SERVER['HTTP_REFERER'] ?? '',
347
      'ip'          => $ip,
348
      'timestamp'   => $this->requestTime,
349
    ];
350
    $message = $this->randomString();
351
    for ($i = 0; $i < $count; $i++) {
352
      $logger->log($severity, $message, $context);
353
    }
354
  }
355
356
  /**
357
   * Verify the logged-in user has the desired access to the log report.
358
   *
359
   * @param int $statusCode
360
   *   HTTP status code.
361
   *
362
   * @throws \Behat\Mink\Exception\ExpectationException
363
   * @throws \Behat\Mink\Exception\ResponseTextException
364
   *
365
   * The first of the assertions would really belong in a functional test for
366
   * the mongodb module. But until it gets a functional test, keeping it here
367
   * saves some test running time over having one more functional test in
368
   * mongodb module just for this.
369
   */
370
  private function verifyReports($statusCode = Response::HTTP_OK) {
371
    // View MongoDB help page.
372
    $this->drupalGet('/admin/help');
373
    $session = $this->assertSession();
374
    $session->statusCodeEquals($statusCode);
375
    if ($statusCode == Response::HTTP_OK) {
376
      $session->pageTextContains('MongoDB');
377
    }
378
379
    $this->drupalGet('/admin/help/mongodb');
380
    $session = $this->assertSession();
381
    $session->statusCodeEquals($statusCode);
382
    if ($statusCode == Response::HTTP_OK) {
383
      // DBLog help was displayed.
384
      $session->pageTextContains('implements a generic interface');
385
    }
386
387
    // View MongoDB watchdog overview report.
388
    $this->drupalGet(static::PATH_OVERVIEW);
389
    $session = $this->assertSession();
390
    $session->statusCodeEquals($statusCode);
391
    if ($statusCode == Response::HTTP_OK) {
392
      // MongoDB watchdog report was displayed.
393
      $expectedTexts = [
394
        'Recent log messages in MongoDB',
395
        'Filter log messages',
396
        'Type',
397
        'Severity',
398
        'Latest',
399
        'Severity',
400
        'Message',
401
        'Source',
402
      ];
403
      foreach ($expectedTexts as $expectedText) {
404
        $session->pageTextContains($expectedText);
405
      }
406
    }
407
408
    // View MongoDB watchdog page-not-found report.
409
    $this->drupalGet(self::PATH_NOT_FOUND);
410
    $session = $this->assertSession();
411
    $session->statusCodeEquals($statusCode);
412
    if ($statusCode == Response::HTTP_OK) {
413
      // MongoDB watchdog page-not-found report was displayed.
414
      $session->pageTextContains("Top 'page not found' errors in MongoDB");
415
    }
416
417
    // View MongoDB watchdog access-denied report.
418
    $this->drupalGet(static::PATH_DENIED);
419
    $session = $this->assertSession();
420
    $session->statusCodeEquals($statusCode);
421
    if ($statusCode == Response::HTTP_OK) {
422
      // MongoDB watchdog access-denied report was displayed.
423
      $session->pageTextContains("Top 'access denied' errors in MongoDB");
424
    }
425
426
    // Create an event to ensure an event page exists, using the standard PSR-3
427
    // service instead of the Drupal logger channel to ensure getting this
428
    // logger with its specific features.
429
    $expectedMessage = $this->randomString(32);
430
    /** @var \Drupal\mongodb_watchdog\Logger $logger */
431
    $logger = $this->container->get(Logger::SERVICE_LOGGER);
432
    $logger->info($expectedMessage, ['with' => 'context']);
433
434
    $selector = ['message' => $expectedMessage];
435
    $event = $logger->templateCollection()
436
      ->findOne($selector, MongoDb::ID_PROJECTION);
437
    $eventId = $event['_id'];
438
439
    // View MongoDB Watchdog event page.
440
    $this->drupalGet(static::PATH_EVENT_BASE . $eventId);
441
    $session = $this->assertSession();
442
    $session->statusCodeEquals($statusCode);
443
    // MongoDB watchdog event page was displayed.
444
    if ($statusCode == Response::HTTP_OK) {
445
      $expectedTexts = [
446
        'Event template',
447
        'ID',
448
        'Changed',
449
        'Count',
450
        'Type',
451
        'Message',
452
        'Severity',
453
        $eventId,
454
        'Event occurrences',
455
        $expectedMessage,
456
      ];
457
      foreach ($expectedTexts as $expectedText) {
458
        $session->pageTextContains($expectedText);
459
      }
460
    }
461
  }
462
463
  /**
464
   * The access and contents of the admin/reports/mongodb/watchdog[/*] pages.
465
   *
466
   * @TODO verifyRowLimit(), verifyCron(), verifyEvents() as per DbLog.
467
   */
468
  public function testLoggerReportsAccess() {
469
    $expectations = [
470
      [$this->adminUser, Response::HTTP_OK],
471
      [$this->bigUser, Response::HTTP_OK],
472
      [$this->anyUser, Response::HTTP_FORBIDDEN],
473
    ];
474
    /** @var \Drupal\user\Entity\User $account */
475
    foreach ($expectations as $expectation) {
476
      [$account, $statusCode] = $expectation;
0 ignored issues
show
Bug introduced by
The variable $account does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $statusCode does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
477
      $this->drupalLogin($account);
478
      $this->verifyReports($statusCode);
479
    }
480
  }
481
482
  /**
483
   * Test the UI clearing feature.
484
   */
485
  public function testLoggerAddAndUiClear() {
486
    // Drop the logger database to ensure no collections.
487
    $this->container->get(MongoDb::SERVICE_DB_FACTORY)
488
      ->get(Logger::DB_LOGGER)
489
      ->drop();
490
491
    /** @var \Drupal\Core\Logger\LoggerChannelInterface $loggerChannel */
492
    $loggerChannel = $this->container->get(Logger::SERVICE_CHANNEL);
493
    // Add a watchdog entry. Be sure not to include placeholder delimiters.
494
    $message = static::neuter($this->randomString(32));
495
    $loggerChannel->notice($message);
496
497
    // Make sure the collections were updated.
498
    $logger = $this->container->get(Logger::SERVICE_LOGGER);
499
    $templates = $logger->templateCollection();
500
    $this->assertEquals(1, MongoDb::countCollection($templates),
501
      'Logging created templates collection and added a template to it.');
502
503
    $template = $templates->findOne(['message' => $message], MongoDb::ID_PROJECTION);
504
    $this->assertNotNull($template, "Logged message was found: [${message}]");
505
    $templateId = $template['_id'];
506
    $events = $logger->eventCollection($templateId);
507
    $this->assertEquals(1, MongoDb::countCollection($events),
508
      'Logging created events collection and added a template to it.');
509
510
    // Login the admin user.
511
    $this->drupalLogin($this->adminUser);
512
    // Now post to clear the db table.
513
    $this->drupalPostForm('admin/reports/mongodb/confirm', [], 'Confirm');
514
515
    // Make the sure logs were dropped. After a UI clear, the templates
516
    // collection should exist, since it is recreated as a capped collection as
517
    // part of the clear, but be empty, and there should be no event collection.
518
    $count = MongoDb::countCollection($templates);
519
    $failMessage = 'Logger templates collection was cleared';
520
    if ($count > 0) {
521
      $options = ['projection' => ['_id' => 0, 'message' => 1]];
522
      $messages = iterator_to_array($templates->find([], $options));
523
      $failMessage = "Logger templates collection still contains messages: "
524
        . json_encode($messages);
525
    }
526
    $this->assertEquals(0, $count, $failMessage);
527
    $this->assertFalse($logger->eventCollections()->valid(),
528
      "Event collections were dropped");
529
  }
530
531
  /**
532
   * Test the dblog filter on admin/reports/dblog.
533
   */
534
  public function testFilter() {
535
    $this->drupalLogin($this->bigUser);
536
537
    // Clear log to ensure that only generated entries are found.
538
    $database = $this->container
539
      ->get(MongoDb::SERVICE_DB_FACTORY)
540
      ->get(Logger::DB_LOGGER);
541
    $database->drop();
542
543
    $logger = $this->container->get(Logger::SERVICE_LOGGER);
544
545
    // Generate watchdog entries.
546
    $typeNames = [];
547
    $types = [];
548
    for ($i = 0; $i < 3; $i++) {
549
      $typeNames[] = $typeName = $this->randomMachineName();
550
      $severity = RfcLogLevel::EMERGENCY;
551
      for ($j = 0; $j < 3; $j++) {
552
        $types[] = $type = [
553
          'count' => mt_rand(1, 5),
554
          'type' => $typeName,
555
          'severity' => $severity++,
556
        ];
557
        $this->insertLogEntries($logger, $type['count'], $type['type'], $type['severity']);
558
      }
559
    }
560
    // View the dblog.
561
    $this->drupalGet(self::PATH_OVERVIEW);
562
563
    // Confirm all the entries are displayed.
564
    $this->assertTypeCount($types);
565
566
    // Filter by each type and confirm that entries with various severities are
567
    // displayed.
568
    foreach ($typeNames as $typeName) {
569
      $edit = [
570
        'type[]' => [$typeName],
571
      ];
572
      $this->drupalPostForm(NULL, $edit, 'Filter');
573
574
      // Check whether the displayed event templates match our filter.
575
      $filteredTypes = array_filter($types, function (array $type) use ($typeName) {
576
        return $type['type'] === $typeName;
577
      });
578
      $this->assertTypeCount($filteredTypes);
579
    }
580
581
    // Set filter to match each of the combined filter sets and confirm the
582
    // entries displayed.
583
    foreach ($types as $type) {
584
      $edit = [
585
        'type[]' => $typeType = $type['type'],
586
        'severity[]' => $typeSeverity = $type['severity'],
587
      ];
588
      $this->drupalPostForm(NULL, $edit, 'Filter');
589
590
      $filteredTypes = array_filter($types, function (array $type) use ($typeType, $typeSeverity) {
591
        return $type['type'] === $typeType && $type['severity'] == $typeSeverity;
592
      });
593
594
      $this->assertTypeCount($filteredTypes);
595
    }
596
  }
597
598
}
599