Skip to content
Draft
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
40 changes: 40 additions & 0 deletions tests/Stubs/WCS_ATT_Product_Schemes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/**
* Minimal WCS_ATT_Product_Schemes stub for unit tests.
*
* The real class is provided by WooCommerce Subscriptions' bundled ("All Products for Subscriptions")
* scheme mechanism, which is not available in the WP_Mock test environment. Tests configure the public
* static properties directly to control the return values of each method for a given scenario.
*/
class WCS_ATT_Product_Schemes {

public static $hasSchemes = false;
public static $activeScheme = null;
public static $defaultScheme = null;
public static $baseScheme = null;

public static function has_subscription_schemes( $product, $context = 'any' ) {
return self::$hasSchemes;
}

public static function get_subscription_scheme( $product, $return = 'key', $scheme_key = '' ) {
return self::$activeScheme;
}

public static function get_default_subscription_scheme( $product, $return = 'key' ) {
return self::$defaultScheme;
}

public static function get_base_subscription_scheme( $product ) {
return self::$baseScheme;
}

/** Resets all configurable state back to its default (no schemes) values. */
public static function reset() {
self::$hasSchemes = false;
self::$activeScheme = null;
self::$defaultScheme = null;
self::$baseScheme = null;
}
}
41 changes: 41 additions & 0 deletions tests/Stubs/WCS_ATT_Scheme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/**
* Minimal WCS_ATT_Scheme stub for unit tests.
*
* The real class is provided by WooCommerce Subscriptions' bundled ("All Products for Subscriptions")
* scheme mechanism, which is not available in the WP_Mock test environment. This stub only implements
* the getters SubscriptionProductHelper reads from a scheme.
*/
class WCS_ATT_Scheme {

private $data;

public function __construct( array $data = [] ) {
$this->data = $data;
}

public function get_period() {
return $this->data['period'] ?? '';
}

public function get_interval() {
return $this->data['interval'] ?? 1;
}

public function get_length() {
return $this->data['length'] ?? 0;
}

public function get_trial_length() {
return $this->data['trial_length'] ?? 0;
}

public function get_trial_period() {
return $this->data['trial_period'] ?? '';
}

public function get_signup_fee() {
return $this->data['signup_fee'] ?? 0.0;
}
}
64 changes: 64 additions & 0 deletions tests/Stubs/WC_Subscriptions_Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/**
* Minimal WC_Subscriptions_Product stub for unit tests.
*
* The real class is provided by the WooCommerce Subscriptions plugin, which is not available in the
* WP_Mock test environment. Tests configure the public static properties directly to control the
* return values of each method for a given scenario.
*/
class WC_Subscriptions_Product {

public static $isSubscription = false;
public static $period = '';
public static $interval = 1;
public static $length = 0;
public static $trialLength = 0;
public static $trialPeriod = '';
public static $signUpFee = 0;
public static $expirationDate = 0;

public static function is_subscription( $product ) {
return self::$isSubscription;
}

public static function get_period( $product ) {
return self::$period;
}

public static function get_interval( $product ) {
return self::$interval;
}

public static function get_length( $product ) {
return self::$length;
}

public static function get_trial_length( $product ) {
return self::$trialLength;
}

public static function get_trial_period( $product ) {
return self::$trialPeriod;
}

public static function get_sign_up_fee( $product ) {
return self::$signUpFee;
}

public static function get_expiration_date( $product, $from_date = '' ) {
return self::$expirationDate;
}

/** Resets all configurable state back to its default (non-subscription) values. */
public static function reset() {
self::$isSubscription = false;
self::$period = '';
self::$interval = 1;
self::$length = 0;
self::$trialLength = 0;
self::$trialPeriod = '';
self::$signUpFee = 0;
self::$expirationDate = 0;
}
}
12 changes: 12 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@
if (! class_exists('WP_Error')) {
require_once __DIR__ . '/Stubs/WP_Error.php';
}

if (! class_exists('WC_Subscriptions_Product')) {
require_once __DIR__ . '/Stubs/WC_Subscriptions_Product.php';
}

if (! class_exists('WCS_ATT_Scheme')) {
require_once __DIR__ . '/Stubs/WCS_ATT_Scheme.php';
}

if (! class_exists('WCS_ATT_Product_Schemes')) {
require_once __DIR__ . '/Stubs/WCS_ATT_Product_Schemes.php';
}
231 changes: 231 additions & 0 deletions tests/unit/Helpers/Subscriptions/SubscriptionProductHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
<?php

namespace SkyVerge\WooCommerce\PluginFramework\v6_3_0\Tests\Unit\Helpers\Subscriptions;

use Mockery;
use SkyVerge\WooCommerce\PluginFramework\v6_3_0\Helpers\Subscriptions\SubscriptionProductHelper;
use SkyVerge\WooCommerce\PluginFramework\v6_3_0\Tests\TestCase;
use WC_Subscriptions_Product;
use WCS_ATT_Product_Schemes;
use WCS_ATT_Scheme;
use WP_Mock;

/**
* @coversDefaultClass \SkyVerge\WooCommerce\PluginFramework\v6_3_0\Helpers\Subscriptions\SubscriptionProductHelper
*/
final class SubscriptionProductHelperTest extends TestCase
{
private const SECONDS_PER_DAY = 86400;
private const SECONDS_PER_WEEK = 604800;
private const SECONDS_PER_MONTH = 2592000;
private const SECONDS_PER_YEAR = 31536000;

public function setUp() : void
{
parent::setUp();

WC_Subscriptions_Product::reset();
WCS_ATT_Product_Schemes::reset();
}

private function getProduct() : \WC_Product
{
return Mockery::mock('WC_Product');
}

private function mockTimeFunctions() : void
{
WP_Mock::userFunction('wcs_date_to_time')
->andReturnUsing(static function (string $date) {
return strtotime($date);
});

WP_Mock::userFunction('wcs_add_time')
->andReturnUsing(static function (int $number, string $period, int $timestamp) {
$secondsByPeriod = [
'day' => self::SECONDS_PER_DAY,
'week' => self::SECONDS_PER_WEEK,
'month' => self::SECONDS_PER_MONTH,
'year' => self::SECONDS_PER_YEAR,
];

return $timestamp + ($number * ($secondsByPeriod[$period] ?? self::SECONDS_PER_DAY));
});
}

/** @covers ::isSubscriptionProduct() */
public function testIsNotSubscriptionProductWhenNeitherMechanismApplies() : void
{
$this->assertFalse(SubscriptionProductHelper::isSubscriptionProduct($this->getProduct()));
}

/** @covers ::isSubscriptionProduct() */
public function testIsSubscriptionProductForLegacySubscriptionType() : void
{
WC_Subscriptions_Product::$isSubscription = true;

$this->assertTrue(SubscriptionProductHelper::isSubscriptionProduct($this->getProduct()));
}

/** @covers ::isSubscriptionProduct() */
public function testIsSubscriptionProductForApfsScheme() : void
{
WCS_ATT_Product_Schemes::$hasSchemes = true;

$this->assertTrue(SubscriptionProductHelper::isSubscriptionProduct($this->getProduct()));
}

/** @covers ::getSubscriptionPeriod() */
/** @covers ::getSubscriptionPeriodInterval() */
/** @covers ::getSubscriptionLength() */
/** @covers ::getSubscriptionTrialLength() */
/** @covers ::getSubscriptionTrialPeriod() */
/** @covers ::getSubscriptionSignUpFee() */
public function testGettersReturnDefaultsWhenNotASubscription() : void
{
$product = $this->getProduct();

$this->assertSame('', SubscriptionProductHelper::getSubscriptionPeriod($product));
$this->assertSame(1, SubscriptionProductHelper::getSubscriptionPeriodInterval($product));
$this->assertSame(0, SubscriptionProductHelper::getSubscriptionLength($product));
$this->assertSame(0, SubscriptionProductHelper::getSubscriptionTrialLength($product));
$this->assertSame('', SubscriptionProductHelper::getSubscriptionTrialPeriod($product));
$this->assertSame(0.0, SubscriptionProductHelper::getSubscriptionSignUpFee($product));
}

/** @covers ::getSubscriptionPeriod() */
/** @covers ::getSubscriptionPeriodInterval() */
/** @covers ::getSubscriptionLength() */
/** @covers ::getSubscriptionTrialLength() */
/** @covers ::getSubscriptionTrialPeriod() */
/** @covers ::getSubscriptionSignUpFee() */
public function testGettersDelegateToLegacySubscriptionsProductForLegacyType() : void
{
WC_Subscriptions_Product::$isSubscription = true;
WC_Subscriptions_Product::$period = 'month';
WC_Subscriptions_Product::$interval = 2;
WC_Subscriptions_Product::$length = 12;
WC_Subscriptions_Product::$trialLength = 1;
WC_Subscriptions_Product::$trialPeriod = 'week';
WC_Subscriptions_Product::$signUpFee = 9.99;

// APFS schemes should be entirely ignored once the legacy type is detected.
WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = new WCS_ATT_Scheme(['period' => 'year', 'length' => 99]);

$product = $this->getProduct();

$this->assertSame('month', SubscriptionProductHelper::getSubscriptionPeriod($product));
$this->assertSame(2, SubscriptionProductHelper::getSubscriptionPeriodInterval($product));
$this->assertSame(12, SubscriptionProductHelper::getSubscriptionLength($product));
$this->assertSame(1, SubscriptionProductHelper::getSubscriptionTrialLength($product));
$this->assertSame('week', SubscriptionProductHelper::getSubscriptionTrialPeriod($product));
$this->assertSame(9.99, SubscriptionProductHelper::getSubscriptionSignUpFee($product));
}

/** @covers ::getSubscriptionPeriod() */
/** @covers ::getSubscriptionLength() */
public function testGettersReadFromActiveApfsScheme() : void
{
WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = new WCS_ATT_Scheme(['period' => 'month', 'length' => 6]);
WCS_ATT_Product_Schemes::$defaultScheme = new WCS_ATT_Scheme(['period' => 'year', 'length' => 1]);
WCS_ATT_Product_Schemes::$baseScheme = new WCS_ATT_Scheme(['period' => 'week', 'length' => 52]);

$product = $this->getProduct();

$this->assertSame('month', SubscriptionProductHelper::getSubscriptionPeriod($product));
$this->assertSame(6, SubscriptionProductHelper::getSubscriptionLength($product));
}

/** @covers ::getSubscriptionPeriod() */
/** @covers ::getSubscriptionLength() */
public function testGettersFallBackToDefaultApfsSchemeWhenNoneIsActive() : void
{
WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = null;
WCS_ATT_Product_Schemes::$defaultScheme = new WCS_ATT_Scheme(['period' => 'year', 'length' => 1]);
WCS_ATT_Product_Schemes::$baseScheme = new WCS_ATT_Scheme(['period' => 'week', 'length' => 52]);

$product = $this->getProduct();

$this->assertSame('year', SubscriptionProductHelper::getSubscriptionPeriod($product));
$this->assertSame(1, SubscriptionProductHelper::getSubscriptionLength($product));
}

/** @covers ::getSubscriptionPeriod() */
/** @covers ::getSubscriptionLength() */
public function testGettersFallBackToBaseApfsSchemeWhenNoActiveOrDefault() : void
{
WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = null;
WCS_ATT_Product_Schemes::$defaultScheme = null;
WCS_ATT_Product_Schemes::$baseScheme = new WCS_ATT_Scheme(['period' => 'week', 'length' => 52]);

$product = $this->getProduct();

$this->assertSame('week', SubscriptionProductHelper::getSubscriptionPeriod($product));
$this->assertSame(52, SubscriptionProductHelper::getSubscriptionLength($product));
}

/** @covers ::getSubscriptionExpirationDate() */
public function testExpirationDateDelegatesToLegacySubscriptionsProductForLegacyType() : void
{
WC_Subscriptions_Product::$isSubscription = true;
WC_Subscriptions_Product::$expirationDate = '2027-01-01 00:00:00';

$this->assertSame(
'2027-01-01 00:00:00',
SubscriptionProductHelper::getSubscriptionExpirationDate($this->getProduct())
);
}

/** @covers ::getSubscriptionExpirationDate() */
public function testExpirationDateIsZeroWhenApfsSchemeHasNoLength() : void
{
WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = new WCS_ATT_Scheme(['period' => 'month', 'length' => 0]);

$this->assertSame(0, SubscriptionProductHelper::getSubscriptionExpirationDate($this->getProduct()));
}

/** @covers ::getSubscriptionExpirationDate() */
public function testExpirationDateIsCalculatedFromApfsSchemeLength() : void
{
$this->mockTimeFunctions();

WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = new WCS_ATT_Scheme(['period' => 'month', 'length' => 3]);

$fromDate = '2027-01-01 00:00:00';
$expected = gmdate('Y-m-d H:i:s', strtotime($fromDate) + (3 * self::SECONDS_PER_MONTH));

$this->assertSame(
$expected,
SubscriptionProductHelper::getSubscriptionExpirationDate($this->getProduct(), $fromDate)
);
}

/** @covers ::getSubscriptionExpirationDate() */
public function testExpirationDateAccountsForApfsSchemeTrialBeforeLength() : void
{
$this->mockTimeFunctions();

WCS_ATT_Product_Schemes::$hasSchemes = true;
WCS_ATT_Product_Schemes::$activeScheme = new WCS_ATT_Scheme([
'period' => 'month',
'length' => 3,
'trial_length' => 2,
'trial_period' => 'week',
]);

$fromDate = '2027-01-01 00:00:00';
$trialExpiration = strtotime($fromDate) + (2 * self::SECONDS_PER_WEEK);
$expected = gmdate('Y-m-d H:i:s', $trialExpiration + (3 * self::SECONDS_PER_MONTH));

$this->assertSame(
$expected,
SubscriptionProductHelper::getSubscriptionExpirationDate($this->getProduct(), $fromDate)
);
}
}
Loading