Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase code coverage #697

Merged
merged 9 commits into from
Aug 25, 2016
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@
"phpdocumentor/reflection-docblock": "^3.0",
"symfony/cache": "^3.1",
"symfony/dependency-injection": "^2.7 || ^3.0",
"symfony/framework-bundle": "^3.1",
"symfony/doctrine-bridge": "^2.8 || ^3.0",
"symfony/phpunit-bridge": "^2.7 || ^3.0",
"symfony/security": "^2.7 || ^3.0",
"symfony/validator": "^2.7 || ^3.0",
"symfony/finder": "^2.7 || ^3.0"
"symfony/finder": "^2.7 || ^3.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should enforce sorted dependencies so that we can stop mucking around with where they should "logically" be placed "correctly"... 😆

"symfony/framework-bundle": "^3.1",
"symfony/twig-bundle": "^2.8 || ^3.1"
},
"suggest": {
"symfony/twig-bundle": "To have a human-readable documentation relying on Swagger UI.",
"friendsofsymfony/user-bundle": "To use the FOSUserBundle bridge.",
"nelmio/api-doc-bundle": "To have the api sandbox & documentation.",
"phpdocumentor/reflection-docblock": "To support extracting metadata from PHPDoc.",
Expand Down
16 changes: 10 additions & 6 deletions features/fos_user.feature
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
Feature: Create-Retrieve-Update-Delete
In order to use an hypermedia API
As a client software developer
I need to be able to retrieve, create, update and delete JSON-LD encoded resources.
Feature: FOSUser integration
In order to use FOSUserBundle
As an API software developer
I need to be able manage users

@createSchema
@dropSchema
Scenario: Create a resource
Scenario: Create a user
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/users" with body:
"""
Expand All @@ -30,3 +29,8 @@ Feature: Create-Retrieve-Update-Delete
"username": "dummy.user"
}
"""

@dropSchema
Scenario: Delete a user
When I send a "DELETE" request to "/users/1"
Then the response status code should be 204
28 changes: 28 additions & 0 deletions features/hydra/entrypoint.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Feature: Entrypoint support
In order to build an auto-discoverable API
As a client software developer
I need to access to an entrypoint listing top-level resources

Scenario: Retrieve the Entrypoint
When I send a "GET" request to "/"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Entrypoint"
And the JSON node "@id" should be equal to "/"
And the JSON node "@type" should be equal to "Entrypoint"
And the JSON node "abstractDummy" should be equal to "/abstract_dummies"
And the JSON node "circularReference" should be equal to "/circular_references"
And the JSON node "compositeItem" should be equal to "/composite_items"
And the JSON node "compositeLabel" should be equal to "/composite_labels"
And the JSON node "compositeRelation" should be equal to "/composite_relations"
And the JSON node "concreteDummy" should be equal to "/concrete_dummies"
And the JSON node "customIdentifierDummy" should be equal to "/custom_identifier_dummies"
And the JSON node "customNormalizedDummy" should be equal to "/custom_normalized_dummies"
And the JSON node "customWritableIdentifierDummy" should be equal to "/custom_writable_identifier_dummies"
And the JSON node "dummy" should be equal to "/dummies"
And the JSON node "relatedDummy" should be equal to "/related_dummies"
And the JSON node "relationEmbedder" should be equal to "/relation_embedders"
And the JSON node "thirdLevel" should be equal to "/third_levels"
And the JSON node "user" should be equal to "/users"
And the JSON node "fileconfigdummy" should be equal to "/fileconfigdummies"
72 changes: 29 additions & 43 deletions features/jsonld/context.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,14 @@ Feature: JSON-LD contexts generation
I need to access to a JSON-LD context describing data types

Scenario: Retrieve Entrypoint context
When I send a "GET" request to "/"
When I send a "GET" request to "/contexts/Entrypoint"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Entrypoint"
And the JSON node "@id" should be equal to "/"
And the JSON node "@type" should be equal to "Entrypoint"
And the JSON node "abstractDummy" should be equal to "/abstract_dummies"
And the JSON node "circularReference" should be equal to "/circular_references"
And the JSON node "compositeItem" should be equal to "/composite_items"
And the JSON node "compositeLabel" should be equal to "/composite_labels"
And the JSON node "compositeRelation" should be equal to "/composite_relations"
And the JSON node "concreteDummy" should be equal to "/concrete_dummies"
And the JSON node "customIdentifierDummy" should be equal to "/custom_identifier_dummies"
And the JSON node "customNormalizedDummy" should be equal to "/custom_normalized_dummies"
And the JSON node "customWritableIdentifierDummy" should be equal to "/custom_writable_identifier_dummies"
And the JSON node "dummy" should be equal to "/dummies"
And the JSON node "relatedDummy" should be equal to "/related_dummies"
And the JSON node "relationEmbedder" should be equal to "/relation_embedders"
And the JSON node "thirdLevel" should be equal to "/third_levels"
And the JSON node "user" should be equal to "/users"
And the JSON node "fileconfigdummy" should be equal to "/fileconfigdummies"
And the JSON node "@context.@vocab" should be equal to "http://example.com/apidoc.jsonld#"
And the JSON node "@context.hydra" should be equal to "http://www.w3.org/ns/hydra/core#"
And the JSON node "@context.dummy.@id" should be equal to "Entrypoint/dummy"
And the JSON node "@context.dummy.@type" should be equal to "@id"

Scenario: Retrieve Dummy context
When I send a "GET" request to "/contexts/Dummy"
Expand All @@ -34,30 +20,30 @@ Feature: JSON-LD contexts generation
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": {
"@vocab": "http://example.com/apidoc.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"description": "https://schema.org/description",
"dummy": "#Dummy/dummy",
"dummyBoolean": "#Dummy/dummyBoolean",
"dummyDate": "#Dummy/dummyDate",
"dummyPrice": "#Dummy/dummyPrice",
"relatedDummy": {
"@id": "#Dummy/relatedDummy",
"@type": "@id"
},
"relatedDummies": {
"@id": "#Dummy/relatedDummies",
"@type": "@id"
},
"jsonData": "#Dummy/jsonData",
"nameConverted": "#Dummy/nameConverted",
"name": "http://schema.org/name",
"alias": "https://schema.org/alternateName",
"foo": "#Dummy/foo"
}
}
{
"@context": {
"@vocab": "http://example.com/apidoc.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"description": "https://schema.org/description",
"dummy": "#Dummy/dummy",
"dummyBoolean": "#Dummy/dummyBoolean",
"dummyDate": "#Dummy/dummyDate",
"dummyPrice": "#Dummy/dummyPrice",
"relatedDummy": {
"@id": "#Dummy/relatedDummy",
"@type": "@id"
},
"relatedDummies": {
"@id": "#Dummy/relatedDummies",
"@type": "@id"
},
"jsonData": "#Dummy/jsonData",
"nameConverted": "#Dummy/nameConverted",
"name": "http://schema.org/name",
"alias": "https://schema.org/alternateName",
"foo": "#Dummy/foo"
}
}
"""

Scenario: Retrieve context of an object with an embed relation
Expand Down
15 changes: 15 additions & 0 deletions features/nelmio_api_doc.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Feature: NelmioApiDoc integration
In order to use NelmioApiDocBundle
As an API software developer
I need to see the generated documentation

Scenario: Create a user
When I send a "GET" request to "/nelmioapidoc"
Then the response status code should be 200
And I should see text matching "AbstractDummy"
And I should see text matching "Dummy"
And I should see text matching "User"
And I should see text matching "Retrieves the collection of Dummy resources."
And I should see text matching "Creates a Dummy resource."
And I should see text matching "Deletes the Dummy resource."
And I should see text matching "Replaces the Dummy resource."
7 changes: 6 additions & 1 deletion features/swagger/doc.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Feature: Documentation support
As a client software developer
I need to know Swagger specifications of objects I send and receive

Scenario: Retrieve the API vocabulary
Scenario: Retrieve the Swagger/OpenAPI documentation
Given I send a "GET" request to "/apidoc.json"
Then the response status code should be 200
And the response should be in JSON
Expand Down Expand Up @@ -33,3 +33,8 @@ Feature: Documentation support
# Properties
And "id" property exists for the Swagger class "Dummy"
And "name" property is required for Swagger class "Dummy"

Scenario: Swagger UI is enabled
Given I send a "GET" request to "/doc"
Then the response status code should be 200
And I should see text matching "My Dummy API"
13 changes: 10 additions & 3 deletions src/Bridge/FosUser/EventListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@

namespace ApiPlatform\Core\Bridge\FosUser;

use ApiPlatform\Core\Exception\RuntimeException;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;

/**
* Bridges between FOSUserBundle and ApiPlatformBundle.
* Bridges between FOSUserBundle and API Platform Core.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Théo FIDRY <theo.fidry@gmail.com>
Expand All @@ -38,14 +40,19 @@ public function __construct(UserManagerInterface $userManager)
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$user = $event->getControllerResult();
$request = $event->getRequest();
try {
RequestAttributesExtractor::extractAttributes($request);
} catch (RuntimeException $e) {
return;
}

$user = $event->getControllerResult();
if (!$user instanceof UserInterface || $request->isMethodSafe()) {
return;
}

switch ($event->getRequest()->getMethod()) {
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
$this->userManager->deleteUser($user);
$event->setControllerResult(null);
Expand Down
72 changes: 52 additions & 20 deletions src/Bridge/Symfony/Routing/OperationMethodResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use ApiPlatform\Core\Exception\RuntimeException;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface;

Expand Down Expand Up @@ -88,35 +89,23 @@ private function getOperationMethod(string $resourceClass, string $operationName
return $method;
}

if ($collection) {
$routeName = $resourceMetadata->getCollectionOperationAttribute($operationName, 'route_name');
} else {
$routeName = $resourceMetadata->getItemOperationAttribute($operationName, 'route_name');
}

if (null === $routeName) {
if (null === $routeName = $this->getRouteName($resourceMetadata, $operationName, $collection)) {
throw new RuntimeException(sprintf('Either a "route_name" or a "method" operation attribute must exist for the operation "%s" of the resource "%s".', $operationName, $resourceClass));
}

/*
* @var Route
*/
foreach ($this->router->getRouteCollection() as $name => $route) {
if ($routeName === $name) {
$methods = $route->getMethods();

if (empty($methods)) {
return 'GET';
}
$route = $this->getRoute($routeName);
$methods = $route->getMethods();

return $methods[0];
}
if (empty($methods)) {
return 'GET';
}

throw new RuntimeException(sprintf('Route "%s" not found for the operation "%s" of the resource "%s".', $routeName, $operationName, $resourceClass));
return $methods[0];
}

/**
* Gets the route related to the given operation.
*
* @param string $resourceClass
* @param string $operationName
* @param bool $collection
Expand All @@ -127,6 +116,11 @@ private function getOperationMethod(string $resourceClass, string $operationName
*/
private function getOperationRoute(string $resourceClass, string $operationName, bool $collection) : Route
{
$routeName = $this->getRouteName($this->resourceMetadataFactory->create($resourceClass), $operationName, $collection);
if (null !== $routeName) {
return $this->getRoute($routeName);
}

$operationNameKey = sprintf('_api_%s_operation_name', $collection ? 'collection' : 'item');

foreach ($this->router->getRouteCollection()->all() as $routeName => $route) {
Expand All @@ -140,4 +134,42 @@ private function getOperationRoute(string $resourceClass, string $operationName,

throw new RuntimeException(sprintf('No route found for operation "%s" for type "%s".', $operationName, $resourceClass));
}

/**
* Gets the route name or null if not defined.
*
* @param ResourceMetadata $resourceMetadata
* @param string $operationName
* @param bool $collection
*
* @return string|null
*/
private function getRouteName(ResourceMetadata $resourceMetadata, string $operationName, bool $collection)
{
if ($collection) {
return $resourceMetadata->getCollectionOperationAttribute($operationName, 'route_name');
}

return $resourceMetadata->getItemOperationAttribute($operationName, 'route_name');
}

/**
* Gets the route with the given name.
*
* @param string $routeName
*
* @throws RuntimeException
*
* @return Route
*/
private function getRoute(string $routeName) : Route
{
foreach ($this->router->getRouteCollection() as $name => $route) {
if ($routeName === $name) {
return $route;
}
}

throw new RuntimeException(sprintf('The route "%s" does not exist.', $routeName));
}
}
10 changes: 5 additions & 5 deletions src/Hydra/Serializer/CollectionFiltersNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
* Enhance the result of collection by adding the filters applied on collection.
* Enhances the result of collection by adding the filters applied on collection.
*
* @author Samuel ROZE <samuel.roze@gmail.com>
*/
Expand Down Expand Up @@ -64,17 +64,17 @@ public function normalize($object, $format = null, array $context = [])

$operationName = $context['collection_operation_name'] ?? null;

if ($operationName) {
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
} else {
if (null === $operationName) {
$resourceFilters = $resourceMetadata->getAttribute('filters', []);
} else {
$resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
}

if ([] === $resourceFilters) {
return $data;
}

$requestParts = parse_url($context['request_uri']);
$requestParts = parse_url($context['request_uri'] ?? '');
if (!is_array($requestParts)) {
return $data;
}
Expand Down
3 changes: 3 additions & 0 deletions src/JsonLd/ContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ public function getResourceContext(string $resourceClass, int $referenceType = U
return $context;
}

/**
* {@inheritdoc}
*/
public function getResourceContextUri(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH) : string
{
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
Expand Down
Loading