expectedLogMessages = []; $this->originalLogger = Logger::getInstance()->log; Logger::getInstance()->log = function ($entry, $type) { if (count($this->expectedLogMessages)) { list($assertion, $needle) = array_shift($this->expectedLogMessages); $assertion($entry, $type); } else { $map = [ E_USER_NOTICE => 'notice', E_USER_WARNING => 'warning', ]; if (isset($map[$type])) { $this->fail('Unexpected \OpenApi\Logger::' . $map[$type] . '("' . $entry . '")'); } else { $this->fail('Unexpected \OpenApi\Logger->getInstance()->log("' . $entry . '",' . $type . ')'); } } }; parent::setUp(); } protected function tearDown(): void { $this->assertEmpty( $this->expectedLogMessages, implode(PHP_EOL . ' => ', array_merge( ['OpenApi\Logger messages were not triggered:'], array_map(function (array $value) { return $value[1]; }, $this->expectedLogMessages) )) ); Logger::getInstance()->log = $this->originalLogger; parent::tearDown(); } public function getPsrLogger(bool $tracking = false): ?LoggerInterface { if (!$tracking) { // allow to test the default behaviour without injected PSR logger switch (strtoupper($_ENV['NON_TRACKING_LOGGER'] ?? 'FALLBACK')) { case 'NULL': return new NullLogger(); case 'FALLBACK': default: // whatever is set up in Logger::$instance->log return null; } } return new class($this) extends AbstractLogger { protected $testCase; public function __construct($testCase) { $this->testCase = $testCase; } public function log($level, $message, array $context = []) { if (count($this->testCase->expectedLogMessages)) { list($assertion, $needle) = array_shift($this->testCase->expectedLogMessages); $assertion($message, $level); } else { $this->testCase->fail('Unexpected \OpenApi\Logger::' . $level . '("' . $message . '")'); } } }; } public function assertOpenApiLogEntryContains($needle, $message = '') { $this->expectedLogMessages[] = [function ($entry, $type) use ($needle, $message) { if ($entry instanceof Exception) { $entry = $entry->getMessage(); } $this->assertStringContainsString($needle, $entry, $message); }, $needle]; } /** * Compare OpenApi specs assuming strings to contain YAML. * * @param array|OpenApi|\stdClass|string $actual The generated output * @param array|OpenApi|\stdClass|string $expected The specification * @param string $message * @param bool $normalized flag indicating whether the inputs are already normalized or not */ protected function assertSpecEquals($actual, $expected, $message = '', $normalized = false) { $normalize = function ($in) { if ($in instanceof OpenApi) { $in = $in->toYaml(); } if (is_string($in)) { // assume YAML try { $in = Yaml::parse($in); } catch (ParseException $e) { $this->fail('Invalid YAML: ' . $e->getMessage() . PHP_EOL . $in); } } return $in; }; if (!$normalized) { $actual = $normalize($actual); $expected = $normalize($expected); } if (is_iterable($actual) && is_iterable($expected)) { foreach ($actual as $key => $value) { $this->assertArrayHasKey($key, (array) $expected, $message . ': property: "' . $key . '" should be absent, but has value: ' . $this->formattedValue($value)); $this->assertSpecEquals($value, ((array) $expected)[$key], $message . ' > ' . $key, true); } foreach ($expected as $key => $value) { $this->assertArrayHasKey($key, (array) $actual, $message . ': property: "' . $key . '" is missing'); $this->assertSpecEquals(((array) $actual)[$key], $value, $message . ' > ' . $key, true); } } else { $this->assertEquals($actual, $expected, $message); } } private function formattedValue($value) { if (is_bool($value)) { return $value ? 'true' : 'false'; } if (is_numeric($value)) { return (string) $value; } if (is_string($value)) { return '"' . $value . '"'; } if (is_object($value)) { return get_class($value); } return gettype($value); } /** * Parse a comment. * * @param string $comment Contents of a comment block * * @return AbstractAnnotation[] */ protected function parseComment($comment) { $analyser = new Analyser(); $context = Context::detect(1); return $analyser->fromComment(" new Info([ 'title' => 'swagger-php Test-API', 'version' => 'test', '_context' => new Context(['unittest' => true]), ]), 'paths' => [ new PathItem(['path' => '/test']), ], '_context' => new Context(['unittest' => true]), ]); } /** * Resolve fixture filenames. * * @param array|string $files one ore more files * * @return array resolved filenames for loading scanning etc */ public function fixtures($files): array { return array_map(function ($file) { return __DIR__ . '/Fixtures/' . $file; }, (array) $files); } public function analysisFromFixtures($files): Analysis { $analyser = new StaticAnalyser(); $analysis = new Analysis([], new Context()); foreach ((array) $files as $file) { $analysis->addAnalysis($analyser->fromFile($this->fixtures($file)[0])); } return $analysis; } public function analysisFromCode(string $code, ?Context $context = null) { return (new StaticAnalyser())->fromCode("fromComment($comment, null); } /** * Collect list of all non abstract annotation classes. * * @return array */ public function allAnnotationClasses() { $classes = []; $dir = new DirectoryIterator(__DIR__ . '/../src/Annotations'); foreach ($dir as $entry) { if (!$entry->isFile() || $entry->getExtension() != 'php') { continue; } $class = $entry->getBasename('.php'); if (in_array($class, ['AbstractAnnotation', 'Operation'])) { continue; } $classes[$class] = ['OpenApi\\Annotations\\' . $class]; } return $classes; } }