Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@
"pluginever/framework-settings": "dev-trunk"
},
"require-dev": {
"byteever/byteever-sniffs": "^1.1.3"
"byteever/byteever-sniffs": "^1.1.3",
"phpunit/phpunit": "^9.6",
"wp-phpunit/wp-phpunit": "^6.8",
"yoast/phpunit-polyfills": "^4.0"
},
"autoload": {
"psr-4": {
"WooCommerceSerialNumbers\\": ["includes/","src/"]
}
},
"autoload-dev": {
"psr-4": {
"WooCommerceSerialNumbers\\Tests\\": "tests/phpunit/"
}
},
"config": {
"platform": {
"php": "7.4"
Expand All @@ -45,6 +53,8 @@
"makepot": "wp --allow-root i18n make-pot . --include=*.php,assets,includes,templates --domain=wc-serial-numbers",
"phpcs": "php ./vendor/bin/phpcs --standard=phpcs.xml -s -v",
"phpcbf": "php ./vendor/bin/phpcbf --standard=phpcs.xml -v",
"test:setup": "bash ./tests/bin/install.sh wordpress_test root root localhost latest",
"test": "phpunit",
"strauss": [
"test -f /tmp/strauss.phar || curl -o /tmp/strauss.phar -L https://github.com/BrianHenryIE/strauss/releases/download/0.24.1/strauss.phar",
"@php /tmp/strauss.phar",
Expand Down
38 changes: 38 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="tests/bootstrap.php"
backupGlobals="false"
colors="true"
verbose="true"
stopOnFailure="false"
cacheResult="false"
testdox="true"
beStrictAboutTestsThatDoNotTestAnything="false"
beStrictAboutOutputDuringTests="false"
convertDeprecationsToExceptions="false">

<testsuites>
<testsuite name="default">
<directory suffix="Test.php">./tests/phpunit</directory>
</testsuite>
</testsuites>

<!-- Coverage configuration. -->
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<exclude>
<directory suffix=".php">./vendor</directory>
<directory suffix=".php">./tests</directory>
</exclude>
</coverage>

<!-- Environment variables for WordPress test suite. -->
<php>
<env name="WP_TESTS_DIR" value="/tmp/wordpress-tests-lib"/>
<ini name="display_errors" value="1"/>
<ini name="error_reporting" value="-1"/>
<ini name="memory_limit" value="512M"/>
</php>
</phpunit>
2 changes: 2 additions & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
coverage/
logs/
77 changes: 77 additions & 0 deletions tests/bin/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/bash

set -e

DB_NAME=${1:-wordpress_test}
DB_USER=${2:-root}
DB_PASS=${3:-''}
DB_HOST=${4:-localhost}
WP_VERSION=${5:-latest}

WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress}
WC_DIR="$WP_CORE_DIR/wp-content/plugins/woocommerce"

# prerequisites
for cmd in wp mysql curl unzip; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "$cmd is required but not installed."
exit 1
fi
done

echo "Setting up WordPress test environment..."
# Download WordPress.
if [ ! -f "$WP_CORE_DIR/wp-load.php" ]; then
echo "Downloading WordPress..."
if [ "$WP_VERSION" = "latest" ]; then
wp core download --path="$WP_CORE_DIR" --force --allow-root || exit 1
else
wp core download --path="$WP_CORE_DIR" --version="$WP_VERSION" --force --allow-root || exit 1
fi
fi

# Download test suite matching WordPress version.
if [ ! -d "$WP_TESTS_DIR/includes" ]; then
echo "Downloading test suite..."
BRANCH="trunk"
[ "$WP_VERSION" != "latest" ] && BRANCH="$WP_VERSION"
rm -rf /tmp/wordpress-develop
git clone --depth=1 --branch "$BRANCH" https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop 2>/dev/null || exit 1
mkdir -p "$WP_TESTS_DIR"
cp -r /tmp/wordpress-develop/tests/phpunit/{includes,data} "$WP_TESTS_DIR/" || exit 1
rm -rf /tmp/wordpress-develop
fi

# Download WooCommerce (the plugin under test requires it to boot).
if [ ! -f "$WC_DIR/woocommerce.php" ]; then
echo "Downloading WooCommerce..."
mkdir -p "$WP_CORE_DIR/wp-content/plugins"
curl -sL "https://downloads.wordpress.org/plugin/woocommerce.zip" -o /tmp/woocommerce.zip || exit 1
unzip -q -o /tmp/woocommerce.zip -d "$WP_CORE_DIR/wp-content/plugins/" || exit 1
rm -f /tmp/woocommerce.zip
fi

# Create config file.
cat > "$WP_TESTS_DIR/wp-tests-config.php" << EOF
<?php
define( 'DB_NAME', '$DB_NAME' );
define( 'DB_USER', '$DB_USER' );
define( 'DB_PASSWORD', '$DB_PASS' );
define( 'DB_HOST', '$DB_HOST' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
define( 'WP_TESTS_DOMAIN', 'example.org' );
define( 'WP_TESTS_EMAIL', 'admin@example.org' );
define( 'WP_TESTS_TITLE', 'Test Blog' );
define( 'WP_PHP_BINARY', 'php' );
define( 'WPLANG', '' );
\$table_prefix = 'wptests_';
define( 'WP_DEBUG', true );
define( 'ABSPATH', '$WP_CORE_DIR/' );
EOF

# Create/reset database.
mysql --user="$DB_USER" --password="$DB_PASS" --host="$DB_HOST" -e "DROP DATABASE IF EXISTS $DB_NAME; CREATE DATABASE $DB_NAME;" 2>/dev/null || exit 1

echo "Setup complete."
48 changes: 48 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
//phpcs:ignoreFile

use WooCommerceSerialNumbers\Installer;

require_once dirname( __DIR__ ) . '/vendor/autoload.php';

// Detect where to load the WordPress test environment from.
if ( false !== getenv( 'WP_TESTS_DIR' ) ) {
$_tests_dir = getenv( 'WP_TESTS_DIR' );
} elseif ( false !== getenv( 'WP_DEVELOP_DIR' ) ) {
$_tests_dir = getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit';
} elseif ( false !== getenv( 'WP_PHPUNIT__DIR' ) ) {
$_tests_dir = getenv( 'WP_PHPUNIT__DIR' );
} else {
$_tests_dir = '/tmp/wordpress-tests-lib';
}

if ( ! file_exists( "{$_tests_dir}/includes/functions.php" ) ) {
echo "Could not find {$_tests_dir}/includes/functions.php\n";
exit( 1 );
}

require_once "{$_tests_dir}/includes/functions.php";

/**
* Load WooCommerce and the plugin under test.
*
* WooCommerce must load first — Serial Numbers self-registers on `woocommerce_loaded`.
*
* @return void
*/
function _manually_load_plugins() {
require_once WP_CONTENT_DIR . '/plugins/woocommerce/woocommerce.php';
require_once dirname( __DIR__ ) . '/wc-serial-numbers.php';
}

tests_add_filter( 'muplugins_loaded', '_manually_load_plugins' );

require_once "{$_tests_dir}/includes/bootstrap.php";

// Create the WooCommerce + plugin tables now that WordPress has loaded.
WC_Install::install();

// Serial Numbers reads orders via get_post(), so keep CPT order storage (HPOS off).
update_option( 'woocommerce_custom_orders_table_enabled', 'no' );

Installer::install();
70 changes: 70 additions & 0 deletions tests/phpunit/CronExpiryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
//phpcs:ignoreFile

namespace WooCommerceSerialNumbers\Tests;

use WooCommerceSerialNumbers\Cron;
use WooCommerceSerialNumbers\Models\Key;

/**
* Tests for the serial-key expiry cron.
*/
class CronExpiryTest extends TestCase {

/**
* Create a sold key with a given validity and order date.
*
* @param string $serial Serial key.
* @param int $validity Validity in days.
* @param string $order_date Order date (Y-m-d H:i:s).
* @return Key
*/
protected function make_dated_key( string $serial, int $validity, string $order_date ): Key {
$product = $this->create_product();
$key = Key::insert(
array(
'product_id' => $product->get_id(),
'serial_key' => $serial,
'status' => 'sold',
'validity' => $validity,
'order_date' => $order_date,
)
);
$this->assertNotWPError( $key );

return $key;
}

/**
* A key past its validity window is expired.
*/
public function testExpiresKeyPastValidity(): void {
$key = $this->make_dated_key( 'EXP-1', 30, gmdate( 'Y-m-d H:i:s', strtotime( '-40 days' ) ) );

Cron::expire_outdated_serials();

$this->assertSame( 'expired', Key::get( $key->get_id() )->get_status() );
}

/**
* A key still within its validity window is not expired.
*/
public function testDoesNotExpireWithinValidity(): void {
$key = $this->make_dated_key( 'EXP-2', 30, gmdate( 'Y-m-d H:i:s', strtotime( '-5 days' ) ) );

Cron::expire_outdated_serials();

$this->assertSame( 'sold', Key::get( $key->get_id() )->get_status() );
}

/**
* A key with zero validity (lifetime) never expires.
*/
public function testZeroValidityNeverExpires(): void {
$key = $this->make_dated_key( 'EXP-3', 0, gmdate( 'Y-m-d H:i:s', strtotime( '-400 days' ) ) );

Cron::expire_outdated_serials();

$this->assertSame( 'sold', Key::get( $key->get_id() )->get_status() );
}
}
54 changes: 54 additions & 0 deletions tests/phpunit/EncryptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
//phpcs:ignoreFile

namespace WooCommerceSerialNumbers\Tests;

use WooCommerceSerialNumbers\Encryption;

/**
* Tests for the Encryption service.
*/
class EncryptionTest extends TestCase {

/**
* Encrypting then decrypting returns the original plaintext.
*/
public function testRoundTripReturnsOriginalPlaintext(): void {
$plain = 'ABC-123-XYZ-789';
$this->assertSame( $plain, wcsn_decrypt_key( wcsn_encrypt_key( $plain ) ) );
}

/**
* Encrypting an already-encrypted value leaves it unchanged.
*/
public function testMaybeEncryptIsIdempotent(): void {
$enc = wcsn_encrypt_key( 'LICENSE-KEY-001' );
$this->assertSame( $enc, wcsn_encrypt_key( $enc ) );
}

/**
* isEncrypted() recognises ciphertext but not plaintext.
*/
public function testIsEncryptedDistinguishesCiphertextFromPlaintext(): void {
$enc = wcsn_encrypt_key( 'SOME-PLAIN-KEY' );
$this->assertTrue( Encryption::isEncrypted( $enc ) );
$this->assertFalse( Encryption::isEncrypted( 'SOME-PLAIN-KEY' ) );
}

/**
* An empty string round-trips to an empty string.
*/
public function testEmptyStringRoundTripsToEmptyString(): void {
$this->assertSame( '', wcsn_decrypt_key( wcsn_encrypt_key( '' ) ) );
}

/**
* The fixed IV makes ciphertext deterministic and reversible.
*/
public function testFixedIvProducesStableCiphertext(): void {
$a = wcsn_encrypt_key( 'STABLE-KEY' );
$b = wcsn_encrypt_key( 'STABLE-KEY' );
$this->assertSame( $a, $b );
$this->assertSame( 'STABLE-KEY', wcsn_decrypt_key( $a ) );
}
}
38 changes: 38 additions & 0 deletions tests/phpunit/InstallerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
//phpcs:ignoreFile

namespace WooCommerceSerialNumbers\Tests;

/**
* Tests for installation: tables, options and cron.
*/
class InstallerTest extends TestCase {

/**
* Both custom tables exist with their key columns.
*/
public function testTablesExist(): void {
global $wpdb;

$this->assertSame( "{$wpdb->prefix}serial_numbers", $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}serial_numbers'" ) );
$this->assertSame( "{$wpdb->prefix}serial_numbers_activations", $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}serial_numbers_activations'" ) );

$this->assertNotEmpty( $wpdb->get_var( "SHOW COLUMNS FROM {$wpdb->prefix}serial_numbers LIKE 'uuid'" ) );
$this->assertNotEmpty( $wpdb->get_var( "SHOW COLUMNS FROM {$wpdb->prefix}serial_numbers LIKE 'order_item_id'" ) );
}

/**
* The current plugin version is stored as the db version.
*/
public function testDbVersionStored(): void {
$this->assertSame( WCSN()->get_version(), WCSN()->get_db_version() );
}

/**
* Installation records the install date and schedules the hourly cron.
*/
public function testInstallDateAndCronScheduled(): void {
$this->assertNotEmpty( get_option( 'wc_serial_numbers_install_date' ) );
$this->assertNotFalse( wp_next_scheduled( 'wc_serial_numbers_hourly_event' ) );
}
}
Loading
Loading