Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
99 / 99
n/a
0 / 0
CRAP
n/a
0 / 0
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10use Phexium\Plugin\QueryBus\Adapter\CachedQueryBus;
11use Phexium\Plugin\QueryBus\Internal\QueryCacheKeyGenerator;
12use Tests\Phexium\Fake\Application\Query\CacheableQuery;
13use Tests\Phexium\Fake\Application\Query\Query;
14use Tests\Phexium\Fake\Plugin\Cache\Cache;
15use Tests\Phexium\Fake\Plugin\Logger\Logger;
16use Tests\Phexium\Fake\Plugin\QueryBus\QueryBus;
17use Tests\Phexium\Fake\Presentation\QueryResponse;
18
19test('Non-cacheable query bypasses cache', function (): void {
20    $innerBus = new QueryBus();
21    $cache = new Cache();
22    $logger = new Logger();
23    $expectedResponse = new QueryResponse(['data' => 'test']);
24    $query = new Query('123');
25
26    $innerBus->stubResponse(Query::class, $expectedResponse);
27
28    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger);
29    $response = $cachedBus->dispatch($query);
30
31    expect($response)->toBe($expectedResponse);
32    expect($innerBus->hasDispatched(Query::class))->toBeTrue();
33    expect($cache->wasMethodCalled('get'))->toBeFalse();
34    expect($cache->wasMethodCalled('set'))->toBeFalse();
35});
36
37test('Cache hit returns cached response without dispatching to inner bus', function (): void {
38    $innerBus = new QueryBus();
39    $cache = new Cache();
40    $logger = new Logger();
41    $cachedResponse = new QueryResponse(['data' => 'cached']);
42    $query = new CacheableQuery('123');
43
44    $cacheKey = QueryCacheKeyGenerator::generate($query);
45    $cache->set($cacheKey, $cachedResponse);
46
47    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger);
48    $response = $cachedBus->dispatch($query);
49
50    expect($response)->toBe($cachedResponse);
51    expect($innerBus->count())->toBe(0);
52    expect($logger->hasLogContaining('debug', 'Cache hit'))->toBeTrue();
53});
54
55test('Cache miss dispatches to inner bus and stores response', function (): void {
56    $innerBus = new QueryBus();
57    $cache = new Cache();
58    $logger = new Logger();
59    $expectedResponse = new QueryResponse(['data' => 'fresh']);
60    $query = new CacheableQuery('456');
61
62    $innerBus->stubResponse(CacheableQuery::class, $expectedResponse);
63
64    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger);
65    $response = $cachedBus->dispatch($query);
66
67    expect($response)->toBe($expectedResponse);
68    expect($innerBus->hasDispatched(CacheableQuery::class))->toBeTrue();
69    expect($cache->wasMethodCalled('set'))->toBeTrue();
70    expect($logger->hasLogContaining('debug', 'Cache miss'))->toBeTrue();
71});
72
73test('Query TTL is used when provided', function (): void {
74    $innerBus = new QueryBus();
75    $cache = new Cache();
76    $logger = new Logger();
77    $expectedResponse = new QueryResponse(['data' => 'test']);
78    $query = new CacheableQuery('789', 600);
79
80    $innerBus->stubResponse(CacheableQuery::class, $expectedResponse);
81
82    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger, 300);
83    $cachedBus->dispatch($query);
84
85    $callHistory = $cache->getCallHistory();
86    $setCall = array_filter($callHistory, fn (array $call): bool => $call['method'] === 'set');
87    $setCall = array_values($setCall)[0];
88
89    expect($setCall['args'][2])->toBe(600);
90});
91
92test('Default TTL is used when query returns null', function (): void {
93    $innerBus = new QueryBus();
94    $cache = new Cache();
95    $logger = new Logger();
96    $expectedResponse = new QueryResponse(['data' => 'test']);
97    $query = new CacheableQuery('abc');
98
99    $innerBus->stubResponse(CacheableQuery::class, $expectedResponse);
100
101    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger, 300);
102    $cachedBus->dispatch($query);
103
104    $callHistory = $cache->getCallHistory();
105    $setCall = array_filter($callHistory, fn (array $call): bool => $call['method'] === 'set');
106    $setCall = array_values($setCall)[0];
107
108    expect($setCall['args'][2])->toBe(300);
109});
110
111test('DateInterval TTL is supported', function (): void {
112    $innerBus = new QueryBus();
113    $cache = new Cache();
114    $logger = new Logger();
115    $expectedResponse = new QueryResponse(['data' => 'test']);
116    $ttl = new DateInterval('PT1H');
117    $query = new CacheableQuery('def', $ttl);
118
119    $innerBus->stubResponse(CacheableQuery::class, $expectedResponse);
120
121    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger);
122    $cachedBus->dispatch($query);
123
124    $callHistory = $cache->getCallHistory();
125    $setCall = array_filter($callHistory, fn (array $call): bool => $call['method'] === 'set');
126    $setCall = array_values($setCall)[0];
127
128    expect($setCall['args'][2])->toBe($ttl);
129});
130
131test('Second dispatch for same query returns cached response', function (): void {
132    $innerBus = new QueryBus();
133    $cache = new Cache();
134    $logger = new Logger();
135    $expectedResponse = new QueryResponse(['data' => 'test']);
136    $query = new CacheableQuery('xyz');
137
138    $innerBus->stubResponse(CacheableQuery::class, $expectedResponse);
139
140    $cachedBus = new CachedQueryBus($innerBus, $cache, $logger);
141
142    $response1 = $cachedBus->dispatch($query);
143    $response2 = $cachedBus->dispatch($query);
144
145    expect($response1)->toBe($expectedResponse);
146    expect($response2)->toBe($expectedResponse);
147    expect($innerBus->count())->toBe(1);
148});