setRawAttributes([ 'source_catalog_item_id' => '20', 'source_variant_id' => '30', 'used_at' => null, 'scanner_user_id' => '15', 'user_id' => '10', ]); $this->assertSame('tickets', $ticket->getTable()); $this->assertFalse($ticket->usesTimestamps()); $this->assertSame(20, $ticket->source_catalog_item_id); $this->assertSame(30, $ticket->source_variant_id); $this->assertNull($ticket->used_at); $this->assertSame(15, $ticket->scanner_user_id); $this->assertSame(10, $ticket->user_id); $this->assertInstanceOf(Tenant::class, $ticket->tenant()->getRelated()); $this->assertInstanceOf(User::class, $ticket->user()->getRelated()); $this->assertInstanceOf(User::class, $ticket->scannerUser()->getRelated()); $this->assertInstanceOf(CatalogItem::class, $ticket->sourceCatalogItem()->getRelated()); $this->assertInstanceOf(Variant::class, $ticket->sourceVariant()->getRelated()); $this->assertInstanceOf(TicketValidityGroup::class, $ticket->validityGroups()->getRelated()); $this->assertInstanceOf(ValidityTime::class, (new TicketValidityGroup)->validityTimes()->getRelated()); } public function test_unused_ticket_without_validity_time_is_valid(): void { $ticket = new Ticket; $this->assertTrue($ticket->isValid()); $this->assertSame(Ticket::STATUS_ACTIVE, $ticket->status); } public function test_fixed_window_controls_ticket_validity(): void { Carbon::setTestNow('2026-07-21 10:00:00'); $ticket = $this->ticketWithValidityTime(new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => now()->subHour(), 'fixed_expires_at' => now()->addHour(), ])); $this->assertTrue($ticket->isValid()); $this->assertFalse($ticket->is_expired); } public function test_time_window_is_resolved_for_current_date(): void { Carbon::setTestNow('2026-07-21 12:00:00'); $ticket = $this->ticketWithValidityGroups([[new ValidityTime([ 'type' => ValidityTimeType::TimeWindow, 'start_time' => '10:00:00', 'end_time' => '14:00:00', ])]]); $this->assertSame('2026-07-21 10:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s')); $this->assertSame('2026-07-21 14:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s')); $this->assertTrue($ticket->isValid()); } public function test_ticket_is_invalid_when_validity_time_expires(): void { Carbon::setTestNow('2026-07-21 10:00:00'); $ticket = $this->ticketWithValidityTime(new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_expires_at' => now(), ])); $this->assertFalse($ticket->isValid()); $this->assertTrue($ticket->is_expired); $this->assertSame(Ticket::STATUS_EXPIRED, $ticket->status); } public function test_ticket_is_valid_when_any_validity_time_is_active(): void { Carbon::setTestNow('2026-07-21 10:00:00'); $ticket = $this->ticketWithValidityGroups([ [new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => now()->subDays(2), 'fixed_expires_at' => now()->subDay(), ])], [new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => now()->subHour(), 'fixed_expires_at' => now()->addHour(), ])], ]); $this->assertTrue($ticket->isValid()); $this->assertFalse($ticket->is_expired); $this->assertSame('2026-07-19 10:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s')); $this->assertSame('2026-07-21 11:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s')); } public function test_ticket_is_invalid_but_not_expired_between_validity_times(): void { Carbon::setTestNow('2026-07-21 10:00:00'); $ticket = $this->ticketWithValidityGroups([ [new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => now()->subDays(2), 'fixed_expires_at' => now()->subDay(), ])], [new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => now()->addDay(), 'fixed_expires_at' => now()->addDays(2), ])], ]); $this->assertFalse($ticket->isValid()); $this->assertFalse($ticket->is_expired); $this->assertSame(Ticket::STATUS_ACTIVE, $ticket->status); } public function test_used_ticket_is_invalid_and_not_reported_as_expired(): void { $ticket = $this->ticketWithValidityTime(new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_expires_at' => now()->subHour(), ])); $ticket->used_at = now()->subMinute(); $this->assertFalse($ticket->is_valid); $this->assertFalse($ticket->is_expired); $this->assertTrue($ticket->is_used); $this->assertSame(Ticket::STATUS_USED, $ticket->status); } public function test_all_validity_times_in_the_same_group_must_be_active(): void { Carbon::setTestNow('2026-08-20 13:00:00'); $ticket = $this->ticketWithValidityGroups([[ new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => '2026-08-20 09:00:00', 'fixed_expires_at' => '2026-08-20 18:00:00', ]), new ValidityTime([ 'type' => ValidityTimeType::TimeWindow, 'start_time' => '12:00:00', 'end_time' => '15:00:00', ]), ]]); $this->assertTrue($ticket->isValid()); $this->assertSame('2026-08-20 12:00:00', $ticket->getEffectiveStartsAt()->format('Y-m-d H:i:s')); $this->assertSame('2026-08-20 15:00:00', $ticket->getEffectiveExpiresAt()->format('Y-m-d H:i:s')); Carbon::setTestNow('2026-08-20 16:00:00'); $this->assertFalse($ticket->isValid()); $this->assertTrue($ticket->is_expired); } private function ticketWithValidityTime(ValidityTime $validityTime): Ticket { return $this->ticketWithValidityGroups([[$validityTime]]); } /** @param array> $validityGroups */ private function ticketWithValidityGroups(array $validityGroups): Ticket { $ticket = new Ticket; $groups = collect($validityGroups)->map(function (array $validityTimes): TicketValidityGroup { $group = new TicketValidityGroup; $group->setRelation('validityTimes', new EloquentCollection($validityTimes)); return $group; }); $ticket->setRelation('validityGroups', new EloquentCollection($groups->all())); return $ticket; } }