Add POST /api/account/lookup route for account lookup by email
New API endpoint secured by X-App-Secret header (no JWT auth required). Accepts an email in the request body and returns the user's id and stripeAccountId if present. Includes 6 unit tests covering all cases (success, missing secret, invalid secret, missing email, user not found). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
53
src/Controller/Api/ApiAccountController.php
Normal file
53
src/Controller/Api/ApiAccountController.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Api;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
#[Route('/api/account')]
|
||||||
|
class ApiAccountController extends AbstractController
|
||||||
|
{
|
||||||
|
use ApiAuthTrait;
|
||||||
|
|
||||||
|
/** @codeCoverageIgnore Autowire injection */
|
||||||
|
public function __construct(
|
||||||
|
#[Autowire('%kernel.secret%')] private string $appSecret,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/lookup', name: 'app_api_account_lookup', methods: ['POST'])]
|
||||||
|
public function lookup(
|
||||||
|
Request $request,
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
): JsonResponse {
|
||||||
|
$secret = $request->headers->get('X-App-Secret', '');
|
||||||
|
|
||||||
|
if ('' === $secret || !hash_equals($this->appSecret, $secret)) {
|
||||||
|
return $this->error('Secret invalide.', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
$email = $data['email'] ?? '';
|
||||||
|
|
||||||
|
if ('' === $email) {
|
||||||
|
return $this->error('Email requis.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $em->getRepository(User::class)->findOneBy(['email' => $email]);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return $this->error('Utilisateur introuvable.', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'id' => $user->getId(),
|
||||||
|
'stripeAccountId' => $user->getStripeAccountId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
136
tests/Controller/Api/ApiAccountControllerTest.php
Normal file
136
tests/Controller/Api/ApiAccountControllerTest.php
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Controller\Api;
|
||||||
|
|
||||||
|
use App\Controller\Api\ApiAccountController;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class ApiAccountControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
private const SECRET = 'test_secret';
|
||||||
|
|
||||||
|
private function createController(): ApiAccountController
|
||||||
|
{
|
||||||
|
$reflection = new \ReflectionClass(ApiAccountController::class);
|
||||||
|
$controller = $reflection->newInstanceWithoutConstructor();
|
||||||
|
|
||||||
|
$prop = $reflection->getProperty('appSecret');
|
||||||
|
$prop->setValue($controller, self::SECRET);
|
||||||
|
|
||||||
|
// Set the container to null-like state — we only use the trait helpers
|
||||||
|
$containerProp = (new \ReflectionClass(\Symfony\Bundle\FrameworkBundle\Controller\AbstractController::class))->getProperty('container');
|
||||||
|
$containerProp->setValue($controller, new \Symfony\Component\DependencyInjection\Container());
|
||||||
|
|
||||||
|
return $controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createEm(?User $user): EntityManagerInterface
|
||||||
|
{
|
||||||
|
$repo = $this->createMock(UserRepository::class);
|
||||||
|
$repo->method('findOneBy')->willReturn($user);
|
||||||
|
|
||||||
|
$em = $this->createMock(EntityManagerInterface::class);
|
||||||
|
$em->method('getRepository')->willReturn($repo);
|
||||||
|
|
||||||
|
return $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createUser(int $id, string $email, ?string $stripeAccountId = null): User
|
||||||
|
{
|
||||||
|
$user = $this->createMock(User::class);
|
||||||
|
$user->method('getId')->willReturn($id);
|
||||||
|
$user->method('getEmail')->willReturn($email);
|
||||||
|
$user->method('getStripeAccountId')->willReturn($stripeAccountId);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeRequest(string $email = '', string $secret = self::SECRET): Request
|
||||||
|
{
|
||||||
|
$request = Request::create('/api/account/lookup', 'POST', [], [], [], [], json_encode(['email' => $email]));
|
||||||
|
$request->headers->set('X-App-Secret', $secret);
|
||||||
|
$request->headers->set('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupSuccess(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController();
|
||||||
|
$user = $this->createUser(42, 'test@example.com', 'acct_123');
|
||||||
|
$em = $this->createEm($user);
|
||||||
|
|
||||||
|
$response = $controller->lookup($this->makeRequest('test@example.com'), $em);
|
||||||
|
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
self::assertTrue($data['success']);
|
||||||
|
self::assertSame(42, $data['data']['id']);
|
||||||
|
self::assertSame('acct_123', $data['data']['stripeAccountId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupSuccessWithoutStripe(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController();
|
||||||
|
$user = $this->createUser(10, 'user@example.com');
|
||||||
|
$em = $this->createEm($user);
|
||||||
|
|
||||||
|
$response = $controller->lookup($this->makeRequest('user@example.com'), $em);
|
||||||
|
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
self::assertTrue($data['success']);
|
||||||
|
self::assertSame(10, $data['data']['id']);
|
||||||
|
self::assertNull($data['data']['stripeAccountId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupInvalidSecret(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController();
|
||||||
|
$em = $this->createEm(null);
|
||||||
|
|
||||||
|
$response = $controller->lookup($this->makeRequest('test@example.com', 'wrong_secret'), $em);
|
||||||
|
|
||||||
|
self::assertSame(401, $response->getStatusCode());
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
self::assertFalse($data['success']);
|
||||||
|
self::assertSame('Secret invalide.', $data['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupEmptySecret(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController();
|
||||||
|
$em = $this->createEm(null);
|
||||||
|
|
||||||
|
$response = $controller->lookup($this->makeRequest('test@example.com', ''), $em);
|
||||||
|
|
||||||
|
self::assertSame(401, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupMissingEmail(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController();
|
||||||
|
$em = $this->createEm(null);
|
||||||
|
|
||||||
|
$response = $controller->lookup($this->makeRequest(''), $em);
|
||||||
|
|
||||||
|
self::assertSame(400, $response->getStatusCode());
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
self::assertSame('Email requis.', $data['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookupUserNotFound(): void
|
||||||
|
{
|
||||||
|
$controller = $this->createController();
|
||||||
|
$em = $this->createEm(null);
|
||||||
|
|
||||||
|
$response = $controller->lookup($this->makeRequest('notfound@example.com'), $em);
|
||||||
|
|
||||||
|
self::assertSame(404, $response->getStatusCode());
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
self::assertSame('Utilisateur introuvable.', $data['error']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user