277 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			277 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			PHP
		
	
	
| <?php declare(strict_types=1);
 | |
| 
 | |
| /**
 | |
|  * @license Apache 2.0
 | |
|  */
 | |
| 
 | |
| namespace OpenApi\Tests;
 | |
| 
 | |
| use Closure;
 | |
| use DirectoryIterator;
 | |
| use Exception;
 | |
| use OpenApi\Analyser;
 | |
| use OpenApi\Analysis;
 | |
| use OpenApi\Annotations\AbstractAnnotation;
 | |
| use OpenApi\Annotations\Info;
 | |
| use OpenApi\Annotations\OpenApi;
 | |
| use OpenApi\Annotations\PathItem;
 | |
| use OpenApi\Context;
 | |
| use OpenApi\Logger;
 | |
| use OpenApi\StaticAnalyser;
 | |
| use PHPUnit\Framework\TestCase;
 | |
| use Psr\Log\AbstractLogger;
 | |
| use Psr\Log\LoggerInterface;
 | |
| use Psr\Log\NullLogger;
 | |
| use Symfony\Component\Yaml\Exception\ParseException;
 | |
| use Symfony\Component\Yaml\Yaml;
 | |
| 
 | |
| class OpenApiTestCase extends TestCase
 | |
| {
 | |
|     /**
 | |
|      * @var array
 | |
|      */
 | |
|     public $expectedLogMessages = [];
 | |
| 
 | |
|     /**
 | |
|      * @var Closure
 | |
|      */
 | |
|     private $originalLogger;
 | |
| 
 | |
|     protected function setUp(): void
 | |
|     {
 | |
|         $this->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("<?php\n/**\n * " . implode("\n * ", explode("\n", $comment)) . "\n*/", $context);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a valid OpenApi object with Info.
 | |
|      */
 | |
|     protected function createOpenApiWithInfo()
 | |
|     {
 | |
|         return new OpenApi([
 | |
|             'info' => 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("<?php\n" . $code, $context ?: new Context());
 | |
|     }
 | |
| 
 | |
|     public function analysisFromDockBlock($comment)
 | |
|     {
 | |
|         return (new Analyser())->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;
 | |
|     }
 | |
| }
 |