feat: ajout d'une modale de gestion des chapitres, permettant la création, l'édition et le déplacement de chapitres. Mise à jour de l'API pour gérer les modifications en lot des chapitres, ainsi que l'intégration de tests pour valider cette nouvelle fonctionnalité. Amélioration de l'interface utilisateur pour une gestion plus fluide des chapitres.

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-07-23 14:25:17 +02:00
parent 00d63dffeb
commit 551db0bf77
19 changed files with 2566 additions and 3 deletions

View File

@@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace App\Tests\Feature\Setting;
use App\Entity\ContentSource;
use App\Tests\Feature\AbstractApiTestCase;
use Symfony\Component\HttpFoundation\Response;
use Zenstruck\Foundry\Test\ResetDatabase;
final class ImportContentSourceTest extends AbstractApiTestCase
{
use ResetDatabase;
protected function setUp(): void
{
parent::setUp();
}
public function testItImportsContentSourcesSuccessfully(): void
{
$importData = [
'contentSources' => [
[
'baseUrl' => 'https://mangadex.org',
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
'scrapingType' => 'html',
'imageSelector' => '.chapter-image img',
'nextPageSelector' => '.next-page',
'chapterSelector' => '.chapter-list a'
],
[
'baseUrl' => 'https://mangakakalot.com',
'chapterUrlFormat' => 'https://mangakakalot.com/chapter/{id}',
'scrapingType' => 'javascript',
'imageSelector' => '.page-image img',
'nextPageSelector' => '.next-button',
'chapterSelector' => '.chapter-link'
]
]
];
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => $importData
]);
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
// Vérifier que les sources ont été créées en base
$sources = $this->entityManager->getRepository(ContentSource::class)->findAll();
$this->assertCount(2, $sources);
$baseUrls = array_map(fn($source) => $source->getBaseUrl(), $sources);
$this->assertContains('https://mangadex.org', $baseUrls);
$this->assertContains('https://mangakakalot.com', $baseUrls);
}
public function testItValidatesRequiredFields(): void
{
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'contentSources' => [
[
'baseUrl' => '',
'chapterUrlFormat' => '',
'scrapingType' => ''
]
]
]
]);
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
}
public function testItValidatesBaseUrlFormat(): void
{
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'contentSources' => [
[
'baseUrl' => 'invalid-url',
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
'scrapingType' => 'html',
'imageSelector' => '.image',
'nextPageSelector' => '.next',
'chapterSelector' => '.chapter'
]
]
]
]);
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
}
public function testItValidatesScrapingType(): void
{
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'contentSources' => [
[
'baseUrl' => 'https://mangadex.org',
'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}',
'scrapingType' => 'invalid-type',
'imageSelector' => '.image',
'nextPageSelector' => '.next',
'chapterSelector' => '.chapter'
]
]
]
]);
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
}
public function testItValidatesContentSourcesArray(): void
{
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'contentSources' => 'not-an-array'
]
]);
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
}
public function testItValidatesNonEmptyContentSources(): void
{
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'contentSources' => []
]
]);
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
}
public function testItValidatesContentSourcesField(): void
{
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'invalidField' => []
]
]);
$this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
}
public function testItImportsSourcesWithOptionalFields(): void
{
$importData = [
'contentSources' => [
[
'baseUrl' => 'https://simple-source.com',
'chapterUrlFormat' => 'https://simple-source.com/chapter/{id}',
'scrapingType' => 'html',
'imageSelector' => '.simple-image',
'nextPageSelector' => '.simple-next',
'chapterSelector' => '.simple-chapter'
]
]
];
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => $importData
]);
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
// Vérifier que la source a été créée
$source = $this->entityManager->getRepository(ContentSource::class)->findOneBy([
'baseUrl' => 'https://simple-source.com'
]);
$this->assertNotNull($source);
$this->assertEquals('html', $source->getScrapingType());
$this->assertEquals('.simple-image', $source->getImageSelector());
$this->assertEquals('.simple-next', $source->getNextPageSelector());
$this->assertEquals('.simple-chapter', $source->getChapterSelector());
}
public function testItHandlesLargeImport(): void
{
$contentSources = [];
for ($i = 1; $i <= 10; $i++) {
$contentSources[] = [
'baseUrl' => "https://source{$i}.com",
'chapterUrlFormat' => "https://source{$i}.com/chapter/{id}",
'scrapingType' => 'html',
'imageSelector' => ".source{$i}-image img",
'nextPageSelector' => ".source{$i}-next",
'chapterSelector' => ".source{$i}-chapter a"
];
}
$response = static::createClient()->request('POST', '/api/content-sources/import', [
'json' => [
'contentSources' => $contentSources
]
]);
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(Response::HTTP_CREATED);
// Vérifier que toutes les sources ont été créées
$sources = $this->entityManager->getRepository(ContentSource::class)->findAll();
$this->assertCount(10, $sources);
}
}