|
|
@@ -34,12 +34,20 @@ final class StatsControllerTest extends AppTestCase
|
|
|
self::assertSame([], $body['reports_24h_by_hour']);
|
|
|
self::assertSame([], $body['top_reporters_24h']);
|
|
|
self::assertSame([], $body['top_categories_24h']);
|
|
|
- // bans_by_day_7d always emits 8 days (today + the 7 prior, zero-filled).
|
|
|
- self::assertCount(8, $body['bans_by_day_7d']);
|
|
|
- foreach ($body['bans_by_day_7d'] as $row) {
|
|
|
- self::assertArrayHasKey('day', $row);
|
|
|
- self::assertArrayHasKey('count', $row);
|
|
|
- self::assertSame(0, $row['count']);
|
|
|
+ // blocked_ips_by_day_7d always emits 8 days (today + the 7 prior, zero-filled).
|
|
|
+ self::assertArrayHasKey('blocked_ips_by_day_7d', $body);
|
|
|
+ $blocked = $body['blocked_ips_by_day_7d'];
|
|
|
+ self::assertArrayHasKey('days', $blocked);
|
|
|
+ self::assertArrayHasKey('series', $blocked);
|
|
|
+ self::assertCount(8, $blocked['days']);
|
|
|
+ // Seeders create the default categories — every active one should
|
|
|
+ // appear as a flat-zero series even with no reports yet.
|
|
|
+ self::assertNotEmpty($blocked['series']);
|
|
|
+ foreach ($blocked['series'] as $row) {
|
|
|
+ self::assertArrayHasKey('category', $row);
|
|
|
+ self::assertArrayHasKey('counts', $row);
|
|
|
+ self::assertCount(8, $row['counts']);
|
|
|
+ self::assertSame(0, array_sum(array_map('intval', $row['counts'])));
|
|
|
}
|
|
|
self::assertSame('moderate', $body['reference_policy']);
|
|
|
}
|
|
|
@@ -88,12 +96,36 @@ final class StatsControllerTest extends AppTestCase
|
|
|
|
|
|
self::assertSame(1, $body['manual_blocks_count']);
|
|
|
self::assertSame(1, $body['allowlist_count']);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function testBlockedIpsByDayBucketsByCategory(): void
|
|
|
+ {
|
|
|
+ $token = $this->createToken(TokenKind::Admin, role: Role::Viewer);
|
|
|
+ $reporterId = $this->createReporter('rep-cat-bucket');
|
|
|
+
|
|
|
+ // Two distinct IPs in `brute_force`, one in `spam`. The same IP
|
|
|
+ // reported twice in `brute_force` must still count as one (the
|
|
|
+ // sum is COUNT DISTINCT ip_bin per category per day).
|
|
|
+ $this->seedReport('203.0.113.10', 'brute_force', $reporterId);
|
|
|
+ $this->seedReport('203.0.113.10', 'brute_force', $reporterId);
|
|
|
+ $this->seedReport('203.0.113.11', 'brute_force', $reporterId);
|
|
|
+ $this->seedReport('203.0.113.20', 'spam', $reporterId);
|
|
|
|
|
|
- // The manual block we just inserted (created_at = NOW per DB
|
|
|
- // default) should land in today's bucket of bans_by_day_7d.
|
|
|
- self::assertCount(8, $body['bans_by_day_7d']);
|
|
|
- $totalBans = array_sum(array_map(static fn (array $r): int => (int) $r['count'], $body['bans_by_day_7d']));
|
|
|
- self::assertSame(1, $totalBans);
|
|
|
+ $body = $this->decode($this->request('GET', '/api/v1/admin/stats/dashboard', [
|
|
|
+ 'Authorization' => 'Bearer ' . $token,
|
|
|
+ ]));
|
|
|
+
|
|
|
+ $blocked = $body['blocked_ips_by_day_7d'];
|
|
|
+ self::assertCount(8, $blocked['days']);
|
|
|
+
|
|
|
+ $byCategory = [];
|
|
|
+ foreach ($blocked['series'] as $row) {
|
|
|
+ $byCategory[$row['category']] = array_sum(array_map('intval', $row['counts']));
|
|
|
+ }
|
|
|
+ self::assertArrayHasKey('brute_force', $byCategory);
|
|
|
+ self::assertArrayHasKey('spam', $byCategory);
|
|
|
+ self::assertSame(2, $byCategory['brute_force']);
|
|
|
+ self::assertSame(1, $byCategory['spam']);
|
|
|
}
|
|
|
|
|
|
private function seedReport(string $ip, string $categorySlug, int $reporterId): void
|