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:
450
tests/bootstrap.php
Normal file
450
tests/bootstrap.php
Normal file
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
/**
|
||||
* PHPUnit bootstrap file for WP Prometheus tests.
|
||||
*
|
||||
* Defines WordPress constants and global function stubs required
|
||||
* for loading plugin source files without a WordPress environment.
|
||||
*
|
||||
* @package WP_Prometheus\Tests
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Magdev\WpPrometheus\Tests\Helpers\GlobalFunctionState;
|
||||
|
||||
// 1. Load Composer autoloader first (for GlobalFunctionState class).
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
// 2. Define WordPress constants required by source files.
|
||||
define('ABSPATH', '/tmp/wordpress/');
|
||||
define('WP_CONTENT_DIR', '/tmp/wordpress/wp-content');
|
||||
define('WP_PROMETHEUS_VERSION', '0.5.0');
|
||||
define('WP_PROMETHEUS_FILE', dirname(__DIR__) . '/wp-prometheus.php');
|
||||
define('WP_PROMETHEUS_PATH', dirname(__DIR__) . '/');
|
||||
define('WP_PROMETHEUS_URL', 'https://example.com/wp-content/plugins/wp-prometheus/');
|
||||
define('WP_PROMETHEUS_BASENAME', 'wp-prometheus/wp-prometheus.php');
|
||||
|
||||
// 3. Define global WordPress function stubs.
|
||||
// These exist so plugin files can be require'd without fatal errors.
|
||||
// Per-test behavior in namespaced code is controlled via php-mock.
|
||||
// Global-scope tests use GlobalFunctionState for controllable behavior.
|
||||
|
||||
// -- Translation functions --
|
||||
if (!function_exists('__')) {
|
||||
function __(string $text, string $domain = 'default'): string
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('_e')) {
|
||||
function _e(string $text, string $domain = 'default'): void
|
||||
{
|
||||
echo $text;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('esc_html__')) {
|
||||
function esc_html__(string $text, string $domain = 'default'): string
|
||||
{
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('esc_html_e')) {
|
||||
function esc_html_e(string $text, string $domain = 'default'): void
|
||||
{
|
||||
echo htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
// -- Escaping functions --
|
||||
if (!function_exists('esc_html')) {
|
||||
function esc_html(string $text): string
|
||||
{
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('esc_attr')) {
|
||||
function esc_attr(string $text): string
|
||||
{
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('esc_url')) {
|
||||
function esc_url(string $url): string
|
||||
{
|
||||
return filter_var($url, FILTER_SANITIZE_URL) ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('esc_url_raw')) {
|
||||
function esc_url_raw(string $url): string
|
||||
{
|
||||
return filter_var($url, FILTER_SANITIZE_URL) ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
// -- Sanitization functions --
|
||||
if (!function_exists('sanitize_text_field')) {
|
||||
function sanitize_text_field(string $str): string
|
||||
{
|
||||
return trim(strip_tags($str));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('sanitize_key')) {
|
||||
function sanitize_key(string $key): string
|
||||
{
|
||||
return preg_replace('/[^a-z0-9_\-]/', '', strtolower($key));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('sanitize_file_name')) {
|
||||
function sanitize_file_name(string $name): string
|
||||
{
|
||||
return preg_replace('/[^a-zA-Z0-9_.\-]/', '', $name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('sanitize_html_class')) {
|
||||
function sanitize_html_class(string $class): string
|
||||
{
|
||||
return preg_replace('/[^a-zA-Z0-9_\-]/', '', $class);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('absint')) {
|
||||
function absint($value): int
|
||||
{
|
||||
return abs((int) $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_unslash')) {
|
||||
function wp_unslash($value)
|
||||
{
|
||||
return is_string($value) ? stripslashes($value) : $value;
|
||||
}
|
||||
}
|
||||
|
||||
// -- WordPress utility functions --
|
||||
if (!function_exists('wp_parse_url')) {
|
||||
function wp_parse_url(string $url, int $component = -1)
|
||||
{
|
||||
return parse_url($url, $component);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_json_encode')) {
|
||||
function wp_json_encode($data, int $options = 0, int $depth = 512)
|
||||
{
|
||||
return json_encode($data, $options, $depth);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_generate_uuid4')) {
|
||||
function wp_generate_uuid4(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_generate_password')) {
|
||||
function wp_generate_password(int $length = 12, bool $special = true): string
|
||||
{
|
||||
return substr(bin2hex(random_bytes($length)), 0, $length);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('size_format')) {
|
||||
function size_format(int $bytes, int $decimals = 0): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
|
||||
return number_format($bytes / pow(1024, $power), $decimals) . ' ' . $units[$power];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('path_is_absolute')) {
|
||||
function path_is_absolute(string $path): bool
|
||||
{
|
||||
return str_starts_with($path, '/') || (bool) preg_match('#^[a-zA-Z]:\\\\#', $path);
|
||||
}
|
||||
}
|
||||
|
||||
// -- Hook functions (no-ops) --
|
||||
if (!function_exists('add_action')) {
|
||||
function add_action(string $hook, $callback, int $priority = 10, int $accepted_args = 1): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('add_filter')) {
|
||||
function add_filter(string $hook, $callback, int $priority = 10, int $accepted_args = 1): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('remove_all_filters')) {
|
||||
function remove_all_filters(string $hook, $priority = false): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('do_action')) {
|
||||
function do_action(string $hook, ...$args): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('apply_filters')) {
|
||||
function apply_filters(string $hook, $value, ...$args)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Option functions (controllable via GlobalFunctionState) --
|
||||
if (!function_exists('get_option')) {
|
||||
function get_option(string $option, $default = false)
|
||||
{
|
||||
if (array_key_exists($option, GlobalFunctionState::$options)) {
|
||||
return GlobalFunctionState::$options[$option];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('update_option')) {
|
||||
function update_option(string $option, $value, $autoload = null): bool
|
||||
{
|
||||
GlobalFunctionState::recordCall('update_option', $option, $value);
|
||||
GlobalFunctionState::$options[$option] = $value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('delete_option')) {
|
||||
function delete_option(string $option): bool
|
||||
{
|
||||
GlobalFunctionState::recordCall('delete_option', $option);
|
||||
unset(GlobalFunctionState::$options[$option]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('delete_transient')) {
|
||||
function delete_transient(string $transient): bool
|
||||
{
|
||||
GlobalFunctionState::recordCall('delete_transient', $transient);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('flush_rewrite_rules')) {
|
||||
function flush_rewrite_rules(bool $hard = true): void
|
||||
{
|
||||
GlobalFunctionState::recordCall('flush_rewrite_rules');
|
||||
}
|
||||
}
|
||||
|
||||
// -- URL functions --
|
||||
if (!function_exists('home_url')) {
|
||||
function home_url(string $path = ''): string
|
||||
{
|
||||
return 'https://example.com' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('admin_url')) {
|
||||
function admin_url(string $path = ''): string
|
||||
{
|
||||
return 'https://example.com/wp-admin/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Conditional functions (controllable via GlobalFunctionState) --
|
||||
if (!function_exists('is_admin')) {
|
||||
function is_admin(): bool
|
||||
{
|
||||
return GlobalFunctionState::$options['__is_admin'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_doing_ajax')) {
|
||||
function wp_doing_ajax(): bool
|
||||
{
|
||||
return GlobalFunctionState::$options['__wp_doing_ajax'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_doing_cron')) {
|
||||
function wp_doing_cron(): bool
|
||||
{
|
||||
return GlobalFunctionState::$options['__wp_doing_cron'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('is_multisite')) {
|
||||
function is_multisite(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Plugin functions --
|
||||
if (!function_exists('load_plugin_textdomain')) {
|
||||
function load_plugin_textdomain(string $domain, $deprecated = false, string $path = ''): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('register_activation_hook')) {
|
||||
function register_activation_hook(string $file, $callback): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('register_deactivation_hook')) {
|
||||
function register_deactivation_hook(string $file, $callback): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// -- Rewrite functions --
|
||||
if (!function_exists('add_rewrite_rule')) {
|
||||
function add_rewrite_rule(string $regex, string $redirect, string $after = 'bottom'): void
|
||||
{
|
||||
GlobalFunctionState::recordCall('add_rewrite_rule', $regex, $redirect, $after);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('add_rewrite_tag')) {
|
||||
function add_rewrite_tag(string $tag, string $regex, string $query = ''): void
|
||||
{
|
||||
GlobalFunctionState::recordCall('add_rewrite_tag', $tag, $regex, $query);
|
||||
}
|
||||
}
|
||||
|
||||
// -- HTTP functions --
|
||||
if (!function_exists('status_header')) {
|
||||
function status_header(int $code, string $description = ''): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('hash_equals')) {
|
||||
// hash_equals is a PHP built-in, but define stub just in case.
|
||||
}
|
||||
|
||||
if (!function_exists('wp_rand')) {
|
||||
function wp_rand(int $min = 0, int $max = 0): int
|
||||
{
|
||||
return random_int($min, max($min, $max ?: PHP_INT_MAX >> 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_bloginfo')) {
|
||||
function get_bloginfo(string $show = '', bool $filter = true): string
|
||||
{
|
||||
return match ($show) {
|
||||
'version' => '6.7',
|
||||
'language' => 'en-US',
|
||||
'name' => 'Test Site',
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('current_user_can')) {
|
||||
function current_user_can(string $capability, ...$args): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('deactivate_plugins')) {
|
||||
function deactivate_plugins($plugins, bool $silent = false, $network_wide = null): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_die')) {
|
||||
function wp_die($message = '', $title = '', $args = []): void
|
||||
{
|
||||
throw new \RuntimeException((string) $message);
|
||||
}
|
||||
}
|
||||
|
||||
// -- Plugin global authentication functions (from wp-prometheus.php) --
|
||||
// Cannot include wp-prometheus.php directly due to constant definitions
|
||||
// and side effects. These mirror the production code for testing.
|
||||
|
||||
if (!function_exists('wp_prometheus_get_authorization_header')) {
|
||||
function wp_prometheus_get_authorization_header(): string
|
||||
{
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
return sanitize_text_field(wp_unslash($_SERVER['HTTP_AUTHORIZATION']));
|
||||
}
|
||||
|
||||
if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||
return sanitize_text_field(wp_unslash($_SERVER['REDIRECT_HTTP_AUTHORIZATION']));
|
||||
}
|
||||
|
||||
if (function_exists('apache_request_headers')) {
|
||||
$headers = apache_request_headers();
|
||||
if (isset($headers['Authorization'])) {
|
||||
return sanitize_text_field($headers['Authorization']);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// -- WordPress core class stubs --
|
||||
if (!class_exists('WP')) {
|
||||
class WP
|
||||
{
|
||||
public array $query_vars = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('wp_prometheus_authenticate_request')) {
|
||||
function wp_prometheus_authenticate_request(): bool
|
||||
{
|
||||
$auth_token = get_option('wp_prometheus_auth_token', '');
|
||||
|
||||
// If no token is set, deny access.
|
||||
if (empty($auth_token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for Bearer token in Authorization header.
|
||||
$auth_header = wp_prometheus_get_authorization_header();
|
||||
if (!empty($auth_header) && preg_match('/Bearer\s+(.*)$/i', $auth_header, $matches)) {
|
||||
return hash_equals($auth_token, $matches[1]);
|
||||
}
|
||||
|
||||
// Check for token in query parameter.
|
||||
if (isset($_GET['token']) && hash_equals($auth_token, sanitize_text_field(wp_unslash($_GET['token'])))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user