From 18c2233baace9eb8b3794038b4946b7b0d03984e Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Wed, 25 Feb 2026 20:01:29 +0000 Subject: [PATCH] add tests for remaining Criteria classes --- .../Filter/Criteria/DelayCriteriaTest.php | 251 ++++++++++++++++++ .../Filter/Criteria/FilterCriteriaTest.php | 169 ++++++++++++ .../Filter/Criteria/HopsCriteriaTest.php | 232 ++++++++++++++++ .../Filter/Criteria/PresenterCriteriaTest.php | 189 +++++++++++++ .../Filter/Criteria/PriorityCriteriaTest.php | 248 +++++++++++++++++ .../Filter/Criteria/RangeExtractorTest.php | 226 ++++++++++++++++ 6 files changed, 1315 insertions(+) create mode 100644 tests/Prime/Storage/Filter/Criteria/DelayCriteriaTest.php create mode 100644 tests/Prime/Storage/Filter/Criteria/FilterCriteriaTest.php create mode 100644 tests/Prime/Storage/Filter/Criteria/HopsCriteriaTest.php create mode 100644 tests/Prime/Storage/Filter/Criteria/PresenterCriteriaTest.php create mode 100644 tests/Prime/Storage/Filter/Criteria/PriorityCriteriaTest.php create mode 100644 tests/Prime/Storage/Filter/Criteria/RangeExtractorTest.php diff --git a/tests/Prime/Storage/Filter/Criteria/DelayCriteriaTest.php b/tests/Prime/Storage/Filter/Criteria/DelayCriteriaTest.php new file mode 100644 index 00000000..088ae109 --- /dev/null +++ b/tests/Prime/Storage/Filter/Criteria/DelayCriteriaTest.php @@ -0,0 +1,251 @@ +assertInstanceOf(DelayCriteria::class, $criteria); + } + + public function testConstructorWithArray(): void + { + $criteria = new DelayCriteria(['min' => 1, 'max' => 10]); + + $this->assertInstanceOf(DelayCriteria::class, $criteria); + } + + public function testConstructorWithInvalidTypeThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "delay"'); + + new DelayCriteria('invalid'); + } + + public function testConstructorWithNullThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "delay"'); + + new DelayCriteria(null); + } + + public function testConstructorWithInvalidMinThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "delay"'); + + new DelayCriteria(['min' => 'invalid', 'max' => 10]); + } + + public function testConstructorWithInvalidMaxThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "max" in criteria "delay"'); + + new DelayCriteria(['min' => 1, 'max' => 'invalid']); + } + + public function testApplyWithMinimumDelay(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(3)]), + new Envelope(new Notification(), [new DelayStamp(7)]), + new Envelope(new Notification(), [new DelayStamp(10)]), + ]; + + $criteria = new DelayCriteria(['min' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithMaximumDelay(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(3)]), + new Envelope(new Notification(), [new DelayStamp(7)]), + new Envelope(new Notification(), [new DelayStamp(10)]), + ]; + + $criteria = new DelayCriteria(['min' => 0, 'max' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithRange(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(3)]), + new Envelope(new Notification(), [new DelayStamp(5)]), + new Envelope(new Notification(), [new DelayStamp(7)]), + new Envelope(new Notification(), [new DelayStamp(10)]), + ]; + + $criteria = new DelayCriteria(['min' => 4, 'max' => 8]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithSingleValueAsMinimum(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(3)]), + new Envelope(new Notification(), [new DelayStamp(5)]), + new Envelope(new Notification(), [new DelayStamp(10)]), + ]; + + $criteria = new DelayCriteria(5); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyFiltersEnvelopesWithoutDelayStamp(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(5)]), + new Envelope(new Notification()), + new Envelope(new Notification(), [new DelayStamp(10)]), + ]; + + $criteria = new DelayCriteria(['min' => 1]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testMatchReturnsTrueForMatchingDelay(): void + { + $envelope = new Envelope(new Notification(), [new DelayStamp(5)]); + + $criteria = new DelayCriteria(['min' => 3, 'max' => 7]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testMatchReturnsFalseForNonMatchingDelay(): void + { + $envelope = new Envelope(new Notification(), [new DelayStamp(10)]); + + $criteria = new DelayCriteria(['min' => 3, 'max' => 7]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchReturnsFalseForEnvelopeWithoutDelayStamp(): void + { + $envelope = new Envelope(new Notification()); + + $criteria = new DelayCriteria(['min' => 1]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithOnlyMinimum(): void + { + $envelope = new Envelope(new Notification(), [new DelayStamp(10)]); + + $criteria = new DelayCriteria(['min' => 5]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testMatchWithDelayBelowMinimum(): void + { + $envelope = new Envelope(new Notification(), [new DelayStamp(3)]); + + $criteria = new DelayCriteria(['min' => 5]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithDelayAboveMaximum(): void + { + $envelope = new Envelope(new Notification(), [new DelayStamp(15)]); + + $criteria = new DelayCriteria(['min' => 1, 'max' => 10]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithExactBoundary(): void + { + $envelope = new Envelope(new Notification(), [new DelayStamp(5)]); + + $criteria = new DelayCriteria(['min' => 5, 'max' => 5]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testApplyWithEmptyArray(): void + { + $criteria = new DelayCriteria(['min' => 1]); + + $result = $criteria->apply([]); + + $this->assertSame([], $result); + } + + public function testApplyWithOnlyMinInArray(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(3)]), + new Envelope(new Notification(), [new DelayStamp(7)]), + ]; + + $criteria = new DelayCriteria(['min' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithOnlyMaxInArray(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(3)]), + new Envelope(new Notification(), [new DelayStamp(7)]), + ]; + + $criteria = new DelayCriteria(['max' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithZeroMinAndNullMax(): void + { + $envelopes = [ + new Envelope(new Notification(), [new DelayStamp(0)]), + new Envelope(new Notification(), [new DelayStamp(5)]), + new Envelope(new Notification(), [new DelayStamp(10)]), + ]; + + $criteria = new DelayCriteria(['min' => 0, 'max' => null]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(3, $result); + } +} diff --git a/tests/Prime/Storage/Filter/Criteria/FilterCriteriaTest.php b/tests/Prime/Storage/Filter/Criteria/FilterCriteriaTest.php new file mode 100644 index 00000000..e98a0910 --- /dev/null +++ b/tests/Prime/Storage/Filter/Criteria/FilterCriteriaTest.php @@ -0,0 +1,169 @@ + $envelopes); + + $this->assertInstanceOf(FilterCriteria::class, $criteria); + } + + public function testConstructorWithArrayOfClosures(): void + { + $criteria = new FilterCriteria([ + fn ($envelopes) => $envelopes, + fn ($envelopes) => array_filter($envelopes), + ]); + + $this->assertInstanceOf(FilterCriteria::class, $criteria); + } + + public function testConstructorWithInvalidTypeThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "filter"'); + + new FilterCriteria('not a closure'); + } + + public function testConstructorWithNullThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "filter"'); + + new FilterCriteria(null); + } + + public function testConstructorWithArrayContainingNonClosureThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Each element must be a closure'); + + new FilterCriteria([fn ($e) => $e, 'not a closure']); + } + + public function testApplyWithSingleClosure(): void + { + $envelopes = [ + new Envelope(new Notification()), + new Envelope(new Notification()), + new Envelope(new Notification()), + ]; + + $criteria = new FilterCriteria(fn ($e) => array_slice($e, 0, 2)); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithMultipleClosures(): void + { + $notification1 = new Notification(); + $notification1->setType('success'); + $notification2 = new Notification(); + $notification2->setType('error'); + $notification3 = new Notification(); + $notification3->setType('success'); + + $envelopes = [ + new Envelope($notification1), + new Envelope($notification2), + new Envelope($notification3), + ]; + + $criteria = new FilterCriteria([ + fn ($e) => array_filter($e, fn (Envelope $envelope) => 'success' === $envelope->getType()), + fn ($e) => array_slice($e, 0, 1), + ]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + $this->assertSame('success', $result[0]->getType()); + } + + public function testApplyWithEmptyArray(): void + { + $criteria = new FilterCriteria(fn ($e) => $e); + + $result = $criteria->apply([]); + + $this->assertSame([], $result); + } + + public function testApplyThatReturnsModifiedEnvelopes(): void + { + $envelopes = [ + new Envelope(new Notification()), + new Envelope(new Notification()), + ]; + + $criteria = new FilterCriteria(fn ($e) => []); + + $result = $criteria->apply($envelopes); + + $this->assertSame([], $result); + } + + public function testClosureReceivesCorrectArguments(): void + { + $received = null; + $envelopes = [new Envelope(new Notification())]; + + $criteria = new FilterCriteria(function ($e) use (&$received) { + $received = $e; + + return $e; + }); + + $criteria->apply($envelopes); + + $this->assertSame($envelopes, $received); + } + + public function testChainedFiltersAreAppliedInOrder(): void + { + $notification1 = new Notification(); + $notification1->setMessage('first'); + $notification2 = new Notification(); + $notification2->setMessage('second'); + $notification3 = new Notification(); + $notification3->setMessage('third'); + + $envelopes = [ + new Envelope($notification1), + new Envelope($notification2), + new Envelope($notification3), + ]; + + $order = []; + $criteria = new FilterCriteria([ + function ($e) use (&$order) { + $order[] = 'first'; + + return $e; + }, + function ($e) use (&$order) { + $order[] = 'second'; + + return array_slice($e, 0, 2); + }, + ]); + + $result = $criteria->apply($envelopes); + + $this->assertSame(['first', 'second'], $order); + $this->assertCount(2, $result); + } +} diff --git a/tests/Prime/Storage/Filter/Criteria/HopsCriteriaTest.php b/tests/Prime/Storage/Filter/Criteria/HopsCriteriaTest.php new file mode 100644 index 00000000..84c1a691 --- /dev/null +++ b/tests/Prime/Storage/Filter/Criteria/HopsCriteriaTest.php @@ -0,0 +1,232 @@ +assertInstanceOf(HopsCriteria::class, $criteria); + } + + public function testConstructorWithArray(): void + { + $criteria = new HopsCriteria(['min' => 1, 'max' => 3]); + + $this->assertInstanceOf(HopsCriteria::class, $criteria); + } + + public function testConstructorWithInvalidTypeThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "hops"'); + + new HopsCriteria('invalid'); + } + + public function testConstructorWithInvalidMinThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "hops"'); + + new HopsCriteria(['min' => 'invalid']); + } + + public function testConstructorWithInvalidMaxThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "max" in criteria "hops"'); + + new HopsCriteria(['max' => 'invalid']); + } + + public function testApplyWithMinimumHops(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification(), [new HopsStamp(2)]), + new Envelope(new Notification(), [new HopsStamp(3)]), + ]; + + $criteria = new HopsCriteria(['min' => 2]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithMaximumHops(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification(), [new HopsStamp(2)]), + new Envelope(new Notification(), [new HopsStamp(5)]), + ]; + + $criteria = new HopsCriteria(['min' => 0, 'max' => 3]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithRange(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification(), [new HopsStamp(2)]), + new Envelope(new Notification(), [new HopsStamp(3)]), + new Envelope(new Notification(), [new HopsStamp(5)]), + ]; + + $criteria = new HopsCriteria(['min' => 2, 'max' => 3]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithSingleValueAsMinimum(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification(), [new HopsStamp(3)]), + ]; + + $criteria = new HopsCriteria(2); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyFiltersEnvelopesWithoutHopsStamp(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification()), + new Envelope(new Notification(), [new HopsStamp(2)]), + ]; + + $criteria = new HopsCriteria(['min' => 1]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testMatchReturnsTrueForMatchingHops(): void + { + $envelope = new Envelope(new Notification(), [new HopsStamp(2)]); + + $criteria = new HopsCriteria(['min' => 1, 'max' => 3]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testMatchReturnsFalseForNonMatchingHops(): void + { + $envelope = new Envelope(new Notification(), [new HopsStamp(10)]); + + $criteria = new HopsCriteria(['min' => 1, 'max' => 3]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchReturnsFalseForEnvelopeWithoutHopsStamp(): void + { + $envelope = new Envelope(new Notification()); + + $criteria = new HopsCriteria(['min' => 1]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithHopsBelowMinimum(): void + { + $envelope = new Envelope(new Notification(), [new HopsStamp(1)]); + + $criteria = new HopsCriteria(['min' => 3]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithHopsAboveMaximum(): void + { + $envelope = new Envelope(new Notification(), [new HopsStamp(10)]); + + $criteria = new HopsCriteria(['min' => 1, 'max' => 5]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithExactBoundary(): void + { + $envelope = new Envelope(new Notification(), [new HopsStamp(5)]); + + $criteria = new HopsCriteria(['min' => 5, 'max' => 5]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testApplyWithEmptyArray(): void + { + $criteria = new HopsCriteria(['min' => 1]); + + $result = $criteria->apply([]); + + $this->assertSame([], $result); + } + + public function testApplyWithOnlyMinInArray(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification(), [new HopsStamp(5)]), + ]; + + $criteria = new HopsCriteria(['min' => 3]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithOnlyMaxInArray(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(1)]), + new Envelope(new Notification(), [new HopsStamp(5)]), + ]; + + $criteria = new HopsCriteria(['max' => 3]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithZeroHops(): void + { + $envelopes = [ + new Envelope(new Notification(), [new HopsStamp(0)]), + new Envelope(new Notification(), [new HopsStamp(1)]), + ]; + + $criteria = new HopsCriteria(['min' => 0, 'max' => 0]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } +} diff --git a/tests/Prime/Storage/Filter/Criteria/PresenterCriteriaTest.php b/tests/Prime/Storage/Filter/Criteria/PresenterCriteriaTest.php new file mode 100644 index 00000000..5a2dfb1c --- /dev/null +++ b/tests/Prime/Storage/Filter/Criteria/PresenterCriteriaTest.php @@ -0,0 +1,189 @@ +assertInstanceOf(PresenterCriteria::class, $criteria); + } + + public function testConstructorWithInvalidTypeThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid type for criteria 'presenter'"); + + new PresenterCriteria(123); + } + + public function testConstructorWithNullThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid type for criteria 'presenter'"); + + new PresenterCriteria(null); + } + + public function testConstructorWithArrayThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid type for criteria 'presenter'"); + + new PresenterCriteria(['html']); + } + + public function testApplyFiltersByPresenterPattern(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + new Envelope(new Notification(), [new PresenterStamp('/json/')]), + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + ]; + + $criteria = new PresenterCriteria('html'); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithExactMatch(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/^html$/')]), + new Envelope(new Notification(), [new PresenterStamp('/^json$/')]), + ]; + + $criteria = new PresenterCriteria('html'); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithWildcardPattern(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/.*/')]), + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + ]; + + $criteria = new PresenterCriteria('anything'); + + $result = $criteria->apply($envelopes); + + // Only the first envelope matches because /.*/ matches anything + // The second requires 'html' in the presenter name + $this->assertCount(1, $result); + } + + public function testApplyFiltersEnvelopesWithoutPresenterStamp(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + new Envelope(new Notification()), + new Envelope(new Notification(), [new PresenterStamp('/json/')]), + ]; + + $criteria = new PresenterCriteria('html'); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithEmptyArray(): void + { + $criteria = new PresenterCriteria('html'); + + $result = $criteria->apply([]); + + $this->assertSame([], $result); + } + + public function testApplyWithCaseSensitivePattern(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/HTML/i')]), + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + ]; + + $criteria = new PresenterCriteria('html'); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithComplexPattern(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/^(html|json)$/i')]), + new Envelope(new Notification(), [new PresenterStamp('/^html$/')]), + new Envelope(new Notification(), [new PresenterStamp('/^json$/')]), + ]; + + $criteria = new PresenterCriteria('json'); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithNoMatch(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + new Envelope(new Notification(), [new PresenterStamp('/json/')]), + ]; + + $criteria = new PresenterCriteria('html'); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + $stamp = $result[0]->get(PresenterStamp::class); + $this->assertSame('/html/', $stamp->getPattern()); + } + + public function testApplyWithInvalidPatternThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The provided regex pattern'); + + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/invalid[/')]), + ]; + + $criteria = new PresenterCriteria('test'); + + $criteria->apply($envelopes); + } + + public function testApplyWithEmptyPresenter(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PresenterStamp('/html/')]), + new Envelope(new Notification()), + ]; + + $criteria = new PresenterCriteria(''); + + $result = $criteria->apply($envelopes); + + // Envelope without stamp uses default pattern /.*/ which matches empty string + // Envelope with /html/ doesn't match empty string + $this->assertCount(1, $result); + } +} diff --git a/tests/Prime/Storage/Filter/Criteria/PriorityCriteriaTest.php b/tests/Prime/Storage/Filter/Criteria/PriorityCriteriaTest.php new file mode 100644 index 00000000..ebae681c --- /dev/null +++ b/tests/Prime/Storage/Filter/Criteria/PriorityCriteriaTest.php @@ -0,0 +1,248 @@ +assertInstanceOf(PriorityCriteria::class, $criteria); + } + + public function testConstructorWithArray(): void + { + $criteria = new PriorityCriteria(['min' => 1, 'max' => 10]); + + $this->assertInstanceOf(PriorityCriteria::class, $criteria); + } + + public function testConstructorWithInvalidTypeThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "priority"'); + + new PriorityCriteria('invalid'); + } + + public function testConstructorWithInvalidMinThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "priority"'); + + new PriorityCriteria(['min' => 'invalid']); + } + + public function testConstructorWithInvalidMaxThrowsException(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "max" in criteria "priority"'); + + new PriorityCriteria(['max' => 'invalid']); + } + + public function testApplyWithMinimumPriority(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(3)]), + new Envelope(new Notification(), [new PriorityStamp(7)]), + new Envelope(new Notification(), [new PriorityStamp(10)]), + ]; + + $criteria = new PriorityCriteria(['min' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithMaximumPriority(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(3)]), + new Envelope(new Notification(), [new PriorityStamp(7)]), + new Envelope(new Notification(), [new PriorityStamp(10)]), + ]; + + $criteria = new PriorityCriteria(['min' => 0, 'max' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithRange(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(1)]), + new Envelope(new Notification(), [new PriorityStamp(5)]), + new Envelope(new Notification(), [new PriorityStamp(7)]), + new Envelope(new Notification(), [new PriorityStamp(10)]), + ]; + + $criteria = new PriorityCriteria(['min' => 3, 'max' => 8]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithSingleValueAsMinimum(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(3)]), + new Envelope(new Notification(), [new PriorityStamp(5)]), + new Envelope(new Notification(), [new PriorityStamp(10)]), + ]; + + $criteria = new PriorityCriteria(5); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyFiltersEnvelopesWithoutPriorityStamp(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(5)]), + new Envelope(new Notification()), + new Envelope(new Notification(), [new PriorityStamp(10)]), + ]; + + $criteria = new PriorityCriteria(['min' => 1]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testMatchReturnsTrueForMatchingPriority(): void + { + $envelope = new Envelope(new Notification(), [new PriorityStamp(5)]); + + $criteria = new PriorityCriteria(['min' => 3, 'max' => 7]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testMatchReturnsFalseForNonMatchingPriority(): void + { + $envelope = new Envelope(new Notification(), [new PriorityStamp(10)]); + + $criteria = new PriorityCriteria(['min' => 3, 'max' => 7]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchReturnsFalseForEnvelopeWithoutPriorityStamp(): void + { + $envelope = new Envelope(new Notification()); + + $criteria = new PriorityCriteria(['min' => 1]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithPriorityBelowMinimum(): void + { + $envelope = new Envelope(new Notification(), [new PriorityStamp(3)]); + + $criteria = new PriorityCriteria(['min' => 5]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithPriorityAboveMaximum(): void + { + $envelope = new Envelope(new Notification(), [new PriorityStamp(15)]); + + $criteria = new PriorityCriteria(['min' => 1, 'max' => 10]); + + $this->assertFalse($criteria->match($envelope)); + } + + public function testMatchWithExactBoundary(): void + { + $envelope = new Envelope(new Notification(), [new PriorityStamp(5)]); + + $criteria = new PriorityCriteria(['min' => 5, 'max' => 5]); + + $this->assertTrue($criteria->match($envelope)); + } + + public function testApplyWithEmptyArray(): void + { + $criteria = new PriorityCriteria(['min' => 1]); + + $result = $criteria->apply([]); + + $this->assertSame([], $result); + } + + public function testApplyWithOnlyMinInArray(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(3)]), + new Envelope(new Notification(), [new PriorityStamp(7)]), + ]; + + $criteria = new PriorityCriteria(['min' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithOnlyMaxInArray(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(3)]), + new Envelope(new Notification(), [new PriorityStamp(7)]), + ]; + + $criteria = new PriorityCriteria(['max' => 5]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } + + public function testApplyWithNegativePriority(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(-5)]), + new Envelope(new Notification(), [new PriorityStamp(0)]), + new Envelope(new Notification(), [new PriorityStamp(5)]), + ]; + + $criteria = new PriorityCriteria(['min' => -10, 'max' => 0]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(2, $result); + } + + public function testApplyWithZeroPriority(): void + { + $envelopes = [ + new Envelope(new Notification(), [new PriorityStamp(0)]), + new Envelope(new Notification(), [new PriorityStamp(1)]), + ]; + + $criteria = new PriorityCriteria(['min' => 0, 'max' => 0]); + + $result = $criteria->apply($envelopes); + + $this->assertCount(1, $result); + } +} diff --git a/tests/Prime/Storage/Filter/Criteria/RangeExtractorTest.php b/tests/Prime/Storage/Filter/Criteria/RangeExtractorTest.php new file mode 100644 index 00000000..6e1e8e10 --- /dev/null +++ b/tests/Prime/Storage/Filter/Criteria/RangeExtractorTest.php @@ -0,0 +1,226 @@ +traitInstance = new class() { + use RangeExtractor; + + public function testExtractRange(string $name, mixed $criteria): array + { + return $this->extractRange($name, $criteria); + } + }; + } + + public function testExtractRangeWithInteger(): void + { + $result = $this->traitInstance->testExtractRange('test', 5); + + $this->assertSame(['min' => 5, 'max' => null], $result); + } + + public function testExtractRangeWithZeroInteger(): void + { + $result = $this->traitInstance->testExtractRange('test', 0); + + $this->assertSame(['min' => 0, 'max' => null], $result); + } + + public function testExtractRangeWithNegativeInteger(): void + { + $result = $this->traitInstance->testExtractRange('test', -5); + + $this->assertSame(['min' => -5, 'max' => null], $result); + } + + public function testExtractRangeWithArrayContainingMin(): void + { + $result = $this->traitInstance->testExtractRange('test', ['min' => 3]); + + $this->assertSame(['min' => 3, 'max' => null], $result); + } + + public function testExtractRangeWithArrayContainingMax(): void + { + $result = $this->traitInstance->testExtractRange('test', ['max' => 10]); + + $this->assertSame(['min' => null, 'max' => 10], $result); + } + + public function testExtractRangeWithArrayContainingBoth(): void + { + $result = $this->traitInstance->testExtractRange('test', ['min' => 3, 'max' => 10]); + + $this->assertSame(['min' => 3, 'max' => 10], $result); + } + + public function testExtractRangeWithEmptyArray(): void + { + $result = $this->traitInstance->testExtractRange('test', []); + + $this->assertSame(['min' => null, 'max' => null], $result); + } + + public function testExtractRangeWithExtraKeys(): void + { + $result = $this->traitInstance->testExtractRange('test', ['min' => 1, 'max' => 5, 'extra' => 'ignored']); + + $this->assertSame(['min' => 1, 'max' => 5], $result); + } + + public function testExtractRangeWithNullMin(): void + { + $result = $this->traitInstance->testExtractRange('test', ['min' => null, 'max' => 5]); + + $this->assertSame(['min' => null, 'max' => 5], $result); + } + + public function testExtractRangeWithNullMax(): void + { + $result = $this->traitInstance->testExtractRange('test', ['min' => 5, 'max' => null]); + + $this->assertSame(['min' => 5, 'max' => null], $result); + } + + public function testExtractRangeWithInvalidTypeString(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "test"'); + $this->expectExceptionMessage('Expected int or array, got "string"'); + + $this->traitInstance->testExtractRange('test', 'invalid'); + } + + public function testExtractRangeWithInvalidTypeFloat(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "test"'); + $this->expectExceptionMessage('Expected int or array, got "float"'); + + $this->traitInstance->testExtractRange('test', 3.14); + } + + public function testExtractRangeWithInvalidTypeNull(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "test"'); + $this->expectExceptionMessage('Expected int or array, got "null"'); + + $this->traitInstance->testExtractRange('test', null); + } + + public function testExtractRangeWithInvalidTypeBool(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "test"'); + $this->expectExceptionMessage('Expected int or array, got "bool"'); + + $this->traitInstance->testExtractRange('test', true); + } + + public function testExtractRangeWithInvalidTypeObject(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "test"'); + $this->expectExceptionMessage('Expected int or array, got "stdClass"'); + + $this->traitInstance->testExtractRange('test', new \stdClass()); + } + + public function testExtractRangeWithInvalidMinTypeString(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "test"'); + $this->expectExceptionMessage('Expected int, got "string"'); + + $this->traitInstance->testExtractRange('test', ['min' => 'invalid']); + } + + public function testExtractRangeWithInvalidMinTypeFloat(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "test"'); + $this->expectExceptionMessage('Expected int, got "float"'); + + $this->traitInstance->testExtractRange('test', ['min' => 3.14]); + } + + public function testExtractRangeWithInvalidMinTypeArray(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "test"'); + $this->expectExceptionMessage('Expected int, got "array"'); + + $this->traitInstance->testExtractRange('test', ['min' => [1, 2]]); + } + + public function testExtractRangeWithInvalidMinTypeNullIsAllowed(): void + { + $result = $this->traitInstance->testExtractRange('test', ['min' => null]); + + $this->assertNull($result['min']); + } + + public function testExtractRangeWithInvalidMaxTypeString(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "max" in criteria "test"'); + $this->expectExceptionMessage('Expected int, got "string"'); + + $this->traitInstance->testExtractRange('test', ['max' => 'invalid']); + } + + public function testExtractRangeWithInvalidMaxTypeFloat(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "max" in criteria "test"'); + $this->expectExceptionMessage('Expected int, got "float"'); + + $this->traitInstance->testExtractRange('test', ['max' => 3.14]); + } + + public function testExtractRangeWithInvalidMaxTypeArray(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "max" in criteria "test"'); + $this->expectExceptionMessage('Expected int, got "array"'); + + $this->traitInstance->testExtractRange('test', ['max' => [1, 2]]); + } + + public function testExtractRangeWithInvalidMaxTypeNullIsAllowed(): void + { + $result = $this->traitInstance->testExtractRange('test', ['max' => null]); + + $this->assertNull($result['max']); + } + + public function testExtractRangeUsesNameInErrorMessage(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for criteria "custom_criteria_name"'); + + $this->traitInstance->testExtractRange('custom_criteria_name', 'invalid'); + } + + public function testExtractRangeWithMinInErrorMessage(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type for "min" in criteria "priority"'); + + $this->traitInstance->testExtractRange('priority', ['min' => 'bad']); + } +}