resolver = new TicketValidityResolver; } public function test_event_dates_and_multi_select_options_expand_to_or_groups(): void { $dates = new EloquentCollection([ $this->eventDate('2026-08-20'), $this->eventDate('2026-08-21'), ]); [$itemAttribute, $definitions] = $this->attributeDimension( allowMultiSelect: true, options: [ 'Desayuno' => $this->timeWindow('07:00:00', '12:00:00'), 'Cena' => $this->timeWindow('20:00:00', '24:00:00'), ], ); $variant = $this->variant($dates, $definitions); $validity = $this->resolver->resolveVariant($variant); $this->assertTrue($validity->isResolvable); $this->assertFalse($validity->isUnrestricted); $this->assertCount(4, $validity->groups); $this->assertTrue($validity->groups->every( fn ($group): bool => $group->validityTimes->count() === 2 )); $this->assertSame($itemAttribute, $definitions->first()->itemAttribute); } public function test_different_temporal_attributes_are_anded_in_the_same_group(): void { [, $scheduleDefinitions] = $this->attributeDimension( allowMultiSelect: false, options: ['Almuerzo' => $this->timeWindow('12:00:00', '15:00:00')], ); [, $admissionDefinitions] = $this->attributeDimension( allowMultiSelect: false, options: ['Ingreso' => $this->timeWindow('11:00:00', '14:00:00')], ); $variant = $this->variant( new EloquentCollection([$this->eventDate('2026-08-20')]), new EloquentCollection([ ...$scheduleDefinitions, ...$admissionDefinitions, ]), ); $validity = $this->resolver->resolveVariant($variant); $this->assertCount(1, $validity->groups); $this->assertCount(3, $validity->groups->sole()->validityTimes); $this->assertSame( '2026-08-20 12:00:00', $validity->effectiveStartsAt(Carbon::parse('2026-08-20 13:00:00'))->format('Y-m-d H:i:s'), ); $this->assertSame( '2026-08-20 14:00:00', $validity->effectiveExpiresAt(Carbon::parse('2026-08-20 13:00:00'))->format('Y-m-d H:i:s'), ); $this->assertTrue($validity->isValid(Carbon::parse('2026-08-20 13:00:00'))); $this->assertFalse($validity->isValid(Carbon::parse('2026-08-20 14:00:00'))); } public function test_variant_without_temporal_dimensions_is_unrestricted(): void { $validity = $this->resolver->resolveVariant( $this->variant(new EloquentCollection, new EloquentCollection) ); $this->assertTrue($validity->isResolvable); $this->assertTrue($validity->isUnrestricted); $this->assertTrue($validity->isValid()); $this->assertFalse($validity->isExpired()); } public function test_non_option_attributes_do_not_affect_validity(): void { $attribute = new Attribute(['type' => FieldType::String]); $attribute->setRelation('options', new EloquentCollection); $itemAttribute = new ItemAttribute(['allow_multi_select' => false]); $itemAttribute->setRelation('attribute', $attribute); $definition = new VariantDefinition(['value' => 'Comedor principal']); $definition->setAttribute('item_attribute_id', 123); $definition->setRelation('itemAttribute', $itemAttribute); $validity = $this->resolver->resolveVariant( $this->variant(new EloquentCollection, new EloquentCollection([$definition])) ); $this->assertTrue($validity->isResolvable); $this->assertTrue($validity->isUnrestricted); } public function test_multiple_values_for_a_single_select_attribute_are_unresolvable(): void { [, $definitions] = $this->attributeDimension( allowMultiSelect: false, options: [ 'Desayuno' => $this->timeWindow('07:00:00', '12:00:00'), 'Cena' => $this->timeWindow('20:00:00', '24:00:00'), ], ); $validity = $this->resolver->resolveVariant( $this->variant(new EloquentCollection, $definitions) ); $this->assertFalse($validity->isResolvable); $this->assertFalse($validity->isValid()); } private function eventDate(string $date): EventDate { $validityTime = new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => "{$date} 00:00:00", 'fixed_expires_at' => "{$date} 23:59:59", ]); $eventDate = new EventDate([ 'date' => $date, 'time_start' => '00:00:00', 'time_end' => '23:59:59', ]); $eventDate->setRelation('validityTime', $validityTime); return $eventDate; } private function timeWindow(string $start, string $end): ValidityTime { return new ValidityTime([ 'type' => ValidityTimeType::TimeWindow, 'start_time' => $start, 'end_time' => $end, ]); } /** * @param array $options * @return array{ItemAttribute, EloquentCollection} */ private function attributeDimension(bool $allowMultiSelect, array $options): array { $attribute = new Attribute([ 'type' => FieldType::Select, ]); $attributeOptions = collect($options)->map( function (?ValidityTime $validityTime, string $value): AttributeOption { $option = new AttributeOption(['value' => $value, 'label' => $value]); $option->setRelation('validityTime', $validityTime); return $option; } ); $attribute->setRelation('options', new EloquentCollection($attributeOptions)); $itemAttribute = new ItemAttribute(['allow_multi_select' => $allowMultiSelect]); $itemAttribute->setRelation('attribute', $attribute); $definitions = $attributeOptions->map(function (AttributeOption $option) use ($itemAttribute): VariantDefinition { $definition = new VariantDefinition(['value' => $option->value]); $definition->setAttribute('item_attribute_id', spl_object_id($itemAttribute)); $definition->setRelation('itemAttribute', $itemAttribute); return $definition; }); return [$itemAttribute, new EloquentCollection($definitions)]; } /** * @param EloquentCollection $eventDates * @param EloquentCollection $definitions */ private function variant(EloquentCollection $eventDates, EloquentCollection $definitions): Variant { $variant = new Variant; $variant->setRelation('eventDates', $eventDates); $variant->setRelation('eventDate', null); $variant->setRelation('definitions', $definitions); return $variant; } }