feat: add TourProgressController and related entities for user tour progress tracking
- Implemented TourProgressController to handle API endpoints for tracking guided tours seen by users. - Created UserTourProgress entity to store the highest version of tours seen by each user. - Developed UserTourProgressRepository for database interactions related to user tour progress. - Introduced TourProgressService to manage business logic for marking tours as seen and retrieving seen maps. - Added comprehensive tests for API endpoints and entity behavior to ensure functionality and data integrity.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\UserProfile;
|
||||
|
||||
use App\Tests\ApiTestCase;
|
||||
use App\UserProfile\Entity\UserTourProgress;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class TourProgressApiTest extends ApiTestCase
|
||||
{
|
||||
public function testSeenListStartsEmptyAndGrowsAfterMarking(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/tours', $user);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertSame([], (array) $body['data']['seen']);
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/tours/appointments/seen', $user, ['version' => 2]);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/tours', $user);
|
||||
self::assertSame(['appointments' => 2], (array) $body['data']['seen']);
|
||||
}
|
||||
|
||||
public function testMarkingTheSameTourAgainUpdatesTheRowInPlace(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/tours/appointments/seen', $user, ['version' => 1]);
|
||||
$this->authJson('POST', '/api/v1/my/tours/appointments/seen', $user, ['version' => 3]);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
|
||||
$rows = $this->em->getRepository(UserTourProgress::class)->findBy(['user' => $user]);
|
||||
self::assertCount(1, $rows);
|
||||
self::assertSame(3, $rows[0]->getVersion());
|
||||
}
|
||||
|
||||
public function testAnOlderVersionNeverLowersTheStoredOne(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/tours/appointments/seen', $user, ['version' => 5]);
|
||||
$this->authJson('POST', '/api/v1/my/tours/appointments/seen', $user, ['version' => 2]);
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/tours', $user);
|
||||
self::assertSame(['appointments' => 5], (array) $body['data']['seen']);
|
||||
}
|
||||
|
||||
public function testSeenListIsPrivateToTheUser(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
$other = $this->createUser();
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/tours/appointments/seen', $other, ['version' => 1]);
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/my/tours', $user);
|
||||
self::assertSame([], (array) $body['data']['seen']);
|
||||
}
|
||||
|
||||
public function testBothEndpointsRequireAuthentication(): void
|
||||
{
|
||||
$this->client->request('GET', '/api/v1/my/tours');
|
||||
self::assertSame(401, $this->responseCode());
|
||||
|
||||
$this->client->request('POST', '/api/v1/my/tours/appointments/seen');
|
||||
self::assertSame(401, $this->responseCode());
|
||||
}
|
||||
|
||||
#[DataProvider('invalidVersions')]
|
||||
public function testInvalidVersionIsRejected(mixed $version): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$body = $this->authJson('POST', '/api/v1/my/tours/appointments/seen', $user, ['version' => $version]);
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
self::assertSame('version', $body['errors'][0]['field']);
|
||||
self::assertSame([], $this->em->getRepository(UserTourProgress::class)->findBy(['user' => $user]));
|
||||
}
|
||||
|
||||
public static function invalidVersions(): iterable
|
||||
{
|
||||
yield 'zero' => [0];
|
||||
yield 'string' => ['1'];
|
||||
yield 'float' => [1.5];
|
||||
}
|
||||
|
||||
public function testAnUnknownTourIdShapeIsNotRouted(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/tours/Appointments%20Page/seen', $user, ['version' => 1]);
|
||||
|
||||
self::assertSame(404, $this->responseCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\UserProfile;
|
||||
|
||||
use App\Tests\ApiTestCase;
|
||||
use App\UserProfile\Entity\UserTourProgress;
|
||||
use App\UserProfile\Repository\UserTourProgressRepository;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
|
||||
class UserTourProgressTest extends ApiTestCase
|
||||
{
|
||||
public function testSeenMapReturnsVersionPerTour(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$this->em->persist(new UserTourProgress($user, 'appointments', 1));
|
||||
$this->em->persist(new UserTourProgress($user, 'patients', 3));
|
||||
$this->em->flush();
|
||||
|
||||
/** @var UserTourProgressRepository $repo */
|
||||
$repo = $this->em->getRepository(UserTourProgress::class);
|
||||
|
||||
self::assertSame(['appointments' => 1, 'patients' => 3], $repo->seenMap($user));
|
||||
}
|
||||
|
||||
public function testSeenMapIsScopedToTheUser(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
$other = $this->createUser();
|
||||
|
||||
$this->em->persist(new UserTourProgress($other, 'appointments', 1));
|
||||
$this->em->flush();
|
||||
|
||||
/** @var UserTourProgressRepository $repo */
|
||||
$repo = $this->em->getRepository(UserTourProgress::class);
|
||||
|
||||
self::assertSame([], $repo->seenMap($user));
|
||||
}
|
||||
|
||||
public function testTheSameTourCannotBeStoredTwiceForOneUser(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
|
||||
$this->em->persist(new UserTourProgress($user, 'appointments', 1));
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist(new UserTourProgress($user, 'appointments', 2));
|
||||
|
||||
$this->expectException(UniqueConstraintViolationException::class);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
public function testMarkSeenNeverLowersTheStoredVersion(): void
|
||||
{
|
||||
$user = $this->createUser();
|
||||
$progress = new UserTourProgress($user, 'appointments', 4);
|
||||
|
||||
$progress->markSeen(2);
|
||||
|
||||
self::assertSame(4, $progress->getVersion());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user