Commit Graph

8 Commits

Author SHA1 Message Date
peptiderepo
573520558c fix(images): CTO review blockers on PR #9
Addresses four blockers from CTO review in
convo/prautoblogger/threads/2026-04-image-pipeline/04-cto-block-pr9.md:

1. PHPUnit red — 2 retry-path tests failing on bad mocks.
   The success-after-retry branch calls wp_remote_retrieve_header()
   only on the success attempt; the original mocks queued two mime
   values and popped the first (text/plain), which made the provider's
   normalize_response_body() throw "unexpected shape". Replaced the
   pop-queue with a single justReturn('image/png'). The retry-after
   header read on the 5xx/429 branch still works (coerces to int 0,
   falls back to exponential backoff).

2. XSS defense-in-depth — wrap untrusted API response bodies in
   esc_html() before they land in exception / user-facing messages.
   - class-cloudflare-image-provider.php: 4xx-throw and
     retry-exhaustion-throw now escape substr($raw, 0, 300) and
     $last_error respectively.
   - class-cloudflare-image-validator.php: network-error and
     HTTP-error-body messages now use esc_html__() on the template
     and esc_html() on the substituted values.

3. PHPCS hygiene on the 4 new files (CTO path A). Converted the
   one-line private-method docblocks to full multi-line format with
   @param / @return / @throws tags. Added a docblock to __construct.

4. File-size cap. The docblock expansion pushed
   class-cloudflare-image-provider.php over 300 lines. Extracted
   credential lookups + URL builder + log forwarders into a new
   class-cloudflare-image-support.php (101 lines). Provider is now
   271 lines, all new files under the 300-line cap.

No production behaviour changed beyond the escape wrapping. The
provider's public API, retry policy, and response handling are
identical to c823618.

File sizes after fix:
- interface-image-provider.php:         100
- class-cloudflare-image-provider.php:  271
- class-cloudflare-image-pricing.php:    78
- class-cloudflare-image-validator.php: 116
- class-cloudflare-image-support.php:   101

Agent-Session: cowork-2026-04-15-image-pipeline-commit-1a-cto-blockers
2026-04-15 14:16:39 +07:00
peptiderepo
c8236189cd test(images): cover retry loop and validator edge cases
Addresses P1 feedback from QA verdict on 11108b3:
- Retry-path tests: 429-then-200, 500-then-200, persistent-502 exhausts
  all attempts and throws.
- Validator guardrail test: validate_credentials_detailed() asserts
  wp_remote_post is never called, locking in the non-destructive probe
  contract so no "Test Connection" click ever burns image-gen budget.
- Validator edge case: missing account_id returns 'account_id_empty'
  without touching the network.
- Bootstrap retry delay set to 0 in tests so the new retry-loop tests
  don't spend real wall time in sleep(). Production value unchanged
  (PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS = 2 in prautoblogger.php).

Risk flags:
- No code under test changed. Same provider diff as 11108b3; this
  commit is purely additional coverage + a zero-impact bootstrap tweak.

Agent-Session: cowork-2026-04-15-image-pipeline-commit-1a-qa-fixup
2026-04-15 10:14:38 +07:00
peptiderepo
11108b3bb5 feat(images): Cloudflare Workers AI image provider (commit 1a)
First commit of the image + Instagram A/B pipeline workstream. Ships the
Cloudflare Workers AI image provider, its pricing + validator helpers,
four new settings fields in a new "Images" admin section, and unit tests
with mocked HTTP. Nothing calls the provider yet from the article
pipeline — integration lands in commit 1b.

Why: CEO-approved on 2026-04-15 (see
convo/prautoblogger/threads/2026-04-image-pipeline/01-cto-handoff.md).
Style is locked to the 90s infomercial pastiche; default model is FLUX.1
[schnell] at ~$0.0011/MP (~$0.01/article for 8 images across 4 placements
x 2 variants).

Risk flags:
- Direct call to Cloudflare Workers AI bypassing AI Gateway (decision
  D-001, documented in convo/prautoblogger/decisions/). Gateway route
  to Workers AI currently 403s; OpenRouter gateway path unaffected.
- prab_event_log structured writes deferred to commit 1b when the
  table lands (decision D-002). Logger-based logging is in place now.
- No schema changes. No existing code paths touched outside settings.
- Backward compatible: provider is instantiable but not wired to any
  consumer yet.

Test plan:
- CI runs PHPUnit including the new CloudflareImageProviderTest.
- After merge: verify the new Images section appears on the settings
  page, the style-suffix textarea pre-fills with the infomercial prompt,
  and entering the existing CLOUDFLARE_AI_TOKEN + account UUID lets a
  future "Test Connection" button return OK (button ships in a later
  commit; the underlying validate_credentials_detailed() works today).

Files:
- includes/providers/interface-image-provider.php   (adopted from CTO draft)
- includes/providers/class-cloudflare-image-provider.php  (283 lines)
- includes/providers/class-cloudflare-image-pricing.php   (78 lines)
- includes/providers/class-cloudflare-image-validator.php (107 lines)
- includes/admin/class-settings-fields.php (+ Images section, 4 fields)
- prautoblogger.php (+ PRAUTOBLOGGER_DEFAULT_IMAGE_MODEL, _STYLE_SUFFIX)
- tests/unit/Providers/CloudflareImageProviderTest.php
- ARCHITECTURE.md (file tree, integrations table, options table, decision #16)
- CONVENTIONS.md (How To: Add a New Image Provider)
- CHANGELOG.md (Unreleased section)

Agent-Session: cowork-2026-04-15-image-pipeline-commit-1a
2026-04-15 10:11:10 +07:00
peptiderepo
a5d426989c 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>
2026-04-12 08:35:01 +07:00
peptiderepo
7728db5671 fix: add WP constants, WP_Query mock, fix arg types and stubs
- bootstrap: define DAY_IN_SECONDS, ARRAY_A, mock WP_Query class
- CostTrackerTest: fix log_api_call signature (post_id is ?int, not string)
- PublisherTest: fix apply_filters usage, remove broken expectation
- OpenRouterPricingTest: fix assertGreaterThanOrEqual direction
- OpenRouterProviderTest: add wp_salt stub, use empty API key
- ContentAnalyzerTest: add apply_filters, current_time, get_results stubs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 08:17:18 +07:00
peptiderepo
ece032efe5 fix: add missing WordPress function stubs to unit tests
- ContentAnalyzerTest: pass Cost_Tracker as 2nd constructor arg
- IdeaScorerTest: stub get_option for topic_exclusions
- CostTrackerTest: stub get_option for monthly_budget_usd
- OpenRouterPricingTest: stub get_transient with cached model data
- OpenRouterProviderTest: stub get_option for API key + get_transient

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 08:05:43 +07:00
peptiderepo
ab8d38a9c8 fix: rewrite all unit tests to match actual class signatures
All 14 test files updated to use correct constructor patterns (array-based
value objects, dependency injection for ContentAnalyzer, etc.) and verified
method names against source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 01:43:02 +07:00
peptiderepo
c6370cf854 Add unit test suite: 54 tests across models, core, and providers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 00:36:59 +07:00