fix: resolve Logger access, wrong model name, and API key guard tests

- bootstrap: add PRAUTOBLOGGER_MAX_RETRIES, API_TIMEOUT, RETRY_BASE_DELAY constants
- BaseTestCase: add wp_json_encode + home_url stubs, reset Logger singleton in tearDown
- CostTrackerTest: use openai/gpt-4o (in MODEL_PRICING), fix log_api_call args,
  allow multiple wpdb inserts (Logger may also insert event_log rows)
- OpenRouterPricingTest: add wpdb mock with prefix for Logger access on unknown models
- OpenRouterProviderTest: test send_chat_completion throws RuntimeException when
  no API key is configured (matches actual guard clause behavior)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
peptiderepo
2026-04-12 08:35:01 +07:00
parent f8b4051437
commit a5d426989c
5 changed files with 72 additions and 19 deletions

View File

@@ -29,6 +29,17 @@ if ( ! defined( 'HOUR_IN_SECONDS' ) ) {
define( 'HOUR_IN_SECONDS', 3600 );
}
// Plugin constants used by OpenRouter provider retry logic.
if ( ! defined( 'PRAUTOBLOGGER_MAX_RETRIES' ) ) {
define( 'PRAUTOBLOGGER_MAX_RETRIES', 3 );
}
if ( ! defined( 'PRAUTOBLOGGER_API_TIMEOUT_SECONDS' ) ) {
define( 'PRAUTOBLOGGER_API_TIMEOUT_SECONDS', 30 );
}
if ( ! defined( 'PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS' ) ) {
define( 'PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS', 1 );
}
// WordPress database constants used in $wpdb queries.
if ( ! defined( 'ARRAY_A' ) ) {
define( 'ARRAY_A', 'ARRAY_A' );

View File

@@ -32,12 +32,27 @@ abstract class BaseTestCase extends TestCase {
Functions\when( 'get_transient' )->justReturn( false );
Functions\when( 'set_transient' )->justReturn( true );
Functions\when( 'delete_transient' )->justReturn( true );
// wp_json_encode is used by Logger and other classes for meta serialisation.
Functions\when( 'wp_json_encode' )->alias( 'json_encode' );
// home_url is used by OpenRouter provider in request headers.
Functions\when( 'home_url' )->justReturn( 'https://test.example.com' );
}
/**
* Tear down Brain\Monkey after each test.
*/
protected function tearDown(): void {
// Reset Logger singleton to prevent state leakage between tests.
// Logger caches its log-level threshold and holds a static instance.
if ( class_exists( 'PRAutoBlogger_Logger', false ) ) {
$ref = new \ReflectionClass( \PRAutoBlogger_Logger::class );
$prop = $ref->getProperty( 'instance' );
$prop->setAccessible( true );
$prop->setValue( null, null );
}
Monkey\tearDown();
parent::tearDown();
}

View File

@@ -142,23 +142,23 @@ class CostTrackerTest extends BaseTestCase {
$tracker = new \PRAutoBlogger_Cost_Tracker();
$tracker->set_run_id( 'run_test' );
// Mock wpdb to expect an insert call.
$this->wpdb->expects( $this->once() )
// Allow multiple inserts — log_api_call inserts a generation log row,
// and Logger may also insert an event log row if a warning fires.
$this->wpdb->expects( $this->atLeastOnce() )
->method( 'insert' )
->willReturn( 1 );
// Signature: log_api_call(?int $post_id, string $stage, string $provider,
// string $model, int $prompt_tokens, int $completion_tokens,
// float $estimated_cost, float $duration_seconds).
// Actual signature: log_api_call(?int $post_id, string $stage,
// string $provider, string $model, int $prompt_tokens,
// int $completion_tokens, string $response_status, string $error_message).
// Use 'openai/gpt-4o' which IS in MODEL_PRICING to avoid unknown-model warnings.
$tracker->log_api_call(
123,
'analysis',
'openrouter',
'openai/gpt-4',
'openai/gpt-4o',
500,
250,
0.01,
1.5
250
);
// If we get here without exception, the method worked.

View File

@@ -14,9 +14,28 @@ use Brain\Monkey\Functions;
class OpenRouterPricingTest extends BaseTestCase {
/**
* @var \PHPUnit\Framework\MockObject\MockObject Mock $wpdb for Logger.
*/
private $wpdb;
protected function setUp(): void {
parent::setUp();
// Logger (called on unknown-model warning) accesses $wpdb->prefix and $wpdb->insert.
$this->wpdb = $this->create_mock_wpdb();
$this->wpdb->method( 'insert' )->willReturn( 1 );
$GLOBALS['wpdb'] = $this->wpdb;
// Stub get_option for Logger's log_level and Pricing's API key lookup.
$this->stub_get_option( [
'prautoblogger_log_level' => 'info',
'prautoblogger_openrouter_api_key' => '',
] );
// Stub current_time — used by Logger when writing log entries.
$this->stub_current_time( '2026-04-12 10:00:00' );
// OpenRouterPricing::get_available_models() calls get_transient() to check cache.
// Return a valid cached model list so it doesn't try to make HTTP calls.
Functions\when( 'get_transient' )->alias(
@@ -44,6 +63,11 @@ class OpenRouterPricingTest extends BaseTestCase {
Functions\when( 'set_transient' )->justReturn( true );
}
protected function tearDown(): void {
unset( $GLOBALS['wpdb'] );
parent::tearDown();
}
/**
* Test Pricing can be instantiated.
*/

View File

@@ -21,8 +21,10 @@ class OpenRouterProviderTest extends BaseTestCase {
// Stub get_option — provider calls get_option('prautoblogger_openrouter_api_key', '')
// for API key retrieval via private get_api_key() method.
// Return empty string so decryption is skipped (no key = empty return).
// Also include prautoblogger_log_level for Logger singleton.
$this->stub_get_option( [
'prautoblogger_openrouter_api_key' => '',
'prautoblogger_log_level' => 'info',
] );
// Stub wp_salt — called by PRAutoBlogger_Encryption during API key decryption.
@@ -131,24 +133,26 @@ class OpenRouterProviderTest extends BaseTestCase {
}
/**
* Test send_chat_completion returns array.
* Test send_chat_completion throws RuntimeException when API key is empty.
*
* The guard clause at the top of send_chat_completion checks for a
* configured API key before making any HTTP calls.
*/
public function test_send_chat_completion_returns_array(): void {
public function test_send_chat_completion_throws_without_api_key(): void {
$provider = new \PRAutoBlogger_OpenRouter_Provider();
$messages = [
[ 'role' => 'user', 'content' => 'Test message' ],
];
$result = $provider->send_chat_completion( $messages, 'model/test', [] );
$this->assertIsArray( $result );
$this->expectException( \RuntimeException::class );
$provider->send_chat_completion( $messages, 'model/test', [] );
}
/**
* Test send_chat_completion with options.
* Test send_chat_completion with options also throws when no key.
*/
public function test_send_chat_completion_with_options(): void {
public function test_send_chat_completion_with_options_throws_without_api_key(): void {
$provider = new \PRAutoBlogger_OpenRouter_Provider();
$messages = [
@@ -156,8 +160,7 @@ class OpenRouterProviderTest extends BaseTestCase {
];
$options = [ 'temperature' => 0.7 ];
$result = $provider->send_chat_completion( $messages, 'model/test', $options );
$this->assertIsArray( $result );
$this->expectException( \RuntimeException::class );
$provider->send_chat_completion( $messages, 'model/test', $options );
}
}