Initial commit: PRAutoBlogger v0.1.0 with frontend posts widget

This commit is contained in:
Terence Ong
2026-04-10 23:33:42 +07:00
parent 68c6f67bcf
commit 88d674d679
65 changed files with 2178 additions and 1084 deletions

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
/**
* Unit tests for Autoblogger_Article_Idea value object.
* Unit tests for PRAutoBlogger_Article_Idea value object.
*
* Article ideas flow from the scorer to the generator to the editor —
* data must survive the full pipeline without mutation or loss.
@@ -22,7 +22,7 @@ class ArticleIdeaTest extends \PHPUnit\Framework\TestCase {
'target_keywords' => [ 'bpc-157 dosage', 'bpc-157 protocol' ],
];
$idea = new Autoblogger_Article_Idea( $data );
$idea = new PRAutoBlogger_Article_Idea( $data );
$this->assertSame( 'BPC-157 dosage protocols', $idea->get_topic() );
$this->assertSame( 'question', $idea->get_article_type() );
@@ -36,7 +36,7 @@ class ArticleIdeaTest extends \PHPUnit\Framework\TestCase {
}
public function test_optional_fields_default_to_empty(): void {
$idea = new Autoblogger_Article_Idea( [
$idea = new PRAutoBlogger_Article_Idea( [
'topic' => 'Test',
'article_type' => 'comparison',
'suggested_title' => 'Test Title',
@@ -51,7 +51,7 @@ class ArticleIdeaTest extends \PHPUnit\Framework\TestCase {
}
public function test_score_cast_from_string(): void {
$idea = new Autoblogger_Article_Idea( [
$idea = new PRAutoBlogger_Article_Idea( [
'topic' => 'Test',
'article_type' => 'question',
'suggested_title' => 'Test',

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
/**
* Unit tests for Autoblogger_Editorial_Review value object.
* Unit tests for PRAutoBlogger_Editorial_Review value object.
*
* The editorial review verdict drives the publish/draft/reject decision.
* A null revised_content on a "revised" verdict caused a bug (now fixed) —
@@ -11,7 +11,7 @@ declare(strict_types=1);
class EditorialReviewTest extends \PHPUnit\Framework\TestCase {
public function test_approved_review(): void {
$review = new Autoblogger_Editorial_Review( [
$review = new PRAutoBlogger_Editorial_Review( [
'verdict' => 'approved',
'notes' => 'Good article, ready to publish.',
'quality_score' => 0.85,
@@ -28,7 +28,7 @@ class EditorialReviewTest extends \PHPUnit\Framework\TestCase {
}
public function test_revised_review_with_content(): void {
$review = new Autoblogger_Editorial_Review( [
$review = new PRAutoBlogger_Editorial_Review( [
'verdict' => 'revised',
'notes' => 'Fixed heading structure.',
'revised_content' => '<h1>Improved Article</h1><p>Better content.</p>',
@@ -49,7 +49,7 @@ class EditorialReviewTest extends \PHPUnit\Framework\TestCase {
* The pipeline handles this with a null-coalesce fallback.
*/
public function test_revised_verdict_with_null_content(): void {
$review = new Autoblogger_Editorial_Review( [
$review = new PRAutoBlogger_Editorial_Review( [
'verdict' => 'revised',
'notes' => 'Supposedly revised but no content provided.',
'revised_content' => null,
@@ -60,7 +60,7 @@ class EditorialReviewTest extends \PHPUnit\Framework\TestCase {
}
public function test_rejected_review(): void {
$review = new Autoblogger_Editorial_Review( [
$review = new PRAutoBlogger_Editorial_Review( [
'verdict' => 'rejected',
'notes' => 'Off-topic and factually inaccurate.',
'quality_score' => 0.2,
@@ -74,7 +74,7 @@ class EditorialReviewTest extends \PHPUnit\Framework\TestCase {
}
public function test_default_scores_are_zero(): void {
$review = new Autoblogger_Editorial_Review( [
$review = new PRAutoBlogger_Editorial_Review( [
'verdict' => 'rejected',
'notes' => 'Minimal data.',
] );

View File

@@ -5,7 +5,7 @@ use Brain\Monkey;
use Brain\Monkey\Functions;
/**
* Unit tests for Autoblogger_Generation_Log value object.
* Unit tests for PRAutoBlogger_Generation_Log value object.
*
* Verifies construction, getters, and to_db_row() serialization.
* These are the records that power the cost dashboard — data integrity is critical.
@@ -42,7 +42,7 @@ class GenerationLogTest extends \PHPUnit\Framework\TestCase {
'created_at' => '2026-04-10 10:00:00',
];
$log = new Autoblogger_Generation_Log( $data );
$log = new PRAutoBlogger_Generation_Log( $data );
$this->assertSame( 42, $log->get_id() );
$this->assertSame( 100, $log->get_post_id() );
@@ -59,7 +59,7 @@ class GenerationLogTest extends \PHPUnit\Framework\TestCase {
}
public function test_constructor_defaults_for_missing_fields(): void {
$log = new Autoblogger_Generation_Log( [] );
$log = new PRAutoBlogger_Generation_Log( [] );
$this->assertSame( 0, $log->get_id() );
$this->assertNull( $log->get_post_id() );
@@ -73,7 +73,7 @@ class GenerationLogTest extends \PHPUnit\Framework\TestCase {
}
public function test_to_db_row_excludes_id(): void {
$log = new Autoblogger_Generation_Log( [
$log = new PRAutoBlogger_Generation_Log( [
'id' => 99,
'post_id' => 50,
'run_id' => 'run-abc',
@@ -95,7 +95,7 @@ class GenerationLogTest extends \PHPUnit\Framework\TestCase {
}
public function test_to_db_row_has_all_expected_keys(): void {
$log = new Autoblogger_Generation_Log( [ 'stage' => 'analysis' ] );
$log = new PRAutoBlogger_Generation_Log( [ 'stage' => 'analysis' ] );
$row = $log->to_db_row();
$expected_keys = [
@@ -110,7 +110,7 @@ class GenerationLogTest extends \PHPUnit\Framework\TestCase {
}
public function test_numeric_fields_are_cast_correctly(): void {
$log = new Autoblogger_Generation_Log( [
$log = new PRAutoBlogger_Generation_Log( [
'id' => '42',
'post_id' => '100',
'prompt_tokens' => '1500',

View File

@@ -5,7 +5,7 @@ use Brain\Monkey;
use Brain\Monkey\Functions;
/**
* Unit tests for Autoblogger_OpenRouter_Pricing cost estimation.
* Unit tests for PRAutoBlogger_OpenRouter_Pricing cost estimation.
*
* Cost calculation accuracy directly affects budget enforcement and the
* metrics dashboard. Getting this wrong means either overspending or
@@ -24,7 +24,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
// Mock the Logger singleton so calls don't break.
// The pricing class calls Logger::instance()->warning() for unknown models.
if ( ! class_exists( 'Autoblogger_Logger' ) ) {
if ( ! class_exists( 'PRAutoBlogger_Logger' ) ) {
require_once dirname( __DIR__, 2 ) . '/includes/core/class-logger.php';
}
@@ -32,12 +32,12 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
Functions\when( 'current_time' )->justReturn( '2026-04-10 12:00:00' );
// Mock Encryption class (used by get_api_key).
if ( ! class_exists( 'Autoblogger_Encryption' ) ) {
if ( ! class_exists( 'PRAutoBlogger_Encryption' ) ) {
require_once dirname( __DIR__, 2 ) . '/includes/class-encryption.php';
}
// Load the class under test.
if ( ! class_exists( 'Autoblogger_OpenRouter_Pricing' ) ) {
if ( ! class_exists( 'PRAutoBlogger_OpenRouter_Pricing' ) ) {
require_once dirname( __DIR__, 2 ) . '/includes/providers/class-openrouter-pricing.php';
}
}
@@ -48,7 +48,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
}
public function test_known_model_cost_calculation(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
// Claude 3.5 Haiku: $0.80/M prompt, $4.00/M completion
// 1000 prompt tokens = 1000 * 0.80 / 1_000_000 = $0.0008
@@ -59,7 +59,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
}
public function test_claude_sonnet_cost(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
// Claude Sonnet 4: $3.00/M prompt, $15.00/M completion
// 2000 prompt + 1000 completion
@@ -70,7 +70,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
}
public function test_gpt4o_mini_cost(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
// GPT-4o-mini: $0.15/M prompt, $0.60/M completion
// 5000 prompt + 2000 completion
@@ -81,13 +81,13 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
}
public function test_zero_tokens_returns_zero_cost(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
$cost = $pricing->estimate_cost( 'anthropic/claude-3.5-haiku', 0, 0 );
$this->assertEqualsWithDelta( 0.0, $cost, 0.00001 );
}
public function test_unknown_model_uses_conservative_estimate(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
// Unknown model: $10.00/M prompt, $30.00/M completion (conservative fallback)
// 1000 prompt + 1000 completion
@@ -97,7 +97,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
}
public function test_cost_scales_linearly_with_tokens(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
$cost_1x = $pricing->estimate_cost( 'anthropic/claude-3.5-haiku', 1000, 500 );
$cost_2x = $pricing->estimate_cost( 'anthropic/claude-3.5-haiku', 2000, 1000 );
@@ -107,7 +107,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
}
public function test_prompt_and_completion_priced_separately(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
// For a model where prompt and completion prices differ (all of them),
// swapping prompt/completion tokens should give a different cost.
@@ -127,7 +127,7 @@ class OpenRouterPricingTest extends \PHPUnit\Framework\TestCase {
* This is a sanity check: if this ever fails, pricing data may be stale.
*/
public function test_full_article_cost_sanity_check(): void {
$pricing = new Autoblogger_OpenRouter_Pricing();
$pricing = new PRAutoBlogger_OpenRouter_Pricing();
// Typical multi-step article: analysis + outline + draft + polish + review
// Using Haiku for analysis, Sonnet for the rest.