diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a12b3fa --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,42 @@ +name: Tests PHP Versions + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - php: 8.1 + laravel: 10.* + - php: 8.2 + laravel: 11.* + - php: 8.3 + laravel: 11.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update + composer update --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: composer test diff --git a/composer.json b/composer.json index cd97a38..9df4160 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,12 @@ "Waad\\ScrambleSwagger\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/" + } + }, "extra": { "laravel": { "providers": [ @@ -42,6 +48,18 @@ "prefer-stable": true, "scripts": { "lint": "vendor/bin/pint", - "test": "vendor/bin/pest" + "test": "vendor/bin/pest", + "post-autoload-dump": [ + "@clear", + "@prepare" + ], + "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", + "prepare": "@php vendor/bin/testbench package:discover --ansi", + "build": "@php vendor/bin/testbench workbench:build --ansi", + "serve": [ + "Composer\\Config::disableProcessTimeout", + "@build", + "@php vendor/bin/testbench serve --ansi" + ] } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..9ae6fc7 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,28 @@ + + + + + ./tests/WithoutVersions + ./tests/WithVersions + + + + + ./src + + + + + + + + + + + + diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..3d54c25 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,4 @@ +in('WithoutVersions'); +uses(Tests\TestCaseWithVersions::class)->in('WithVersions'); diff --git a/tests/TestCaseWithVersions.php b/tests/TestCaseWithVersions.php new file mode 100644 index 0000000..e862d6f --- /dev/null +++ b/tests/TestCaseWithVersions.php @@ -0,0 +1,34 @@ +set('scramble-swagger.versions', ['all', 'v1', 'v2']); + $app['config']->set('scramble-swagger.default_version', 'v2'); + } + + public function defineRoutes($router): void + { + $router->get('api/v1/test', [TestController::class, 'index']); + $router->get('api/v1/test/test', [TestController::class, 'index']); + $router->get('api/v2/test', [TestController::class, 'index']); + $router->get('api/v2/test/test', [TestController::class, 'index']); + } +} diff --git a/tests/TestCaseWithoutVersions.php b/tests/TestCaseWithoutVersions.php new file mode 100644 index 0000000..65cc4c8 --- /dev/null +++ b/tests/TestCaseWithoutVersions.php @@ -0,0 +1,37 @@ +set('scramble-swagger', [ + // 'enable' => true, + // 'url' => 'docs/swagger', + // 'versions' => [ + // 'all', + // ], + // 'default_version' => 'all', + // ]); + } + + public function defineRoutes($router): void + { + $router->get('api/test', [TestController::class, 'index']); + } +} diff --git a/tests/WithVersions/ApiDocumentationTest.php b/tests/WithVersions/ApiDocumentationTest.php new file mode 100644 index 0000000..37ba6a5 --- /dev/null +++ b/tests/WithVersions/ApiDocumentationTest.php @@ -0,0 +1,64 @@ +status())->toBe(200); + $json = $response->getContent(); + expect($json)->toBeJson(); + + // main + $array = json_decode($json, true); + expect($array)->toHaveKey('openapi'); + expect($array)->toHaveKey('info'); + expect($array)->toHaveKey('servers'); + expect($array)->toHaveKey('paths'); + expect($array)->toHaveKey('components'); + + // sub + expect($array)->toHaveKey('servers.0.url'); + expect($array)->toHaveKey('info.title'); + expect($array)->toHaveKey('info.version'); + expect($array)->toHaveKey('paths./v1/test'); + expect($array)->toHaveKey('paths./v1/test/test'); + expect($array)->toHaveKey('paths./v2/test'); + expect($array)->toHaveKey('paths./v2/test/test'); + expect($array)->toHaveKey('components.responses'); + expect($array)->toHaveKey('components.paths'); +}); + +it('test controller endpoint returns correct data', function () { + $response = get('api/v2/test?per_page=2&page=1'); + + $response->assertStatus(200) + ->assertJson([ + ['id' => 1, 'name' => 'John Doe'], + ['id' => 2, 'name' => 'Jane Ronaldo'], + ]); +}); + +it('test controller search returns filtered results', function () { + $response = get('api/v2/test?per_page=5&search=John'); + + $response->assertStatus(200) + ->assertJsonCount(2) + ->assertJsonFragment(['name' => 'John Doe']) + ->assertJsonFragment(['name' => 'John Smith']); +}); + +it('test controller pagination works correctly', function () { + $response = get('api/v2/test?per_page=3&page=2'); + + $response->assertStatus(200) + ->assertJsonCount(3) + ->assertJsonFragment(['id' => 4]) + ->assertJsonFragment(['id' => 5]) + ->assertJsonFragment(['id' => 6]); +}); + +it('test controller validates required parameters', function () { + $response = get('api/v2/test'); + expect($response->status())->toBe(422); +}); diff --git a/tests/WithVersions/SwaggerTest.php b/tests/WithVersions/SwaggerTest.php new file mode 100644 index 0000000..272e448 --- /dev/null +++ b/tests/WithVersions/SwaggerTest.php @@ -0,0 +1,32 @@ +status())->toBe(200) + ->and($response->getContent())->toContain('
'); +}); + +it('swagger ui endpoint is accessible and contains all versions', function () { + $response = get('docs/swagger'); + expect($response->status())->toBe(200); + + expect($response->getContent()) + ->toContain('
') + ->toContain("url: 'http://localhost/docs/swagger/json?version=all'") + ->toContain("url: 'http://localhost/docs/swagger/json?version=v1'") + ->toContain("url: 'http://localhost/docs/swagger/json?version=v2'"); +}); + +it('swagger ui is disabled when config is false', function () { + config(['scramble-swagger.enable' => false]); + + $response = get('docs/swagger'); + expect($response->status())->toBe(404); +}); + +it('swagger ui url can be configured', function () { + $response = get('doc/doc'); + expect($response->status())->toBe(404); +}); diff --git a/tests/WithoutVersions/ApiDocumentationTest.php b/tests/WithoutVersions/ApiDocumentationTest.php new file mode 100644 index 0000000..a0364b9 --- /dev/null +++ b/tests/WithoutVersions/ApiDocumentationTest.php @@ -0,0 +1,61 @@ +status())->toBe(200); + $json = $response->getContent(); + expect($json)->toBeJson(); + + // main + $array = json_decode($json, true); + expect($array)->toHaveKey('openapi'); + expect($array)->toHaveKey('info'); + expect($array)->toHaveKey('servers'); + expect($array)->toHaveKey('paths'); + expect($array)->toHaveKey('components'); + + // sub + expect($array)->toHaveKey('servers.0.url'); + expect($array)->toHaveKey('info.title'); + expect($array)->toHaveKey('info.version'); + expect($array)->toHaveKey('paths./test'); + expect($array)->toHaveKey('components.responses'); + expect($array)->toHaveKey('components.paths'); +}); + +it('test controller endpoint returns correct data', function () { + $response = get('api/test?per_page=2&page=1'); + + $response->assertStatus(200) + ->assertJson([ + ['id' => 1, 'name' => 'John Doe'], + ['id' => 2, 'name' => 'Jane Ronaldo'], + ]); +}); + +it('test controller search returns filtered results', function () { + $response = get('api/test?per_page=5&search=John'); + + $response->assertStatus(200) + ->assertJsonCount(2) + ->assertJsonFragment(['name' => 'John Doe']) + ->assertJsonFragment(['name' => 'John Smith']); +}); + +it('test controller pagination works correctly', function () { + $response = get('api/test?per_page=3&page=2'); + + $response->assertStatus(200) + ->assertJsonCount(3) + ->assertJsonFragment(['id' => 4]) + ->assertJsonFragment(['id' => 5]) + ->assertJsonFragment(['id' => 6]); +}); + +it('test controller validates required parameters', function () { + $response = get('api/test'); + expect($response->status())->toBe(422); +}); diff --git a/tests/WithoutVersions/SwaggerTest.php b/tests/WithoutVersions/SwaggerTest.php new file mode 100644 index 0000000..b195c47 --- /dev/null +++ b/tests/WithoutVersions/SwaggerTest.php @@ -0,0 +1,23 @@ +status())->toBe(200) + ->and($response->getContent()) + ->toContain('
') + ->toContain("url: 'http://localhost/docs/swagger/json?version=all'"); +}); + +it('swagger ui is disabled when config is false', function () { + config(['scramble-swagger.enable' => false]); + + $response = get('docs/swagger'); + expect($response->status())->toBe(404); +}); + +it('swagger ui url can be configured', function () { + $response = get('doc/doc'); + expect($response->status())->toBe(404); +}); diff --git a/workbench/app/Http/Controllers/TestController.php b/workbench/app/Http/Controllers/TestController.php new file mode 100644 index 0000000..f8c10a8 --- /dev/null +++ b/workbench/app/Http/Controllers/TestController.php @@ -0,0 +1,51 @@ + 1, 'name' => 'John Doe'], + ['id' => 2, 'name' => 'Jane Ronaldo'], + ['id' => 3, 'name' => 'John Smith'], + ['id' => 4, 'name' => 'Jane Messi'], + ['id' => 5, 'name' => 'Mario Khabib'], + ['id' => 6, 'name' => 'Luigi Jordin'], + ['id' => 7, 'name' => 'Mario Sami'], + ['id' => 8, 'name' => 'Roni Maikel'], + ['id' => 9, 'name' => 'Sarah Connor'], + ['id' => 10, 'name' => 'Tony Stark'], + ['id' => 11, 'name' => 'Bruce Wayne'], + ['id' => 12, 'name' => 'Peter Parker'], + ['id' => 13, 'name' => 'Clark Kent'], + ]; + + public function index(Request $request) + { + try { + $request->validate([ + 'per_page' => 'required|integer', + 'page' => 'nullable|integer', + 'search' => 'nullable|string', + ]); + } catch (\Illuminate\Validation\ValidationException $e) { + return response()->json(['error' => $e->errors()], 422); + } + + $page = $request->page ?? 1; + $perPage = $request->per_page ?? 5; + $search = $request->search ?? null; + + $data = collect(self::DATA); + + if ($search) { + $data = $data->filter(function ($item) use ($search) { + return str_contains($item['name'], $search); + }); + } + + return $data->skip(($page - 1) * $perPage)->take($perPage)->toArray(); + } +} diff --git a/workbench/app/Models/.gitkeep b/workbench/app/Models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php new file mode 100644 index 0000000..de910c2 --- /dev/null +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -0,0 +1,24 @@ +usePublicPath(__DIR__.'/../public'); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/workbench/app/public/.gitkeep b/workbench/app/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/workbench/app/public/scramble-swagger/.gitignore b/workbench/app/public/scramble-swagger/.gitignore new file mode 100644 index 0000000..ac23051 --- /dev/null +++ b/workbench/app/public/scramble-swagger/.gitignore @@ -0,0 +1,2 @@ +*.* +!.gitignore \ No newline at end of file