v1.3.2: security hardening, CI linting, test scaffold
- Add nonce + CSRF verification to newsletter form (footer.php) - Add sanitize_callback to all 5 Customizer settings - Add PHPCS (WordPress-Core) and JS syntax checks to CI pipeline - Safeguard rsync --delete with dev-file excludes - Scaffold PHPUnit tests (bootstrap, helpers, theme setup) - Add composer.json, .phpcs.xml.dist, phpunit.xml - Bump version to 1.3.2 - Update ARCHITECTURE.md and CHANGELOG.md Addresses CTO audit findings: security (A- → A), testing (F → D+), CI/CD (C → B). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
63
tests/bootstrap.php
Normal file
63
tests/bootstrap.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* PHPUnit bootstrap file for Peptide Starter theme tests.
|
||||
*
|
||||
* What: Loads WordPress test framework and the theme.
|
||||
* Who calls it: PHPUnit via phpunit.xml bootstrap attribute.
|
||||
* Dependencies: WordPress test suite (WP_TESTS_DIR env var or default path).
|
||||
*
|
||||
* @package peptide-starter
|
||||
*/
|
||||
|
||||
// Composer autoloader.
|
||||
$autoloader = dirname( __DIR__ ) . '/vendor/autoload.php';
|
||||
if ( file_exists( $autoloader ) ) {
|
||||
require_once $autoloader;
|
||||
}
|
||||
|
||||
/*
|
||||
* Path to the WordPress test suite.
|
||||
* Set WP_TESTS_DIR env var, or fall back to /tmp/wordpress-tests-lib.
|
||||
* CI pipelines should install the WP test suite before running tests.
|
||||
*/
|
||||
$_tests_dir = getenv( 'WP_TESTS_DIR' );
|
||||
if ( ! $_tests_dir ) {
|
||||
$_tests_dir = '/tmp/wordpress-tests-lib';
|
||||
}
|
||||
|
||||
// Check that the test suite is installed.
|
||||
if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) {
|
||||
echo "WordPress test suite not found at {$_tests_dir}.\n";
|
||||
echo "Run: bash bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version]\n";
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// Load the WP test suite functions.
|
||||
require_once $_tests_dir . '/includes/functions.php';
|
||||
|
||||
/**
|
||||
* Register the theme so WordPress loads it during test setup.
|
||||
*/
|
||||
function _register_theme() {
|
||||
$theme_dir = dirname( __DIR__ );
|
||||
$theme_name = basename( $theme_dir );
|
||||
|
||||
// Tell WordPress where our theme lives.
|
||||
register_theme_directory( dirname( $theme_dir ) );
|
||||
add_filter(
|
||||
'pre_option_stylesheet',
|
||||
function () use ( $theme_name ) {
|
||||
return $theme_name;
|
||||
}
|
||||
);
|
||||
add_filter(
|
||||
'pre_option_template',
|
||||
function () use ( $theme_name ) {
|
||||
return $theme_name;
|
||||
}
|
||||
);
|
||||
}
|
||||
tests_add_filter( 'muplugins_loaded', '_register_theme' );
|
||||
|
||||
// Boot the WP test framework.
|
||||
require $_tests_dir . '/includes/bootstrap.php';
|
||||
105
tests/test-functions.php
Normal file
105
tests/test-functions.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for functions.php helper functions.
|
||||
*
|
||||
* What: Validates theme helper functions return correct values.
|
||||
* Who calls it: PHPUnit via the "theme" test suite.
|
||||
* Dependencies: WordPress test framework, theme functions.php loaded.
|
||||
*
|
||||
* @package peptide-starter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test helper functions defined in functions.php.
|
||||
*/
|
||||
class Test_Theme_Functions extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Verify the theme version constant is defined and matches semver format.
|
||||
*/
|
||||
public function test_version_constant_defined() {
|
||||
$this->assertTrue( defined( 'PEPTIDE_STARTER_VERSION' ), 'PEPTIDE_STARTER_VERSION must be defined.' );
|
||||
$this->assertMatchesRegularExpression( '/^\d+\.\d+\.\d+$/', PEPTIDE_STARTER_VERSION, 'Version must follow semver (x.y.z).' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify directory constant points to an existing path.
|
||||
*/
|
||||
public function test_dir_constant_defined() {
|
||||
$this->assertTrue( defined( 'PEPTIDE_STARTER_DIR' ), 'PEPTIDE_STARTER_DIR must be defined.' );
|
||||
$this->assertDirectoryExists( PEPTIDE_STARTER_DIR );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hero title should return the site name when no customizer override is set.
|
||||
*/
|
||||
public function test_get_hero_title_returns_default() {
|
||||
$title = peptide_starter_get_hero_title();
|
||||
$this->assertNotEmpty( $title, 'Hero title must not be empty.' );
|
||||
$this->assertEquals( get_bloginfo( 'name' ), $title, 'Default hero title should match site name.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hero subtitle should return a non-empty string.
|
||||
*/
|
||||
public function test_get_hero_subtitle_returns_default() {
|
||||
$subtitle = peptide_starter_get_hero_subtitle();
|
||||
$this->assertNotEmpty( $subtitle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search placeholder should return a non-empty string.
|
||||
*/
|
||||
public function test_get_search_placeholder_returns_default() {
|
||||
$placeholder = peptide_starter_get_search_placeholder();
|
||||
$this->assertNotEmpty( $placeholder );
|
||||
}
|
||||
|
||||
/**
|
||||
* Footer copyright should return a non-empty string.
|
||||
*/
|
||||
public function test_get_footer_copyright_returns_default() {
|
||||
$copyright = peptide_starter_get_footer_copyright();
|
||||
$this->assertNotEmpty( $copyright );
|
||||
}
|
||||
|
||||
/**
|
||||
* Newsletter form visibility filter should default to true.
|
||||
*/
|
||||
public function test_show_newsletter_form_default_true() {
|
||||
$this->assertTrue( peptide_starter_show_newsletter_form() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Newsletter form visibility can be disabled via filter.
|
||||
*/
|
||||
public function test_show_newsletter_form_filterable() {
|
||||
add_filter( 'peptide_starter_show_newsletter', '__return_false' );
|
||||
$this->assertFalse( peptide_starter_show_newsletter_form() );
|
||||
remove_filter( 'peptide_starter_show_newsletter', '__return_false' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark mode body class should only be added when dark_mode_default is true.
|
||||
*/
|
||||
public function test_dark_mode_body_class_not_added_by_default() {
|
||||
$classes = peptide_starter_dark_mode_body_class( array() );
|
||||
$this->assertNotContains( 'dark-mode-default', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize checkbox should return true for truthy input.
|
||||
*/
|
||||
public function test_sanitize_checkbox_truthy() {
|
||||
$this->assertTrue( peptide_starter_sanitize_checkbox( true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize checkbox should return false for falsy input.
|
||||
*/
|
||||
public function test_sanitize_checkbox_falsy() {
|
||||
$this->assertFalse( peptide_starter_sanitize_checkbox( false ) );
|
||||
$this->assertFalse( peptide_starter_sanitize_checkbox( null ) );
|
||||
$this->assertFalse( peptide_starter_sanitize_checkbox( '' ) );
|
||||
}
|
||||
}
|
||||
72
tests/test-theme-setup.php
Normal file
72
tests/test-theme-setup.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for theme setup and registration.
|
||||
*
|
||||
* What: Validates theme supports, nav menus, and widget areas are registered.
|
||||
* Who calls it: PHPUnit via the "theme" test suite.
|
||||
* Dependencies: WordPress test framework, theme functions.php loaded.
|
||||
*
|
||||
* @package peptide-starter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test theme setup: supports, menus, widgets, scripts.
|
||||
*/
|
||||
class Test_Theme_Setup extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Verify essential theme supports are registered.
|
||||
*/
|
||||
public function test_theme_supports_registered() {
|
||||
$this->assertTrue( current_theme_supports( 'title-tag' ), 'Theme must support title-tag.' );
|
||||
$this->assertTrue( current_theme_supports( 'post-thumbnails' ), 'Theme must support post-thumbnails.' );
|
||||
$this->assertTrue( current_theme_supports( 'custom-logo' ), 'Theme must support custom-logo.' );
|
||||
$this->assertTrue( current_theme_supports( 'html5' ), 'Theme must support html5.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify navigation menus are registered.
|
||||
*/
|
||||
public function test_nav_menus_registered() {
|
||||
$menus = get_registered_nav_menus();
|
||||
$this->assertArrayHasKey( 'primary', $menus, 'Primary menu must be registered.' );
|
||||
$this->assertArrayHasKey( 'footer', $menus, 'Footer menu must be registered.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify footer widget areas are registered.
|
||||
*/
|
||||
public function test_footer_widget_areas_registered() {
|
||||
global $wp_registered_sidebars;
|
||||
|
||||
for ( $i = 1; $i <= 4; $i++ ) {
|
||||
$this->assertArrayHasKey(
|
||||
'footer-' . $i,
|
||||
$wp_registered_sidebars,
|
||||
"Footer widget area footer-{$i} must be registered."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the custom nav walker class exists.
|
||||
*/
|
||||
public function test_nav_walker_class_exists() {
|
||||
$this->assertTrue(
|
||||
class_exists( 'Peptide_Starter_Nav_Walker' ),
|
||||
'Peptide_Starter_Nav_Walker class must be defined.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify scripts and styles are enqueued on the front end.
|
||||
*/
|
||||
public function test_scripts_and_styles_enqueued() {
|
||||
// Simulate a front-end page load.
|
||||
do_action( 'wp_enqueue_scripts' );
|
||||
|
||||
$this->assertTrue( wp_style_is( 'peptide-starter-style', 'enqueued' ), 'Main stylesheet must be enqueued.' );
|
||||
$this->assertTrue( wp_script_is( 'peptide-starter-navigation', 'enqueued' ), 'Navigation script must be enqueued.' );
|
||||
$this->assertTrue( wp_script_is( 'peptide-starter-theme', 'enqueued' ), 'Theme script must be enqueued.' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user