View source
<?php
declare (strict_types=1);
namespace Drupal\Tests\mongodb_watchdog\Functional;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Site\Settings;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\mongodb\MongoDb;
use Drupal\mongodb_watchdog\Logger;
use Drupal\Tests\BrowserTestBase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\HttpFoundation\Response;
class ControllerTest extends BrowserTestBase {
use StringTranslationTrait;
const DEFAULT_URI = 'mongodb://localhost:27017';
const CLIENT_TEST_ALIAS = 'test';
const DB_DEFAULT_ALIAS = 'default';
const PATH_DENIED = '/admin/reports/mongodb/watchdog/access-denied';
const PATH_EVENT_BASE = "/admin/reports/mongodb/watchdog/";
const PATH_NOT_FOUND = '/admin/reports/mongodb/watchdog/page-not-found';
const PATH_OVERVIEW = 'admin/reports/mongodb/watchdog';
const LEVEL_TRANSLATION = [
LogLevel::EMERGENCY => RfcLogLevel::EMERGENCY,
LogLevel::ALERT => RfcLogLevel::ALERT,
LogLevel::CRITICAL => RfcLogLevel::CRITICAL,
LogLevel::ERROR => RfcLogLevel::ERROR,
LogLevel::WARNING => RfcLogLevel::WARNING,
LogLevel::NOTICE => RfcLogLevel::NOTICE,
LogLevel::INFO => RfcLogLevel::INFO,
LogLevel::DEBUG => RfcLogLevel::DEBUG,
];
protected static $modules = [
'help',
MongoDb::MODULE,
Logger::MODULE,
];
protected $adminUser;
protected $anyUser;
protected $bigUser;
protected $collection;
protected $defaultTheme = 'stark';
protected $requestTime;
protected $uri;
protected static function neuter(string $message) : string {
return str_replace([
'{',
'}',
'@',
'%',
':',
], '', $message);
}
public function setUp() : void {
$this->uri = $_ENV['MONGODB_URI'] ?? $_SERVER['MONGODB_URI'] ?? static::DEFAULT_URI;
$this->settings = new Settings([
MongoDb::MODULE => $this
->getSettingsArray(),
]);
parent::setUp();
$this->adminUser = $this
->drupalCreateUser([], 'test_admin', TRUE);
$this->bigUser = $this
->drupalCreateUser([
'administer site configuration',
'access administration pages',
'access site reports',
'administer users',
], 'test_honcho');
$this->anyUser = $this
->drupalCreateUser([
'access content',
], 'test_lambda');
$this->requestTime = $this->container
->get('datetime.time')
->getCurrentTime();
try {
$this->collection = $this->container
->get(MongoDb::SERVICE_DB_FACTORY)
->get(Logger::DB_LOGGER)
->selectCollection(Logger::TEMPLATE_COLLECTION);
} catch (\Exception $e) {
$this->collection = NULL;
}
$this
->assertNotNull($this->collection, (string) $this
->t('Access MongoDB watchdog collection'));
}
public function tearDown() : void {
$database = $this->container
->get(MongoDb::SERVICE_DB_FACTORY)
->get(Logger::DB_LOGGER);
parent::tearDown();
$database
->drop();
}
protected function writeSettings(array $settings) {
include_once DRUPAL_ROOT . '/core/includes/install.inc';
$filename = $this->siteDirectory . '/settings.php';
$settings['settings'] += [
MongoDb::MODULE => (object) [
'value' => $this
->getSettingsArray(),
'required' => TRUE,
],
];
chmod($filename, 0666);
drupal_rewrite_settings($settings, $filename);
}
protected function getSettingsArray() : array {
return [
'clients' => [
static::CLIENT_TEST_ALIAS => [
'uri' => $this->uri,
'uriOptions' => [],
'driverOptions' => [],
],
],
'databases' => [
static::DB_DEFAULT_ALIAS => [
static::CLIENT_TEST_ALIAS,
$this
->getDatabasePrefix(),
],
Logger::DB_LOGGER => [
static::CLIENT_TEST_ALIAS,
$this
->getDatabasePrefix(),
],
],
];
}
protected function getDatabasePrefix() : string {
return $this->databasePrefix ?? '';
}
protected function getLogEntries() : array {
$entries = [];
if ($table = $this
->getLogsEntriesTable()) {
foreach ($table as $row) {
$cells = $row
->findAll('css', 'td');
$entries[] = [
'severity' => $this
->getSeverityConstant($cells[2]
->getAttribute('class')),
'type' => $cells[3]
->getText(),
'message' => $cells[4]
->getText(),
];
}
}
return $entries;
}
protected function getSeverityConstant(string $class) : int {
$level = substr($class, 28);
return static::LEVEL_TRANSLATION[$level];
}
protected function getLogsEntriesTable() : array {
return $this
->xpath('.//table/tbody/tr');
}
protected function assertTypeCount(array $types) {
$entries = $this
->getLogEntries();
$reducer = function ($accu, $curr) {
$accu[$curr['type'] . '-' . $curr['severity']] = [
$curr['type'],
$curr['severity'],
];
return $accu;
};
$actual = array_reduce($entries, $reducer, []);
$expected = array_reduce($types, $reducer, []);
$this
->assertEquals($expected, $actual, "Inserted events are found on page");
}
private function insertLogEntries(LoggerInterface $logger, int $count, string $type = 'custom', int $severity = RfcLogLevel::EMERGENCY) {
$ip = '::1';
$context = [
'channel' => $type,
'link' => NULL,
'user' => [
'uid' => $this->bigUser
->id(),
],
'request_uri' => "http://[{$ip}]/",
'referer' => $_SERVER['HTTP_REFERER'] ?? '',
'ip' => $ip,
'timestamp' => $this->requestTime,
];
$message = $this
->randomString();
for ($i = 0; $i < $count; $i++) {
$logger
->log($severity, $message, $context);
}
}
private function verifyReports($statusCode = Response::HTTP_OK) {
$this
->drupalGet('/admin/help');
$session = $this
->assertSession();
$session
->statusCodeEquals($statusCode);
if ($statusCode == Response::HTTP_OK) {
$session
->pageTextContains('MongoDB');
}
$this
->drupalGet('/admin/help/mongodb');
$session = $this
->assertSession();
$session
->statusCodeEquals($statusCode);
if ($statusCode == Response::HTTP_OK) {
$session
->pageTextContains('implements a generic interface');
}
$this
->drupalGet(static::PATH_OVERVIEW);
$session = $this
->assertSession();
$session
->statusCodeEquals($statusCode);
if ($statusCode == Response::HTTP_OK) {
$expectedTexts = [
'Recent log messages in MongoDB',
'Filter log messages',
'Type',
'Severity',
'Latest',
'Severity',
'Message',
'Source',
];
foreach ($expectedTexts as $expectedText) {
$session
->pageTextContains($expectedText);
}
}
$this
->drupalGet(self::PATH_NOT_FOUND);
$session = $this
->assertSession();
$session
->statusCodeEquals($statusCode);
if ($statusCode == Response::HTTP_OK) {
$session
->pageTextContains("Top 'page not found' errors in MongoDB");
}
$this
->drupalGet(static::PATH_DENIED);
$session = $this
->assertSession();
$session
->statusCodeEquals($statusCode);
if ($statusCode == Response::HTTP_OK) {
$session
->pageTextContains("Top 'access denied' errors in MongoDB");
}
$expectedMessage = $this
->randomString(32);
$logger = $this->container
->get(Logger::SERVICE_LOGGER);
$logger
->info($expectedMessage, [
'with' => 'context',
]);
$selector = [
'message' => $expectedMessage,
];
$event = $logger
->templateCollection()
->findOne($selector, MongoDb::ID_PROJECTION);
$eventId = $event['_id'];
$this
->drupalGet(static::PATH_EVENT_BASE . $eventId);
$session = $this
->assertSession();
$session
->statusCodeEquals($statusCode);
if ($statusCode == Response::HTTP_OK) {
$expectedTexts = [
'Event template',
'ID',
'Changed',
'Count',
'Type',
'Message',
'Severity',
$eventId,
'Event occurrences',
$expectedMessage,
];
foreach ($expectedTexts as $expectedText) {
$session
->pageTextContains($expectedText);
}
}
}
public function testLoggerReportsAccess() {
$expectations = [
[
$this->adminUser,
Response::HTTP_OK,
],
[
$this->bigUser,
Response::HTTP_OK,
],
[
$this->anyUser,
Response::HTTP_FORBIDDEN,
],
];
foreach ($expectations as $expectation) {
[
$account,
$statusCode,
] = $expectation;
$this
->drupalLogin($account);
$this
->verifyReports($statusCode);
}
}
public function testLoggerAddAndUiClear() {
$this->container
->get(MongoDb::SERVICE_DB_FACTORY)
->get(Logger::DB_LOGGER)
->drop();
$loggerChannel = $this->container
->get(Logger::SERVICE_CHANNEL);
$message = static::neuter($this
->randomString(32));
$loggerChannel
->notice($message);
$logger = $this->container
->get(Logger::SERVICE_LOGGER);
$templates = $logger
->templateCollection();
$this
->assertEquals(1, $templates
->countDocuments(), 'Logging created templates collection and added a template to it.');
$template = $templates
->findOne([
'message' => $message,
], MongoDb::ID_PROJECTION);
$this
->assertNotNull($template, "Logged message was found: [{$message}]");
$templateId = $template['_id'];
$events = $logger
->eventCollection($templateId);
$this
->assertEquals(1, $events
->countDocuments(), 'Logging created events collection and added a template to it.');
$this
->drupalLogin($this->adminUser);
$this
->drupalGet('admin/reports/mongodb/confirm');
$this
->submitForm([], 'Confirm');
$count = $templates
->countDocuments();
$failMessage = 'Logger templates collection was cleared';
if ($count > 0) {
$options = [
'projection' => [
'_id' => 0,
'message' => 1,
],
];
$messages = iterator_to_array($templates
->find([], $options));
$failMessage = "Logger templates collection still contains messages: " . json_encode($messages);
}
$this
->assertEquals(0, $count, $failMessage);
$this
->assertFalse($logger
->eventCollections()
->valid(), "Event collections were dropped");
}
public function testFilter() {
$this
->drupalLogin($this->bigUser);
$database = $this->container
->get(MongoDb::SERVICE_DB_FACTORY)
->get(Logger::DB_LOGGER);
$database
->drop();
$logger = $this->container
->get(Logger::SERVICE_LOGGER);
$typeNames = [];
$types = [];
for ($i = 0; $i < 3; $i++) {
$typeNames[] = $typeName = $this
->randomMachineName();
$severity = RfcLogLevel::EMERGENCY;
for ($j = 0; $j < 3; $j++) {
$types[] = $type = [
'count' => mt_rand(1, 5),
'type' => $typeName,
'severity' => $severity++,
];
$this
->insertLogEntries($logger, $type['count'], $type['type'], $type['severity']);
}
}
$this
->drupalGet(self::PATH_OVERVIEW);
$this
->assertTypeCount($types);
foreach ($typeNames as $typeName) {
$edit = [
'type[]' => [
$typeName,
],
];
$this
->submitForm($edit, 'Filter');
$filteredTypes = array_filter($types, function (array $type) use ($typeName) {
return $type['type'] === $typeName;
});
$this
->assertTypeCount($filteredTypes);
}
foreach ($types as $type) {
$edit = [
'type[]' => $typeType = $type['type'],
'severity[]' => $typeSeverity = $type['severity'],
];
$this
->submitForm($edit, 'Filter');
$filteredTypes = array_filter($types, function (array $type) use ($typeType, $typeSeverity) {
return $type['type'] === $typeType && $type['severity'] == $typeSeverity;
});
$this
->assertTypeCount($filteredTypes);
}
}
}