Skip to content

Commit 46e84ff

Browse files
feat(symfony): add mercure asserts (#5764)
1 parent 0d04f28 commit 46e84ff

File tree

16 files changed

+322
-17
lines changed

16 files changed

+322
-17
lines changed

.github/workflows/ci.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,96 @@ jobs:
497497
php-coveralls --coverage_clover=build/logs/behat/clover.xml
498498
continue-on-error: true
499499

500+
mercure:
501+
name: PHPUnit + Behat (PHP ${{ matrix.php }}) (Mercure)
502+
runs-on: ubuntu-latest
503+
timeout-minutes: 20
504+
strategy:
505+
matrix:
506+
php:
507+
- '8.1'
508+
- '8.2'
509+
fail-fast: false
510+
env:
511+
APP_ENV: mercure
512+
MERCURE_URL: 'http://localhost:1337/.well-known/mercure'
513+
MERCURE_JWT_SECRET: 'eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM'
514+
services:
515+
mercure:
516+
image: dunglas/mercure
517+
env:
518+
SERVER_NAME: :1337
519+
MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
520+
MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
521+
MERCURE_EXTRA_DIRECTIVES: |
522+
# Custom directives, see https://mercure.rocks/docs/hub/config
523+
anonymous
524+
cors_origins *
525+
ports:
526+
- 1337:1337
527+
steps:
528+
- name: Checkout
529+
uses: actions/checkout@v3
530+
- name: Setup PHP
531+
uses: shivammathur/setup-php@v2
532+
with:
533+
php-version: ${{ matrix.php }}
534+
tools: pecl, composer
535+
extensions: intl, bcmath, curl, openssl, mbstring, mongodb
536+
coverage: pcov
537+
ini-values: memory_limit=-1
538+
- name: Get composer cache directory
539+
id: composercache
540+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
541+
- name: Cache dependencies
542+
uses: actions/cache@v3
543+
with:
544+
path: ${{ steps.composercache.outputs.dir }}
545+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
546+
restore-keys: ${{ runner.os }}-composer-
547+
- name: Update project dependencies
548+
run: composer update --no-interaction --no-progress --ansi
549+
- name: Install PHPUnit
550+
run: vendor/bin/simple-phpunit --version
551+
- name: Clear test app cache
552+
run: tests/Fixtures/app/console cache:clear --ansi
553+
- name: Run PHPUnit tests
554+
run: vendor/bin/simple-phpunit --log-junit build/logs/phpunit/junit.xml --coverage-clover build/logs/phpunit/clover.xml --group mercure
555+
- name: Run Behat tests
556+
run: |
557+
mkdir -p build/logs/behat
558+
vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=mercure-coverage --no-interaction
559+
- name: Merge code coverage reports
560+
run: |
561+
wget -qO /usr/local/bin/phpcov https://phar.phpunit.de/phpcov.phar
562+
chmod +x /usr/local/bin/phpcov
563+
mkdir -p build/coverage
564+
phpcov merge --clover build/logs/behat/clover.xml build/coverage
565+
continue-on-error: true
566+
- name: Upload test artifacts
567+
if: always()
568+
uses: actions/upload-artifact@v3
569+
with:
570+
name: behat-logs-php${{ matrix.php }}
571+
path: build/logs/behat
572+
continue-on-error: true
573+
- name: Upload coverage results to Codecov
574+
uses: codecov/codecov-action@v3
575+
with:
576+
directory: build/logs/behat
577+
name: behat-php${{ matrix.php }}
578+
flags: behat
579+
fail_ci_if_error: true
580+
continue-on-error: true
581+
- name: Upload coverage results to Coveralls
582+
env:
583+
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
584+
run: |
585+
composer global require --prefer-dist --no-interaction --no-progress --ansi php-coveralls/php-coveralls
586+
export PATH="$PATH:$HOME/.composer/vendor/bin"
587+
php-coveralls --coverage_clover=build/logs/behat/clover.xml
588+
continue-on-error: true
589+
500590
elasticsearch:
501591
name: Behat (PHP ${{ matrix.php }}) (Elasticsearch)
502592
runs-on: ubuntu-latest

behat.yml.dist

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ default:
1616
- 'Behat\MinkExtension\Context\MinkContext'
1717
- 'behatch:context:rest'
1818
filters:
19-
tags: '~@postgres&&~@mongodb&&~@elasticsearch&&~@controller'
19+
tags: '~@postgres&&~@mongodb&&~@elasticsearch&&~@controller&&~@mercure'
2020
extensions:
2121
'FriendsOfBehat\SymfonyExtension':
2222
bootstrap: 'tests/Fixtures/app/bootstrap.php'
@@ -52,7 +52,7 @@ postgres:
5252
- 'Behat\MinkExtension\Context\MinkContext'
5353
- 'behatch:context:rest'
5454
filters:
55-
tags: '~@sqlite&&~@mongodb&&~@elasticsearch&&~@controller'
55+
tags: '~@sqlite&&~@mongodb&&~@elasticsearch&&~@controller&&~@mercure'
5656

5757
mongodb:
5858
suites:
@@ -73,7 +73,28 @@ mongodb:
7373
- 'Behat\MinkExtension\Context\MinkContext'
7474
- 'behatch:context:rest'
7575
filters:
76-
tags: '~@sqlite&&~@elasticsearch&&~@!mongodb'
76+
tags: '~@sqlite&&~@elasticsearch&&~@!mongodb&&~@mercure'
77+
78+
mercure:
79+
suites:
80+
default: false
81+
mercure: &mercure-suite
82+
contexts:
83+
- 'ApiPlatform\Tests\Behat\CommandContext'
84+
- 'ApiPlatform\Tests\Behat\DoctrineContext'
85+
- 'ApiPlatform\Tests\Behat\GraphqlContext'
86+
- 'ApiPlatform\Tests\Behat\JsonContext'
87+
- 'ApiPlatform\Tests\Behat\HydraContext'
88+
- 'ApiPlatform\Tests\Behat\OpenApiContext'
89+
- 'ApiPlatform\Tests\Behat\HttpCacheContext'
90+
- 'ApiPlatform\Tests\Behat\JsonApiContext'
91+
- 'ApiPlatform\Tests\Behat\JsonHalContext'
92+
- 'ApiPlatform\Tests\Behat\MercureContext'
93+
- 'ApiPlatform\Tests\Behat\XmlContext'
94+
- 'Behat\MinkExtension\Context\MinkContext'
95+
- 'behatch:context:rest'
96+
filters:
97+
tags: '@mercure'
7798

7899
elasticsearch:
79100
suites:
@@ -88,7 +109,7 @@ elasticsearch:
88109
- 'Behat\MinkExtension\Context\MinkContext'
89110
- 'behatch:context:rest'
90111
filters:
91-
tags: '@elasticsearch'
112+
tags: '@elasticsearch&&~@mercure'
92113

93114
default-coverage:
94115
suites:
@@ -130,6 +151,27 @@ mongodb-coverage:
130151
- 'Behat\MinkExtension\Context\MinkContext'
131152
- 'behatch:context:rest'
132153

154+
mercure-coverage:
155+
suites:
156+
default: false
157+
mongodb: &mercure-coverage-suite
158+
<<: *mercure-suite
159+
contexts:
160+
- 'ApiPlatform\Tests\Behat\CommandContext'
161+
- 'ApiPlatform\Tests\Behat\DoctrineContext'
162+
- 'ApiPlatform\Tests\Behat\GraphqlContext'
163+
- 'ApiPlatform\Tests\Behat\JsonContext'
164+
- 'ApiPlatform\Tests\Behat\HydraContext'
165+
- 'ApiPlatform\Tests\Behat\OpenApiContext'
166+
- 'ApiPlatform\Tests\Behat\HttpCacheContext'
167+
- 'ApiPlatform\Tests\Behat\JsonApiContext'
168+
- 'ApiPlatform\Tests\Behat\JsonHalContext'
169+
- 'ApiPlatform\Tests\Behat\MercureContext'
170+
- 'ApiPlatform\Tests\Behat\CoverageContext'
171+
- 'ApiPlatform\Tests\Behat\XmlContext'
172+
- 'Behat\MinkExtension\Context\MinkContext'
173+
- 'behatch:context:rest'
174+
133175
elasticsearch-coverage:
134176
suites:
135177
default: false

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"symfony/routing": "^6.1",
8181
"symfony/security-bundle": "^6.1",
8282
"symfony/security-core": "^6.1",
83+
"symfony/stopwatch": "^6.1",
8384
"symfony/twig-bundle": "^6.1",
8485
"symfony/uid": "^6.1",
8586
"symfony/validator": "^6.1",

features/graphql/subscription.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Feature: GraphQL subscription support
164164
And the header "Content-Type" should be equal to "application/json"
165165
And the JSON node "data.updateDummyMercureSubscribe.dummyMercure.id" should be equal to "/dummy_mercures/1"
166166
And the JSON node "data.updateDummyMercureSubscribe.dummyMercure.name" should be equal to "Dummy Mercure #1"
167-
And the JSON node "data.updateDummyMercureSubscribe.mercureUrl" should match "@^https://demo.mercure.rocks/hub\?topic=http://example.com/subscriptions/[a-f0-9]+$@"
167+
And the JSON node "data.updateDummyMercureSubscribe.mercureUrl" should match "@^https://demo.mercure.rocks\?topic=http://example.com/subscriptions/[a-f0-9]+$@"
168168
And the JSON node "data.updateDummyMercureSubscribe.clientSubscriptionId" should be equal to "myId"
169169

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

187187
Scenario: Receive Mercure updates with different payloads from subscriptions (legacy PUT in non-standard mode)
188188
When I add "Accept" header equal to "application/ld+json"

features/mercure/discover.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Feature: Mercure discovery support
66
@createSchema
77
Scenario: Checks that the Mercure Link is added
88
Given I send a "GET" request to "/dummy_mercures"
9-
Then the header "Link" should contain '<https://demo.mercure.rocks/hub>; rel="mercure"'
9+
Then the header "Link" should contain '<https://demo.mercure.rocks>; rel="mercure"'
1010

1111
Scenario: Checks that the Mercure Link is not added on endpoints where updates are not dispatched
1212
Given I send a "GET" request to "/"

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<groups>
4040
<exclude>
4141
<group>mongodb</group>
42+
<group>mercure</group>
4243
</exclude>
4344
</groups>
4445
</phpunit>

phpunit10.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<groups>
2222
<exclude>
2323
<group>mongodb</group>
24+
<group>mercure</group>
2425
</exclude>
2526
</groups>
2627

src/GraphQl/Tests/Subscription/MercureSubscriptionIriGeneratorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class MercureSubscriptionIriGeneratorTest extends TestCase
3535
*/
3636
protected function setUp(): void
3737
{
38-
$this->defaultHub = new Hub('https://demo.mercure.rocks/hub', new StaticTokenProvider('xx'));
38+
$this->defaultHub = new Hub('https://demo.mercure.rocks', new StaticTokenProvider('xx'));
3939
$this->managedHub = new Hub('https://demo.mercure.rocks/managed', new StaticTokenProvider('xx'));
4040

4141
$this->registry = new HubRegistry($this->defaultHub, ['default' => $this->defaultHub, 'managed' => $this->managedHub]);

src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
use PHPUnit\Framework\ExpectationFailedException;
2424
use Symfony\Bundle\FrameworkBundle\Test\BrowserKitAssertionsTrait;
2525
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
26+
use Symfony\Component\Mercure\Debug\TraceableHub;
27+
use Symfony\Component\Mercure\HubRegistry;
28+
use Symfony\Component\Mercure\Update;
2629
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
2730
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
2831
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
@@ -135,6 +138,32 @@ public static function assertMatchesResourceItemJsonSchema(string $resourceClass
135138
static::assertMatchesJsonSchema($schema->getArrayCopy());
136139
}
137140

141+
/**
142+
* @return Update[]
143+
*/
144+
public static function getMercureMessages(string $hubName = null): array
145+
{
146+
return array_map(fn (array $update) => $update['object'], self::getMercureHub($hubName)->getMessages());
147+
}
148+
149+
public static function getMercureMessage(int $index = 0, string $hubName = null): ?Update
150+
{
151+
return static::getMercureMessages($hubName)[$index] ?? null;
152+
}
153+
154+
/**
155+
* @throws \JsonException
156+
*/
157+
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
158+
{
159+
static::assertSame($topics, $update->getTopics(), $message);
160+
static::assertThat(json_decode($update->getData(), true, \JSON_THROW_ON_ERROR), new MatchesJsonSchema($jsonSchema), $message);
161+
static::assertSame($private, $update->isPrivate(), $message);
162+
static::assertSame($id, $update->getId(), $message);
163+
static::assertSame($type, $update->getType(), $message);
164+
static::assertSame($retry, $update->getRetry(), $message);
165+
}
166+
138167
private static function getHttpClient(Client $newClient = null): ?Client
139168
{
140169
static $client;
@@ -185,4 +214,24 @@ private static function getResourceMetadataCollectionFactory(): ?ResourceMetadat
185214

186215
return $resourceMetadataFactoryCollection;
187216
}
217+
218+
private static function getMercureRegistry(): HubRegistry
219+
{
220+
$container = static::getContainer();
221+
if ($container->has(HubRegistry::class)) {
222+
return $container->get(HubRegistry::class);
223+
}
224+
225+
static::fail('A client must have Mercure enabled to make update assertions. Did you forget to require symfony/mercure?');
226+
}
227+
228+
private static function getMercureHub(string $name = null): TraceableHub
229+
{
230+
$hub = self::getMercureRegistry()->getHub($name);
231+
if (!$hub instanceof TraceableHub) {
232+
static::fail('You must enabled the profiler to make Mercure update assertions.');
233+
}
234+
235+
return $hub;
236+
}
188237
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
18+
19+
#[ApiResource(mercure: true, extraProperties: ['standard_put' => false])]
20+
#[ODM\Document]
21+
class DirectMercure
22+
{
23+
#[ODM\Id(strategy: 'INCREMENT', type: 'int')]
24+
public $id;
25+
#[ODM\Field(type: 'string')]
26+
public $name;
27+
}

0 commit comments

Comments
 (0)