diff --git a/includes/class-ajax-handlers.php b/includes/class-ajax-handlers.php index 86e06fe..d2c6ab5 100644 --- a/includes/class-ajax-handlers.php +++ b/includes/class-ajax-handlers.php @@ -151,60 +151,59 @@ class PRAutoBlogger_Ajax_Handlers { wp_send_json_success( $results ); } + /** + * AJAX handler: sync Runware model catalog on demand. + * + * Nonce-protected and requires manage_options capability. + * Returns the updated cache timestamp and model count. + * + * Side effects: makes HTTP request to Runware API, writes WP options. + */ + public function on_ajax_sync_runware_models_now(): void { + check_ajax_referer( 'prautoblogger_sync_runware_models_now', 'nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ), 403 ); + return; + } + + try { + $catalog = new PRAutoBlogger_Runware_Model_Catalog(); + $success = $catalog->sync(); + + if ( ! $success ) { + wp_send_json_error( array( 'message' => __( 'Catalog sync failed. Check logs for details.', 'prautoblogger' ) ) ); + return; + } + + $models = $catalog->get_models(); + $synced_at = $catalog->get_last_synced_at(); + $synced_at_str = $synced_at + ? wp_date( __( 'Y-m-d H:i:s', 'prautoblogger' ), $synced_at ) + : __( 'Never', 'prautoblogger' ); + + wp_send_json_success( + array( + 'message' => __( 'Runware model catalog synced successfully.', 'prautoblogger' ), + 'model_count' => count( $models ), + 'synced_at' => $synced_at_str, + ) + ); + } catch ( \Throwable $e ) { + PRAutoBlogger_Logger::instance()->error( + sprintf( 'AJAX sync Runware models failed: %s (%s)', $e->getMessage(), get_class( $e ) ), + 'runware-catalog' + ); + wp_send_json_error( array( 'message' => $e->getMessage() ) ); + } + } + /** * Get the shared model registry instance. * * @return PRAutoBlogger_OpenRouter_Model_Registry */ public function get_registry(): PRAutoBlogger_OpenRouter_Model_Registry { - - /** - * AJAX handler: sync Runware model catalog on demand. - * - * Nonce-protected and requires manage_options capability. - * Returns the updated cache timestamp and model count. - * - * Side effects: makes HTTP request to Runware API, writes WP options. - */ - public function on_ajax_sync_runware_models_now(): void { - check_ajax_referer( 'prautoblogger_sync_runware_models_now', 'nonce' ); - - if ( ! current_user_can( 'manage_options' ) ) { - wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ), 403 ); - return; - } - - try { - $catalog = new PRAutoBlogger_Runware_Model_Catalog(); - $success = $catalog->sync(); - - if ( ! $success ) { - wp_send_json_error( array( 'message' => __( 'Catalog sync failed. Check logs for details.', 'prautoblogger' ) ) ); - return; - } - - $models = $catalog->get_models(); - $synced_at = $catalog->get_last_synced_at(); - $synced_at_str = $synced_at - ? wp_date( __( 'Y-m-d H:i:s', 'prautoblogger' ), $synced_at ) - : __( 'Never', 'prautoblogger' ); - - wp_send_json_success( - array( - 'message' => __( 'Runware model catalog synced successfully.', 'prautoblogger' ), - 'model_count' => count( $models ), - 'synced_at' => $synced_at_str, - ) - ); - } catch ( \Throwable $e ) { - PRAutoBlogger_Logger::instance()->error( - sprintf( 'AJAX sync Runware models failed: %s (%s)', $e->getMessage(), get_class( $e ) ), - 'runware-catalog' - ); - wp_send_json_error( array( 'message' => $e->getMessage() ) ); - } - } - return $this->model_registry; } } diff --git a/includes/class-prautoblogger.php b/includes/class-prautoblogger.php index f407a03..f32df6d 100644 --- a/includes/class-prautoblogger.php +++ b/includes/class-prautoblogger.php @@ -181,7 +181,7 @@ class PRAutoBlogger { add_action( 'wp_ajax_prautoblogger_test_connection', array( $this->ajax_handlers, 'on_ajax_test_connection' ) ); add_action( 'wp_ajax_prautoblogger_get_models', array( $this->ajax_handlers, 'on_ajax_get_models' ) ); add_action( 'wp_ajax_prautoblogger_refresh_models', array( new PRAutoBlogger_Model_Registry_Refresh( $this->ajax_handlers->get_registry() ), 'handle' ) ); - t add_action( 'wp_ajax_prautoblogger_sync_runware_models_now', array( $this->ajax_handlers, 'on_ajax_sync_runware_models_now' ) ); + add_action( 'wp_ajax_prautoblogger_sync_runware_models_now', array( $this->ajax_handlers, 'on_ajax_sync_runware_models_now' ) ); $review_queue = new PRAutoBlogger_Review_Queue(); add_action( 'wp_ajax_prautoblogger_approve_post', array( $review_queue, 'on_ajax_approve_post' ) ); diff --git a/tests/unit/Providers/RunwareModelCatalogTest.php b/tests/unit/Providers/RunwareModelCatalogTest.php index d985ed1..8b97ac8 100644 --- a/tests/unit/Providers/RunwareModelCatalogTest.php +++ b/tests/unit/Providers/RunwareModelCatalogTest.php @@ -122,4 +122,59 @@ class RunwareModelCatalogTest extends BaseTestCase { $this->assertContains( 'image_generation', $model['capabilities'] ); } } + + /** Happy path: successful sync caches models and get_models returns them. */ + public function test_sync_success_caches_and_returns_models(): void { + // Mock a valid Runware API response. + Functions\when( 'wp_remote_post' )->justReturn( + array( + 'response' => array( 'code' => 200 ), + 'body' => wp_json_encode( + array( + 'data' => array( + array( + 'id' => 'test-model-1', + 'name' => 'Test Model 1', + 'cost' => 0.001, + 'provider' => 'runware', + ), + array( + 'id' => 'test-model-2', + 'name' => 'Test Model 2', + 'cost' => 0.002, + 'provider' => 'runware', + ), + ), + ) + ), + ) + ); + + // Mock the API key to exist. + Functions\when( 'get_option' )->alias( function ( $key, $default = false ) { + static $options = array(); + if ( 'prautoblogger_runware_api_key' === $key ) { + return 'test-api-key'; + } + return $options[ $key ] ?? $default; + } ); + + Functions\when( 'update_option' )->alias( function ( $key, $value ) { + static $options = array(); + $options[ $key ] = $value; + return true; + } ); + + // Perform the sync. + $catalog = new \PRAutoBlogger_Runware_Model_Catalog(); + $result = $catalog->sync(); + $this->assertTrue( $result, 'Sync should return true on success' ); + + // Verify get_models returns the synced models (not fallback). + $models = $catalog->get_models(); + $this->assertIsArray( $models ); + $this->assertCount( 2, $models ); + $this->assertSame( 'test-model-1', $models[0]['id'] ); + $this->assertSame( 'test-model-2', $models[1]['id'] ); + } }