Skip to content

Commit 7d1b1de

Browse files
Merge pull request #54697 from nextcloud/fix/unit-test-for-storage-auto-expire-list
2 parents dac8fe4 + fb2716a commit 7d1b1de

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
namespace OCA\Files_Versions\Tests;
9+
10+
use OCA\Files_Versions\Storage;
11+
use Test\TestCase;
12+
13+
class GetAutoExpireListTest extends TestCase {
14+
15+
/**
16+
* Frozen reference time for all tests
17+
*/
18+
private const NOW = 1600000000;
19+
20+
/**
21+
* Helper to call the private retention logic
22+
*
23+
* @param int $now
24+
* @param array $versions
25+
* @return array{array<int,array>, int}
26+
*/
27+
private static function callGetAutoExpireList(int $now, array $versions): array {
28+
$ref = new \ReflectionClass(Storage::class);
29+
$method = $ref->getMethod('getAutoExpireList');
30+
31+
/** @var array{array<int,array>, int} */
32+
return $method->invoke(null, $now, $versions);
33+
}
34+
35+
#[\PHPUnit\Framework\Attributes\DataProvider('provideBucketKeepsLatest')]
36+
public function testBucketKeepsLatest(
37+
int $newerAge,
38+
int $olderAge,
39+
int $newerSize,
40+
int $olderSize,
41+
): void {
42+
// Assert provider contract: newer must be younger than older
43+
$this->assertLessThan(
44+
$olderAge,
45+
$newerAge,
46+
'Invalid test data: newerAge must be smaller than olderAge'
47+
);
48+
49+
$now = time();
50+
51+
$newer = $now - $newerAge;
52+
$older = $now - $olderAge;
53+
54+
$versions = [
55+
$newer => [
56+
'version' => $newer,
57+
'size' => $newerSize,
58+
'path' => 'f',
59+
],
60+
$older => [
61+
'version' => $older,
62+
'size' => $olderSize,
63+
'path' => 'f',
64+
],
65+
];
66+
67+
[$toDelete, $deletedSize] = self::callGetAutoExpireList($now, $versions);
68+
69+
$deletedKeys = array_map('intval', array_keys($toDelete));
70+
71+
$this->assertSame(
72+
[$older],
73+
$deletedKeys
74+
);
75+
76+
$this->assertSame(
77+
$olderSize,
78+
$deletedSize
79+
);
80+
}
81+
82+
public static function provideBucketKeepsLatest(): array {
83+
$DAY = 24 * 60 * 60;
84+
85+
return [
86+
'seconds-range' => [
87+
8, // newer (8s old)
88+
9, // older (9s old)
89+
5,
90+
6,
91+
],
92+
'minutes-range' => [
93+
120, // 2 minutes old
94+
150, // 2.5 minutes old
95+
10,
96+
11,
97+
],
98+
'hours-range' => [
99+
5 * 3600, // 5 hours old
100+
5 * 3600 + 1800, // 5.5 hours old
101+
20,
102+
21,
103+
],
104+
'days-range' => [
105+
2 * $DAY, // 2 days old
106+
2 * $DAY + 6 * 3600, // 2.25 days old
107+
40,
108+
41,
109+
],
110+
'weeks-range' => [
111+
5 * $DAY, // 5 days old
112+
5 * $DAY + 12 * 3600, // 5.5 days old
113+
30,
114+
31,
115+
],
116+
'months-range' => [
117+
35 * $DAY, // 35 days old
118+
37 * $DAY, // 37 days old
119+
42,
120+
43,
121+
],
122+
'beyond-year-range' => [
123+
400 * $DAY, // ~13.3 months old
124+
405 * $DAY, // ~13.5 months old
125+
50,
126+
51,
127+
],
128+
];
129+
}
130+
131+
132+
#[\PHPUnit\Framework\Attributes\DataProvider('provideVersionRetentionRanges')]
133+
public function testRetentionOverTimeEveryTenMinutes(
134+
int $days,
135+
int $expectedMin,
136+
int $expectedMax,
137+
): void {
138+
$now = time();
139+
$versions = [];
140+
141+
// One version every 10 minutes
142+
$interval = 600; // 10 minutes
143+
$total = $days * 24 * 6;
144+
145+
for ($i = 0; $i < $total; $i++) {
146+
$ts = $now - ($i * $interval);
147+
$versions[$ts] = [
148+
'version' => $ts,
149+
'size' => 1,
150+
'path' => 'f',
151+
];
152+
}
153+
154+
[$toDelete, $size] = self::callGetAutoExpireList($now, $versions);
155+
156+
$retained = array_diff(array_keys($versions), array_keys($toDelete));
157+
$retainedCount = count($retained);
158+
159+
$this->assertGreaterThanOrEqual(
160+
$expectedMin,
161+
$retainedCount,
162+
"Too few versions retained for {$days} days"
163+
);
164+
165+
$this->assertLessThanOrEqual(
166+
$expectedMax,
167+
$retainedCount,
168+
"Too many versions retained for {$days} days"
169+
);
170+
}
171+
172+
public static function provideVersionRetentionRanges(): array {
173+
return [
174+
'5 days' => [
175+
5,
176+
28,
177+
33,
178+
],
179+
'30 days' => [
180+
30,
181+
54,
182+
60,
183+
],
184+
'1 year' => [
185+
365,
186+
100,
187+
140,
188+
],
189+
];
190+
}
191+
192+
#[\PHPUnit\Framework\Attributes\DataProvider('provideExactRetentionCounts')]
193+
public function testExactRetentionCounts(
194+
int $days,
195+
int $expectedRetained,
196+
): void {
197+
$now = self::NOW;
198+
$versions = [];
199+
200+
// One version per hour, safely inside bucket slots
201+
for ($i = 0; $i < $days * 24; $i++) {
202+
$ts = $now - ($i * 3600) - 1;
203+
$versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f'];
204+
}
205+
206+
[$toDelete] = self::callGetAutoExpireList($now, $versions);
207+
$retained = array_diff_key($versions, $toDelete);
208+
209+
$this->assertSame(
210+
$expectedRetained,
211+
count($retained),
212+
"Exact retention count mismatch for {$days} days"
213+
);
214+
}
215+
216+
/**
217+
* @return array<string, array{int,int}>
218+
*/
219+
public static function provideExactRetentionCounts(): array {
220+
return [
221+
'five-days' => [
222+
5,
223+
self::expectedHourlyRetention(5),
224+
],
225+
'thirty-days' => [
226+
30,
227+
self::expectedHourlyRetention(30),
228+
],
229+
'one-year' => [
230+
365,
231+
self::expectedHourlyRetention(365),
232+
],
233+
'one-year-plus' => [
234+
500,
235+
self::expectedHourlyRetention(500),
236+
],
237+
];
238+
}
239+
240+
private static function expectedHourlyRetention(int $days): int {
241+
// Hourly for first day
242+
$hourly = min(24, $days * 24);
243+
244+
// Daily from day 1 to day 30
245+
$dailyDays = max(0, min($days, 30) - 1);
246+
$daily = $dailyDays;
247+
248+
// Weekly beyond 30 days
249+
$weeklyDays = max(0, $days - 30);
250+
$weekly = intdiv($weeklyDays, 7) + ($weeklyDays > 0 ? 1 : 0);
251+
252+
return $hourly + $daily + $weekly;
253+
}
254+
}

0 commit comments

Comments
 (0)