12245759ac
CI / test (push) Has been cancelled
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>
158 lines
5.2 KiB
PHP
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);
|
|
}
|
|
}
|