Skip to content

Commit

Permalink
feat(symfony): add mercure asserts (#5764)
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentchalamon authored Sep 8, 2023
1 parent 0d04f28 commit 46e84ff
Show file tree
Hide file tree
Showing 16 changed files with 322 additions and 17 deletions.
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,96 @@ jobs:
php-coveralls --coverage_clover=build/logs/behat/clover.xml
continue-on-error: true

mercure:
name: PHPUnit + Behat (PHP ${{ matrix.php }}) (Mercure)
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
php:
- '8.1'
- '8.2'
fail-fast: false
env:
APP_ENV: mercure
MERCURE_URL: 'http://localhost:1337/.well-known/mercure'
MERCURE_JWT_SECRET: 'eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM'
services:
mercure:
image: dunglas/mercure
env:
SERVER_NAME: :1337
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
MERCURE_EXTRA_DIRECTIVES: |
# Custom directives, see https://mercure.rocks/docs/hub/config
anonymous
cors_origins *
ports:
- 1337:1337
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: pecl, composer
extensions: intl, bcmath, curl, openssl, mbstring, mongodb
coverage: pcov
ini-values: memory_limit=-1
- name: Get composer cache directory
id: composercache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Update project dependencies
run: composer update --no-interaction --no-progress --ansi
- name: Install PHPUnit
run: vendor/bin/simple-phpunit --version
- name: Clear test app cache
run: tests/Fixtures/app/console cache:clear --ansi
- name: Run PHPUnit tests
run: vendor/bin/simple-phpunit --log-junit build/logs/phpunit/junit.xml --coverage-clover build/logs/phpunit/clover.xml --group mercure
- name: Run Behat tests
run: |
mkdir -p build/logs/behat
vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=mercure-coverage --no-interaction
- name: Merge code coverage reports
run: |
wget -qO /usr/local/bin/phpcov https://phar.phpunit.de/phpcov.phar
chmod +x /usr/local/bin/phpcov
mkdir -p build/coverage
phpcov merge --clover build/logs/behat/clover.xml build/coverage
continue-on-error: true
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v3
with:
name: behat-logs-php${{ matrix.php }}
path: build/logs/behat
continue-on-error: true
- name: Upload coverage results to Codecov
uses: codecov/codecov-action@v3
with:
directory: build/logs/behat
name: behat-php${{ matrix.php }}
flags: behat
fail_ci_if_error: true
continue-on-error: true
- name: Upload coverage results to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer global require --prefer-dist --no-interaction --no-progress --ansi php-coveralls/php-coveralls
export PATH="$PATH:$HOME/.composer/vendor/bin"
php-coveralls --coverage_clover=build/logs/behat/clover.xml
continue-on-error: true

elasticsearch:
name: Behat (PHP ${{ matrix.php }}) (Elasticsearch)
runs-on: ubuntu-latest
Expand Down
50 changes: 46 additions & 4 deletions behat.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ default:
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'
filters:
tags: '~@postgres&&~@mongodb&&~@elasticsearch&&~@controller'
tags: '~@postgres&&~@mongodb&&~@elasticsearch&&~@controller&&~@mercure'
extensions:
'FriendsOfBehat\SymfonyExtension':
bootstrap: 'tests/Fixtures/app/bootstrap.php'
Expand Down Expand Up @@ -52,7 +52,7 @@ postgres:
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'
filters:
tags: '~@sqlite&&~@mongodb&&~@elasticsearch&&~@controller'
tags: '~@sqlite&&~@mongodb&&~@elasticsearch&&~@controller&&~@mercure'

mongodb:
suites:
Expand All @@ -73,7 +73,28 @@ mongodb:
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'
filters:
tags: '~@sqlite&&~@elasticsearch&&~@!mongodb'
tags: '~@sqlite&&~@elasticsearch&&~@!mongodb&&~@mercure'

mercure:
suites:
default: false
mercure: &mercure-suite
contexts:
- 'ApiPlatform\Tests\Behat\CommandContext'
- 'ApiPlatform\Tests\Behat\DoctrineContext'
- 'ApiPlatform\Tests\Behat\GraphqlContext'
- 'ApiPlatform\Tests\Behat\JsonContext'
- 'ApiPlatform\Tests\Behat\HydraContext'
- 'ApiPlatform\Tests\Behat\OpenApiContext'
- 'ApiPlatform\Tests\Behat\HttpCacheContext'
- 'ApiPlatform\Tests\Behat\JsonApiContext'
- 'ApiPlatform\Tests\Behat\JsonHalContext'
- 'ApiPlatform\Tests\Behat\MercureContext'
- 'ApiPlatform\Tests\Behat\XmlContext'
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'
filters:
tags: '@mercure'

elasticsearch:
suites:
Expand All @@ -88,7 +109,7 @@ elasticsearch:
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'
filters:
tags: '@elasticsearch'
tags: '@elasticsearch&&~@mercure'

default-coverage:
suites:
Expand Down Expand Up @@ -130,6 +151,27 @@ mongodb-coverage:
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'

mercure-coverage:
suites:
default: false
mongodb: &mercure-coverage-suite
<<: *mercure-suite
contexts:
- 'ApiPlatform\Tests\Behat\CommandContext'
- 'ApiPlatform\Tests\Behat\DoctrineContext'
- 'ApiPlatform\Tests\Behat\GraphqlContext'
- 'ApiPlatform\Tests\Behat\JsonContext'
- 'ApiPlatform\Tests\Behat\HydraContext'
- 'ApiPlatform\Tests\Behat\OpenApiContext'
- 'ApiPlatform\Tests\Behat\HttpCacheContext'
- 'ApiPlatform\Tests\Behat\JsonApiContext'
- 'ApiPlatform\Tests\Behat\JsonHalContext'
- 'ApiPlatform\Tests\Behat\MercureContext'
- 'ApiPlatform\Tests\Behat\CoverageContext'
- 'ApiPlatform\Tests\Behat\XmlContext'
- 'Behat\MinkExtension\Context\MinkContext'
- 'behatch:context:rest'

elasticsearch-coverage:
suites:
default: false
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"symfony/routing": "^6.1",
"symfony/security-bundle": "^6.1",
"symfony/security-core": "^6.1",
"symfony/stopwatch": "^6.1",
"symfony/twig-bundle": "^6.1",
"symfony/uid": "^6.1",
"symfony/validator": "^6.1",
Expand Down
4 changes: 2 additions & 2 deletions features/graphql/subscription.feature
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ Feature: GraphQL subscription support
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.updateDummyMercureSubscribe.dummyMercure.id" should be equal to "/dummy_mercures/1"
And the JSON node "data.updateDummyMercureSubscribe.dummyMercure.name" should be equal to "Dummy Mercure #1"
And the JSON node "data.updateDummyMercureSubscribe.mercureUrl" should match "@^https://demo.mercure.rocks/hub\?topic=http://example.com/subscriptions/[a-f0-9]+$@"
And the JSON node "data.updateDummyMercureSubscribe.mercureUrl" should match "@^https://demo.mercure.rocks\?topic=http://example.com/subscriptions/[a-f0-9]+$@"
And the JSON node "data.updateDummyMercureSubscribe.clientSubscriptionId" should be equal to "myId"

When I send the following GraphQL request:
Expand All @@ -182,7 +182,7 @@ Feature: GraphQL subscription support
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.updateDummyMercureSubscribe.dummyMercure.id" should be equal to "/dummy_mercures/2"
And the JSON node "data.updateDummyMercureSubscribe.mercureUrl" should match "@^https://demo.mercure.rocks/hub\?topic=http://example.com/subscriptions/[a-f0-9]+$@"
And the JSON node "data.updateDummyMercureSubscribe.mercureUrl" should match "@^https://demo.mercure.rocks\?topic=http://example.com/subscriptions/[a-f0-9]+$@"

Scenario: Receive Mercure updates with different payloads from subscriptions (legacy PUT in non-standard mode)
When I add "Accept" header equal to "application/ld+json"
Expand Down
2 changes: 1 addition & 1 deletion features/mercure/discover.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Feature: Mercure discovery support
@createSchema
Scenario: Checks that the Mercure Link is added
Given I send a "GET" request to "/dummy_mercures"
Then the header "Link" should contain '<https://demo.mercure.rocks/hub>; rel="mercure"'
Then the header "Link" should contain '<https://demo.mercure.rocks>; rel="mercure"'

Scenario: Checks that the Mercure Link is not added on endpoints where updates are not dispatched
Given I send a "GET" request to "/"
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<groups>
<exclude>
<group>mongodb</group>
<group>mercure</group>
</exclude>
</groups>
</phpunit>
1 change: 1 addition & 0 deletions phpunit10.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<groups>
<exclude>
<group>mongodb</group>
<group>mercure</group>
</exclude>
</groups>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class MercureSubscriptionIriGeneratorTest extends TestCase
*/
protected function setUp(): void
{
$this->defaultHub = new Hub('https://demo.mercure.rocks/hub', new StaticTokenProvider('xx'));
$this->defaultHub = new Hub('https://demo.mercure.rocks', new StaticTokenProvider('xx'));
$this->managedHub = new Hub('https://demo.mercure.rocks/managed', new StaticTokenProvider('xx'));

$this->registry = new HubRegistry($this->defaultHub, ['default' => $this->defaultHub, 'managed' => $this->managedHub]);
Expand Down
49 changes: 49 additions & 0 deletions src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
use PHPUnit\Framework\ExpectationFailedException;
use Symfony\Bundle\FrameworkBundle\Test\BrowserKitAssertionsTrait;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\Mercure\Debug\TraceableHub;
use Symfony\Component\Mercure\HubRegistry;
use Symfony\Component\Mercure\Update;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
Expand Down Expand Up @@ -135,6 +138,32 @@ public static function assertMatchesResourceItemJsonSchema(string $resourceClass
static::assertMatchesJsonSchema($schema->getArrayCopy());
}

/**
* @return Update[]
*/
public static function getMercureMessages(string $hubName = null): array
{
return array_map(fn (array $update) => $update['object'], self::getMercureHub($hubName)->getMessages());
}

public static function getMercureMessage(int $index = 0, string $hubName = null): ?Update
{
return static::getMercureMessages($hubName)[$index] ?? null;
}

/**
* @throws \JsonException
*/
public static function assertMercureUpdateMatchesJsonSchema(Update $update, array $topics, array|object|string $jsonSchema = '', bool $private = false, string $id = null, string $type = null, int $retry = null, string $message = ''): void
{
static::assertSame($topics, $update->getTopics(), $message);
static::assertThat(json_decode($update->getData(), true, \JSON_THROW_ON_ERROR), new MatchesJsonSchema($jsonSchema), $message);
static::assertSame($private, $update->isPrivate(), $message);
static::assertSame($id, $update->getId(), $message);
static::assertSame($type, $update->getType(), $message);
static::assertSame($retry, $update->getRetry(), $message);
}

private static function getHttpClient(Client $newClient = null): ?Client
{
static $client;
Expand Down Expand Up @@ -185,4 +214,24 @@ private static function getResourceMetadataCollectionFactory(): ?ResourceMetadat

return $resourceMetadataFactoryCollection;
}

private static function getMercureRegistry(): HubRegistry
{
$container = static::getContainer();
if ($container->has(HubRegistry::class)) {
return $container->get(HubRegistry::class);
}

static::fail('A client must have Mercure enabled to make update assertions. Did you forget to require symfony/mercure?');
}

private static function getMercureHub(string $name = null): TraceableHub
{
$hub = self::getMercureRegistry()->getHub($name);
if (!$hub instanceof TraceableHub) {
static::fail('You must enabled the profiler to make Mercure update assertions.');
}

return $hub;
}
}
27 changes: 27 additions & 0 deletions tests/Fixtures/TestBundle/Document/DirectMercure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Metadata\ApiResource;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

#[ApiResource(mercure: true, extraProperties: ['standard_put' => false])]
#[ODM\Document]
class DirectMercure
{
#[ODM\Id(strategy: 'INCREMENT', type: 'int')]
public $id;
#[ODM\Field(type: 'string')]
public $name;
}
29 changes: 29 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DirectMercure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Metadata\ApiResource;
use Doctrine\ORM\Mapping as ORM;

#[ApiResource(mercure: ['enable_async_update' => false])]
#[ORM\Entity]
class DirectMercure
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
public $id;
#[ORM\Column]
public $name;
}
14 changes: 8 additions & 6 deletions tests/Fixtures/app/config/config_common.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
parameters:
container.autowiring.strict_mode: true
.container.dumper.inline_class_loader: true
env(MERCURE_URL): https://demo.mercure.rocks
env(MERCURE_JWT_SECRET): eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM

doctrine:
dbal:
driver: 'pdo_sqlite'
Expand All @@ -19,8 +25,8 @@ web_profiler:
mercure:
hubs:
default:
url: https://demo.mercure.rocks/hub
jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM
url: '%env(MERCURE_URL)%'
jwt: '%env(MERCURE_JWT_SECRET)%'

api_platform:
title: 'My Dummy API'
Expand Down Expand Up @@ -88,10 +94,6 @@ api_platform:
mercure:
include_type: true

parameters:
container.autowiring.strict_mode: true
.container.dumper.inline_class_loader: true

services:
test.client:
class: ApiPlatform\Tests\Fixtures\TestBundle\BrowserKit\Client
Expand Down
Loading

0 comments on commit 46e84ff

Please sign in to comment.