Files
Pod2CPT/tests/SmokeTest.php
T
Keith Solomon 7887f7d9ce feature: Add reset-cache action to settings page
Adds a 'Reset cache' button to the Podcast RSS Import settings page that
clears the SimplePie feed-cache rows (both the cached XML and the
modification timestamp) plus the importer's per-feed import lock for the
configured feed. Multisite site-transient variants are also cleared.

Use this when the importer is skipping items or refusing to re-fetch a
feed that has changed at the source. The next 'Import now' click will
re-pull the XML from the network.

clear_feed_cache() is exposed as a public method so it is unit-testable,
and a do_action hook (rss2cpt_cache_cleared) lets other code react when
the cache is cleared. Two smoke tests pin down the expected option and
site-transient names, and a third ensures an empty feed URL is a no-op.
2026-08-24 11:05:16 -05:00

179 lines
6.4 KiB
PHP

<?php
/**
* Basic project smoke tests.
*
* @package RSS2CPT
*/
use PHPUnit\Framework\TestCase;
require_once dirname( __DIR__ ) . '/src/Scheduler.php';
require_once dirname( __DIR__ ) . '/src/Admin/Settings.php';
/**
* Verifies project-level invariants without loading WordPress.
*/
final class SmokeTest extends TestCase {
/**
* Verify the runtime supports the plugin baseline.
*/
public function test_supported_php_runtime(): void {
$this->assertTrue( version_compare( PHP_VERSION, '7.4', '>=' ) );
}
/**
* Verify the persisted option name remains stable.
*/
public function test_option_key_is_stable(): void {
$this->assertSame( 'rss2cpt_settings', RSS2CPT\Options::KEY );
}
/**
* Verify backfills use a safe bounded default.
*/
public function test_default_import_limit_is_bounded(): void {
$settings = RSS2CPT\Options::get();
$this->assertSame( 25, $settings['item_limit'] );
}
/**
* Verify ongoing imports do not inherit WordPress's long feed cache.
*/
public function test_importer_uses_short_feed_cache(): void {
$importer = new RSS2CPT\Import\Importer();
$this->assertSame( 300, $importer->filter_feed_cache_lifetime( 43200, 'https://example.test/feed.xml' ) );
}
/**
* Verify the imported post_date/post_date_gmt come from the feed timestamp
* on both create and update paths, with no timezone drift.
*/
public function test_postarr_uses_feed_timestamp_when_present(): void {
$importer = new RSS2CPT\Import\Importer();
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
$post_type->setAccessible( true );
$post_type->setValue( $importer, 'podcast_episode' );
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'build_postarr' );
$method->setAccessible( true );
$timestamp = 1700000000; // 2023-11-14T22:13:20Z, a known UTC moment.
$data = self::sample_item_data( $timestamp );
$expected_gmt = gmdate( 'Y-m-d H:i:s', $timestamp );
$postarr = $method->invoke(
$importer,
$data,
'https://example.test/feed.xml',
array(
'post_status' => 'publish',
'post_author' => 1,
)
);
$this->assertSame( $expected_gmt, $postarr['post_date_gmt'] );
$this->assertSame( get_date_from_gmt( $expected_gmt ), $postarr['post_date'] );
}
/**
* Verify that a feed item without a timestamp leaves post_date unset,
* letting WordPress default to the import time on insert/update.
*/
public function test_postarr_omits_post_date_when_feed_timestamp_missing(): void {
$importer = new RSS2CPT\Import\Importer();
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
$post_type->setAccessible( true );
$post_type->setValue( $importer, 'podcast_episode' );
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'build_postarr' );
$method->setAccessible( true );
$postarr = $method->invoke(
$importer,
self::sample_item_data( 0 ),
'https://example.test/feed.xml',
array(
'post_status' => 'publish',
'post_author' => 1,
)
);
$this->assertArrayNotHasKey( 'post_date', $postarr );
$this->assertArrayNotHasKey( 'post_date_gmt', $postarr );
}
/**
* Build a complete sample item-data array.
*
* Both build_postarr() and the helper it calls, build_meta_input(), read
* every field that map_item() produces. Supplying the full set keeps the
* tests focused on the post_date behavior we are pinning down.
*
* @param int $timestamp Optional feed timestamp (zero means "no date").
* @return array<string,mixed>
*/
private static function sample_item_data( int $timestamp ): array {
return array(
'title' => 'Episode',
'content' => 'Body',
'excerpt' => 'Teaser',
'timestamp' => $timestamp,
'link' => 'https://example.test/episode',
'audio_url' => 'https://example.test/episode.mp3',
'audio_type' => 'audio/mpeg',
'audio_length' => 0,
'duration' => '',
'duration_seconds' => 0,
'season' => 0,
'episode' => 0,
'episode_type' => '',
'explicit' => '0',
'source_id' => 'src-1',
'source_key' => str_repeat( 'a', 64 ),
'creator' => '',
'fingerprint' => str_repeat( 'b', 64 ),
);
}
/**
* Verify that clear_feed_cache deletes the SimplePie feed-cache option
* rows, the importer's per-feed lock, and the multisite site-transient
* variants derived from the configured feed URL.
*/
public function test_clear_feed_cache_removes_lock_and_simplepie_transient_keys(): void {
rss2cpt_test_reset_delete_logs();
$importer = new RSS2CPT\Import\Importer();
$settings = new RSS2CPT\Admin\Settings( $importer, new RSS2CPT\Scheduler( $importer ) );
$cleared = $settings->clear_feed_cache( 'https://example.test/feed.xml', 'podcast_episode' );
$this->assertTrue( $cleared );
$this->assertContains( '_transient_feed_' . md5( 'https://example.test/feed.xml' ), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertContains( '_transient_timeout_feed_' . md5( 'https://example.test/feed.xml' ), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertContains( '_transient_feed_mod_' . md5( 'https://example.test/feed.xml' ), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertContains( '_transient_timeout_feed_mod_' . md5( 'https://example.test/feed.xml' ), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertContains( 'rss2cpt_lock_' . md5( 'https://example.test/feed.xml|podcast_episode' ), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertContains( 'feed_' . md5( 'https://example.test/feed.xml' ), $GLOBALS['rss2cpt_test_deleted_site_transients'] );
$this->assertContains( 'feed_mod_' . md5( 'https://example.test/feed.xml' ), $GLOBALS['rss2cpt_test_deleted_site_transients'] );
}
/**
* Verify that clear_feed_cache refuses to do anything destructive when
* the feed URL is empty (so an unconfigured install cannot wipe someone
* else's transient rows by accident).
*/
public function test_clear_feed_cache_is_noop_when_feed_url_empty(): void {
rss2cpt_test_reset_delete_logs();
$importer = new RSS2CPT\Import\Importer();
$settings = new RSS2CPT\Admin\Settings( $importer, new RSS2CPT\Scheduler( $importer ) );
$cleared = $settings->clear_feed_cache( '', 'podcast_episode' );
$this->assertFalse( $cleared );
$this->assertSame( array(), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertSame( array(), $GLOBALS['rss2cpt_test_deleted_site_transients'] );
}
}