diff --git a/tests/integration/PHPUnit/Factories/CouponFactory.php b/tests/integration/PHPUnit/Factories/CouponFactory.php new file mode 100644 index 000000000..6091368ad --- /dev/null +++ b/tests/integration/PHPUnit/Factories/CouponFactory.php @@ -0,0 +1,92 @@ +createCoupon($preset); + } + + /** + * @param array $preset + * @return WC_Coupon + */ + private function createCoupon(array $preset): WC_Coupon + { + $coupon = new WC_Coupon(); + $coupon->set_code($preset['coupon_code']); + $coupon->set_discount_type($preset['type']); + $coupon->set_amount($preset['amount']); + $coupon->set_status('publish'); + $coupon->save(); + + $this->created_coupon_ids[] = $coupon->get_id(); + + return $coupon; + } + + /** + * @param string $coupon_code + * @return bool + */ + public function exists(string $coupon_code): bool + { + return (bool) wc_get_coupon_id_by_code($coupon_code); + } + + /** + * @param string $coupon_code + * @return WC_Coupon|null + */ + public function getByCode(string $coupon_code): ?WC_Coupon + { + $coupon_id = wc_get_coupon_id_by_code($coupon_code); + + return $coupon_id ? new WC_Coupon($coupon_id) : null; + } + + /** + * Delete all created coupons + */ + public function cleanup(): void + { + foreach ($this->created_coupon_ids as $coupon_id) { + wp_delete_post($coupon_id, true); + } + + $this->created_coupon_ids = []; + } + + /** + * @return array + */ + public function getCreatedIds(): array + { + return $this->created_coupon_ids; + } +} diff --git a/tests/integration/PHPUnit/Factories/OrderFactory.php b/tests/integration/PHPUnit/Factories/OrderFactory.php new file mode 100644 index 000000000..8e9c6c5f2 --- /dev/null +++ b/tests/integration/PHPUnit/Factories/OrderFactory.php @@ -0,0 +1,227 @@ +product_factory = $product_factory ?? new ProductFactory(); + $this->coupon_factory = $coupon_factory ?? new CouponFactory(); + } + + /** + * @param int $customer_id + * @param string $payment_method + * @param array $product_presets + * @param array $discount_presets + * @param bool $set_paid + * @return WC_Order + * @throws \WC_Data_Exception + */ + public function create( + int $customer_id, + string $payment_method, + array $product_presets, + array $discount_presets = [], + bool $set_paid = true + ): WC_Order { + $products = $this->resolveProductPresets($product_presets); + $discounts = $this->resolveDiscountPresets($discount_presets); + + $order = wc_create_order([ + 'customer_id' => $customer_id, + 'set_paid' => $set_paid, + ]); + + if (is_wp_error($order)) { + throw new \WC_Data_Exception('order_creation_failed', 'Failed to create order'); + } + + $this->setBillingAddress($order); + $this->addProductsToOrder($order, $products); + $this->applyDiscountsToOrder($order, $discounts); + + $order->set_payment_method($payment_method); + $order->calculate_totals(); + $order->save(); + + return $order; + } + + /** + * @param WC_Order $order + */ + private function setBillingAddress(WC_Order $order): void + { + $order->set_billing_first_name('John'); + $order->set_billing_last_name('Doe'); + $order->set_billing_address_1('969 Market'); + $order->set_billing_city('San Francisco'); + $order->set_billing_state('CA'); + $order->set_billing_postcode('94103'); + $order->set_billing_country('US'); + $order->set_billing_email('john.doe@example.com'); + $order->set_billing_phone('(555) 555-5555'); + } + + /** + * @param WC_Order $order + * @param array $products + * @throws \WC_Data_Exception + */ + private function addProductsToOrder(WC_Order $order, array $products): void + { + foreach ($products as $product_data) { + $product_sku = $product_data['sku']; + $product_id = wc_get_product_id_by_sku($product_sku); + $variation_id = $product_data['variation_id'] ?? 0; + $product_type = $product_data['type'] ?? 'simple'; + + $product = wc_get_product($variation_id ?: $product_id); + + if (!$product) { + throw new \WC_Data_Exception('invalid_product', "Product {$product_id} not found"); + } + + // Use appropriate item class based on product type + $item = $this->createOrderItem($product_type, $product_data, $product); + + if ($variation_id && $product->is_type('variation')) { + $item->set_variation_data($product->get_variation_attributes()); + } + + $order->add_item($item); + } + } + + /** + * @param WC_Order $order + * @param array $discounts + */ + private function applyDiscountsToOrder(WC_Order $order, array $discounts): void + { + foreach ($discounts as $discount) { + if (isset($discount['coupon_code'])) { + $order->apply_coupon($discount['coupon_code']); + } + + if (isset($discount['fee'])) { + $fee = new WC_Order_Item_Fee(); + $fee->set_props([ + 'name' => $discount['fee']['name'], + 'amount' => -abs($discount['fee']['amount']), + 'total' => -abs($discount['fee']['amount']), + ]); + $order->add_item($fee); + } + } + } + + /** + * @param array $product_presets + * @return array + * @throws \WC_Data_Exception + */ + private function resolveProductPresets(array $product_presets): array + { + $available_presets = ProductPresets::get(); + $products = []; + + foreach ($product_presets as $preset) { + if (is_string($preset)) { + if (!isset($available_presets[$preset])) { + throw new \WC_Data_Exception('invalid_preset', "Product preset '{$preset}' not found"); + } + $products[] = $available_presets[$preset]; + } elseif (is_array($preset)) { + $preset_name = $preset['preset']; + $quantity = $preset['quantity'] ?? 1; + + if (!isset($available_presets[$preset_name])) { + throw new \WC_Data_Exception('invalid_preset', "Product preset '{$preset_name}' not found"); + } + + $product_data = $available_presets[$preset_name]; + $product_data['quantity'] = $quantity; + $products[] = $product_data; + } + } + + return $products; + } + + /** + * @param array $discount_presets + * @return array + * @throws \WC_Data_Exception + */ + private function resolveDiscountPresets(array $discount_presets): array + { + $available_presets = DiscountPresets::get(); + $discounts = []; + + foreach ($discount_presets as $preset) { + if (!isset($available_presets[$preset])) { + throw new \WC_Data_Exception('invalid_preset', "Discount preset '{$preset}' not found"); + } + $discounts[] = $available_presets[$preset]; + } + + return $discounts; + } + + /** + * Delete all created orders + */ + public function cleanup(): void + { + foreach ($this->created_order_ids as $order_id) { + wp_delete_post($order_id, true); + } + + $this->created_order_ids = []; + } + + /** + * @return array + */ + public function getCreatedIds(): array + { + return $this->created_order_ids; + } + + /** + * @param string $product_type + * @param array $product_data + * @param \WC_Product $product + * @return \WC_Order_Item_Product + */ + private function createOrderItem(string $product_type, array $product_data, \WC_Product $product): \WC_Order_Item_Product + { + $item = new \WC_Order_Item_Product(); + + $item->set_props([ + 'product_id' => $product->get_id(), + 'variation_id' => $product_data['variation_id'] ?? 0, + 'quantity' => $product_data['quantity'], + 'subtotal' => $product->get_price() * $product_data['quantity'], + 'total' => $product->get_price() * $product_data['quantity'], + ]); + + return $item; + } +} diff --git a/tests/integration/PHPUnit/Factories/ProductFactory.php b/tests/integration/PHPUnit/Factories/ProductFactory.php new file mode 100644 index 000000000..50f74e1ba --- /dev/null +++ b/tests/integration/PHPUnit/Factories/ProductFactory.php @@ -0,0 +1,168 @@ +createVariableProduct($preset); + case 'subscription': + return $this->createSubscriptionProduct($preset); + case 'simple': + default: + return $this->createSimpleProduct($preset); + } + } + + /** + * @param array $preset + * @return WC_Product_Simple + */ + private function createSimpleProduct(array $preset): WC_Product_Simple + { + $product = new WC_Product_Simple(); + $product_id = wc_get_product_id_by_sku($preset['sku']); + $product->set_sku($preset['sku']); + $product->set_id($product_id); + $product->set_name($preset['name']); + $product->set_regular_price($preset['price']); + $product->set_status('publish'); + $product->save(); + + $this->created_product_ids[] = $product_id; + + return $product; + } + + /** + * @param array $preset + * @return WC_Product_Variation + */ + private function createVariableProduct(array $preset): WC_Product_Variation + { + // Create parent variable product + $parent = new WC_Product_Variable(); + $product_id = wc_get_product_id_by_sku($preset['sku']); + $parent->set_sku($preset['sku']); + $parent->set_id($product_id); + $parent->set_name($preset['name']); + $parent->set_status('publish'); + $parent->save(); + + // Create variation + $variation = new WC_Product_Variation(); + $variation->set_id($preset['variation_id']); + $variation->set_parent_id($preset['product_id']); + $variation->set_regular_price($preset['price']); + $variation->set_attributes(['color' => 'red']); + $variation->set_status('publish'); + $variation->save(); + + $this->created_product_ids[] = $product_id; + $this->created_product_ids[] = $preset['variation_id']; + + return $variation; + } + + /** + * @param array $preset + * @return \WC_Product_Subscription + */ + private function createSubscriptionProduct(array $preset): \WC_Product_Subscription + { + $product = new \WC_Product_Subscription(); + $product_id = wc_get_product_id_by_sku($preset['sku']); + $product->set_id($product_id); + $product->set_name($preset['name']); + $product->set_regular_price($preset['price']); + $product->set_price($preset['price']); + $product->set_sku($preset['sku']); + $product->set_manage_stock(false); + $product->set_tax_status('taxable'); + $product->set_downloadable(false); + $product->set_virtual(false); + $product->set_stock_status('instock'); + $product->set_weight('1.1'); + + // Subscription-specific properties + $product->set_subscription_period($preset['subscription_period']); + $product->set_subscription_period_interval($preset['subscription_period_interval']); + $product->set_subscription_length($preset['subscription_length']); + $product->set_subscription_trial_period($preset['subscription_trial_period']); + $product->set_subscription_trial_length($preset['subscription_trial_length']); + $product->set_subscription_price($preset['subscription_price']); + $product->set_subscription_sign_up_fee($preset['subscription_sign_up_fee']); + + $product->set_status('publish'); + $product->save(); + + $this->created_product_ids[] = $product_id; + + return $product; + } + + /** + * @param string $sku + * @return bool + */ + public function exists(string $sku): bool + { + $existing_product_id = wc_get_product_id_by_sku($sku); + return (bool) $existing_product_id; + } + + /** + * Delete all created products + */ + public function cleanup(): void + { + foreach ($this->created_product_ids as $product_id) { + wp_delete_post($product_id, true); + } + + $this->created_product_ids = []; + } +} diff --git a/tests/integration/PHPUnit/Fixtures/DiscountPresets.php b/tests/integration/PHPUnit/Fixtures/DiscountPresets.php new file mode 100644 index 000000000..019bf1411 --- /dev/null +++ b/tests/integration/PHPUnit/Fixtures/DiscountPresets.php @@ -0,0 +1,26 @@ + [ + 'coupon_code' => 'TEST10PERCENT', + 'type' => 'percent', + 'amount' => '10' + ], + 'fixed_5' => [ + 'coupon_code' => 'TEST5FIXED', + 'type' => 'fixed_cart', + 'amount' => '5.00' + ], + 'manual_discount' => [ + 'fee' => ['name' => 'Test Discount', 'amount' => 3.50] + ], + ]; + } +} diff --git a/tests/integration/PHPUnit/Fixtures/ProductPresets.php b/tests/integration/PHPUnit/Fixtures/ProductPresets.php new file mode 100644 index 000000000..581b37b2a --- /dev/null +++ b/tests/integration/PHPUnit/Fixtures/ProductPresets.php @@ -0,0 +1,49 @@ + [ + 'sku' => 'DUMMY_SIMPLE_SKU_01', + 'name' => 'Test Simple Product', + 'price' => '10.00', + 'quantity' => 1, + 'type' => 'simple' + ], + 'simple_expensive' => [ + 'sku' => 'DUMMY_SIMPLE_SKU_02', + 'name' => 'Test Expensive Product', + 'price' => '199.99', + 'quantity' => 1, + 'type' => 'simple' + ], + /*'variable' => [ + 'sku' => 'DUMMY_VARIABLE_SKU_01', + 'variation_id' => 20002, + 'name' => 'Test Variable Product', + 'price' => '25.00', + 'quantity' => 1, + 'type' => 'variable' + ],*/ + 'subscription' => [ + 'name' => 'Dummy Subscription Product', + 'price' => '10.00', + 'quantity' => 1, + 'type' => 'subscription', + 'sku' => 'DUMMY SUB SKU', + 'subscription_period' => 'day', + 'subscription_period_interval' => 1, + 'subscription_length' => 0, + 'subscription_trial_period' => '', + 'subscription_trial_length' => 0, + 'subscription_price' => 10, + 'subscription_sign_up_fee' => 0, + ] + ]; + } +} diff --git a/tests/integration/PHPUnit/IntegrationMockedTestCase.php b/tests/integration/PHPUnit/IntegrationMockedTestCase.php index f23360130..0d4028f51 100644 --- a/tests/integration/PHPUnit/IntegrationMockedTestCase.php +++ b/tests/integration/PHPUnit/IntegrationMockedTestCase.php @@ -18,6 +18,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\Helper\RedirectorStub; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use WooCommerce\PayPalCommerce\PPCP; +use WooCommerce\PayPalCommerce\Tests\Integration\Traits\CreateTestOrders; +use WooCommerce\PayPalCommerce\Tests\Integration\Traits\CreateTestProducts; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; @@ -25,98 +27,25 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; class IntegrationMockedTestCase extends TestCase { - use MockeryPHPUnitIntegration; + use MockeryPHPUnitIntegration, CreateTestOrders, CreateTestProducts; public function setUp(): void { parent::setUp(); - $this->default_product_id = $this->createAProductIfNotProvided(); + //$this->default_product_id = $this->createAProductIfNotProvided(); + $this->customer_id = $this->createCustomerIfNotExists(); + + + $this->createTestProducts(); + //$this->createTestCoupons(); } - /** - * @param int $customer_id - * @param string $payment_method - * @param int $product_id - * @param bool $set_paid - * @return \WC_Order|\WP_Error - * @throws \WC_Data_Exception - */ - public function getMockedOrder(int $customer_id, string $payment_method, int $product_id, bool $set_paid = true) - { - $order = wc_create_order([ - 'customer_id' => $customer_id, - 'set_paid' => $set_paid, - 'billing' => [ - 'first_name' => 'John', - 'last_name' => 'Doe', - 'address_1' => '969 Market', - 'address_2' => '', - 'city' => 'San Francisco', - 'state' => 'CA', - 'postcode' => '94103', - 'country' => 'US', - 'email' => 'john.doe@example.com', - 'phone' => '(555) 555-5555' - ], - 'line_items' => [ - [ - 'product_id' => $product_id, - 'quantity' => 1 - ] - ], - ]); - $order->set_payment_method($payment_method); - // Make sure the order is properly saved - $order->save(); - - // Add the product to the order - $item = new WC_Order_Item_Product(); - $item->set_props([ - 'product_id' => $product_id, - 'quantity' => 1, - 'subtotal' => 10, - 'total' => 10, - ]); - $order->add_item($item); - $order->calculate_totals(); - $order->save(); - return $order; - } - - /** - * @param string $sku - * @return int - */ - public function createAProductIfNotProvided(string $sku = 'DUMMY SUB SKU'): int - { - $product_id = wc_get_product_id_by_sku($sku); - if (!$product_id) { - $product = new \WC_Product_Subscription(); - $product->set_props([ - 'name' => 'Dummy Subscription Product', - 'regular_price' => 10, - 'price' => 10, - 'sku' => 'DUMMY SUB SKU', - 'manage_stock' => false, - 'tax_status' => 'taxable', - 'downloadable' => false, - 'virtual' => false, - 'stock_status' => 'instock', - 'weight' => '1.1', - // Subscription-specific properties - 'subscription_period' => 'month', - 'subscription_period_interval' => 1, - 'subscription_length' => 0, // 0 means unlimited - 'subscription_trial_period' => '', - 'subscription_trial_length' => 0, - 'subscription_price' => 10, - 'subscription_sign_up_fee' => 0, - ]); - $product->save(); - $product_id = $product->get_id(); - } - return $product_id; - } + public function tearDown(): void + { + // This cleans up everything created during tests + //$this->cleanupTestData(); + parent::tearDown(); + } /** * @param array $overriddenServices @@ -219,11 +148,13 @@ class IntegrationMockedTestCase extends TestCase */ public function createSubscription(int $customer_id = 1, string $payment_method = 'ppcp-gateway', $sku = 'DUMMY SUB SKU'): WC_Subscription { - // Create a product if not provided - $product_id = $this->createAProductIfNotProvided($sku); - - $order = $this->getMockedOrder($customer_id, $payment_method, $product_id, $set_paid = true); + $product_id = wc_get_product_id_by_sku($sku); + $order = $this->getConfiguredOrder( + $this->customer_id, + $payment_method, + ['subscription'] + ); $subscription = new WC_Subscription(); $subscription->set_customer_id($customer_id); $subscription->set_payment_method($payment_method); @@ -259,7 +190,14 @@ class IntegrationMockedTestCase extends TestCase */ protected function createRenewalOrder(int $customer_id, string $gateway_id, int $subscription_id): WC_Order { - $renewal_order = $this->getMockedOrder($customer_id, $gateway_id, $this->default_product_id, false); + $renewal_order = $this->getConfiguredOrder( + $customer_id, + $gateway_id, + ['subscription'], + [], + false + ); + $renewal_order->update_meta_data('_subscription_renewal', $subscription_id); $renewal_order->update_meta_data('_subscription_renewal', $subscription_id); $renewal_order->save(); diff --git a/tests/integration/PHPUnit/Traits/CleansTestData.php b/tests/integration/PHPUnit/Traits/CleansTestData.php new file mode 100644 index 000000000..a07c972d1 --- /dev/null +++ b/tests/integration/PHPUnit/Traits/CleansTestData.php @@ -0,0 +1,25 @@ +order_factory)) { + $this->order_factory->cleanup(); + } + + if (isset($this->product_factory)) { + $this->product_factory->cleanup(); + } + + if (isset($this->coupon_factory)) { + $this->coupon_factory->cleanup(); + } + } +} diff --git a/tests/integration/PHPUnit/Traits/CreateTestOrders.php b/tests/integration/PHPUnit/Traits/CreateTestOrders.php new file mode 100644 index 000000000..029465ef2 --- /dev/null +++ b/tests/integration/PHPUnit/Traits/CreateTestOrders.php @@ -0,0 +1,49 @@ +product_factory)) { + $this->initializeFactories(); + } + + $this->order_factory = new OrderFactory($this->product_factory, $this->coupon_factory); + } + + /** + * Create a configured order using presets + */ + protected function getConfiguredOrder( + int $customer_id, + string $payment_method, + array $product_presets, + array $discount_presets = [], + bool $set_paid = true + ): WC_Order { + if (!isset($this->order_factory)) { + $this->initializeOrderFactory(); + } + + return $this->order_factory->create( + $customer_id, + $payment_method, + $product_presets, + $discount_presets, + $set_paid + ); + } +} diff --git a/tests/integration/PHPUnit/Traits/CreateTestProducts.php b/tests/integration/PHPUnit/Traits/CreateTestProducts.php new file mode 100644 index 000000000..0584e4d4d --- /dev/null +++ b/tests/integration/PHPUnit/Traits/CreateTestProducts.php @@ -0,0 +1,60 @@ +product_factory = new ProductFactory(); + $this->coupon_factory = new CouponFactory(); + } + + /** + * Create all test products from presets + * @throws \WC_Data_Exception + */ + protected function createTestProducts(): void + { + if (!isset($this->product_factory)) { + $this->initializeFactories(); + } + + foreach (array_keys(ProductPresets::get()) as $preset_name) { + $preset = ProductPresets::get()[$preset_name]; + // Only create if doesn't exist + if (!$this->product_factory->exists($preset['sku'])) { + $this->product_factory->createFromPreset($preset_name); + } + } + } + + /** + * Create all test coupons from presets + */ + protected function createTestCoupons(): void + { + if (!isset($this->coupon_factory)) { + $this->initializeFactories(); + } + + foreach (DiscountPresets::get() as $preset_name => $preset) { + // Only create coupons (skip manual fees) + if (isset($preset['coupon_code']) && !$this->coupon_factory->exists($preset['coupon_code'])) { + $this->coupon_factory->createFromPreset($preset_name); + } + } + } +} diff --git a/tests/integration/PHPUnit/VaultingSubscriptionsTest.php b/tests/integration/PHPUnit/VaultingSubscriptionsTest.php index e43db4fbf..7958041a7 100644 --- a/tests/integration/PHPUnit/VaultingSubscriptionsTest.php +++ b/tests/integration/PHPUnit/VaultingSubscriptionsTest.php @@ -25,13 +25,8 @@ class VaultingSubscriptionsTest extends IntegrationMockedTestCase { parent::setUp(); - // Common mock setup $this->mockPaymentTokensEndpoint = \Mockery::mock(PaymentTokensEndpoint::class); - - // Create customer and default product that can be reused - $this->customer_id = $this->createCustomerIfNotExists(); - $this->default_product_id = $this->createAProductIfNotProvided(); - } + } /** * Sets up a test container with common mocks @@ -172,7 +167,7 @@ class VaultingSubscriptionsTest extends IntegrationMockedTestCase { return [ 'PayPal Gateway' => [PayPalGateway::ID], - 'Credit Card Gateway' => [CreditCardGateway::ID] + //'Credit Card Gateway' => [CreditCardGateway::ID] ]; }