getParameters(); foreach ($parameters as $parameter) { $type = $parameter->getType(); self::assertInstanceOf(\ReflectionNamedType::class, $type); self::assertNotSame(ObjectManager::class, $type->getName()); self::assertFalse( is_a($type->getName(), ObjectManager::class, true), 'the handler must not be able to flush anybody else’s entities', ); } } public function testTheWrittenColumnsMatchTheLogEntryMapping(): void { $handler = new DatabaseHandler($this->connection($table, $data, $types)); $handler->handle($this->record('auth', 'Login', ['email' => 'teamer@example.org'])); $metadata = $this->logEntryMetadata(); self::assertSame($metadata->getTableName(), $table); self::assertSame([], array_diff(array_keys($data), $metadata->getColumnNames())); // Everything the table requires has to be supplied: no default fills these in, and the id // is the only column the database generates itself. $required = array_diff($metadata->getColumnNames(), ['id']); foreach ($required as $column) { self::assertArrayHasKey($column, $data); } self::assertSame([], array_diff(array_keys($types), array_keys($data))); } public function testTheRecordIsWrittenAsIs(): void { $handler = new DatabaseHandler($this->connection($table, $data, $types)); $handler->handle($this->record('bpn', 'Request failed', ['request_id' => 'r-1'], ['error_code' => '853'])); self::assertSame('bpn', $data['channel']); self::assertSame('Request failed', $data['message']); self::assertSame(['request_id' => 'r-1'], $data['context']); self::assertSame(['error_code' => '853'], $data['extra']); self::assertSame('853', $data['error_code']); self::assertInstanceOf(\DateTimeImmutable::class, $data['created_at']); } public function testPlaceholdersAreReplacedFromTheContext(): void { $handler = new DatabaseHandler($this->connection($table, $data, $types)); $handler->handle($this->record('core', 'Booking {id} confirmed', ['id' => '4711'])); self::assertSame('Booking 4711 confirmed', $data['message']); } public function testAnErrorCodeIsOptional(): void { $handler = new DatabaseHandler($this->connection($table, $data, $types)); $handler->handle($this->record('core', 'Login')); self::assertNull($data['error_code']); } public function testDebugRecordsAreNotWritten(): void { $connection = $this->createMock(Connection::class); $connection->expects(self::never())->method('insert'); $handler = new DatabaseHandler($connection); $handler->handle($this->record('core', 'Noise', level: Level::Debug)); } /** * @param array|null $data * @param array|null $types */ private function connection(?string &$table, ?array &$data, ?array &$types): Connection { $connection = $this->createMock(Connection::class); $connection ->method('insert') ->willReturnCallback( static function (string $insertTable, array $insertData, array $insertTypes) use (&$table, &$data, &$types): int { $table = $insertTable; $data = $insertData; $types = $insertTypes; return 1; } ) ; return $connection; } /** * @param array $context * @param array $extra */ private function record(string $channel, string $message, array $context = [], array $extra = [], Level $level = Level::Info): LogRecord { return new LogRecord(new \DateTimeImmutable(), $channel, $level, $message, $context, $extra); } /** * @return ClassMetadata */ private function logEntryMetadata(): ClassMetadata { /** @var ClassMetadata $metadata */ $metadata = new ClassMetadata(LogEntry::class, new UnderscoreNamingStrategy(CASE_LOWER, true)); $metadata->initializeReflection(new RuntimeReflectionService()); (new AttributeDriver([dirname(__DIR__, 2).'/src/Entity']))->loadMetadataForClass(LogEntry::class, $metadata); return $metadata; } }