feat(blog): add admin endpoint for blog details and cache invalidation

- Implemented `adminDetail()` method in `BlogController` to retrieve blog posts of any status for admin editing.
- Introduced `BlogCacheInvalidator` service to handle cache invalidation after blog create/update/delete actions.
- Updated existing methods in `BlogController` and `RepresentationBlogController` to call cache invalidation on blog modifications.
- Enhanced `BlogFormPage` and `RepresentationBlogFormPage` to utilize the new admin endpoint for fetching blog data.
- Added tests for `BlogCacheInvalidator` to ensure proper functionality and error handling.
- Updated documentation to reflect new API endpoint and cache invalidation behavior.
This commit is contained in:
hamed
2026-07-27 18:54:35 +03:30
parent e4edaea9b8
commit 15abcb5c8a
12 changed files with 972 additions and 47 deletions
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace App\Tests\Blog;
use App\Auth\Entity\User;
use App\Blog\Entity\Blog;
use App\Blog\Service\BlogCacheInvalidator;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* باطل‌سازی کش ISR سایت عمومی پس از هر نوشتن روی بلاگ.
* fail-open است: شکست شبکه نباید ذخیرهٔ مقاله را بشکند.
*/
class BlogCacheInvalidatorTest extends TestCase
{
private function makeBlog(): Blog
{
return new Blog(new User('09120000000'), 'عنوان آزمایشی', 'متن آزمایشی مقاله');
}
/** @param list<array{0:string,1:string,2:array}> $calls */
private function recordingClient(array &$calls, int $status = 200): HttpClientInterface
{
return new MockHttpClient(function (string $method, string $url, array $options) use (&$calls, $status) {
$calls[] = [$method, $url, $options];
return new MockResponse('{"revalidated":true}', ['http_code' => $status]);
});
}
public function testSendsTagsForSlugUuidAndList(): void
{
$calls = [];
$blog = $this->makeBlog();
(new BlogCacheInvalidator(
$this->recordingClient($calls),
new NullLogger(),
'https://nobat724.com/api/revalidate',
's3cret',
))->invalidate($blog);
self::assertCount(1, $calls);
[$method, $url, $options] = $calls[0];
self::assertSame('POST', $method);
self::assertSame('https://nobat724.com/api/revalidate', $url);
self::assertContains('X-Revalidate-Secret: s3cret', $options['headers']);
$body = json_decode($options['body'], true);
self::assertSame(
['blog-' . $blog->getSlug(), 'blog-' . $blog->getUuid(), 'blog-list'],
$body['tags']
);
}
public function testNetworkFailureIsSwallowed(): void
{
$client = new MockHttpClient(function (): MockResponse {
throw new class ('boom') extends \RuntimeException implements TransportExceptionInterface {};
});
$invalidator = new BlogCacheInvalidator($client, new NullLogger(), 'https://nobat724.com/api/revalidate', 's3cret');
$invalidator->invalidate($this->makeBlog());
self::assertTrue(true, 'شکست شبکه نباید exception بدهد');
}
public function testNoRequestWhenWebhookIsNotConfigured(): void
{
$calls = [];
// URL خالی — محیط dev/تست بدون سایت عمومی
(new BlogCacheInvalidator($this->recordingClient($calls), new NullLogger(), '', 's3cret'))
->invalidate($this->makeBlog());
// سکرت خالی — پیکربندی ناقص نباید درخواست بدون احراز بفرستد
(new BlogCacheInvalidator($this->recordingClient($calls), new NullLogger(), 'https://nobat724.com/api/revalidate', ''))
->invalidate($this->makeBlog());
self::assertSame([], $calls);
}
}