浏览代码

Add tests for plugin system

shalvah 5 年之前
父节点
当前提交
90560e9729
共有 4 个文件被更改,包括 288 次插入18 次删除
  1. 4 7
      docs/plugins.md
  2. 10 1
      src/Strategies/Strategy.php
  3. 8 10
      src/Tools/Generator.php
  4. 266 0
      tests/Unit/GeneratorPluginSystemTestCase.php

+ 4 - 7
docs/plugins.md

@@ -105,7 +105,9 @@ public function __invoke(Route $route, \ReflectionClass $controller, \Reflection
 }
 ```
 
-The strategy class also has access to the current apidoc configuration via its config property. For instance, you can retrieve the deafult group with `$this->config->get('default_group')`.
+The strategy class also has access to the current apidoc configuration via its `config` property. For instance, you can retrieve the default group with `$this->config->get('default_group')`.
+
+Yopu are also provided with the instance pproperty `stage`, which is set to the name of the currently executing stage.
 
 
 ## Utilities
@@ -135,9 +137,4 @@ Each strategy class must implement the __invoke method with the parameters as de
 'required', // boolean
 'value', // An example value for the parameter
 ```
-- In the `responses` stage, your strategy should return an array containing the responses for different status codes. Each key in the array should be a HTTP status code, and each value should be an array with two keys:
-
-```
-status // The same status code
-content // The response content as a string
-```
+- In the `responses` stage, your strategy should return an array containing the responses for different status codes. Each key in the array should be a HTTP status code, and each value should be a string containing the response.

+ 10 - 1
src/Strategies/Strategy.php

@@ -9,10 +9,19 @@ use Mpociot\ApiDoc\Tools\DocumentationConfig;
 
 abstract class Strategy
 {
+    /**
+     * @var DocumentationConfig The apidoc config
+     */
     protected $config;
 
-    public function __construct(DocumentationConfig $config)
+    /**
+     * @var string The current stage of route processing
+     */
+    protected $stage;
+
+    public function __construct(string $stage, DocumentationConfig $config)
     {
+        $this->stage = $stage;
         $this->config = $config;
     }
 

+ 8 - 10
src/Tools/Generator.php

@@ -75,8 +75,6 @@ class Generator
 
         $parsedRoute['headers'] = $rulesToApply['headers'] ?? [];
 
-        // Currently too lazy to tinker with Blade files; change this later
-        unset($parsedRoute['metadata']);
         $parsedRoute += $metadata;
 
         return $parsedRoute;
@@ -85,7 +83,7 @@ class Generator
     protected function fetchMetadata(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
     {
         $context['metadata'] = [
-            'groupName' => $this->config->get('default_group'),
+            'groupName' => $this->config->get('default_group', ''),
             'groupDescription' => '',
             'title' => '',
             'description' => '',
@@ -120,12 +118,12 @@ class Generator
         return null;
     }
 
-    protected function iterateThroughStrategies(string $key, array $context, array $arguments)
+    protected function iterateThroughStrategies(string $stage, array $context, array $arguments)
     {
-        $strategies = $this->config->get("strategies.$key", []);
-        $context[$key] = $context[$key] ?? [];
+        $strategies = $this->config->get("strategies.$stage", []);
+        $context[$stage] = $context[$stage] ?? [];
         foreach ($strategies as $strategyClass) {
-            $strategy = new $strategyClass($this->config);
+            $strategy = new $strategyClass($stage, $this->config);
             $arguments[] = $context;
             $results = $strategy(...$arguments);
             if (! is_null($results)) {
@@ -135,16 +133,16 @@ class Generator
                     // and also allows values to be overwritten
 
                     // Don't allow overwriting if an empty value is trying to replace a set one
-                    if (! in_array($context[$key], [null, ''], true) && in_array($item, [null, ''], true)) {
+                    if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
                         continue;
                     } else {
-                        $context[$key][$index] = $item;
+                        $context[$stage][$index] = $item;
                     }
                 }
             }
         }
 
-        return $context[$key];
+        return $context[$stage];
     }
 
     /**

+ 266 - 0
tests/Unit/GeneratorPluginSystemTestCase.php

@@ -0,0 +1,266 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tests\Unit;
+
+use Illuminate\Routing\Route;
+use Illuminate\Support\Arr;
+use Mpociot\ApiDoc\Strategies\Strategy;
+use Orchestra\Testbench\TestCase;
+use Mpociot\ApiDoc\Tools\Generator;
+use Mpociot\ApiDoc\Tools\DocumentationConfig;
+use Mpociot\ApiDoc\Tests\Fixtures\TestController;
+use Mpociot\ApiDoc\ApiDocGeneratorServiceProvider;
+use Mpociot\ApiDoc\Tests\Fixtures\TestResourceController;
+use ReflectionClass;
+use ReflectionMethod;
+
+class GeneratorPluginSystemTestCase extends LaravelGeneratorTest
+{
+    /**
+     * @var \Mpociot\ApiDoc\Tools\Generator
+     */
+    protected $generator;
+
+    protected function getPackageProviders($app)
+    {
+        return [
+            ApiDocGeneratorServiceProvider::class,
+        ];
+    }
+
+    /** @test */
+    public function only_specified_strategies_are_loaded()
+    {
+        $config = [
+            'strategies' => [
+                'metadata' => [ EmptyStrategy1::class, ],
+                'bodyParameters' => [
+                    EmptyStrategy1::class,
+                    EmptyStrategy2::class,
+                ],
+                'responses' => [ EmptyStrategy1::class ],
+            ],
+        ];
+        $route = $this->createRoute('GET', '/api/test', 'dummy', true, TestController::class);
+        $generator = new Generator(new DocumentationConfig($config));
+        $generator->processRoute($route);
+
+        // Probably not the best way to do this, but 🤷‍♂️
+        $this->assertTrue(EmptyStrategy1::$called['metadata']);
+
+        $this->assertTrue(EmptyStrategy1::$called['bodyParameters']);
+        $this->assertTrue(EmptyStrategy2::$called['bodyParameters']);
+
+        $this->assertArrayNotHasKey('queryParameters', EmptyStrategy1::$called);
+
+        $this->assertTrue(EmptyStrategy1::$called['responses']);
+
+    }
+
+    /** @test */
+    public function combines_responses_from_different_strategies()
+    {
+        $config = [
+            'strategies' => [
+                'responses' => [ DummyResponseStrategy200::class, DummyResponseStrategy400::class ],
+            ],
+        ];
+        $route = $this->createRoute('GET', '/api/test', 'dummy', true, TestController::class);
+        $generator = new Generator(new DocumentationConfig($config));
+        $parsed = $generator->processRoute($route);
+
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertCount(2, $parsed['response']);
+        $first = array_shift($parsed['response']);
+        $this->assertTrue(is_array($first));
+        $this->assertEquals(200, $first['status']);
+        $this->assertEquals('dummy', $first['content']);
+        $second = array_shift($parsed['response']);
+        $this->assertTrue(is_array($second));
+        $this->assertEquals(400, $second['status']);
+        $this->assertEquals('dummy2', $second['content']);
+    }
+
+    // This is a generalized test, as opposed to the one above for responses only
+    /** @test */
+    public function combines_results_from_different_strategies_in_same_stage()
+    {
+        $config = [
+            'strategies' => [
+                'metadata' => [PartialDummyMetadataStrategy1::class, PartialDummyMetadataStrategy2::class],
+            ],
+        ];
+        $route = $this->createRoute('GET', '/api/test', 'dummy', true, TestController::class);
+        $generator = new Generator(new DocumentationConfig($config));
+        $parsed = $generator->processRoute($route);
+
+        $expectedMetadata = [
+            'groupName' => 'dummy',
+            'groupDescription' => 'dummy',
+            'title' => 'dummy',
+            'description' => 'dummy',
+            'authenticated' => false,
+        ];
+        $this->assertArraySubset($expectedMetadata, $parsed['metadata']); // Forwards-compatibility
+        $this->assertArraySubset($expectedMetadata, $parsed); // Backwards-compatibility
+    }
+
+    /** @test */
+    public function missing_metadata_is_filled_in()
+    {
+        $config = [
+            'strategies' => [
+                'metadata' => [ PartialDummyMetadataStrategy2::class, ],
+            ],
+        ];
+        $route = $this->createRoute('GET', '/api/test', 'dummy', true, TestController::class);
+        $generator = new Generator(new DocumentationConfig($config));
+        $parsed = $generator->processRoute($route);
+
+        $expectedMetadata = [
+            'groupName' => '',
+            'groupDescription' => 'dummy',
+            'title' => '',
+            'description' => 'dummy',
+            'authenticated' => false,
+        ];
+        $this->assertArraySubset($expectedMetadata, $parsed['metadata']); // Forwards-compatibility
+        $this->assertArraySubset($expectedMetadata, $parsed); // Backwards-compatibility
+    }
+
+    /** @test */
+    public function overwrites_results_from_previous_strategies_in_same_stage()
+    {
+        $config = [
+            'strategies' => [
+                'responses' => [ DummyResponseStrategy200::class, StillDummyResponseStrategyAlso200::class ],
+            ],
+        ];
+        $route = $this->createRoute('GET', '/api/test', 'dummy', true, TestController::class);
+        $generator = new Generator(new DocumentationConfig($config));
+        $parsed = $generator->processRoute($route);
+
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertCount(1, $parsed['response']);
+        $first = array_shift($parsed['response']);
+        $this->assertTrue(is_array($first));
+        $this->assertEquals(200, $first['status']);
+        $this->assertEquals('stilldummy', $first['content']);
+
+        $config = [
+            'strategies' => [
+                'metadata' => [NotDummyMetadataStrategy::class, PartialDummyMetadataStrategy1::class],
+            ],
+        ];
+        $route = $this->createRoute('GET', '/api/test', 'dummy', true, TestController::class);
+        $generator = new Generator(new DocumentationConfig($config));
+        $parsed = $generator->processRoute($route);
+
+        $expectedMetadata = [
+            'groupName' => 'dummy',
+            'groupDescription' => 'notdummy',
+            'title' => 'dummy',
+            'description' => 'dummy',
+            'authenticated' => false,
+        ];
+        $this->assertArraySubset($expectedMetadata, $parsed['metadata']); // Forwards-compatibility
+        $this->assertArraySubset($expectedMetadata, $parsed); // Backwards-compatibility
+    }
+
+    public function dataResources()
+    {
+        return [
+            [
+                null,
+                '{"data":{"id":1,"description":"Welcome on this test versions","name":"TestName"}}',
+            ],
+            [
+                'League\Fractal\Serializer\JsonApiSerializer',
+                '{"data":{"type":null,"id":"1","attributes":{"description":"Welcome on this test versions","name":"TestName"}}}',
+            ],
+        ];
+    }
+}
+
+
+class EmptyStrategy1 extends Strategy
+{
+    public static $called = [];
+
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        static::$called[$this->stage] = true;
+    }
+}
+
+class EmptyStrategy2 extends Strategy
+{
+    public static $called = [];
+
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        static::$called[$this->stage] = true;
+    }
+}
+
+class NotDummyMetadataStrategy extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return [
+            'groupName' => 'notdummy',
+            'groupDescription' => 'notdummy',
+            'title' => 'notdummy',
+            'description' => 'notdummy',
+            'authenticated' => true,
+        ];
+    }
+}
+
+class PartialDummyMetadataStrategy1 extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return [
+            'groupName' => 'dummy',
+            'title' => 'dummy',
+            'description' => 'dummy',
+            'authenticated' => false,
+        ];
+    }
+}
+
+class PartialDummyMetadataStrategy2 extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return [
+            'description' => 'dummy',
+            'groupDescription' => 'dummy',
+        ];
+    }
+}
+
+class DummyResponseStrategy200 extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return [ 200 => 'dummy', ];
+    }
+}
+
+class StillDummyResponseStrategyAlso200 extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return [ 200 => 'stilldummy', ];
+    }
+}
+
+class DummyResponseStrategy400 extends Strategy
+{
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
+    {
+        return [ 400 => 'dummy2', ];
+    }
+}