feature: Assign feed season to a configurable taxonomy on import
CI / Composer, lint, static analysis, and unit tests (push) Successful in 2m10s
CI / WordPress activation smoke test (push) Failing after 2m0s

Adds a 'Season taxonomy' setting (default empty) that points at an existing
taxonomy registered against the destination post type. On every successful
import the plugin reads itunes:season, looks up (or creates) a term with
that numeric name in the configured taxonomy, and assigns it to the
imported post via wp_set_object_terms.

Behavior:
- Empty option: no-op. Existing posts and installs without a season
  taxonomy are unaffected.
- Tax that doesn't exist or isn't attached to the post type: no-op.
- Feed item with no itunes:season: no-op. Manual taxonomy assignments
  are preserved on subsequent refreshes.
- Otherwise: the feed value wins, on both create and update paths.

The new assign_season_term() helper is public so it is unit-testable,
and four smoke tests cover the lookup, create-on-miss, unconfigured, and
no-season paths.
This commit is contained in:
Keith Solomon
2026-08-24 12:04:09 -05:00
parent 5b5a83ed0e
commit 1f4c6754e2
5 changed files with 318 additions and 0 deletions
+126
View File
@@ -175,4 +175,130 @@ final class SmokeTest extends TestCase {
$this->assertSame( array(), $GLOBALS['rss2cpt_test_deleted_options'] );
$this->assertSame( array(), $GLOBALS['rss2cpt_test_deleted_site_transients'] );
}
/**
* Verify that assign_season_term resolves an existing numeric term and
* assigns it to the post without creating a duplicate.
*/
public function test_assign_season_term_uses_existing_term(): void {
rss2cpt_test_reset_term_stores();
$GLOBALS['rss2cpt_test_taxonomies']['series_season'] = true;
$GLOBALS['rss2cpt_test_object_in_taxonomy']['podcast_episode']['series_season'] = true;
$GLOBALS['rss2cpt_test_terms']['series_season']['8'] = 42;
$importer = new RSS2CPT\Import\Importer();
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
$method->setAccessible( true );
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
$post_type->setAccessible( true );
$post_type->setValue( $importer, 'podcast_episode' );
$term_id = $method->invoke(
$importer,
123,
array( 'season' => 8 ),
array( 'season_taxonomy' => 'series_season' )
);
$this->assertSame( 42, $term_id );
$this->assertCount( 1, $GLOBALS['rss2cpt_test_object_terms'] );
$this->assertSame(
array(
'object_id' => 123,
'term_ids' => array( 42 ),
'taxonomy' => 'series_season',
),
$GLOBALS['rss2cpt_test_object_terms'][0]
);
}
/**
* Verify that assign_season_term creates the term when the feed exposes
* a season number whose term does not yet exist (e.g. a brand-new season).
*/
public function test_assign_season_term_creates_missing_term(): void {
rss2cpt_test_reset_term_stores();
$GLOBALS['rss2cpt_test_taxonomies']['series_season'] = true;
$GLOBALS['rss2cpt_test_object_in_taxonomy']['podcast_episode']['series_season'] = true;
$importer = new RSS2CPT\Import\Importer();
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
$method->setAccessible( true );
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
$post_type->setAccessible( true );
$post_type->setValue( $importer, 'podcast_episode' );
$term_id = $method->invoke(
$importer,
456,
array( 'season' => 9 ),
array( 'season_taxonomy' => 'series_season' )
);
$this->assertGreaterThan( 0, $term_id );
$this->assertArrayHasKey( '9', $GLOBALS['rss2cpt_test_terms']['series_season'] );
$this->assertSame( $term_id, $GLOBALS['rss2cpt_test_terms']['series_season']['9'] );
$this->assertCount( 1, $GLOBALS['rss2cpt_test_object_terms'] );
$this->assertSame( array( 456, $term_id, 'series_season' ), array( $GLOBALS['rss2cpt_test_object_terms'][0]['object_id'], $GLOBALS['rss2cpt_test_object_terms'][0]['term_ids'][0], $GLOBALS['rss2cpt_test_object_terms'][0]['taxonomy'] ) );
}
/**
* Verify that assign_season_term is a no-op when no season taxonomy is
* configured (the option is empty by default for installs that don't use one).
*/
public function test_assign_season_term_is_noop_when_taxonomy_unconfigured(): void {
rss2cpt_test_reset_term_stores();
$importer = new RSS2CPT\Import\Importer();
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
$method->setAccessible( true );
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
$post_type->setAccessible( true );
$post_type->setValue( $importer, 'podcast_episode' );
$result = $method->invoke(
$importer,
789,
array( 'season' => 3 ),
array( 'season_taxonomy' => '' )
);
$this->assertFalse( $result );
$this->assertSame( array(), $GLOBALS['rss2cpt_test_object_terms'] );
}
/**
* Verify that assign_season_term is a no-op when the feed item has no
* itunes:season value (so existing manual taxonomy assignments are not
* stripped).
*/
public function test_assign_season_term_is_noop_when_feed_has_no_season(): void {
rss2cpt_test_reset_term_stores();
$GLOBALS['rss2cpt_test_taxonomies']['series_season'] = true;
$GLOBALS['rss2cpt_test_object_in_taxonomy']['podcast_episode']['series_season'] = true;
$importer = new RSS2CPT\Import\Importer();
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
$method->setAccessible( true );
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
$post_type->setAccessible( true );
$post_type->setValue( $importer, 'podcast_episode' );
$result = $method->invoke(
$importer,
321,
array( 'season' => 0 ),
array( 'season_taxonomy' => 'series_season' )
);
$this->assertFalse( $result );
$this->assertSame( array(), $GLOBALS['rss2cpt_test_object_terms'] );
}
}