You've already forked wp-prometheus
feat: Add comprehensive PHPUnit test suite and CI/CD test gating (v0.5.0)
189 tests across 8 test classes covering all core plugin classes: CustomMetricBuilder, StorageFactory, Authentication, DashboardProvider, RuntimeCollector, Installer, Collector, and MetricsEndpoint. Added test job to Gitea release workflow that gates build-release. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
234
tests/Unit/Metrics/RuntimeCollectorTest.php
Normal file
234
tests/Unit/Metrics/RuntimeCollectorTest.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magdev\WpPrometheus\Tests\Unit\Metrics;
|
||||
|
||||
use Magdev\WpPrometheus\Metrics\RuntimeCollector;
|
||||
use Magdev\WpPrometheus\Tests\Helpers\GlobalFunctionState;
|
||||
use Magdev\WpPrometheus\Tests\Unit\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
|
||||
#[CoversClass(RuntimeCollector::class)]
|
||||
class RuntimeCollectorTest extends TestCase
|
||||
{
|
||||
private RuntimeCollector $collector;
|
||||
private array $originalServer = [];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->originalServer = $_SERVER;
|
||||
$this->collector = $this->createInstance();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$_SERVER = $this->originalServer;
|
||||
$this->resetSingleton();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
// ── Singleton ────────────────────────────────────────────────────
|
||||
|
||||
#[Test]
|
||||
public function get_instance_returns_singleton(): void
|
||||
{
|
||||
$instance1 = RuntimeCollector::get_instance();
|
||||
$instance2 = RuntimeCollector::get_instance();
|
||||
|
||||
$this->assertSame($instance1, $instance2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function get_instance_returns_runtime_collector(): void
|
||||
{
|
||||
$instance = RuntimeCollector::get_instance();
|
||||
$this->assertInstanceOf(RuntimeCollector::class, $instance);
|
||||
}
|
||||
|
||||
// ── get_stored_metrics() ─────────────────────────────────────────
|
||||
|
||||
#[Test]
|
||||
public function get_stored_metrics_returns_default_when_empty(): void
|
||||
{
|
||||
$metrics = $this->collector->get_stored_metrics();
|
||||
$this->assertSame([], $metrics);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function get_stored_metrics_returns_stored_data(): void
|
||||
{
|
||||
$stored = [
|
||||
'counters' => ['key' => ['name' => 'test', 'value' => 5]],
|
||||
'histograms' => [],
|
||||
'last_reset' => 1000000,
|
||||
];
|
||||
GlobalFunctionState::$options['wp_prometheus_runtime_metrics'] = $stored;
|
||||
|
||||
$metrics = $this->collector->get_stored_metrics();
|
||||
$this->assertSame($stored, $metrics);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function get_stored_metrics_returns_default_for_non_array(): void
|
||||
{
|
||||
GlobalFunctionState::$options['wp_prometheus_runtime_metrics'] = 'invalid';
|
||||
|
||||
$metrics = $this->collector->get_stored_metrics();
|
||||
$this->assertArrayHasKey('counters', $metrics);
|
||||
$this->assertArrayHasKey('histograms', $metrics);
|
||||
$this->assertArrayHasKey('last_reset', $metrics);
|
||||
}
|
||||
|
||||
// ── reset_metrics() ──────────────────────────────────────────────
|
||||
|
||||
#[Test]
|
||||
public function reset_metrics_deletes_option(): void
|
||||
{
|
||||
GlobalFunctionState::$options['wp_prometheus_runtime_metrics'] = ['data'];
|
||||
|
||||
RuntimeCollector::reset_metrics();
|
||||
|
||||
$this->assertSame(1, GlobalFunctionState::getCallCount('delete_option'));
|
||||
$this->assertArrayNotHasKey(
|
||||
'wp_prometheus_runtime_metrics',
|
||||
GlobalFunctionState::$options
|
||||
);
|
||||
}
|
||||
|
||||
// ── get_duration_buckets() ───────────────────────────────────────
|
||||
|
||||
#[Test]
|
||||
public function get_duration_buckets_returns_expected_values(): void
|
||||
{
|
||||
$buckets = RuntimeCollector::get_duration_buckets();
|
||||
|
||||
$this->assertIsArray($buckets);
|
||||
$this->assertCount(11, $buckets);
|
||||
$this->assertSame(0.005, $buckets[0]);
|
||||
$this->assertEquals(10, end($buckets));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function duration_buckets_are_in_ascending_order(): void
|
||||
{
|
||||
$buckets = RuntimeCollector::get_duration_buckets();
|
||||
$sorted = $buckets;
|
||||
sort($sorted);
|
||||
|
||||
$this->assertSame($sorted, $buckets);
|
||||
}
|
||||
|
||||
// ── get_normalized_endpoint() (private, via reflection) ──────────
|
||||
|
||||
#[Test]
|
||||
public function normalized_endpoint_returns_admin_when_is_admin(): void
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = '/wp-admin/options-general.php';
|
||||
GlobalFunctionState::$options['__is_admin'] = true;
|
||||
|
||||
$result = $this->callPrivateMethod('get_normalized_endpoint');
|
||||
$this->assertSame('admin', $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function normalized_endpoint_returns_ajax_when_doing_ajax(): void
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = '/wp-admin/admin-ajax.php';
|
||||
GlobalFunctionState::$options['__wp_doing_ajax'] = true;
|
||||
|
||||
$result = $this->callPrivateMethod('get_normalized_endpoint');
|
||||
$this->assertSame('ajax', $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function normalized_endpoint_returns_cron_when_doing_cron(): void
|
||||
{
|
||||
// Use a generic URI (not /wp-cron.php) to ensure function is checked, not URL pattern.
|
||||
$_SERVER['REQUEST_URI'] = '/some-page';
|
||||
GlobalFunctionState::$options['__wp_doing_cron'] = true;
|
||||
|
||||
$result = $this->callPrivateMethod('get_normalized_endpoint');
|
||||
$this->assertSame('cron', $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('urlEndpointProvider')]
|
||||
public function normalized_endpoint_from_url_pattern(string $uri, string $expected): void
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = $uri;
|
||||
|
||||
$result = $this->callPrivateMethod('get_normalized_endpoint');
|
||||
$this->assertSame($expected, $result);
|
||||
}
|
||||
|
||||
public static function urlEndpointProvider(): array
|
||||
{
|
||||
return [
|
||||
'rest api' => ['/wp-json/wp/v2/posts', 'rest-api'],
|
||||
'login page' => ['/wp-login.php', 'login'],
|
||||
'login with query' => ['/wp-login.php?action=login', 'login'],
|
||||
'wp-cron' => ['/wp-cron.php', 'cron'],
|
||||
'feed root' => ['/feed/', 'feed'],
|
||||
'feed trailing' => ['/category/news/feed', 'feed'],
|
||||
'feed with slash' => ['/feed', 'feed'],
|
||||
'homepage' => ['/', 'frontend'],
|
||||
'page' => ['/about-us', 'frontend'],
|
||||
'post' => ['/2024/01/hello-world', 'frontend'],
|
||||
];
|
||||
}
|
||||
|
||||
// ── is_metrics_request() (private, via reflection) ───────────────
|
||||
|
||||
#[Test]
|
||||
public function is_metrics_request_true_for_metrics_uri(): void
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = '/metrics';
|
||||
$this->assertTrue($this->callPrivateMethod('is_metrics_request'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function is_metrics_request_true_with_trailing_slash(): void
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = '/metrics/';
|
||||
$this->assertTrue($this->callPrivateMethod('is_metrics_request'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function is_metrics_request_false_for_other_uri(): void
|
||||
{
|
||||
$_SERVER['REQUEST_URI'] = '/some-page';
|
||||
$this->assertFalse($this->callPrivateMethod('is_metrics_request'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function is_metrics_request_false_when_no_uri(): void
|
||||
{
|
||||
unset($_SERVER['REQUEST_URI']);
|
||||
$this->assertFalse($this->callPrivateMethod('is_metrics_request'));
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────
|
||||
|
||||
private function createInstance(): RuntimeCollector
|
||||
{
|
||||
$reflection = new \ReflectionClass(RuntimeCollector::class);
|
||||
return $reflection->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
private function resetSingleton(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(RuntimeCollector::class);
|
||||
$property = $reflection->getProperty('instance');
|
||||
$property->setValue(null, null);
|
||||
}
|
||||
|
||||
private function callPrivateMethod(string $method, array $args = []): mixed
|
||||
{
|
||||
$reflection = new \ReflectionMethod($this->collector, $method);
|
||||
return $reflection->invoke($this->collector, ...$args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user