recurly-8.x-1.x-dev/tests/src/Functional/SubscriptionChangeTest.php
tests/src/Functional/SubscriptionChangeTest.php
<?php
namespace Drupal\Tests\recurly\Functional;
use Drupal\recurly_test_client\RecurlyMockClient;
/**
* Tests ability to change a recurly subscription plan.
*
* @group recurly
*/
class SubscriptionChangeTest extends RecurlyBrowserTestBase {
/**
* User with subscription to silver plan.
*
* @var \Drupal\user\Entity\User
*/
protected $user;
/**
* Current subscription object.
*
* @var \Recurly_Subscription
*/
protected $subscription;
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('system_messages_block');
// Enable both silver and bedrock mock plans.
$this->config('recurly.settings')
->set('recurly_subscription_plans', [
'silver' => [
'status' => 1,
'weight' => 0,
],
'bedrock' => [
'status' => 1,
'weight' => 1,
],
])
->save();
// Create a user with a subscription to the silver plan.
$this->user = $this->createUserWithSubscription();
// Get current subscription object.
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=200', 'subscriptions/index-200-single.xml');
$recurly_account = recurly_account_load([
'entity_type' => $this->user->bundle(),
'entity_id' => $this->user->id(),
], TRUE);
$subscriptions = recurly_account_get_subscriptions($recurly_account->account_code, 'active');
/** @var \Recurly_Subscription $subscription */
$this->subscription = reset($subscriptions);
$this->drupalLogin($this->user);
}
/**
* Test proper error handling for routes.
*
* @covers \Drupal\recurly\Controller\RecurlySubscriptionChangeController::changePlan
*/
public function testRoutingErrorHandling() {
RecurlyMockClient::clear();
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/BAD-UUID/change/bedrock');
$this->assertSession()->statusCodeEquals(404);
$this->assertSession()->pageTextContains('Subscription not found');
// Test loading old plan code.
// The old plan code is the plan associated with the current subscription,
// which for our mock subscription is the "silver" plan.
RecurlyMockClient::addResponse('GET', '/plans/silver', 'plans/missing-404.xml');
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/' . $this->subscription->uuid . '/change/bedrock');
$this->assertSession()->statusCodeEquals(404);
$this->assertSession()->pageTextContains('Plan code "silver" not found');
// Test loading bad new plan code.
// The new plan code is specified in the URL when changing plans.
RecurlyMockClient::addResponse('GET', '/plans/silver', 'plans/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans/bad-plan-code', 'plans/missing-404.xml');
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/' . $this->subscription->uuid . '/change/bad-plan-code');
$this->assertSession()->statusCodeEquals(404);
$this->assertSession()->pageTextContains('Plan code "bad-plan-code" not found');
}
/**
* Test ability for user to see plans they can change too.
*/
public function testChangePlanList() {
RecurlyMockClient::clear();
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890', 'accounts/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans', 'plans/index-200.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=200', 'subscriptions/index-200-single.xml');
$this->drupalGet('/user/' . $this->user->id() . '/subscription/change');
$this->assertSession()->pageTextContains('Change plan');
$this->assertSession()
->elementTextContains('css', '.plan-silver', 'Selected');
$this->assertSession()
->elementTextContains('css', '.plan-bedrock', 'Select');
}
/**
* Upgrade to a more expensive plan immediately.
*
* This one also confirms the logic for a plan change. The other tests just
* verify wording on the confirmation form since the logic after submission
* is the same regardless.
*
* @covers \Drupal\recurly\Form\RecurlySubscriptionChangeConfirmForm
* @covers \Drupal\recurly\Form\RecurlySubscriptionChangeConfirmForm::submitForm
* @covers \Drupal\recurly\Controller\RecurlySubscriptionChangeController::changePlan
*/
public function testUpgradeImmediate() {
RecurlyMockClient::clear();
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890', 'accounts/show-200.xml');
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans/silver', 'plans/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans/bedrock', 'plans/show-200-bedrock.xml');
// When the plan change happens you get redirected to user/ID/subscriptions
// which result in a bunch of extra requests getting made.
RecurlyMockClient::addResponse('HEAD', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', 'subscriptions/head-200-single.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', 'subscriptions/index-200-single.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=200', 'subscriptions/index-200-single.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?state=past_due', 'subscriptions/empty-200.xml');
$this->config('recurly.settings')
->set('recurly_subscription_upgrade_timeframe', 'now')
->save();
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/' . $this->subscription->uuid . '/change/bedrock');
$this->assertSession()->pageTextContains('The new plan will take effect immediately and a prorated charge (or credit) will be applied to this account.');
// Verify an error is displayed if we get no response from Recurly.
$this->getSession()->getPage()->pressButton('Change plan');
$this->assertTrue(RecurlyMockClient::assertRequestMade('PUT', '/subscriptions/' . $this->subscription->uuid));
$this->assertSession()->pageTextContains('The plan could not be updated because the billing service encountered an error.');
// Now mock the response and try again.
RecurlyMockClient::clearRequestLog();
RecurlyMockClient::addResponse('PUT', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
$this->getSession()->getPage()->pressButton('Change plan');
$this->assertTrue(RecurlyMockClient::assertRequestMade('PUT', '/subscriptions/' . $this->subscription->uuid));
$this->assertSession()->pageTextContains('Plan changed to Bedrock Plan!');
}
/**
* Upgrade to a more expensive plan on renewal.
*
* @covers \Drupal\recurly\Form\RecurlySubscriptionChangeConfirmForm
*/
public function testUpgradeOnRenewal() {
RecurlyMockClient::clear();
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans/silver', 'plans/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans/bedrock', 'plans/show-200-bedrock.xml');
$this->config('recurly.settings')
->set('recurly_subscription_upgrade_timeframe', 'renewal')
->save();
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/' . $this->subscription->uuid . '/change/bedrock');
$this->assertSession()->pageTextContains('The new plan will take effect on the next billing cycle.');
}
/**
* Verify messaging for downgrades is accurate.
*
* @covers \Drupal\recurly\Form\RecurlySubscriptionChangeConfirmForm
*/
public function testDowngradeMessaging() {
RecurlyMockClient::clear();
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
RecurlyMockClient::addResponse('GET', '/plans/silver', 'plans/show-200.xml');
// Make the bedrock plan cheaper so that it is a downgrade and not an
// upgrade.
RecurlyMockClient::addResponse('GET', '/plans/bedrock', [
'fixture' => 'plans/show-200-bedrock.xml',
'alterations' => [
'//unit_amount_in_cents/USD' => 10,
],
]);
$this->config('recurly.settings')
->set('recurly_subscription_downgrade_timeframe', 'now')
->save();
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/' . $this->subscription->uuid . '/change/bedrock');
$this->assertSession()->pageTextContains('The new plan will take effect immediately and a prorated charge (or credit) will be applied to this account.');
$this->config('recurly.settings')
->set('recurly_subscription_downgrade_timeframe', 'renewal')
->save();
$this->drupalGet('/user/' . $this->user->id() . '/subscription/id/' . $this->subscription->uuid . '/change/bedrock');
$this->assertSession()->pageTextContains('The new plan will take effect on the next billing cycle.');
}
/**
* Test access to quantity change form based on module configuration.
*
* @covers \Drupal\recurly\Access\RecurlyAccessQuantity
*/
public function testChangeQuantityAccess() {
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890', 'accounts/show-200.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=200', 'subscriptions/index-200-single.xml');
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
$this->drupalLogin($this->user);
$this->config('recurly.settings')
->set('recurly_subscription_multiple', 0)
->save();
$url = recurly_url('quantity', [
'entity_type' => $this->user->bundle(),
'entity' => $this->user,
'subscription' => $this->subscription,
]);
$this->drupalGet($url);
$this->assertSession()->statusCodeEquals(403);
$this->config('recurly.settings')
->set('recurly_subscription_multiple', 1)
->save();
$this->drupalGet($url);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldExists('quantity');
}
/**
* Test the change quantity form.
*
* @covers \Drupal\recurly\Form\RecurlyChangeQuantityForm
*/
public function testChangeQuantityForm() {
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890', 'accounts/show-200.xml');
$this->config('recurly.settings')
->set('recurly_subscription_multiple', 1)
->save();
$url = recurly_url('quantity', [
'entity_type' => $this->user->bundle(),
'entity' => $this->user,
'subscription' => $this->subscription,
]);
// Error is displayed if attempt to load current subscription fails.
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/missing-404.xml');
$this->drupalGet($url);
$this->assertTrue(RecurlyMockClient::assertRequestMade('GET', '/subscriptions/' . $this->subscription->uuid));
$this->assertSession()->pageTextContains('Unable to retrieve subscription information.');
// Add a valid response and refresh the page.
RecurlyMockClient::addResponse('GET', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
$this->drupalGet($url);
$this->assertSession()->fieldValueEquals('quantity', 1);
// This should fail.
$this->submitForm(['quantity' => -5], 'Preview quantity change');
$this->assertSession()->pageTextContains('Please enter a valid quantity');
// Submit again with a valid quantity and it should create a confirmation
// step with a preview.
RecurlyMockClient::addResponse('POST', '/subscriptions/' . $this->subscription->uuid . '/preview', [
'fixture' => 'subscriptions/preview-200-change.xml',
'alterations' => [
'//uuid' => $this->subscription->uuid,
'//quantity' => 5,
],
]);
$this->submitForm(['quantity' => 5], 'Preview quantity change');
$this->assertSession()->pageTextContains('Preview changes');
// Pricing data from fixtures.
$this->assertSession()->pageTextContains('You are changing from 1 x Silver ($59.82 USD) to 5 x Silver ($20.00 USD)');
// Verifies the preview invoice from the fixture is displayed.
$this->assertSession()->elementTextContains('css', '.invoice', 'Total Due: $10.00 USD');
// If you try and submit the confirmation form with a quantity that's
// different than the preview it should re-build the confirmation instead.
$this->submitForm(['quantity' => 6], 'Confirm quantity change');
$this->assertSession()->pageTextContains('Previewed quantity must match submitted quantity. Please update the preview and try again.');
$this->submitForm(['quantity' => 6], 'Preview quantity change');
// The quantity in this string is generated from the form value not the
// Recurly API fixture so this check should work.
$this->assertSession()->pageTextContains('You are changing from 1 x Silver ($59.82 USD) to 6 x Silver ($20.00 USD)');
// This should result in an error.
RecurlyMockClient::addResponse('PUT', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/missing-404.xml');
$this->submitForm(['quantity' => 6], 'Confirm quantity change');
$this->assertSession()->pageTextContains('Unable to update subscription quantity.');
// But this should work.
RecurlyMockClient::addResponse('PUT', '/subscriptions/' . $this->subscription->uuid, 'subscriptions/show-200.xml');
// And results in a redirect so we need these mocks too for the main
// subscription list page.
RecurlyMockClient::addResponse('HEAD', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', 'subscriptions/head-200-single.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', 'subscriptions/index-200-single.xml');
RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?state=past_due', 'subscriptions/empty-200.xml');
$this->submitForm(['quantity' => 6], 'Confirm quantity change');
$this->assertSession()->pageTextContains('Your subscription has been updated.');
}
}
