Skip to content

Commit

Permalink
Open Api example test
Browse files Browse the repository at this point in the history
  • Loading branch information
Ferror committed Dec 30, 2023
1 parent 04c9a1b commit 05d7bb5
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 17 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ARG COMPOSER_VERSION
ARG PHP_VERSION
ARG EXTENSION_INSTALLER_VERSION
ARG XDEBUG_VERSION

FROM composer:${COMPOSER_VERSION} AS composer
FROM mlocati/php-extension-installer:${EXTENSION_INSTALLER_VERSION} AS extensions
Expand All @@ -10,6 +11,6 @@ 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
RUN install-php-extensions xdebug-${XDEBUG_VERSION}

WORKDIR /app/
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ services:
PHP_VERSION: 8.2
COMPOSER_VERSION: 2.5
EXTENSION_INSTALLER_VERSION: 2.1
XDEBUG_VERSION: 3.3.1
command: ["composer", "install", "--no-interaction"]
environment:
XDEBUG_MODE: coverage
Expand Down
3 changes: 2 additions & 1 deletion src/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public function __construct(

public function equals(self $self): bool
{
return $this->path === $self->path && $this->method === $self->method;
return strtoupper($this->path) === strtoupper($self->path)
&& strtoupper($this->method) === strtoupper($self->method);
}
}
26 changes: 21 additions & 5 deletions src/Symfony/Console/CheckCoverageCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Nelmio\ApiDocBundle\Render\RenderOpenApi;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\RouterInterface;
Expand Down Expand Up @@ -60,23 +61,38 @@ public function execute(InputInterface $input, OutputInterface $output): int

$missingPaths = RouteCollection::create();

// Calculate not documented paths
foreach ($openApiPaths->items as $path) {
if (!$paths->contains($path)) {
$missingPaths = $missingPaths->add($path);
}
}

$notExistingPaths = RouteCollection::create();

// Calculate not existing, documented paths
foreach ($paths->items as $path) {
if (!$openApiPaths->contains($path)) {
$missingPaths->add($path);
$notExistingPaths = $notExistingPaths->add($path);
}
}

$coverageCalculator = new CoverageCalculator($paths->count(), $openApiPaths->count());
$coverageCalculator = new CoverageCalculator($paths->count(), $openApiPaths->count() - $notExistingPaths->count());

$output->writeln('Open API coverage: ' . $coverageCalculator->calculate()->asPercentage() . '%');

if ($missingPaths->count() === 0) {
$output->writeln('OpenAPI schema covers all Symfony routes. Good job!');
} else {
$output->writeln('Missing paths in OpenAPI schema:');
foreach ($missingPaths->items as $path) {
$output->writeln($path->path);
$table = new Table($output);
$table->setHeaderTitle('Missing documentation');
$table->setHeaders(['path', 'method']);

foreach ($missingPaths->items as $route) {
$table->addRow([$route->path, $route->method]);
}

$table->render();
}

return Command::SUCCESS;
Expand Down
21 changes: 11 additions & 10 deletions tests/Integration/CheckCoverageCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ public function testExecuteClass(): void

$display = $commandTester->getDisplay();

// $expectedDisplay = <<<TEXT
//Open API coverage: 0%
//Missing paths in OpenAPI schema:
///products
///products
///products/:id
//
//TEXT;
//
// $this->assertEquals($expectedDisplay, $display);
$expectedDisplay = <<<TEXT
Open API coverage: 75%
+- Missing documenta... -+
| path | method |
+---------------+--------+
| /products/:id | get |
+---------------+--------+
TEXT;

$this->assertEquals($expectedDisplay, $display);
}
}
1 change: 1 addition & 0 deletions tests/Integration/Service/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(__DIR__ . '/config/framework.yaml');
$loader->load(__DIR__ . '/config/library.yaml');
$loader->load(__DIR__ . '/config/services.yaml');

$loader->load(function (ContainerBuilder $container) {
$container->loadFromExtension('framework', [
Expand Down
20 changes: 20 additions & 0 deletions tests/Integration/Service/MockDescriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Ferror\OpenapiCoverage\Integration\Service;

use Nelmio\ApiDocBundle\Describer\DescriberInterface;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
use OpenApi\Annotations\OpenApi;
use Symfony\Component\Yaml\Yaml;

final readonly class MockDescriber implements DescriberInterface
{
public function describe(OpenApi $api): void
{
$schema = Yaml::parseFile(__DIR__ . '/resources/openapi.yaml');

Util::merge($api, $schema, true);
}
}
4 changes: 4 additions & 0 deletions tests/Integration/Service/config/routes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ rest_product_post:
rest_product_delete:
path: /v1/products/:id
methods: DELETE

rest_product_put:
path: /v1/products/:id
methods: PUT
3 changes: 3 additions & 0 deletions tests/Integration/Service/config/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
services:
Ferror\OpenapiCoverage\Integration\Service\MockDescriber:
tags: ['nelmio_api_doc.describer']
58 changes: 58 additions & 0 deletions tests/Integration/Service/resources/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: Get all products
responses:
'200':
description: Successful response
post:
summary: Create a new product
requestBody:
description: Product object
required: true
content:
application/json:
example:
id: 1
name: "Sample Product"
description: "A sample product description"
price: 29.99
responses:
'201':
description: Product created successfully
'400':
description: Invalid request

/products/:id:
get:
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: ID of the product
summary: Get a product by ID
responses:
'200':
description: Successful response
'404':
description: Product not found
delete:
parameters:
- name: id
in: path
required: true
schema:
type: integer
description: ID of the product
summary: Delete a product by ID
responses:
'204':
description: Product deleted successfully
'404':
description: Product not found
7 changes: 7 additions & 0 deletions tests/Unit/CoverageCalculatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
final class CoverageCalculatorTest extends TestCase
{
public function testCalculate(): void
{
$calculator = new CoverageCalculator(2, 1);

$this->assertEquals(new Coverage(0.5), $calculator->calculate());
}

public function testCalculateHalf(): void
{
$calculator = new CoverageCalculator(1, 1);

Expand Down
7 changes: 7 additions & 0 deletions tests/Unit/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ public function testEquals(): void
$this->assertTrue($route->equals(new Route('products', 'get')));
$this->assertFalse($route->equals(new Route('products', 'post')));
}

public function testEqualsWithDifferentCase(): void
{
$route = new Route('products', 'get');

$this->assertTrue($route->equals(new Route('products', 'GET')));
}
}

0 comments on commit 05d7bb5

Please sign in to comment.