fix: false draft-restored message
This commit is contained in:
@@ -52,6 +52,7 @@ class ParticipantControllerTest extends TestCase
|
||||
$bookingDto = $this->createBookingDto();
|
||||
$bookingData = $this->createBooking();
|
||||
$participant = $bookingDto->participants[0];
|
||||
$bookingDto->originalFingerprint = (new BookingChangeTracker())->generateFingerprint($bookingDto);
|
||||
$participant->lastName = ParticipantDataPrefiller::TOKEN;
|
||||
|
||||
$form = $this->createMock(FormInterface::class);
|
||||
@@ -254,6 +255,96 @@ class ParticipantControllerTest extends TestCase
|
||||
$this->assertSame(['participant_form', 'booking_summary'], $controller->renderedBlocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression: saving the participant form without touching anything used to write a
|
||||
* draft identical to the API state. The overview then reloaded from the API, found
|
||||
* that draft, applied it and told the user their draft had been restored — for edits
|
||||
* they never made.
|
||||
*/
|
||||
public function testSubmitWithoutChangesDeletesDraftInsteadOfSavingIt(): void
|
||||
{
|
||||
$draftService = $this->createMock(BookingEditDraftManager::class);
|
||||
$draftService->expects($this->never())->method('saveDraft');
|
||||
$draftService->expects($this->once())->method('deleteDraft')->with($this->isInstanceOf(User::class), 42);
|
||||
|
||||
$this->runValidSubmit($draftService, dirty: false);
|
||||
}
|
||||
|
||||
public function testSubmitWithChangesPersistsDraft(): void
|
||||
{
|
||||
$draftService = $this->createMock(BookingEditDraftManager::class);
|
||||
$draftService->expects($this->once())->method('saveDraft');
|
||||
$draftService->expects($this->never())->method('deleteDraft');
|
||||
|
||||
$this->runValidSubmit($draftService, dirty: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drives editParticipant() through a valid, non-dummy submit.
|
||||
*/
|
||||
private function runValidSubmit(BookingEditDraftManager $draftService, bool $dirty): void
|
||||
{
|
||||
$request = Request::create('/bookings/42/edit/participants/0', 'POST');
|
||||
$user = $this->createUser();
|
||||
$bookingDto = $this->createBookingDto();
|
||||
$bookingData = $this->createBooking();
|
||||
$participant = $bookingDto->participants[0];
|
||||
$participant->firstName = 'Anna';
|
||||
|
||||
$bookingDto->originalFingerprint = (new BookingChangeTracker())->generateFingerprint($bookingDto);
|
||||
|
||||
if (true === $dirty) {
|
||||
$participant->firstName = 'Berta';
|
||||
}
|
||||
|
||||
$form = $this->createStub(FormInterface::class);
|
||||
$form->method('handleRequest')->willReturnSelf();
|
||||
$form->method('isSubmitted')->willReturn(true);
|
||||
$form->method('isValid')->willReturn(true);
|
||||
|
||||
$dataLoader = $this->createStub(BookingEditDataLoader::class);
|
||||
$dataLoader->method('fetchBookingData')->willReturn($bookingData);
|
||||
|
||||
$bookingSessionService = $this->createMock(BookingSessionManager::class);
|
||||
$bookingSessionService->method('getBookingDto')->willReturn($bookingDto);
|
||||
$bookingSessionService->expects($this->once())
|
||||
->method('saveBookingDto')
|
||||
->with($request, $bookingDto, BookingDto::MODE_EDIT);
|
||||
|
||||
$participantFormSupportService = $this->createStub(ParticipantFormSupport::class);
|
||||
$participantFormSupportService->method('ensureParticipantExists')->willReturn($participant);
|
||||
$participantFormSupportService->method('createParticipantEditDto')
|
||||
->willReturn(new ParticipantEditDto($participant, $bookingDto));
|
||||
$participantFormSupportService->method('getParticipantFormOptions')
|
||||
->willReturn(['booking_context' => $bookingDto]);
|
||||
$participantFormSupportService->method('collectAndClearNotifications')->willReturn([]);
|
||||
|
||||
$contextFactory = $this->createStub(BookingEditContextFactory::class);
|
||||
$contextFactory->method('createParticipantContext')->willReturn(
|
||||
new BookingEditContext($bookingDto, $bookingData, null, $this->createStub(BookingSummaryDto::class))
|
||||
);
|
||||
|
||||
$controller = new TestableParticipantController(
|
||||
$dataLoader,
|
||||
$draftService,
|
||||
$contextFactory,
|
||||
$this->createStub(BookingConfigurator::class),
|
||||
$bookingSessionService,
|
||||
new ParticipantDataPrefiller(
|
||||
$this->createStub(ApiClient::class),
|
||||
$this->createStub(Crypt::class),
|
||||
$this->createStub(LoggerInterface::class),
|
||||
),
|
||||
$participantFormSupportService,
|
||||
$user,
|
||||
$form,
|
||||
);
|
||||
|
||||
$response = $controller->editParticipant(42, 0, $request);
|
||||
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
}
|
||||
|
||||
private function createUser(): User
|
||||
{
|
||||
return new User('[email protected]');
|
||||
@@ -445,6 +536,7 @@ final class TestableParticipantController extends ParticipantController
|
||||
$draftService,
|
||||
$editContextFactory,
|
||||
$bookingService,
|
||||
new BookingChangeTracker(),
|
||||
$bookingSessionService,
|
||||
$prepopulationService,
|
||||
$participantFormSupportService,
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\DataProcessor\BookingDataProcessor;
|
||||
use App\BusProNet\Model\BaseData;
|
||||
use App\BusProNet\Model\Booking;
|
||||
use App\BusProNet\Model\ServiceAvailabilityResponse;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\BusProNet\XmlLoader\AgencyLoader;
|
||||
use App\Entity\BookingEditDraft;
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Security\Crypt;
|
||||
use App\Service\BookingChangeTracker;
|
||||
use App\Service\BookingEditDataLoader;
|
||||
use App\Service\BookingEditDraftManager;
|
||||
use App\Service\BookingSessionManager;
|
||||
use App\Service\TravelDataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
/**
|
||||
* Covers the "draft restored" signal that drives the overview flash message.
|
||||
*
|
||||
* A draft is written on every participant save, including saves that changed nothing.
|
||||
* Such a draft reproduces the API state exactly, so applying it is a no-op and must not
|
||||
* be reported as a restore — otherwise the user is told their draft was restored for
|
||||
* edits they never made.
|
||||
*/
|
||||
class BookingEditDataLoaderDraftRestoreTest extends TestCase
|
||||
{
|
||||
public function testNoOpDraftIsNotReportedAsRestored(): void
|
||||
{
|
||||
$loader = $this->createLoader(static function (BookingDto $dto): void {
|
||||
// Draft reproduces the API state: nothing changes.
|
||||
});
|
||||
|
||||
$loader->initializeFromApi(new Request(), 42, $this->createUser());
|
||||
|
||||
$this->assertFalse($loader->isDraftRestored());
|
||||
}
|
||||
|
||||
public function testDraftThatChangesDataIsReportedAsRestored(): void
|
||||
{
|
||||
$loader = $this->createLoader(static function (BookingDto $dto): void {
|
||||
$dto->participants[0]->firstName = 'Berta';
|
||||
});
|
||||
|
||||
$loader->initializeFromApi(new Request(), 42, $this->createUser());
|
||||
|
||||
$this->assertTrue($loader->isDraftRestored());
|
||||
}
|
||||
|
||||
private function createUser(): User
|
||||
{
|
||||
$user = new User('[email protected]');
|
||||
$user->setPassword('encrypted');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(BookingDto): void $applyDraft what the stored draft does to the DTO
|
||||
*/
|
||||
private function createLoader(callable $applyDraft): BookingEditDataLoader
|
||||
{
|
||||
$booking = new Booking();
|
||||
$booking->id = 42;
|
||||
$booking->dateId = 1234;
|
||||
$booking->hotelId = 7;
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->id = 1234;
|
||||
|
||||
$bookingDto = new BookingDto($travel, 77);
|
||||
$bookingDto->booking = $booking;
|
||||
$participant = new ParticipantDto();
|
||||
$participant->index = 0;
|
||||
$participant->firstName = 'Anna';
|
||||
$bookingDto->participants = [$participant];
|
||||
|
||||
$cache = $this->createStub(TagAwareCacheInterface::class);
|
||||
$cache->method('get')->willReturn($booking);
|
||||
|
||||
$travelDataService = $this->createStub(TravelDataProvider::class);
|
||||
$travelDataService->method('getTravelData')->willReturn($travel);
|
||||
$travelDataService->method('getMutabilityData')->willReturn($this->createStub(BaseData::class));
|
||||
$travelDataService->method('getAvailabilityData')
|
||||
->willReturn($this->createStub(ServiceAvailabilityResponse::class));
|
||||
|
||||
$bookingDataProcessor = $this->createStub(BookingDataProcessor::class);
|
||||
$bookingDataProcessor->method('createBookingDtoFromBooking')->willReturn($bookingDto);
|
||||
|
||||
$draftService = $this->createStub(BookingEditDraftManager::class);
|
||||
$draftService->method('findDraft')->willReturn($this->createStub(BookingEditDraft::class));
|
||||
$draftService->method('applyDraftToDto')->willReturnCallback(
|
||||
static function (BookingEditDraft $draft, BookingDto $dto, Travel $travel) use ($applyDraft): bool {
|
||||
$applyDraft($dto);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
$crypt = $this->createStub(Crypt::class);
|
||||
$crypt->method('decrypt')->willReturn('secret');
|
||||
|
||||
return new BookingEditDataLoader(
|
||||
$this->createStub(ApiClient::class),
|
||||
$bookingDataProcessor,
|
||||
$this->createStub(BookingSessionManager::class),
|
||||
new BookingChangeTracker(),
|
||||
$travelDataService,
|
||||
$draftService,
|
||||
$this->createStub(AgencyLoader::class),
|
||||
$crypt,
|
||||
$cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user