Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Ferror committed Dec 21, 2023
0 parents commit a6d7a36
Show file tree
Hide file tree
Showing 14 changed files with 337 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/.github export-ignore
/tests export-ignore
.gitignore export-ignore
phpstan.neon export-ignore
phpunit.xml export-ignore
README.md export-ignore
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push: ~
workflow_dispatch: ~

jobs:
tests:
runs-on: ubuntu-latest

steps:
- # Copies the repository files to the Action Runner
name: Checkout Repository
uses: actions/checkout@v3

- # Installs PHP and other necessary tools
name: Setup PHP
uses: shivammathur/[email protected]
with:
php-version: 8.3

- # Installs and caches PHP dependencies
name: Install Dependencies
uses: ramsey/[email protected]

- # Validates composer.json structure and required fields
name: Validate composer.json
run: composer validate --ansi --strict --no-check-publish

- # Runs code quality tools, like phpstan etc.
name: Run Code Quality Tools
run: composer analyse

- # Runs unit and integration tests, like phpspec, phpunit etc.
name: Run Tests
run: composer test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/var/
/vendor/
/composer.lock
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ARG COMPOSER_VERSION
ARG PHP_VERSION
ARG EXTENSION_INSTALLER_VERSION

FROM composer:${COMPOSER_VERSION} AS composer
FROM mlocati/php-extension-installer:${EXTENSION_INSTALLER_VERSION} AS extensions

FROM php:${PHP_VERSION}-cli-alpine AS php

COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY --from=extensions /usr/bin/install-php-extensions /usr/bin/install-php-extensions

RUN install-php-extensions xdebug

WORKDIR /app/
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
APP := @docker compose run --rm app

.PHONY:
ci: analyse test

.PHONY:
install:
$(APP) composer install

.PHONY:
update:
$(APP) composer update

.PHONY:
analyse:
$(APP) composer analyse

.PHONY:
test:
$(APP) composer test

.PHONY:
coverage:
$(APP) composer coverage

.PHONY:
sh:
$(APP) sh
34 changes: 34 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "ferror/openapi-coverage",
"type": "library",
"license": "MIT",
"require": {
"php": "^8.2",
"nelmio/api-doc-bundle": "^4.12",
"symfony/console": "^6.4",
"symfony/dependency-injection": "^6.4",
"symfony/http-kernel": "^6.4",
"symfony/routing": "^6.4"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-symfony": "^1.3",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^10.5"
},
"autoload": {
"psr-4": {
"Ferror\\OpenapiCoverage\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Ferror\\OpenapiCoverage\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/phpunit",
"coverage": "vendor/bin/phpunit --coverage-html=var/coverage",
"analyse": "vendor/bin/phpstan analyse"
}
}
16 changes: 16 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
app:
build:
context: .
target: php
args:
PHP_VERSION: 8.2
COMPOSER_VERSION: 2.5
EXTENSION_INSTALLER_VERSION: 2.1
command: ["composer", "install", "--no-interaction"]
environment:
XDEBUG_MODE: coverage
# XDEBUG_MODE: debug
# XDEBUG_CONFIG: "profiler_enable=on idekey=PHPSTORM client_host=host.docker.internal"
volumes:
- ./:/app:delegate
11 changes: 11 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
includes:
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-symfony/rules.neon
- vendor/phpstan/phpstan-phpunit/rules.neon

parameters:
level: 6
paths:
- src
- tests
34 changes: 34 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory="var/cache/phpunit"
executionOrder="random"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true"
colors="true"
displayDetailsOnTestsThatTriggerWarnings="true"
>
<testsuites>
<testsuite name="all">
<directory>tests</directory>
</testsuite>
</testsuites>

<php>
<env name="KERNEL_CLASS" value="Ferror\OpenapiCoverage\Integration\Service\Kernel" />
</php>

<source
restrictDeprecations="true"
restrictNotices="true"
restrictWarnings="true"
>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
38 changes: 38 additions & 0 deletions src/Symfony/Bundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Ferror\OpenapiCoverage\Symfony;

use Ferror\OpenapiCoverage\Symfony\Console\CheckCoverageCommand;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class Bundle extends AbstractBundle
{
protected string $extensionAlias = 'ferror_openapi_coverage';

public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void

Check failure on line 18 in src/Symfony/Bundle.php

View workflow job for this annotation

GitHub Actions / tests

Method Ferror\OpenapiCoverage\Symfony\Bundle::loadExtension() has parameter $config with no value type specified in iterable type array.
{
$builder
->register('ferror.openapi_coverage.console', CheckCoverageCommand::class)
->addArgument(new Reference('router'))
->addArgument(new Reference('nelmio_api_doc.render_docs'))
->addTag('console.command')
;
}

public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()

Check failure on line 30 in src/Symfony/Bundle.php

View workflow job for this annotation

GitHub Actions / tests

Call to an undefined method Symfony\Component\Config\Definition\Builder\NodeDefinition::children().
->children()
->arrayNode('excluded_routes')
->scalarPrototype()->end()
->end()
->end()
;
}
}
65 changes: 65 additions & 0 deletions src/Symfony/Console/CheckCoverageCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Ferror\OpenapiCoverage\Symfony\Console;

use Nelmio\ApiDocBundle\Render\RenderOpenApi;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface;

class CheckCoverageCommand extends Command
{
protected static $defaultName = 'ferror:check-openapi-coverage';

public function __construct(

Check failure on line 19 in src/Symfony/Console/CheckCoverageCommand.php

View workflow job for this annotation

GitHub Actions / tests

Method Ferror\OpenapiCoverage\Symfony\Console\CheckCoverageCommand::__construct() has parameter $excludedPaths with no value type specified in iterable type array.
private RouterInterface $router,
private RenderOpenApi $renderOpenApi,
private array $excludedPaths = [],
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setDescription('Check OpenAPI coverage against Symfony routes')
->addArgument('openapi-schema', InputArgument::OPTIONAL, 'Path to OpenAPI schema YAML file')
;
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$symfonyPaths = $this->router->getRouteCollection();
$symfonyPaths = array_map(fn (Route $route) => $route->getPath(), $symfonyPaths->all());
$symfonyPaths = array_values($symfonyPaths);
$symfonyPaths = array_filter($symfonyPaths, fn (string $route) => str_starts_with($route, '/risk/v1'));
$symfonyPaths = array_map(fn (string $route) => str_replace('/risk/v1', '', $route), $symfonyPaths);
$symfonyPaths = array_filter($symfonyPaths, fn(string $route) => !in_array($route, $this->excludedPaths, true));

$openApi = $this->renderOpenApi->render('json', 'default');
$openApi = json_decode($openApi, true, 512, JSON_THROW_ON_ERROR);

$openApiPaths = array_keys($openApi['paths']);

$missingPaths = array_diff($symfonyPaths, $openApiPaths);

$missingPathCoverage = count($openApiPaths) / count($symfonyPaths);
$output->writeln('Open API coverage: ' . round($missingPathCoverage * 100, 2) . '%');

if (empty($missingPaths)) {
$output->writeln('OpenAPI schema covers all Symfony routes. Good job!');
} else {
$output->writeln('Missing paths in OpenAPI schema:');
foreach ($missingPaths as $path) {
$output->writeln("- $path");
}
}

return Command::SUCCESS;
}
}
26 changes: 26 additions & 0 deletions tests/Integration/CheckCoverageCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Ferror\OpenapiCoverage\Integration;

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;

class CheckCoverageCommandTest extends KernelTestCase
{
public function testExecuteClass(): void
{
$kernel = self::bootKernel();
$application = new Application($kernel);

$command = $application->find('ferror:check-openapi-coverage');
$commandTester = new CommandTester($command);
$commandTester->execute([]);

$commandTester->assertCommandIsSuccessful();

$display = $commandTester->getDisplay();
}
}
22 changes: 22 additions & 0 deletions tests/Integration/Service/Kernel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Ferror\OpenapiCoverage\Integration\Service;

use Ferror\OpenapiCoverage\Symfony\Bundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel as SymfonyKernel;

class Kernel extends SymfonyKernel
{
public function registerBundles(): iterable
{
yield new Bundle();
}

public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(__DIR__ . '/config/library.yaml');
}
}
3 changes: 3 additions & 0 deletions tests/Integration/Service/config/library.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ferror_openapi_coverage:
excluded_routes:
- rest-docs.json

0 comments on commit a6d7a36

Please sign in to comment.