Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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 @@ -79,6 +79,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 <dunglas@gmail.com>
*
* 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 <dunglas@gmail.com>
*
* 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