Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactoring the Domain, Guided by Tests

pelshoff
February 20, 2018

Refactoring the Domain, Guided by Tests

Slides of my first showing, tonight in Veenendaal
https://www.meetup.com/Web-Apps-Veenendaal/events/246607957/

pelshoff

February 20, 2018
Tweet

More Decks by pelshoff

Other Decks in Programming

Transcript

  1. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $duration; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, MeetingDuration $duration, bool $isPublished, string $subTitle, Program $program) { $this->meetingId = $meetingId; $this->title = $title; $this->description = $description; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->subTitle = $subTitle; $this->program = $program; } }
  2. final class MeetingDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { $this->start = $start; $this->end = $end; $this->meetingCannotEndBeforeStart (); } private function meetingCannotEndBeforeStart (): void { if ($this->start > $this->end) { throw InvalidMeetingDuration::becauseDurationEndsBeforeStarting() ; } } }
  3. final class Program { private $program; /** @param Slot[] $program

    */ public function __construct(array $program) { $this->program = $program; $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime (); } private function programSlotsCannotOccurInTheSameRoomAtTheSameTime (): void { foreach ($this->slotsStartingAt (0) as $index => $thisSlot) { foreach ($this->slotsStartingAt ($index + 1) as $thatSlot) { if ($thisSlot->overlapsWith($thatSlot)) { throw InvalidProgram:: becauseProgramSlotsOverlap(); } } } } /** * @param int $index * @return Slot[] */ private function slotsStartingAt (int $index): array { return array_slice($this->program, $index); } }
  4. final class Slot { private $duration; private $title; private $room;

    public function __construct(SlotDuration $duration, string $title, string $room) { $this->duration = $duration; $this->title = $title; $this->room = $room; } public function overlapsWith(Slot $that): bool { return $this->room === $that->room && $this->duration->overlapsWith($that->duration); } }
  5. final class SlotDuration { private $start; private $end; public function

    __construct(DateTimeImmutable $start, DateTimeImmutable $end) { $this->start = $start; $this->end = $end; $this->slotCannotEndBeforeStart (); $this->slotMustStartAndEndOnTheSameDay (); } private function slotCannotEndBeforeStart (): void { /**/ } private function slotMustStartAndEndOnTheSameDay (): void { /**/ } public function overlapsWith(SlotDuration $that): bool { return !$this->before($that) && !$that->before($this); } private function before(SlotDuration $that): bool { return $that->start >= $this->end; } }
  6. final class Meeting { private $meetingId; private $title; private $description;

    private $code; private $duration; private $isPublished; private $subTitle; private $program; public function __construct(Uuid $meetingId, string $title, string $description, string $code, MeetingDuration $duration, bool $isPublished, string $subTitle, Program $program) { $this->meetingId = $meetingId; $this->title = $title; $this->description = $description; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->subTitle = $subTitle; $this->program = $program; } // ... }
  7. final class MeetingTest extends TestCase { public function testThatValidMeetingsCanBeInstantiated ()

    { $this->assertInstanceOf (Meeting::class, new Meeting( Uuid:: uuid4(), 'This is a test' , 'This is a test description' , 'M01', new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) ) , false, 'This is a test sub title' , new Program([]) )); } } $ vendor/bin/phpunit -c phpunit.xml.dist tests/ PHPUnit 5.7.27 by Sebastian Bergmann and contributors. ........ 8 / 8 (100%) Time: 25 ms, Memory: 4.00MB OK (8 tests, 8 assertions)
  8. final class MeetingTest extends TestCase { public function testThatValidMeetingsCanBeInstantiated ()

    { $this->assertInstanceOf (Meeting::class, new Meeting( Uuid:: uuid4(), new MeetingPresentation( 'This is a test' , 'This is a test description' , 'This is a test sub title' ), 'M01', new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) ) , false, new Program([]) )); } } ........ 8 / 8 (100%) Time: 24 ms, Memory: 4.00MB OK (8 tests, 8 assertions)
  9. final class MeetingPresentationTest extends TestCase { public function testThatPresentationMustHaveTitle ()

    { $this->expectException (InvalidMeetingPresentation:: class); new MeetingPresentation( '', '', ''); } } ..F...... 9 / 9 (100%) Time: 25 ms, Memory: 4.00MB There was 1 failure: 1) Pelshoff\Meeting\test\MeetingPresentationTest::testThatPresentationMustHaveTitle Failed asserting that exception of type "Pelshoff\Meeting\InvalidMeetingPresentation" is thrown. FAILURES! Tests: 9, Assertions: 9, Failures: 1.
  10. final class MeetingPresentation { private $title; private $description; private $subTitle;

    public function __construct(string $title, string $description, string $subTitle){ $this->title = $title; $this->description = $description; $this->subTitle = $subTitle; $this->presentationMustHaveTitle (); } private function presentationMustHaveTitle (): void { if (mb_strlen($this->title) < 5) { throw InvalidMeetingPresentation:: becauseTitleIsMissing(); } } } ......... 9 / 9 (100%) Time: 26 ms, Memory: 4.00MB OK (9 tests, 9 assertions)
  11. final class Meeting { private $meetingId; private $presentation; private $code;

    private $duration; private $isPublished; private $program; public function __construct(UuidInterface $meetingId, MeetingPresentation $presentation, string $code, MeetingDuration $duration, bool $isPublished, Program $program) { $this->meetingId = $meetingId; $this->presentation = $presentation; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->program = $program; } }
  12. final class Meeting { private $meetingId; private $presentation; private $code;

    private $duration; private $isPublished; private $program; public function __construct(UuidInterface $meetingId, MeetingPresentation $presentation, string $code, MeetingDuration $duration, bool $isPublished, Program $program) { $this->meetingId = $meetingId; $this->presentation = $presentation; $this->code = $code; $this->duration = $duration; $this->isPublished = $isPublished; $this->program = $program; } }
  13. final class Meeting { private $meetingId; private $presentation; private $code;

    private $isPublished; private $program; public function __construct(UuidInterface $meetingId, MeetingPresentation $presentation, string $code, bool $isPublished, Program $program) { $this->meetingId = $meetingId; $this->presentation = $presentation; $this->code = $code; $this->isPublished = $isPublished; $this->program = $program; }// final class Program { private $duration; private $program; public function __construct(MeetingDuration $duration, array $program) { $this->duration = $duration; $this->program = $program; $this->programSlotsCannotOccurInTheSameRoomAtTheSameTime (); }// OK (9 tests, 9 assertions)
  14. public function testThatMeetingCanBeRescheduled () { $meeting = new Meeting($meetingId =

    Uuid::uuid4(), /**/ new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) )/**/ new SlotDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 20:00' ) ) /**/; $expected = new Meeting($meetingId, /**/ new MeetingDuration( new DateTimeImmutable( '2018-02-21 07:00' ), new DateTimeImmutable( '2018-02-21 10:00' ) )/**/ new SlotDuration( new DateTimeImmutable( '2018-02-21 07:00' ), new DateTimeImmutable( '2018-02-21 08:00' ) ) ; $meeting->reschedule(new DateTimeImmutable( '2018-02-21 07:00' )); $this->assertEquals($expected, $meeting); } Error: Call to undefined method Pelshoff\Meeting\Meeting::reschedule()
  15. final class Meeting { /**/ public function reschedule(DateTimeImmutable $newStartDate): void

    { $this->program = $this->program->rescheduled($newStartDate); } }
  16. final class Program { /**/ public function rescheduled(DateTimeImmutable $newStartDate): Program

    { $newDuration = $this->duration->rescheduled($newStartDate); $diff = $this->duration->intervalToStartOf ($newDuration); $newProgram = array_map(function (Slot $slot) use ($diff) { return $slot->movedBy($diff); }, $this->program); return new self($newDuration, $newProgram); } }
  17. final class MeetingDuration { /**/ public function rescheduled(DateTimeImmutable $newStartDate): MeetingDuration

    { $newEndDate = $this->end->add($this->start->diff($newStartDate)); return new self($newStartDate, $newEndDate); } public function intervalToStartOf (MeetingDuration $that): DateInterval { return $this->start->diff($that->start); } }
  18. final class Slot { /**/ public function movedBy(DateInterval $diff): Slot

    { return new self( $this->duration->movedBy($diff), $this->title, $this->room ); } }
  19. final class SlotDuration { /**/ public function movedBy(DateInterval $diff): SlotDuration

    { return new self($this->start->add($diff), $this->end->add($diff)); } } .......... 10 / 10 (100%) Time: 29 ms, Memory: 4.00MB OK (10 tests, 10 assertions)
  20. final class TicketService { /** @var TicketRepository */ private $ticketRepository

    ; public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, []) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, $currentSoldPerTicket ) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  21. final class TicketService { /** @var TicketRepository */ private $ticketRepository

    ; public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, []) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, $currentSoldPerTicket ) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  22. private function checkAvailability(Ticket $ticket, array $currentSoldPerTicket): bool { if (!$ticket->getIsPublic())

    { return false; } if ($ticket->getNumberOfTickets() && isset($currentSoldPerTicket[$ticket->getId()]) && $currentSoldPerTicket[$ticket->getId()] >= $ticket->getNumberOfTickets()) { return false; } if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()) { return false; } if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { return false; } return true; }
  23. final class Ticket { private $id; private $isPublic; private $numberOfTickets

    ; private $startDate; private $endDate; public function __construct(int $id, bool $isPublic, int $numberOfTickets , ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate) { $this->id = $id; $this->isPublic = $isPublic; $this->numberOfTickets = $numberOfTickets ; $this->startDate = $startDate; $this->endDate = $endDate; }
  24. interface TicketRepository { /** * @param Meeting $meeting * @return

    Ticket[] * * Do a query */ public function getMeetingTickets (Meeting $meeting): array; /** * @param Meeting $meeting * @return array * * Do a query */ public function getNumberOfCurrentSoldTickets (Meeting $meeting): array; /** * @param Ticket $ticket * @param User $user * @return bool * * allowed by user invitation? * allowed by group? * allowed by selection? * Anyway, do a query */ public function checkAllowed(Ticket $ticket, User $user): bool; }
  25. final class TicketServiceTest extends TestCase { public function testThatGetAvailableTicketsGivesTheOnlyTicketAvailable ()

    { $meeting = $this->givenAnUpcomingMeeting (); $ticket = $this->givenAPublicTicket (); $user = $this->givenAUser(); $this->whenTicketsForMeeting ([$ticket], $meeting); $this->whenNoTicketsSold (); $expected = [$ticket->getId() => $ticket]; $actual = $this->ticketService->getAvailableTickets ($meeting, false, $user, false); $this->assertEquals($expected, $actual); } ........... 11 / 11 (100%) Time: 32 ms, Memory: 4.00MB OK (11 tests, 11 assertions)
  26. private function givenAnUpcomingMeeting () { return new Meeting(/**/); } private

    function givenAPublicTicket () { return new Ticket(mt_rand() , true, 0, null, null); } private function whenTicketsForMeeting (array $tickets, Meeting $meeting) { $this->ticketRepository ->expects($this->any()) ->method('getMeetingTickets' ) ->with($meeting) ->will($this->returnValue($tickets)); } private function whenNoTicketsSold () { $this->ticketRepository ->expects($this->any()) ->method('getNumberOfCurrentSoldTickets' ) ->will($this->returnValue([])); } private function givenAUser() { return new User(); } protected function setUp() { $this->ticketRepository = $this->getMockBuilder (TicketRepository:: class) ->getMock(); $this->ticketService = new TicketService( $this->ticketRepository ); }
  27. public function testThatGetAvailableTicketsFiltersDisallowedTickets () { $meeting = $this->givenAnUpcomingMeeting (); $allTickets

    = [ $this->givenAPublicTicket (), $this->givenAPublicTicket (), $this->givenAPublicTicket (), $allowedTicket = $this->givenAPublicTicket (), ]; $user = $this->givenAUser(); $this->whenTicketsForMeeting ($allTickets, $meeting); $this->whenUserOnlyAllowedForTicket ($allowedTicket , $user); $this->whenNoTicketsSold (); $expected = [$allowedTicket ->getId() => $allowedTicket ]; $actual = $this->ticketService->getAvailableTickets ($meeting, true, $user, false); $this->assertEquals($expected, $actual); } ............ 12 / 12 (100%) Time: 33 ms, Memory: 4.00MB OK (12 tests, 12 assertions)
  28. private function whenUserOnlyAllowedForTicket (Ticket $ticket, User $user) { $this->ticketRepository ->expects($this->any())

    ->method('checkAllowed' ) ->withConsecutive ($this->anything(), $this->anything(), $this->anything(), [$ticket, $user]) ->willReturnOnConsecutiveCalls (false, false, false, true); }
  29. public function testThatSoldOutTicketsAreNotAvailable () { $meeting = $this->givenAnUpcomingMeeting (); $ticket

    = $this->givenALimitedTicket (); $user = $this->givenAUser(); $this->whenTicketsForMeeting ([$ticket], $meeting); $this->whenTicketsAreAllowed (); $this->whenTicketIsSoldOut ($ticket); $expected = []; $actual = $this->ticketService->getAvailableTickets ($meeting, true, $user, false); $this->assertEquals($expected, $actual); } public function testThatSoldOutTicketsAreAvailableForReserve () { $meeting = $this->givenAnUpcomingMeeting (); $ticket = $this->givenALimitedTicket (); $user = $this->givenAUser(); $this->whenTicketsForMeeting ([$ticket], $meeting); $this->whenTicketsAreAllowed (); $this->whenTicketIsSoldOut ($ticket); $expected = [$ticket->getId() => $ticket]; $actual = $this->ticketService->getAvailableTickets ($meeting, true, $user, true); $this->assertEquals($expected, $actual); } OK (14 tests, 14 assertions)
  30. final class TicketService { /** @var TicketRepository */ private $ticketRepository

    ; public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, []) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if (!$publicOnly || ($this->checkAvailability ($ticket, $currentSoldPerTicket ) && $this->ticketRepository ->checkAllowed ($ticket, $user))) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  31. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; foreach ($tickets as $ticket) { $ticketIsAvailableForReserve [$ticket->getId()] = $this->checkAvailability ($ticket, []); $ticketIsAvailableOtherwise [$ticket->getId()] = $this->checkAvailability ($ticket, $currentSoldPerTicket ); $ticketIsAllowedForUser [$ticket->getId()] = $this->ticketRepository ->checkAllowed ($ticket, $user); } /** @var Ticket[] $availableTickets */ $availableTickets = []; if ($forReserve ) { foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailableForReserve [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailableOtherwise [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; }
  32. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; foreach ($tickets as $ticket) { $ticketIsAvailableForReserve [$ticket->getId()] = $this->checkAvailability ($ticket, []); $ticketIsAvailableOtherwise [$ticket->getId()] = $this->checkAvailability ($ticket, $currentSoldPerTicket ); $ticketIsAllowedForUser [$ticket->getId()] = $this->ticketRepository ->checkAllowed ($ticket, $user); } $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise ; /** @var Ticket[] $availableTickets */ $availableTickets = []; foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailable [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } .............. 14 / 14 (100%) Time: 32 ms, Memory: 4.00MB OK (14 tests, 14 assertions)
  33. private function checkAvailability (Ticket $ticket, array $currentSoldPerTicket ): bool {

    if (!$ticket->isPublic()) { return false; } if ($ticket->getNumberOfTickets () && isset($currentSoldPerTicket [$ticket->getId()]) && $currentSoldPerTicket [$ticket->getId()] >= $ticket->getNumberOfTickets ()) { return false; } if ($ticket->getStartDate() && new DateTimeImmutable() < $ticket->getStartDate()){ return false; } if ($ticket->getEndDate() && new DateTimeImmutable() > $ticket->getEndDate()) { return false; } return true; }
  34. private function checkAvailability (Ticket $ticket, array $currentSoldPerTicket ): bool {

    return $this->ticketIsOpenForSale ($ticket) && $this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket ); } private function ticketIsOpenForSale (Ticket $ticket): bool { if (!$ticket->isPublic ()) { return false; } if ($ticket->getStartDate () && new DateTimeImmutable() < $ticket->getStartDate ()) { return false; } if ($ticket->getEndDate () && new DateTimeImmutable() > $ticket->getEndDate ()) { return false; } return true; } private function thereAreTicketsLeftForSale (Ticket $ticket, array $currentSoldPerTicket ): bool { if ($ticket->getNumberOfTickets () && isset($currentSoldPerTicket [$ticket->getId()]) && $currentSoldPerTicket [$ticket->getId()] >= $ticket->getNumberOfTickets ()) { return false; } return true; }
  35. private function checkAvailability (Ticket $ticket, array $currentSoldPerTicket ): bool {

    return $ticket->ticketIsOpenForSaleOn (new DateTimeImmutable()) && $this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket ); } private function thereAreTicketsLeftForSale (Ticket $ticket, array $currentSoldPerTicket ): bool { if ($ticket->getNumberOfTickets () && isset($currentSoldPerTicket [$ticket->getId()]) && $currentSoldPerTicket [$ticket->getId()] >= $ticket->getNumberOfTickets ()) { return false; } return true; } final class Ticket { public function ticketIsOpenForSaleOn (DateTimeImmutable $date): bool { if (!$this->isPublic ()) { return false; } if ($this->getStartDate () && $date < $this->getStartDate ()) { return false; } if ($this->getEndDate () && $date > $this->getEndDate ()) { return false; } return true; }
  36. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $ticketIsAvailableForReserve = $ticketIsAvailableOtherwise = $ticketIsAllowedForUser = []; $today = new DateTimeImmutable() ; foreach ($tickets as $ticket) { $ticketIsAvailableForReserve [$ticket->getId()] = $ticket->ticketIsOpenForSaleOn ($today); $ticketIsAvailableOtherwise [$ticket->getId()] = $ticket->ticketIsOpenForSaleOn ($today) && $this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket ); $ticketIsAllowedForUser [$ticket->getId()] = $this->ticketRepository ->checkAllowed ($ticket, $user); } $ticketIsAvailable = $forReserve ? $ticketIsAvailableForReserve : $ticketIsAvailableOtherwise ; /** @var Ticket[] $availableTickets */ $availableTickets = []; foreach ($tickets as $ticket) { if (!$publicOnly || ($ticketIsAvailable [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()])) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } private function thereAreTicketsLeftForSale (Ticket $ticket, array $currentSoldPerTicket ): bool { /**/ }
  37. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { if (!$publicOnly ) { return $this->ticketRepository ->getMeetingTickets ($meeting ); } // ... Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( - 1623135705 => Pelshoff\Meeting\Ticket Object (...) + 0 => Pelshoff\Meeting\Ticket Object (...) ) /data/presentatie/tests/TicketServiceTest.php:77 FAILURES! Tests: 14, Assertions: 14, Failures: 3.
  38. public function getAvailableTickets (Meeting $meeting , bool $publicOnly , User

    $user, bool $forReserve ): array { if ($publicOnly ) { return $this->getPubliclyAvailableTickets ($meeting , $user, $forReserve ); } return $this->getPrivatelyAvailableTickets ($meeting ); } public function getPrivatelyAvailableTickets (Meeting $meeting ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); foreach ($tickets as $ticket) { $availableTickets [$ticket->getId()] = $ticket; } return $availableTickets ; } public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve ): array { /**/ /** @var Ticket[] $availableTickets */ $availableTickets = []; foreach ($tickets as $ticket) { if ($ticketIsAvailable [$ticket->getId()] && $ticketIsAllowedForUser [$ticket->getId()]) { $availableTickets [$ticket->getId()] = $ticket; } } return $availableTickets ; } OK (14 tests, 14 assertions)
  39. public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve

    ): array { $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $today = new DateTimeImmutable() ; $openTickets = []; foreach ($tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($today)) { $openTickets [$ticket->getId()] = $ticket; } } $leftTickets = []; if ($forReserve ) { $leftTickets = $openTickets ; } else { foreach ($openTickets as $ticket) { if ($this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket )) { $leftTickets [$ticket->getId()] = $ticket; } } } $allowedTickets = []; foreach ($leftTickets as $ticket) { if ($this->ticketRepository ->checkAllowed ($ticket, $user)) { $allowedTickets [$ticket->getId()] = $ticket; } } return $allowedTickets ; }
  40. public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve

    ): array { $today = new DateTimeImmutable() ; $tickets = $this->ticketRepository ->getMeetingTickets ($meeting ); $openTickets = []; foreach ($tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($today)) { $openTickets [$ticket->getId()] = $ticket; } } $allowedTickets = []; foreach ($openTickets as $ticket) { if ($this->ticketRepository ->checkAllowed ($ticket, $user)) { $allowedTickets [$ticket->getId()] = $ticket; } } if ($forReserve ) { return $allowedTickets ; } $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); $leftTickets = []; foreach ($allowedTickets as $ticket) { if ($this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket )) { $leftTickets [$ticket->getId()] = $ticket; } } return $leftTickets ; }
  41. public function getPubliclyAvailableTickets (Meeting $meeting , User $user, bool $forReserve

    ): array { if ($forReserve ) { return $this->getTicketsUserCanReserve ($meeting , $user); } return $this->getTicketsUserCanPurchase ($meeting , $user); } public function getTicketsUserCanReserve (Meeting $meeting , User $user): array { $tickets = $this->getTicketsOpenForSaleOn ($meeting , new DateTimeImmutable()) ; $allowedTickets = []; foreach ($tickets as $ticket) { if ($this->ticketRepository ->checkAllowed ($ticket, $user)) { $allowedTickets [$ticket->getId()] = $ticket; } } return $allowedTickets ; } public function getTicketsUserCanPurchase (Meeting $meeting , User $user): array { $tickets = $this->getTicketsUserCanReserve ($meeting , $user); $leftTickets = []; $currentSoldPerTicket = $this->ticketRepository ->getNumberOfCurrentSoldTickets ($meeting ); foreach ($tickets as $ticket) { if ($this->thereAreTicketsLeftForSale ($ticket, $currentSoldPerTicket )) { $leftTickets [$ticket->getId()] = $ticket; } } return $leftTickets ; } OK (14 tests, 14 assertions)
  42. private function getTicketsOpenForSaleOn (Meeting $meeting, DateTimeImmutable $date): array { $tickets

    = $this->ticketRepository ->getMeetingTickets ($meeting); $openTickets = []; foreach ($tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($date)) { $openTickets[$ticket->getId()] = $ticket; } } return $openTickets; }
  43. final class Meeting { /**/ public function getTickets(): array {

    return $this->tickets; } public function getTicketsOpenForSaleOn (DateTimeImmutable $date): array { $ticketsOpenForSale = []; foreach ($this->tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($date)) { $ticketsOpenForSale [$ticket->getId()] = $ticket; } } return $ticketsOpenForSale ; } } .............. 14 / 14 (100%) Time: 43 ms, Memory: 4.00MB OK (14 tests, 14 assertions)
  44. final class TicketService { /** @deprecated Use getPrivatelyAvailableTickets, getTicketsUserCanReserve or

    getTicketsUserCanPurchase */ public function getAvailableTickets (Meeting $meeting, bool $publicOnly, User $user, bool $forReserve): array { /**/ } public function getPrivatelyAvailableTickets (Meeting $meeting): array { /**/ } /** @deprecated Use getTicketsUserCanReserve or getTicketsUserCanPurchase */ public function getPubliclyAvailableTickets (Meeting $meeting, User $user, bool $forReserve): array {/**/} public function getTicketsUserCanReserve (Meeting $meeting, User $user): array { /**/ } public function getTicketsUserCanPurchase (Meeting $meeting, User $user): array { /**/ }
  45. public function testThatTicketsCannotBeSoldWhenMeetingHasStarted () { $meeting = new Meeting( Uuid::uuid4(),

    new MeetingPresentation( 'This is a test' , '', ''), 'M01', false, new Program( new MeetingDuration( new DateTimeImmutable( '2018-02-20 19:00' ), new DateTimeImmutable( '2018-02-20 22:00' ) ), [] ), [new Ticket(1, true, 0, null, null)] ); $expected = []; $actual = $meeting->getTicketsOpenForSaleOn ( new DateTimeImmutable( '2018-02-20 20:00' )); $this->assertEquals($expected, $actual); } --- Expected +++ Actual @@ @@ Array ( + 1 => Pelshoff\Meeting\Ticket Object (...) )
  46. public function getTicketsOpenForSaleOn (DateTimeImmutable $date): array { if ($this->program->hasStartedOn($date)) {

    return []; } $ticketsOpenForSale = []; foreach ($this->tickets as $ticket) { if ($ticket->ticketIsOpenForSaleOn ($date)) { $ticketsOpenForSale [$ticket->getId()] = $ticket; } } return $ticketsOpenForSale ; } ............... 15 / 15 (100%) Time: 84 ms, Memory: 4.00MB OK (15 tests, 15 assertions)