- New Cost_Tracker class: logs every API call (model, tokens, cost), enforces monthly budgets with hard-stop mode, provides aggregated reporting - New llm_costs database table with indexes on created_at, model, operation - Modified LLM class: call_openrouter_with_usage() extracts token usage from OpenRouter responses; budget gate checks before each API call - Admin settings: monthly budget (USD), enforcement mode (disabled/warn/hard-stop), cost log retention period - Admin cost dashboard: budget progress bar, daily cost chart (Chart.js), per-model and per-operation breakdowns, recent API call log - PHPUnit tests: 16 tests covering cost calculation, pricing lookups, budget enforcement logic, and model validation - Updated ARCHITECTURE.md with new table schema, data flow, and key decisions - Updated CONVENTIONS.md with "how to add cost tracking for a new operation" - Version bump to 2.4.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
110 lines
3.3 KiB
PHP
Executable File
110 lines
3.3 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Unit tests for Peptide_News_Admin.
|
|
*
|
|
* @package PeptideNews\Tests
|
|
*/
|
|
|
|
namespace PeptideNews\Tests\Unit;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
// Load the class under test.
|
|
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'admin/class-peptide-news-admin.php';
|
|
|
|
class AdminTest extends TestCase {
|
|
|
|
/**
|
|
* @var \Peptide_News_Admin
|
|
*/
|
|
private $admin;
|
|
|
|
protected function setUp(): void {
|
|
$this->admin = new \Peptide_News_Admin( 'peptide-news', '1.2.1' );
|
|
|
|
// Reset global test state.
|
|
global $_test_settings_errors;
|
|
$_test_settings_errors = array();
|
|
}
|
|
|
|
/**
|
|
* Constructor should set plugin_name property.
|
|
*/
|
|
public function test_constructor_sets_plugin_name(): void {
|
|
$reflection = new \ReflectionClass( $this->admin );
|
|
$prop = $reflection->getProperty( 'plugin_name' );
|
|
$prop->setAccessible( true );
|
|
|
|
$this->assertEquals( 'peptide-news', $prop->getValue( $this->admin ) );
|
|
}
|
|
|
|
/**
|
|
* Constructor should set version property.
|
|
*/
|
|
public function test_constructor_sets_version(): void {
|
|
$reflection = new \ReflectionClass( $this->admin );
|
|
$prop = $reflection->getProperty( 'version' );
|
|
$prop->setAccessible( true );
|
|
|
|
$this->assertEquals( '1.2.1', $prop->getValue( $this->admin ) );
|
|
}
|
|
|
|
/**
|
|
* sanitize_model_id() should accept a valid model ID.
|
|
*/
|
|
public function test_sanitize_model_id_accepts_valid_id(): void {
|
|
$valid = 'openai/gpt-4o-mini';
|
|
$this->assertEquals( $valid, $this->admin->sanitize_model_id( $valid ) );
|
|
}
|
|
|
|
/**
|
|
* sanitize_model_id() should accept model IDs with colons and dots.
|
|
*/
|
|
public function test_sanitize_model_id_accepts_complex_ids(): void {
|
|
$valid = 'google/gemini-2.0-flash:free';
|
|
$this->assertEquals( $valid, $this->admin->sanitize_model_id( $valid ) );
|
|
}
|
|
|
|
/**
|
|
* sanitize_model_id() should reject empty strings.
|
|
*/
|
|
public function test_sanitize_model_id_allows_empty_to_clear(): void {
|
|
$result = $this->admin->sanitize_model_id( '' );
|
|
$this->assertEquals( '', $result );
|
|
}
|
|
|
|
/**
|
|
* sanitize_model_id() should reject strings with spaces.
|
|
*/
|
|
public function test_sanitize_model_id_rejects_spaces(): void {
|
|
global $_test_settings_errors;
|
|
|
|
$result = $this->admin->sanitize_model_id( 'invalid model id' );
|
|
|
|
$this->assertNotEmpty( $_test_settings_errors, 'Expected a settings error for invalid model ID.' );
|
|
}
|
|
|
|
/**
|
|
* sanitize_model_id() should reject strings with special characters.
|
|
*/
|
|
public function test_sanitize_model_id_rejects_special_chars(): void {
|
|
global $_test_settings_errors;
|
|
|
|
$result = $this->admin->sanitize_model_id( '<script>alert(1)</script>' );
|
|
|
|
$this->assertNotEmpty( $_test_settings_errors, 'Expected a settings error for XSS attempt.' );
|
|
}
|
|
|
|
/**
|
|
* enqueue_styles() should not enqueue on non-plugin pages.
|
|
*
|
|
* We verify it returns early by checking no errors are thrown
|
|
* when called with a non-matching hook.
|
|
*/
|
|
public function test_enqueue_styles_skips_non_plugin_pages(): void {
|
|
// Should return without error when hook doesn't match.
|
|
$this->admin->enqueue_styles( 'edit.php' );
|
|
$this->assertTrue( true ); // No exception = pass.
|
|
}
|
|
}
|