test: ajout tests SetPasswordController, SonarBadgeController, StatusPageController, WebhookDocuSealController

tests/Controller/SetPasswordControllerTest.php (nouveau, 5 tests):
- testGetFormRendered: token valide, affiche le formulaire
- testTokenExpired: token invalide, affiche la page expired
- testPostPasswordTooShort: mot de passe < 8 caracteres, erreur
- testPostPasswordMismatch: confirmation differente, erreur
- testPostSuccess: mot de passe valide, flush + redirect 302

tests/Controller/SonarBadgeControllerTest.php (nouveau, 2 tests):
- testBadgeSuccess: metric valide, retourne SVG avec Content-Type image/svg+xml
- testBadgeInvalidMetric: metric invalide, retourne 404

tests/Controller/StatusPageControllerTest.php (reecrit, 2 tests):
- testIndexEmpty: aucune categorie, retourne 200
- testIndexWithServices: categorie avec service, QueryBuilder mocke
  pour les logs, retourne 200

tests/Controller/WebhookDocuSealControllerTest.php (nouveau, 9 tests):
- testUnauthorized: mauvais secret dans le header, retourne 401
- testInvalidPayload: JSON invalide, retourne 400
- testIgnoredDocType: doc_type autre que attestation, retourne ignored
- testEmptySecret: secret vide bypass la verification
- testFormViewedAttestationNotFound: attestation introuvable, retourne 404
- testFormViewedAttestationFound: attestation trouvee, retourne 200
- testFormStarted: evenement started, retourne 200
- testFormDeclined: evenement declined, retourne 200
- testUnknownEvent: evenement inconnu, retourne ignored

Resultat: 368 tests, 718 assertions, 0 failures, 0 notices

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-03 00:12:54 +02:00
parent 03d0ebbfba
commit 0f7c752d9a
4 changed files with 398 additions and 37 deletions

View File

@@ -0,0 +1,145 @@
<?php
namespace App\Tests\Controller;
use App\Controller\SetPasswordController;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\RouterInterface;
use Twig\Environment;
class SetPasswordControllerTest extends TestCase
{
private function createController(Request $request): SetPasswordController
{
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html></html>');
$router = $this->createStub(RouterInterface::class);
$router->method('generate')->willReturn('/');
$requestStack = new RequestStack();
$requestStack->push($request);
$container = $this->createStub(ContainerInterface::class);
$container->method('has')->willReturn(true);
$container->method('get')->willReturnMap([
['twig', $twig],
['router', $router],
['request_stack', $requestStack],
]);
$controller = new SetPasswordController();
$controller->setContainer($container);
return $controller;
}
public function testGetFormRendered(): void
{
$user = new User();
$user->setEmail('t@t.com');
$user->setFirstName('T');
$user->setLastName('T');
$user->setPassword('h');
$user->setTempPassword('valid-token');
$repo = $this->createStub(UserRepository::class);
$repo->method('findOneBy')->willReturn($user);
$request = new Request();
$request->setSession(new Session(new MockArraySessionStorage()));
$controller = $this->createController($request);
$response = $controller->index('valid-token', $request, $repo, $this->createStub(UserPasswordHasherInterface::class), $this->createStub(EntityManagerInterface::class));
$this->assertSame(200, $response->getStatusCode());
}
public function testTokenExpired(): void
{
$repo = $this->createStub(UserRepository::class);
$repo->method('findOneBy')->willReturn(null);
$request = new Request();
$request->setSession(new Session(new MockArraySessionStorage()));
$controller = $this->createController($request);
$response = $controller->index('bad-token', $request, $repo, $this->createStub(UserPasswordHasherInterface::class), $this->createStub(EntityManagerInterface::class));
$this->assertSame(200, $response->getStatusCode());
}
public function testPostPasswordTooShort(): void
{
$user = new User();
$user->setEmail('t@t.com');
$user->setFirstName('T');
$user->setLastName('T');
$user->setPassword('h');
$user->setTempPassword('token');
$repo = $this->createStub(UserRepository::class);
$repo->method('findOneBy')->willReturn($user);
$request = new Request([], ['password' => 'short', 'password_confirm' => 'short']);
$request->setMethod('POST');
$request->setSession(new Session(new MockArraySessionStorage()));
$controller = $this->createController($request);
$response = $controller->index('token', $request, $repo, $this->createStub(UserPasswordHasherInterface::class), $this->createStub(EntityManagerInterface::class));
$this->assertSame(200, $response->getStatusCode());
}
public function testPostPasswordMismatch(): void
{
$user = new User();
$user->setEmail('t@t.com');
$user->setFirstName('T');
$user->setLastName('T');
$user->setPassword('h');
$user->setTempPassword('token');
$repo = $this->createStub(UserRepository::class);
$repo->method('findOneBy')->willReturn($user);
$request = new Request([], ['password' => 'password123', 'password_confirm' => 'different123']);
$request->setMethod('POST');
$request->setSession(new Session(new MockArraySessionStorage()));
$controller = $this->createController($request);
$response = $controller->index('token', $request, $repo, $this->createStub(UserPasswordHasherInterface::class), $this->createStub(EntityManagerInterface::class));
$this->assertSame(200, $response->getStatusCode());
}
public function testPostSuccess(): void
{
$user = new User();
$user->setEmail('t@t.com');
$user->setFirstName('T');
$user->setLastName('T');
$user->setPassword('h');
$user->setTempPassword('token');
$repo = $this->createStub(UserRepository::class);
$repo->method('findOneBy')->willReturn($user);
$hasher = $this->createStub(UserPasswordHasherInterface::class);
$hasher->method('hashPassword')->willReturn('hashed');
$em = $this->createMock(EntityManagerInterface::class);
$em->expects($this->once())->method('flush');
$request = new Request([], ['password' => 'newpassword8', 'password_confirm' => 'newpassword8']);
$request->setMethod('POST');
$request->setSession(new Session(new MockArraySessionStorage()));
$controller = $this->createController($request);
$response = $controller->index('token', $request, $repo, $hasher, $em);
$this->assertSame(302, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Tests\Controller;
use App\Controller\SonarBadgeController;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
class SonarBadgeControllerTest extends TestCase
{
public function testBadgeSuccess(): void
{
$httpResponse = $this->createStub(ResponseInterface::class);
$httpResponse->method('getContent')->willReturn('<svg>badge</svg>');
$httpClient = $this->createStub(HttpClientInterface::class);
$httpClient->method('request')->willReturn($httpResponse);
$controller = new SonarBadgeController();
$response = $controller->badge('coverage', $httpClient, 'https://sonar.test', 'project', 'token');
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('image/svg+xml', $response->headers->get('Content-Type'));
$this->assertSame('<svg>badge</svg>', $response->getContent());
}
public function testBadgeInvalidMetric(): void
{
$httpClient = $this->createStub(HttpClientInterface::class);
$controller = new SonarBadgeController();
$response = $controller->badge('invalid_metric', $httpClient, 'https://sonar.test', 'project', 'token');
$this->assertSame(404, $response->getStatusCode());
}
}

View File

@@ -5,41 +5,75 @@ namespace App\Tests\Controller;
use App\Controller\StatusPageController;
use App\Entity\Service;
use App\Entity\ServiceCategory;
use App\Entity\ServiceMessage;
use App\Repository\ServiceCategoryRepository;
use App\Repository\ServiceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;
class StatusPageControllerTest extends TestCase
{
public function testIndex(): void
public function testIndexEmpty(): void
{
$categoryRepo = $this->createStub(ServiceCategoryRepository::class);
$serviceRepo = $this->createStub(ServiceRepository::class);
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html></html>');
$container = $this->createStub(ContainerInterface::class);
$container->method('has')->willReturn(true);
$container->method('get')->willReturnMap([['twig', $twig]]);
$catRepo = $this->createStub(ServiceCategoryRepository::class);
$catRepo->method('findBy')->willReturn([]);
$svcRepo = $this->createStub(ServiceRepository::class);
$msgRepo = $this->createStub(EntityRepository::class);
$msgRepo->method('findBy')->willReturn([]);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($msgRepo);
$category = new ServiceCategory('Web', 'web');
$service = new Service('Website', 'website', $category);
$service->setStatus('up');
// Manual addition since it's not persisted
$ref = new \ReflectionProperty(ServiceCategory::class, 'services');
$ref->setValue($category, new ArrayCollection([$service]));
$controller = new StatusPageController();
$controller->setContainer($container);
$categoryRepo->method('findBy')->willReturn([$category]);
$serviceRepo->method('getHistoryForDays')->willReturn([]);
$serviceRepo->method('getDailyStatus')->willReturn([]);
$response = $controller->index($catRepo, $svcRepo, $em);
$this->assertSame(200, $response->getStatusCode());
}
$messageRepo = $this->createStub(EntityRepository::class);
$messageRepo->method('findBy')->willReturn([]);
$em->method('getRepository')->willReturn($messageRepo);
#[AllowMockObjectsWithoutExpectations]
public function testIndexWithServices(): void
{
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html></html>');
$container = $this->createStub(ContainerInterface::class);
$container->method('has')->willReturn(true);
$container->method('get')->willReturnMap([['twig', $twig]]);
$category = new ServiceCategory('Infra', 'infra');
$service = new Service('Esy-Web', 'esy-web', $category);
$catRepo = $this->createStub(ServiceCategoryRepository::class);
$catRepo->method('findBy')->willReturn([$category]);
$svcRepo = $this->createStub(ServiceRepository::class);
$svcRepo->method('getHistoryForDays')->willReturn([]);
$svcRepo->method('getDailyStatus')->willReturn([]);
$msgRepo = $this->createStub(EntityRepository::class);
$msgRepo->method('findBy')->willReturn([]);
$query = $this->getMockBuilder(Query::class)
->disableOriginalConstructor()
->onlyMethods(['getResult', 'getSQL', 'execute'])
->getMock();
$query->method('getResult')->willReturn([]);
$qb = $this->createStub(QueryBuilder::class);
$qb->method('select')->willReturn($qb);
@@ -48,30 +82,16 @@ class StatusPageControllerTest extends TestCase
$qb->method('andWhere')->willReturn($qb);
$qb->method('setParameter')->willReturn($qb);
$qb->method('orderBy')->willReturn($qb);
$query = $this->createStub(Query::class);
$query->method('getResult')->willReturn([]);
$qb->method('getQuery')->willReturn($query);
$em = $this->createStub(EntityManagerInterface::class);
$em->method('getRepository')->willReturn($msgRepo);
$em->method('createQueryBuilder')->willReturn($qb);
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html></html>');
$container = $this->createStub(ContainerInterface::class);
$container->method('has')->willReturnMap([
['twig', true],
['parameter_bag', true],
]);
$container->method('get')->willReturnMap([
['twig', $twig],
]);
$controller = new StatusPageController();
$controller->setContainer($container);
$response = $controller->index($categoryRepo, $serviceRepo, $em);
$this->assertInstanceOf(Response::class, $response);
$this->assertEquals('<html></html>', $response->getContent());
$response = $controller->index($catRepo, $svcRepo, $em);
$this->assertSame(200, $response->getStatusCode());
}
}

View File

@@ -0,0 +1,158 @@
<?php
namespace App\Tests\Controller;
use App\Controller\WebhookDocuSealController;
use App\Entity\Attestation;
use App\Repository\AttestationRepository;
use App\Service\MailerService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Twig\Environment;
class WebhookDocuSealControllerTest extends TestCase
{
private function invoke(array $payload, string $secret = 'test', string $headerSecret = 'X-Sign', string $headerValue = 'test'): JsonResponse
{
$controller = new WebhookDocuSealController();
$repo = $this->createStub(AttestationRepository::class);
$mailer = $this->createStub(MailerService::class);
$em = $this->createStub(EntityManagerInterface::class);
$twig = $this->createStub(Environment::class);
$twig->method('render')->willReturn('<html></html>');
$request = new Request([], [], [], [], [], [], json_encode($payload));
$request->headers->set($headerSecret, $headerValue);
$request->headers->set('Content-Type', 'application/json');
return $controller->__invoke($request, $repo, $mailer, $em, $twig, $headerSecret, $secret, '/tmp');
}
public function testUnauthorized(): void
{
$response = $this->invoke([], 'correct-secret', 'X-Sign', 'wrong-secret');
$this->assertSame(401, $response->getStatusCode());
}
public function testInvalidPayload(): void
{
$controller = new WebhookDocuSealController();
$repo = $this->createStub(AttestationRepository::class);
$mailer = $this->createStub(MailerService::class);
$em = $this->createStub(EntityManagerInterface::class);
$twig = $this->createStub(Environment::class);
$request = new Request([], [], [], [], [], [], 'invalid json{{{');
$request->headers->set('X-Sign', 'test');
$response = $controller->__invoke($request, $repo, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
$this->assertSame(400, $response->getStatusCode());
}
public function testIgnoredDocType(): void
{
$response = $this->invoke([
'event_type' => 'form.viewed',
'data' => ['id' => 1, 'metadata' => ['doc_type' => 'other']],
]);
$data = json_decode($response->getContent(), true);
$this->assertSame('ignored', $data['status']);
}
public function testEmptySecret(): void
{
$response = $this->invoke(['event_type' => 'form.viewed', 'data' => []], '', 'X-Sign', '');
// Empty secret = bypass verification
$this->assertSame(200, $response->getStatusCode());
}
public function testFormViewedAttestationNotFound(): void
{
$response = $this->invoke([
'event_type' => 'form.viewed',
'data' => ['id' => 999, 'metadata' => ['doc_type' => 'attestation', 'reference' => 'REF-123']],
]);
$this->assertSame(404, $response->getStatusCode());
}
public function testFormViewedAttestationFound(): void
{
$attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');
$controller = new WebhookDocuSealController();
$repo = $this->createStub(AttestationRepository::class);
$repo->method('findOneBy')->willReturn($attestation);
$mailer = $this->createStub(MailerService::class);
$em = $this->createStub(EntityManagerInterface::class);
$twig = $this->createStub(Environment::class);
$request = new Request([], [], [], [], [], [], json_encode([
'event_type' => 'form.viewed',
'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation', 'reference' => $attestation->getReference()]],
]));
$request->headers->set('X-Sign', 'test');
$response = $controller->__invoke($request, $repo, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
$this->assertSame(200, $response->getStatusCode());
}
public function testFormStarted(): void
{
$attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');
$repo = $this->createStub(AttestationRepository::class);
$repo->method('findOneBy')->willReturn($attestation);
$controller = new WebhookDocuSealController();
$mailer = $this->createStub(MailerService::class);
$em = $this->createStub(EntityManagerInterface::class);
$twig = $this->createStub(Environment::class);
$request = new Request([], [], [], [], [], [], json_encode([
'event_type' => 'form.started',
'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation']],
]));
$request->headers->set('X-Sign', 'test');
$response = $controller->__invoke($request, $repo, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
$this->assertSame(200, $response->getStatusCode());
}
public function testFormDeclined(): void
{
$attestation = new Attestation('access', '1.1.1.1', 't@t.com', 'secret');
$repo = $this->createStub(AttestationRepository::class);
$repo->method('findOneBy')->willReturn($attestation);
$controller = new WebhookDocuSealController();
$mailer = $this->createStub(MailerService::class);
$em = $this->createStub(EntityManagerInterface::class);
$twig = $this->createStub(Environment::class);
$request = new Request([], [], [], [], [], [], json_encode([
'event_type' => 'form.declined',
'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation']],
]));
$request->headers->set('X-Sign', 'test');
$response = $controller->__invoke($request, $repo, $mailer, $em, $twig, 'X-Sign', 'test', '/tmp');
$this->assertSame(200, $response->getStatusCode());
}
public function testUnknownEvent(): void
{
$response = $this->invoke([
'event_type' => 'form.unknown',
'data' => ['id' => 1, 'metadata' => ['doc_type' => 'attestation']],
]);
$data = json_decode($response->getContent(), true);
$this->assertSame('ignored', $data['status']);
}
}