Files
football2801 12245759ac
CI / test (push) Has been cancelled
chore: stage all in-progress work before repo split
Web app: new entities (Image, RenderedAsset, SharedImage, Token,
DeviceImageHistory), enums, repositories, controllers, message handlers,
migrations, tests, frontend upload/library/sticker UI, Vue components.

Firmware: EPD background screen binaries + gen scripts, setup_bg header.

Infra: ddev config, test bundle, gitignore coverage dir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:11:31 -04:00

158 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Unit\Service;
use App\Entity\Image;
use App\Entity\Token;
use App\Entity\User;
use App\Enum\TokenType;
use App\Repository\TokenRepository;
use App\Service\TokenService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class TokenServiceTest extends TestCase
{
private function makeService(): array
{
$repo = $this->createStub(TokenRepository::class);
$em = $this->createStub(EntityManagerInterface::class);
$service = new TokenService($repo, $em);
return [$service, $repo, $em];
}
private function makeServiceWithMockEm(): array
{
$repo = $this->createStub(TokenRepository::class);
$em = $this->createMock(EntityManagerInterface::class);
$service = new TokenService($repo, $em);
return [$service, $repo, $em];
}
private function makeImage(): Image
{
$user = new User();
$image = new Image();
$image->setUser($user)->setOriginalFilename('test.jpg')->setStoragePath('x');
return $image;
}
public function test_issue_returns_token_with_correct_type(): void
{
[$service, , $em] = $this->makeServiceWithMockEm();
$em->expects($this->once())->method('persist');
$token = $service->issue(TokenType::ShareApprove, $this->makeImage(), null, 'a@b.com', 7);
$this->assertSame(TokenType::ShareApprove, $token->getType());
}
public function test_issue_expiry_is_in_the_future(): void
{
[$service] = $this->makeService();
$token = $service->issue(TokenType::ShareApprove, $this->makeImage(), null, null, 7);
$this->assertGreaterThan(new \DateTimeImmutable(), $token->getExpiresAt());
}
public function test_issue_calls_em_persist(): void
{
[$service, , $em] = $this->makeServiceWithMockEm();
$em->expects($this->once())->method('persist')->with($this->isInstanceOf(Token::class));
$service->issue(TokenType::ShareApprove, $this->makeImage(), null, 'a@b.com', 7);
}
public function test_consume_calls_token_consume(): void
{
$repo = $this->createStub(TokenRepository::class);
$em = $this->createMock(EntityManagerInterface::class);
$service = new TokenService($repo, $em);
/** @var Token&MockObject $token */
$token = $this->createMock(Token::class);
$token->expects($this->once())->method('consume');
$em->expects($this->once())->method('flush');
$repo->method('findValidToken')->willReturn($token);
$service->consume('some-uuid', TokenType::ShareApprove);
}
public function test_consume_calls_em_flush(): void
{
$repo = $this->createStub(TokenRepository::class);
$em = $this->createMock(EntityManagerInterface::class);
$service = new TokenService($repo, $em);
$token = $this->createStub(Token::class);
$em->expects($this->once())->method('flush');
$repo->method('findValidToken')->willReturn($token);
$service->consume('some-uuid', TokenType::ShareApprove);
}
public function test_consume_returns_the_token(): void
{
$repo = $this->createStub(TokenRepository::class);
$em = $this->createStub(EntityManagerInterface::class);
$service = new TokenService($repo, $em);
$token = $this->createStub(Token::class);
$repo->method('findValidToken')->willReturn($token);
$result = $service->consume('some-uuid', TokenType::ShareApprove);
$this->assertSame($token, $result);
}
public function test_consume_throws_when_token_not_found(): void
{
[$service, $repo] = $this->makeService();
$repo->method('findValidToken')->willReturn(null);
$this->expectException(\RuntimeException::class);
$service->consume('invalid-uuid', TokenType::ShareApprove);
}
/** T-05: expired token — repo already excludes it, returns null → RuntimeException */
public function test_consume_throws_for_expired_token(): void
{
[$service, $repo] = $this->makeService();
$repo->method('findValidToken')->willReturn(null);
$this->expectException(\RuntimeException::class);
$service->consume('expired-uuid', TokenType::ShareApprove);
}
/** T-06: already-used token — repo excludes usedAt IS NOT NULL, returns null → RuntimeException */
public function test_consume_throws_for_already_used_token(): void
{
[$service, $repo] = $this->makeService();
$repo->method('findValidToken')->willReturn(null);
$this->expectException(\RuntimeException::class);
$service->consume('used-uuid', TokenType::ShareApprove);
}
/** T-07: type mismatch — repo WHERE clause filters type, returns null → RuntimeException */
public function test_consume_throws_for_type_mismatch(): void
{
[$service, $repo] = $this->makeService();
$repo->method('findValidToken')->willReturn(null);
$this->expectException(\RuntimeException::class);
// UUID issued as ShareDecline but consumed as ShareApprove
$service->consume('some-uuid', TokenType::ShareApprove);
}
}