From ec94d728bcfe65b34443d00fc44146bf4fd422a1 Mon Sep 17 00:00:00 2001 From: Laurent Constantin Date: Fri, 29 Nov 2024 22:35:00 +0100 Subject: [PATCH] test(kobo): Mock StoreProxy and add tests --- src/Kobo/Proxy/KoboStoreProxy.php | 28 +++++++++-- src/Kobo/UpstreamSyncMerger.php | 11 ++++- tests/Contraints/JSONIsValidSyncResponse.php | 15 +++++- .../Kobo/AbstractKoboControllerTest.php | 28 +++++++++++ .../Kobo/KoboSyncControllerTest.php | 48 +++++++++++++++++++ 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/Kobo/Proxy/KoboStoreProxy.php b/src/Kobo/Proxy/KoboStoreProxy.php index f9a92248..743c3746 100644 --- a/src/Kobo/Proxy/KoboStoreProxy.php +++ b/src/Kobo/Proxy/KoboStoreProxy.php @@ -4,6 +4,7 @@ use App\Security\KoboTokenExtractor; use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Promise\PromiseInterface; use Nyholm\Psr7\Factory\Psr17Factory; @@ -31,6 +32,7 @@ public function __construct( protected KoboProxyConfiguration $configuration, protected LoggerInterface $koboProxyLogger, protected KoboTokenExtractor $tokenExtractor, + protected ?ClientInterface $client = null, ) { } @@ -93,12 +95,10 @@ private function _proxy(Request $request, string $hostname, array $config = []): $config = $this->getConfig($config); $psrRequest = $this->convertRequest($request, $hostname); - $accessToken = $this->tokenExtractor->extractAccessToken($request) ?? 'unknown'; + $client = $this->getClient($request); - $client = new Client(); $psrResponse = $client->send($psrRequest, [ 'base_uri' => $hostname, - 'handler' => $this->koboProxyLoggerFactory->createStack($accessToken), 'http_errors' => false, 'connect_timeout' => 5, ] + $config @@ -141,7 +141,7 @@ public function proxyAsync(Request $request, bool $streamAllowed): PromiseInterf $accessToken = $this->tokenExtractor->extractAccessToken($request) ?? 'unknown'; - $client = new Client(); + $client = $this->getClient($request); return $client->sendAsync($psrRequest, [ 'base_uri' => $upstreamUrl, @@ -189,7 +189,7 @@ private function getUpstreamUrl(Request $request): string private function getConfig(array $config): array { - // By default, we do not follow redirects, except if explicitly asked otherwise + // By default, we do not follow redirects, except if explicitly$handler asked otherwise $config['redirect.disable'] ??= true; // By default, we do stream, except if explicitly asked otherwise @@ -197,4 +197,22 @@ private function getConfig(array $config): array return $config; } + + public function setClient(?ClientInterface $client): void + { + $this->client = $client; + } + + private function getClient(Request $request): ClientInterface + { + if ($this->client instanceof ClientInterface) { + return $this->client; + } + + $accessToken = $this->tokenExtractor->extractAccessToken($request) ?? 'unknown'; + + return new Client([ + 'handler' => $this->koboProxyLoggerFactory->createStack($accessToken), + ]); + } } diff --git a/src/Kobo/UpstreamSyncMerger.php b/src/Kobo/UpstreamSyncMerger.php index 492dea51..325235d9 100644 --- a/src/Kobo/UpstreamSyncMerger.php +++ b/src/Kobo/UpstreamSyncMerger.php @@ -5,6 +5,7 @@ use App\Entity\KoboDevice; use App\Kobo\Proxy\KoboStoreProxy; use App\Kobo\Response\SyncResponse; +use GuzzleHttp\Exception\GuzzleException; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -28,7 +29,15 @@ public function merge(KoboDevice $device, SyncResponse $syncResponse, Request $r return false; } - $response = $this->koboStoreProxy->proxy($request, ['stream' => false]); + try { + $response = $this->koboStoreProxy->proxy($request, ['stream' => false]); + } catch (GuzzleException $e) { + $this->koboSyncLogger->error('Unable to sync with upstream: {exception}', [ + 'exception' => $e, + ]); + + return false; + } if (false === $response->isOk()) { $this->koboSyncLogger->error('Sync response is not ok. Got '.$response->getStatusCode()); diff --git a/tests/Contraints/JSONIsValidSyncResponse.php b/tests/Contraints/JSONIsValidSyncResponse.php index 5f8fc32a..13f35946 100644 --- a/tests/Contraints/JSONIsValidSyncResponse.php +++ b/tests/Contraints/JSONIsValidSyncResponse.php @@ -20,7 +20,15 @@ public function __construct(protected array $expectedKeysCount) } } - const KNOWN_TYPES = ["NewEntitlement", "ChangedTag", "NewTag", "RemovedPublication", "ChangedEntitlement"]; + const KNOWN_TYPES = [ + "ChangedEntitlement", + "ChangedReadingState", + "ChangedTag", + "DeletedTag", + "NewEntitlement", + "NewTag", + "RemovedPublication", + ]; public function matches($other): bool{ try{ $this->test($other); @@ -49,6 +57,8 @@ private function test(mixed $other): void "NewEntitlement" => $this->assertNewEntitlement($item['NewEntitlement']), "ChangedTag" => $this->assertChangedTag(), "NewTag" => $this->assertNewTag(), + "DeletedTag" => $this->assertDeletedTag(), + "ChangedReadingState" => null, "RemovedPublication" => $this->assertRemovedPublication(), "ChangedEntitlement" => $this->assertChangedEntitlement($item['ChangedEntitlement']), default => throw new \InvalidArgumentException('Unknown type') @@ -71,6 +81,9 @@ private function assertChangedTag(): void private function assertNewTag(): void { } + private function assertDeletedTag(): void + { + } private function assertRemovedPublication(): void { diff --git a/tests/Controller/Kobo/AbstractKoboControllerTest.php b/tests/Controller/Kobo/AbstractKoboControllerTest.php index 14676dec..b946bd6a 100644 --- a/tests/Controller/Kobo/AbstractKoboControllerTest.php +++ b/tests/Controller/Kobo/AbstractKoboControllerTest.php @@ -3,7 +3,13 @@ namespace App\Tests\Controller\Kobo; use App\Kobo\Kepubify\KepubifyEnabler; +use App\Kobo\Proxy\KoboProxyConfiguration; +use App\Kobo\Proxy\KoboStoreProxy; use App\Tests\InjectFakeFileSystemTrait; +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; use Symfony\Component\BrowserKit\AbstractBrowser; use App\DataFixtures\BookFixture; use App\Entity\Book; @@ -90,5 +96,27 @@ protected function getKepubifyEnabler(): KepubifyEnabler return $service; } + protected function getKoboStoreProxy(): KoboStoreProxy + { + $service = self::getContainer()->get(KoboStoreProxy::class); + assert($service instanceof KoboStoreProxy); + + return $service; + } + protected function getKoboProxyConfiguration(): KoboProxyConfiguration + { + $service = self::getContainer()->get(KoboProxyConfiguration::class); + assert($service instanceof KoboProxyConfiguration); + return $service; + } + protected function getMockClient(string $returnValue): ClientInterface + { + $mock = new MockHandler([ + new \GuzzleHttp\Psr7\Response(200, ['Content-Type' => 'application/json'], $returnValue), + ]); + + $handlerStack = HandlerStack::create($mock); + return new Client(['handler' => $handlerStack]); + } } \ No newline at end of file diff --git a/tests/Controller/Kobo/KoboSyncControllerTest.php b/tests/Controller/Kobo/KoboSyncControllerTest.php index 9338d0d4..34795a4b 100644 --- a/tests/Controller/Kobo/KoboSyncControllerTest.php +++ b/tests/Controller/Kobo/KoboSyncControllerTest.php @@ -9,7 +9,14 @@ class KoboSyncControllerTest extends AbstractKoboControllerTest { + protected function tearDown(): void + { + $this->getKoboStoreProxy()->setClient(null); + $this->getKoboProxyConfiguration()->setEnabled(false); + $this->getEntityManager()->getRepository(KoboSyncedBook::class)->deleteAllSyncedBooks(1); + parent::tearDown(); + } public function assertPreConditions(): void { $count = $this->getEntityManager()->getRepository(KoboSyncedBook::class)->count(['koboDevice' => 1]); @@ -24,6 +31,8 @@ public function testSyncControllerWithForce() : void $client = static::getClient(); $this->injectFakeFileSystemManager(); + $this->getEntityManager()->getRepository(KoboSyncedBook::class)->deleteAllSyncedBooks(1); + $client?->request('GET', '/kobo/'.$this->accessKey.'/v1/library/sync?force=1'); $this->getEntityManager()->getRepository(KoboSyncedBook::class)->deleteAllSyncedBooks(1); @@ -61,6 +70,43 @@ public function testSyncControllerWithoutForce() : void } + public function testSyncControllerWithRemote() : void + { + $client = static::getClient(); + + // Enable remote sync + $this->getKoboDevice()->setUpstreamSync(true); + $this->getKoboProxyConfiguration()->setEnabled(true); + $this->getEntityManager()->flush(); + $this->getKoboDevice(true); + + $this->getKoboStoreProxy()->setClient($this->getMockClient('[{ + "DeletedTag": { + "Tag": { + "Id": "28521096-ed64-4709-a043-781a0ed0695f", + "LastModified": "2024-02-02T13:35:31.0000000Z" + } + } + }]')); + + $this->injectFakeFileSystemManager(); + + $client?->request('GET', '/kobo/'.$this->accessKey.'/v1/library/sync'); + + $response = self::getJsonResponse(); + self::assertResponseIsSuccessful(); + self::assertThat($response, new JSONIsValidSyncResponse([ + 'NewEntitlement' => 1, + 'NewTag' => 1, + 'DeletedTag' => 1 + ]), 'Response is not a valid sync response'); + + + $this->getEntityManager()->getRepository(KoboSyncedBook::class)->deleteAllSyncedBooks(1); + $this->getKoboDevice()->setUpstreamSync(false); + $this->getEntityManager()->flush(); + } + public function testSyncControllerMetadata() : void { $uuid = $this->getBook()->getUuid(); @@ -92,4 +138,6 @@ public function testSyncControllerMetadataWithConversion() : void $this->getEntityManager()->getRepository(KoboSyncedBook::class)->deleteAllSyncedBooks(1); } + + } \ No newline at end of file