feat(cpt): consolidate peptide CPT with PSA; drop pr_peptide (v0.2.0) (#1)

Consolidates the `peptide` CPT under PR Core ownership, dropping the `pr_peptide` slug-collision that was 404ing all 89 peptide detail pages.

- POST_TYPE: pr_peptide -> peptide
- TAX_CATEGORY: pr_peptide_category -> peptide_category
- TAX_FAMILY + pr_peptide_family taxonomy removed
- Defensive post_type_exists/taxonomy_exists guards on both registrations
- Args harmonized as superset of PSA + prior PR Core (adds thumbnail support)
- Activation hook + init:999 version-change flush handler
- Uninstall scoped to _pr_core_authored flag (PSA-authored posts survive)
- Plugin version 0.1.1 -> 0.2.0, CHANGELOG in same commit
- ARCHITECTURE.md + CONVENTIONS.md updated with CPT ownership rule

QA gate verdict: PASS (qa-reviews/peptide-repo-core/2026-04-22-874f93b.md)
Sibling release: peptiderepo/peptide-search-ai#15 (PSA v4.5.0)

Known follow-up: manage_peptide_content capability is not granted to any role as of v0.2.0. No current UI/theme reads or writes PR Core-defined meta keys, so no regression. Follow-up thread to decide role assignment.
This commit is contained in:
peptiderepo
2026-04-22 16:44:04 +07:00
committed by GitHub
parent c70352eb40
commit f086b89e2d
16 changed files with 592 additions and 78 deletions

163
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,163 @@
<?php
/**
* Minimal test bootstrap — stubs the WordPress functions used by the classes
* under test so they can be exercised in plain PHP without a WP test install.
*
* This is intentionally NOT a full WP_UnitTestCase harness. It exists so the
* targeted unit assertions in tests/unit/ can verify guard behavior, constant
* shape, and args payload shape on CI (PHP lint job) without introducing a
* PHPUnit + wp-env dependency. A proper integration test harness is a
* follow-up (see CHANGELOG 0.2.0 notes).
*
* @package PeptideRepoCore
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
if ( ! defined( 'PR_CORE_VERSION' ) ) {
define( 'PR_CORE_VERSION', '0.2.0' );
}
if ( ! defined( 'PR_CORE_PLUGIN_DIR' ) ) {
define( 'PR_CORE_PLUGIN_DIR', realpath( __DIR__ . '/..' ) . '/' );
}
if ( ! defined( 'PR_CORE_PLUGIN_URL' ) ) {
define( 'PR_CORE_PLUGIN_URL', 'http://example.test/wp-content/plugins/peptide-repo-core/' );
}
if ( ! defined( 'PR_CORE_PLUGIN_FILE' ) ) {
define( 'PR_CORE_PLUGIN_FILE', PR_CORE_PLUGIN_DIR . 'peptide-repo-core.php' );
}
if ( ! function_exists( 'trailingslashit' ) ) {
function trailingslashit( string $path ): string {
return rtrim( $path, '/\\' ) . '/';
}
}
/**
* Global harness state. Tests reset this between cases.
*
* @var array<string, mixed>
*/
$GLOBALS['pr_core_test_state'] = [
'existing_post_types' => [],
'existing_taxonomies' => [],
'registered_post_types' => [],
'registered_taxonomies' => [],
'registered_meta' => [],
'added_actions' => [],
];
function pr_core_test_reset(): void {
$GLOBALS['pr_core_test_state'] = [
'existing_post_types' => [],
'existing_taxonomies' => [],
'registered_post_types' => [],
'registered_taxonomies' => [],
'registered_meta' => [],
'added_actions' => [],
];
}
function post_type_exists( string $post_type ): bool {
return in_array( $post_type, $GLOBALS['pr_core_test_state']['existing_post_types'], true );
}
function taxonomy_exists( string $taxonomy ): bool {
return in_array( $taxonomy, $GLOBALS['pr_core_test_state']['existing_taxonomies'], true );
}
function register_post_type( string $post_type, array $args = [] ) {
$GLOBALS['pr_core_test_state']['registered_post_types'][ $post_type ] = $args;
$GLOBALS['pr_core_test_state']['existing_post_types'][] = $post_type;
return (object) [ 'name' => $post_type, 'args' => $args ];
}
function register_taxonomy( string $taxonomy, $object_type, array $args = [] ): void {
$GLOBALS['pr_core_test_state']['registered_taxonomies'][ $taxonomy ] = [
'object_type' => $object_type,
'args' => $args,
];
$GLOBALS['pr_core_test_state']['existing_taxonomies'][] = $taxonomy;
}
function register_post_meta( string $post_type, string $key, array $args = [] ): bool {
$GLOBALS['pr_core_test_state']['registered_meta'][ $post_type ][ $key ] = $args;
return true;
}
function add_action( string $hook, $callback, int $priority = 10, int $accepted_args = 1 ): bool {
$GLOBALS['pr_core_test_state']['added_actions'][] = [
'hook' => $hook,
'callback' => $callback,
'priority' => $priority,
];
return true;
}
function __( string $text, string $domain = 'default' ): string {
return $text;
}
function current_user_can( string $capability ): bool {
return true;
}
function sanitize_text_field( $value ): string {
return is_scalar( $value ) ? trim( (string) $value ) : '';
}
function wp_json_encode( $data ): string {
return json_encode( $data ) ?: '[]';
}
function absint( $value ): int {
return abs( (int) $value );
}
/* ── Test assertion helpers ─────────────────────────────────────────── */
$GLOBALS['pr_core_test_report'] = [ 'pass' => 0, 'fail' => 0, 'failures' => [] ];
function pr_assert( bool $condition, string $label ): void {
if ( $condition ) {
$GLOBALS['pr_core_test_report']['pass']++;
echo " PASS: {$label}\n";
} else {
$GLOBALS['pr_core_test_report']['fail']++;
$GLOBALS['pr_core_test_report']['failures'][] = $label;
echo " FAIL: {$label}\n";
}
}
function pr_assert_equals( $expected, $actual, string $label ): void {
$pass = ( $expected === $actual );
pr_assert( $pass, $label . ( $pass ? '' : ' — expected ' . var_export( $expected, true ) . ', got ' . var_export( $actual, true ) ) );
}
function pr_test_summary(): int {
$r = $GLOBALS['pr_core_test_report'];
echo "\n---\n";
echo "Totals: {$r['pass']} passed, {$r['fail']} failed\n";
if ( $r['fail'] > 0 ) {
echo "Failures:\n";
foreach ( $r['failures'] as $f ) {
echo " - {$f}\n";
}
return 1;
}
return 0;
}
/* ── Load the plugin's autoloader + required classes ─────────────────── */
require_once __DIR__ . '/../includes/class-pr-core-autoloader.php';
PR_Core_Autoloader::register();
require_once __DIR__ . '/../includes/cpt/class-pr-core-peptide-cpt.php';