Skip to content

Commit d1dad98

Browse files
committed
add tests for average interval calculations with various edge cases
1 parent 631941c commit d1dad98

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed

tests/FeedIo/Reader/Result/UpdateStatsTest.php

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,281 @@ public function testSleepyFeed()
6565
}
6666

6767

68+
/**
69+
* Test getAverageInterval with a single interval
70+
* Edge case: count = 1
71+
*/
72+
public function testAverageIntervalWithSingleInterval()
73+
{
74+
$feed = new Feed();
75+
$feed->setLastModified(new \DateTime('-1 day'));
76+
77+
// Two items = 1 interval
78+
$item1 = new Feed\Item();
79+
$item1->setLastModified(new \DateTime('-1 day'));
80+
$feed->add($item1);
81+
82+
$item2 = new Feed\Item();
83+
$item2->setLastModified(new \DateTime('-2 days'));
84+
$feed->add($item2);
85+
86+
$stats = new UpdateStats($feed);
87+
$intervals = $stats->getIntervals();
88+
89+
$this->assertCount(1, $intervals);
90+
$this->assertIsInt($stats->getAverageInterval());
91+
$this->assertGreaterThanOrEqual(0, $stats->getAverageInterval());
92+
}
93+
94+
/**
95+
* Test getAverageInterval with two intervals
96+
* Edge case: count = 2
97+
*/
98+
public function testAverageIntervalWithTwoIntervals()
99+
{
100+
$feed = new Feed();
101+
$feed->setLastModified(new \DateTime('-1 day'));
102+
103+
// Three items = 2 intervals
104+
$item1 = new Feed\Item();
105+
$item1->setLastModified(new \DateTime('-1 day'));
106+
$feed->add($item1);
107+
108+
$item2 = new Feed\Item();
109+
$item2->setLastModified(new \DateTime('-2 days'));
110+
$feed->add($item2);
111+
112+
$item3 = new Feed\Item();
113+
$item3->setLastModified(new \DateTime('-3 days'));
114+
$feed->add($item3);
115+
116+
$stats = new UpdateStats($feed);
117+
$intervals = $stats->getIntervals();
118+
119+
$this->assertCount(2, $intervals);
120+
$this->assertIsInt($stats->getAverageInterval());
121+
$this->assertGreaterThanOrEqual(0, $stats->getAverageInterval());
122+
}
123+
124+
/**
125+
* Test getAverageInterval with three intervals
126+
* Edge case: count = 3
127+
*/
128+
public function testAverageIntervalWithThreeIntervals()
129+
{
130+
$feed = new Feed();
131+
$feed->setLastModified(new \DateTime('-1 day'));
132+
133+
// Four items = 3 intervals
134+
$item1 = new Feed\Item();
135+
$item1->setLastModified(new \DateTime('-1 day'));
136+
$feed->add($item1);
137+
138+
$item2 = new Feed\Item();
139+
$item2->setLastModified(new \DateTime('-2 days'));
140+
$feed->add($item2);
141+
142+
$item3 = new Feed\Item();
143+
$item3->setLastModified(new \DateTime('-3 days'));
144+
$feed->add($item3);
145+
146+
$item4 = new Feed\Item();
147+
$item4->setLastModified(new \DateTime('-4 days'));
148+
$feed->add($item4);
149+
150+
$stats = new UpdateStats($feed);
151+
$intervals = $stats->getIntervals();
152+
153+
$this->assertCount(3, $intervals);
154+
$this->assertIsInt($stats->getAverageInterval());
155+
$this->assertGreaterThanOrEqual(0, $stats->getAverageInterval());
156+
}
157+
158+
/**
159+
* Test getAverageInterval with four intervals
160+
* Edge case: count = 4 (minimum for proper quartile calculation)
161+
*/
162+
public function testAverageIntervalWithFourIntervals()
163+
{
164+
$feed = new Feed();
165+
$feed->setLastModified(new \DateTime('-1 day'));
166+
167+
// Five items = 4 intervals
168+
$item1 = new Feed\Item();
169+
$item1->setLastModified(new \DateTime('-1 day'));
170+
$feed->add($item1);
171+
172+
$item2 = new Feed\Item();
173+
$item2->setLastModified(new \DateTime('-2 days'));
174+
$feed->add($item2);
175+
176+
$item3 = new Feed\Item();
177+
$item3->setLastModified(new \DateTime('-3 days'));
178+
$feed->add($item3);
179+
180+
$item4 = new Feed\Item();
181+
$item4->setLastModified(new \DateTime('-4 days'));
182+
$feed->add($item4);
183+
184+
$item5 = new Feed\Item();
185+
$item5->setLastModified(new \DateTime('-5 days'));
186+
$feed->add($item5);
187+
188+
$stats = new UpdateStats($feed);
189+
$intervals = $stats->getIntervals();
190+
191+
$this->assertCount(4, $intervals);
192+
$this->assertIsInt($stats->getAverageInterval());
193+
$this->assertGreaterThanOrEqual(0, $stats->getAverageInterval());
194+
}
195+
196+
/**
197+
* Test getAverageInterval with empty intervals
198+
* Edge case: count = 0
199+
*/
200+
public function testAverageIntervalWithNoIntervals()
201+
{
202+
$feed = new Feed();
203+
$feed->setLastModified(new \DateTime('-1 day'));
204+
205+
// Single item = 0 intervals
206+
$item = new Feed\Item();
207+
$item->setLastModified(new \DateTime('-1 day'));
208+
$feed->add($item);
209+
210+
$stats = new UpdateStats($feed);
211+
$intervals = $stats->getIntervals();
212+
213+
$this->assertCount(0, $intervals);
214+
$this->assertEquals(0, $stats->getAverageInterval());
215+
}
216+
217+
/**
218+
* Test getAverageInterval with outliers that should be filtered
219+
*/
220+
public function testAverageIntervalWithOutliers()
221+
{
222+
$feed = new Feed();
223+
$feed->setLastModified(new \DateTime('-1 day'));
224+
225+
// Create items with one extreme outlier
226+
$dates = [
227+
'-1 day',
228+
'-2 days',
229+
'-3 days',
230+
'-4 days',
231+
'-5 days',
232+
'-100 days', // Outlier
233+
];
234+
235+
foreach ($dates as $date) {
236+
$item = new Feed\Item();
237+
$item->setLastModified(new \DateTime($date));
238+
$feed->add($item);
239+
}
240+
241+
$stats = new UpdateStats($feed);
242+
$intervals = $stats->getIntervals();
243+
244+
$this->assertCount(5, $intervals);
245+
246+
// The average should be reasonable and not heavily skewed by the outlier
247+
$average = $stats->getAverageInterval();
248+
$this->assertIsInt($average);
249+
$this->assertGreaterThanOrEqual(0, $average);
250+
251+
// Verify the outlier filtering works by checking that average is close to 86400 (1 day)
252+
// and not close to the raw average which would be much higher
253+
$rawAverage = array_sum($intervals) / count($intervals);
254+
$this->assertLessThan($rawAverage, $average);
255+
}
256+
257+
/**
258+
* Test that Q1 and Q3 indices don't go out of bounds
259+
*/
260+
public function testQuartileIndicesWithinBounds()
261+
{
262+
// Test with various array sizes
263+
$testCases = [1, 2, 3, 4, 5, 10, 100];
264+
265+
foreach ($testCases as $itemCount) {
266+
$feed = new Feed();
267+
$feed->setLastModified(new \DateTime('-1 day'));
268+
269+
// Add items
270+
for ($i = 0; $i < $itemCount + 1; $i++) {
271+
$item = new Feed\Item();
272+
$item->setLastModified(new \DateTime("-{$i} days"));
273+
$feed->add($item);
274+
}
275+
276+
$stats = new UpdateStats($feed);
277+
278+
// This should not throw any errors
279+
$average = $stats->getAverageInterval();
280+
$this->assertIsInt($average);
281+
$this->assertGreaterThanOrEqual(0, $average);
282+
}
283+
}
284+
285+
/**
286+
* Test getAverageInterval with uniform intervals
287+
*/
288+
public function testAverageIntervalWithUniformIntervals()
289+
{
290+
$feed = new Feed();
291+
$feed->setLastModified(new \DateTime('-1 hour'));
292+
293+
// Create items with exactly 1 hour intervals
294+
for ($i = 1; $i <= 10; $i++) {
295+
$item = new Feed\Item();
296+
$item->setLastModified(new \DateTime("-{$i} hours"));
297+
$feed->add($item);
298+
}
299+
300+
$stats = new UpdateStats($feed);
301+
$intervals = $stats->getIntervals();
302+
303+
$this->assertCount(9, $intervals);
304+
305+
// All intervals should be 3600 seconds (1 hour)
306+
foreach ($intervals as $interval) {
307+
$this->assertEquals(3600, $interval);
308+
}
309+
310+
// Average should be 3600
311+
$this->assertEquals(3600, $stats->getAverageInterval());
312+
}
313+
314+
/**
315+
* Test that the implementation handles edge cases safely
316+
*/
317+
public function testBothImplementationsAreSafe()
318+
{
319+
for ($itemCount = 1; $itemCount <= 20; $itemCount++) {
320+
$feed = new Feed();
321+
$feed->setLastModified(new \DateTime('-1 day'));
322+
323+
// Add items with varied intervals
324+
for ($i = 0; $i <= $itemCount; $i++) {
325+
$item = new Feed\Item();
326+
$item->setLastModified(new \DateTime("-" . ($i * 2) . " hours"));
327+
$feed->add($item);
328+
}
329+
330+
$stats = new UpdateStats($feed);
331+
332+
// This should never throw an error
333+
try {
334+
$average = $stats->getAverageInterval();
335+
$this->assertIsInt($average);
336+
$this->assertGreaterThanOrEqual(0, $average);
337+
} catch (\Throwable $e) {
338+
$this->fail("getAverageInterval() threw an exception with {$itemCount} intervals: " . $e->getMessage());
339+
}
340+
}
341+
}
342+
68343
private function getDates(): array
69344
{
70345
return [

0 commit comments

Comments
 (0)