diff --git a/CRM/Member/BAO/Membership.php b/CRM/Member/BAO/Membership.php index c927be1aff4a24cb6288002bce62e7b033cce411..4a4ed1ca5b6b50f45610ff86ba3359cb6d5db0e2 100644 --- a/CRM/Member/BAO/Membership.php +++ b/CRM/Member/BAO/Membership.php @@ -2164,21 +2164,50 @@ INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND * IMPORTANT: * Sending renewal reminders has been migrated from this job to the Scheduled Reminders function as of 4.3. * + * @param array $params + * only_active_membership_types, exclude_test_memberships, exclude_membership_status_ids + * * @return array * * @throws \CiviCRM_API3_Exception * @throws \CRM_Core_Exception */ - public static function updateAllMembershipStatus() { - // Tests for this function are in api_v3_JobTest. Please add tests for all updates. - - $updateCount = $processCount = self::updateDeceasedMembersStatuses(); - + public static function updateAllMembershipStatus($params = []) { // We want all of the statuses as id => name, even the disabled ones (cf. // CRM-15475), to identify which are Pending, Deceased, Cancelled, and // Expired. $allStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'validate'); - $allTypes = CRM_Member_PseudoConstant::membershipType(); + if (empty($params['exclude_membership_status_ids'])) { + $params['exclude_membership_status_ids'] = [ + array_search('Pending', $allStatus), + array_search('Cancelled', $allStatus), + array_search('Expired', $allStatus) ?: 0, + array_search('Deceased', $allStatus), + ]; + } + // Deceased is *always* excluded because it is has very specific processing below. + elseif (!in_array(array_search('Deceased', $allStatus), $params['exclude_membership_status_ids'])) { + $params['exclude_membership_status_ids'][] = array_search('Deceased', $allStatus); + } + + for ($index = 0; $index < count($params['exclude_membership_status_ids']); $index++) { + $queryParams[$index] = [$params['exclude_membership_status_ids'][$index], 'Integer']; + } + $membershipStatusClause = 'civicrm_membership.status_id NOT IN (%' . implode(', %', array_keys($queryParams)) . ')'; + + // Tests for this function are in api_v3_JobTest. Please add tests for all updates. + + $updateCount = $processCount = self::updateDeceasedMembersStatuses(); + + $whereClauses[] = 'civicrm_contact.is_deceased = 0'; + if ($params['exclude_test_memberships']) { + $whereClauses[] = 'civicrm_membership.is_test = 0'; + } + $whereClause = implode(' AND ', $whereClauses); + $activeMembershipClause = ''; + if ($params['only_active_membership_types']) { + $activeMembershipClause = ' AND civicrm_membership_type.is_active = 1'; + } // This query retrieves ALL memberships of active types. $baseQuery = " @@ -2197,15 +2226,8 @@ SELECT civicrm_membership.id as membership_id, FROM civicrm_membership INNER JOIN civicrm_contact ON ( civicrm_membership.contact_id = civicrm_contact.id ) INNER JOIN civicrm_membership_type ON - (civicrm_membership.membership_type_id = civicrm_membership_type.id AND civicrm_membership_type.is_active = 1) -WHERE civicrm_membership.is_test = 0 - AND civicrm_contact.is_deceased = 0 "; - - $deceaseStatusId = array_search('Deceased', $allStatus); - $pendingStatusId = array_search('Pending', $allStatus); - $cancelledStatusId = array_search('Cancelled', $allStatus); - // Expired is not reserved so might not exist. A value of `0` won't break. - $expiredStatusId = array_search('Expired', $allStatus) ?: 0; + (civicrm_membership.membership_type_id = civicrm_membership_type.id {$activeMembershipClause}) +WHERE {$whereClause}"; $query = $baseQuery . " AND civicrm_membership.is_override IS NOT NULL AND civicrm_membership.status_override_end_date IS NOT NULL"; $dao1 = CRM_Core_DAO::executeQuery($query); @@ -2214,18 +2236,14 @@ WHERE civicrm_membership.is_test = 0 } $query = $baseQuery . " AND (civicrm_membership.is_override = 0 OR civicrm_membership.is_override IS NULL) - AND civicrm_membership.status_id NOT IN (%1, %2, %3, %4) + AND {$membershipStatusClause} AND civicrm_membership.owner_membership_id IS NULL "; - $params = [ - 1 => [$pendingStatusId, 'Integer'], - 2 => [$cancelledStatusId, 'Integer'], - 3 => [$expiredStatusId, 'Integer'], - 4 => [$deceaseStatusId, 'Integer'], - ]; - $dao2 = CRM_Core_DAO::executeQuery($query, $params); + + $allMembershipTypes = CRM_Member_BAO_MembershipType::getAllMembershipTypes(); + + $dao2 = CRM_Core_DAO::executeQuery($query, $queryParams); while ($dao2->fetch()) { - // echo "."; $processCount++; // Put common parameters into array for easy access @@ -2234,7 +2252,7 @@ WHERE civicrm_membership.is_test = 0 'status_id' => $dao2->status_id, 'contact_id' => $dao2->contact_id, 'membership_type_id' => $dao2->membership_type_id, - 'membership_type' => $allTypes[$dao2->membership_type_id], + 'membership_type' => $allMembershipTypes[$dao2->membership_type_id]['name'], 'join_date' => $dao2->join_date, 'start_date' => $dao2->start_date, 'end_date' => $dao2->end_date, @@ -2261,7 +2279,6 @@ WHERE civicrm_membership.is_test = 0 $memParams = $memberParams; $memParams['status_id'] = $statusId; $memParams['createActivity'] = TRUE; - $memParams['version'] = 3; // Unset columns which should remain unchanged from their current saved // values. This avoids race condition in which these values may have @@ -2278,7 +2295,7 @@ WHERE civicrm_membership.is_test = 0 //since there is change in status. //process member record. - civicrm_api('membership', 'create', $memParams); + civicrm_api3('membership', 'create', $memParams); $updateCount++; } } diff --git a/api/v3/Job.php b/api/v3/Job.php index f34d7d48a2245f1f81713596c068f6a61fc74525..e791235232659d41f17084fb41d6cd9ee782a646 100644 --- a/api/v3/Job.php +++ b/api/v3/Job.php @@ -466,7 +466,14 @@ function civicrm_api3_job_process_membership($params) { return civicrm_api3_create_error('Could not acquire lock, another Membership Processing process is running'); } - $result = CRM_Member_BAO_Membership::updateAllMembershipStatus(); + // We need to pass this through as a simple array of membership status IDs as values. + if (!empty($params['exclude_membership_status_ids'])) { + is_array($params['exclude_membership_status_ids']) ?: $params['exclude_membership_status_ids'] = [$params['exclude_membership_status_ids']]; + } + if (!empty($params['exclude_membership_status_ids']['IN'])) { + $params['exclude_membership_status_ids'] = $params['exclude_membership_status_ids']['IN']; + } + $result = CRM_Member_BAO_Membership::updateAllMembershipStatus($params); $lock->release(); if ($result['is_error'] == 0) { @@ -477,6 +484,25 @@ function civicrm_api3_job_process_membership($params) { } } +function _civicrm_api3_job_process_membership_spec(&$params) { + $params['exclude_test_memberships']['api.default'] = TRUE; + $params['exclude_test_memberships']['title'] = 'Exclude test memberships'; + $params['exclude_test_memberships']['description'] = 'Exclude test memberships from calculations (default = TRUE)'; + $params['exclude_test_memberships']['type'] = CRM_Utils_Type::T_BOOLEAN; + $params['only_active_membership_types']['api.default'] = TRUE; + $params['only_active_membership_types']['title'] = 'Exclude disabled membership types'; + $params['only_active_membership_types']['description'] = 'Exclude disabled membership types from calculations (default = TRUE)'; + $params['only_active_membership_types']['type'] = CRM_Utils_Type::T_BOOLEAN; + $params['exclude_membership_status_ids']['title'] = 'Exclude membership status IDs from calculations'; + $params['exclude_membership_status_ids']['description'] = 'Default: Exclude Pending, Cancelled, Expired. Deceased will always be excluded'; + $params['exclude_membership_status_ids']['type'] = CRM_Utils_Type::T_INT; + $params['exclude_membership_status_ids']['pseudoconstant'] = [ + 'table' => 'civicrm_membership_status', + 'keyColumn' => 'id', + 'labelColumn' => 'label', + ]; +} + /** * This api checks and updates the status of all survey respondents. * diff --git a/tests/phpunit/CRM/Member/BAO/MembershipTest.php b/tests/phpunit/CRM/Member/BAO/MembershipTest.php index 8e0307e799d6365895c9bbd6e32f79e344989a5e..8cebb74c1c40edd5a21cec6f822619fa4d4c545f 100644 --- a/tests/phpunit/CRM/Member/BAO/MembershipTest.php +++ b/tests/phpunit/CRM/Member/BAO/MembershipTest.php @@ -550,7 +550,7 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase { $createdMembership = CRM_Member_BAO_Membership::create($params); - CRM_Member_BAO_Membership::updateAllMembershipStatus(); + civicrm_api3('Job', 'process_membership'); $membershipAfterProcess = civicrm_api3('Membership', 'get', [ 'sequential' => 1, @@ -578,7 +578,7 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase { $createdMembership = CRM_Member_BAO_Membership::create($params); - CRM_Member_BAO_Membership::updateAllMembershipStatus(); + civicrm_api3('Job', 'process_membership'); $membershipAfterProcess = civicrm_api3('Membership', 'get', [ 'sequential' => 1, @@ -605,7 +605,7 @@ class CRM_Member_BAO_MembershipTest extends CiviUnitTestCase { $createdMembership = CRM_Member_BAO_Membership::create($params); - CRM_Member_BAO_Membership::updateAllMembershipStatus(); + civicrm_api3('Job', 'process_membership'); $membershipAfterProcess = civicrm_api3('Membership', 'get', [ 'sequential' => 1, diff --git a/tests/phpunit/api/v3/JobProcessMembershipTest.php b/tests/phpunit/api/v3/JobProcessMembershipTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e0eaccbd5415eaeff6b2f4d55ea377422e35cb12 --- /dev/null +++ b/tests/phpunit/api/v3/JobProcessMembershipTest.php @@ -0,0 +1,515 @@ +<?php +/* + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC. All rights reserved. | + | | + | This work is published under the GNU AGPLv3 license with some | + | permitted exceptions and without any warranty. For full license | + | and copyright information, see https://civicrm.org/licensing | + +--------------------------------------------------------------------+ + */ + +/** + * Specific tests for the `process_membership` job. + * + * @link https://github.com/civicrm/civicrm-core/pull/16298 + * @package CiviCRM_APIv3 + * @subpackage API_Job + * + * @copyright CiviCRM LLC https://civicrm.org/licensing + */ + +/** + * Class api_v3_JobProcessMembershipTest + * @group headless + */ +class api_v3_JobProcessMembershipTest extends CiviUnitTestCase { + protected $_apiversion = 3; + + public $DBResetRequired = FALSE; + public $_entity = 'Job'; + + /** + * Caches membership status names in a key, value array + * + * @var array + */ + public $_statuses; + + /** + * Caches membership types in a key, value array + * + * @var array + */ + public $_types; + + /** + * Caches some reference dates + * + * @var string + */ + public $_yesterday; + + /** + * @var string + */ + public $_today; + + /** + * @var string + */ + public $_tomorrow; + + public function setUp() { + parent::setUp(); + $this->loadReferenceDates(); + $this->loadMembershipStatuses(); + $this->loadMembershipTypes(); + } + + public function loadMembershipStatuses() { + $statuses = civicrm_api3('MembershipStatus', 'get', ['options' => ['limit' => 0]])['values']; + $this->_statuses = array_map( + function($status) { + return $status['name']; + }, + $statuses + ); + } + + public function loadMembershipTypes() { + $this->membershipTypeCreate(['name' => 'General']); + $this->membershipTypeCreate(['name' => 'Old']); + $types = civicrm_api3('MembershipType', 'get', ['options' => ['limit' => 0]])['values']; + $this->_types = array_map( + function($type) { + return $type['name']; + }, + $types + ); + } + + public function loadReferenceDates() { + $this->_yesterday = date('Y-m-d', time() - 60 * 60 * 24); + $this->_today = date('Y-m-d'); + $this->_tomorrow = date('Y-m-d', time() + 60 * 60 * 24); + } + + public function tearDown() { + parent::tearDown(); + + // For each case, the `old` membershipt type must start as + // active, so we can assign it (we'll disabled it after + // assigning it) + $this->callAPISuccess('MembershipType', 'create', [ + 'id' => array_search('Old', $this->_types), + 'is_active' => TRUE, + ]); + } + + /** + * Creates a membership that is expired but that should be ignored + * by the process as it is in `deceased` status. + */ + public function createDeceasedMembershipThatShouldBeExpired() { + $contactId = $this->individualCreate(['is_deceased' => FALSE]); + $membershipId = $this->contactMembershipCreate([ + 'contact_id' => $contactId, + 'start_date' => $this->_yesterday, + 'end_date' => $this->_yesterday, + ]); + + $this->callAPISuccess('Membership', 'create', [ + 'id' => $membershipId, + 'status_id' => array_search('Deceased', $this->_statuses), + ]); + + return $membershipId; + } + + /** + * Creates a test membership in `grace` status that should be + * in `current` status but that won't be updated unless the process + * is explicitly told not to exclude tests. + */ + public function createTestMembershipThatShouldBeCurrent() { + $contactId = $this->individualCreate(); + $membershipId = $this->contactMembershipCreate([ + 'contact_id' => $contactId, + 'start_date' => $this->_yesterday, + 'end_date' => $this->_tomorrow, + 'is_test' => TRUE, + ]); + + $this->callAPISuccess('Membership', 'create', [ + 'id' => $membershipId, + 'status_id' => array_search('Grace', $this->_statuses), + ]); + + return $membershipId; + } + + /** + * Creates a grace membership that should be in `current` status + * that should be fixed even when the process is executed with + * the default parameters. + */ + public function createGraceMembershipThatShouldBeCurrent() { + $contactId = $this->individualCreate(); + $membershipId = $this->contactMembershipCreate([ + 'contact_id' => $contactId, + 'start_date' => $this->_yesterday, + 'end_date' => $this->_tomorrow, + ]); + + $this->callAPISuccess('Membership', 'create', [ + 'id' => $membershipId, + 'status_id' => array_search('Grace', $this->_statuses), + ]); + + return $membershipId; + } + + /** + * Creates a pending membership that should be in `current` status + * that won't be fixed unless the process is executed + * with an explicit `exclude_membership_status_ids` list that + * doesn't include it. + */ + public function createPendingMembershipThatShouldBeCurrent() { + $contactId = $this->individualCreate(); + $membershipId = $this->contactMembershipCreate([ + 'contact_id' => $contactId, + 'start_date' => $this->_yesterday, + 'end_date' => $this->_tomorrow, + ]); + + $this->callAPISuccess('Membership', 'create', [ + 'id' => $membershipId, + 'status_id' => array_search('Pending', $this->_statuses), + ]); + + return $membershipId; + } + + /** + * Creates a membership that uses an inactive membership type + * and should be in `current` status. + */ + public function createOldMembershipThatShouldBeCurrent() { + $contactId = $this->individualCreate(); + $membershipId = $this->contactMembershipCreate([ + 'contact_id' => $contactId, + 'start_date' => $this->_yesterday, + 'end_date' => $this->_tomorrow, + 'membership_type_id' => array_search('Old', $this->_types), + ]); + + $this->callAPISuccess('Membership', 'create', [ + 'id' => $membershipId, + 'status_id' => array_search('Grace', $this->_statuses), + ]); + + $this->callAPISuccess('MembershipType', 'create', [ + 'id' => array_search('Old', $this->_types), + 'is_active' => FALSE, + ]); + + return $membershipId; + } + + /** + * Returns the name of the status of a membership given its id. + */ + public function getMembershipStatus($membershipId) { + $membership = $this->callAPISuccess('Membership', 'getsingle', ['id' => $membershipId]); + $statusId = $membership['status_id']; + return $this->_statuses[$statusId]; + } + + /** + * Test that by default test memberships are excluded. + */ + public function testByDefaultTestsAreExcluded() { + $testId = $this->createTestMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', []); + + $this->assertEquals('Grace', $this->getMembershipStatus($testId)); + } + + /** + * Test that by default memberships of inactive types are excluded. + */ + public function testByDefaultInactiveAreExcluded() { + $oldId = $this->createOldMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', []); + + $this->assertEquals('Grace', $this->getMembershipStatus($oldId)); + } + + /** + * Test that by default grace memberships are considered. + */ + public function testByDefaultGraceIsConsidered() { + $graceId = $this->createGraceMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', []); + + $this->assertEquals('Current', $this->getMembershipStatus($graceId)); + } + + /** + * Test that by default pending memberships are excluded. + * + * The pending status is still excluded as it's in the + * exclude_membership_status_ids list by default. + */ + public function testByDefaultPendingIsExcluded() { + $pendingId = $this->createPendingMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', []); + + $this->assertEquals('Pending', $this->getMembershipStatus($pendingId)); + } + + /** + * Test that by default memberships of type deceased are excluded. + */ + public function testByDefaultDeceasedIsExcluded() { + $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired(); + + $this->callAPISuccess('job', 'process_membership', []); + + $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId)); + } + + /** + * Test that when including test memberships, + * pending memberships are excluded. + * + * The pending status is still excluded as it's in the + * exclude_membership_status_ids list by default. + */ + public function testIncludingTestMembershipsExcludesPending() { + $pendingId = $this->createPendingMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_test_memberships' => FALSE, + ]); + + $this->assertEquals('Pending', $this->getMembershipStatus($pendingId)); + } + + /** + * Test that when including test memberships, + * grace memberships are considered. + */ + public function testIncludingTestMembershipsConsidersGrace() { + $graceId = $this->createGraceMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_test_memberships' => FALSE, + ]); + + $this->assertEquals('Current', $this->getMembershipStatus($graceId)); + } + + /** + * Test that when including test memberships, + * memberships of inactive types are still ignored. + */ + public function testIncludingTestMembershipsIgnoresInactive() { + $oldId = $this->createOldMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_test_memberships' => FALSE, + ]); + + $this->assertEquals('Grace', $this->getMembershipStatus($oldId)); + } + + /** + * Test that when including test memberships, + * acually includes test memberships. + */ + public function testIncludingTestMembershipsActuallyIncludesThem() { + $testId = $this->createTestMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_test_memberships' => FALSE, + ]); + + $this->assertEquals('Current', $this->getMembershipStatus($testId)); + } + + /** + * Test that when including test memberships, + * memberships of type deceased are still ignored. + */ + public function testIncludingTestMembershipsStillIgnoresDeceased() { + $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_test_memberships' => FALSE, + ]); + + $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId)); + } + + /** + * Test that when including inactive membership types, + * pending memberships are considered. + * + * The pending status is still excluded as it's in the + * exclude_membership_status_ids list by default. + */ + public function testIncludingInactiveMembershipTypesStillExcludesPending() { + $pendingId = $this->createPendingMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'only_active_membership_types' => FALSE, + ]); + + $this->assertEquals('Pending', $this->getMembershipStatus($pendingId)); + } + + /** + * Test that when including inactive membership types, + * grace memberships are considered. + */ + public function testIncludingInactiveMembershipTypesConsidersGrace() { + $graceId = $this->createGraceMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'only_active_membership_types' => FALSE, + ]); + + $this->assertEquals('Current', $this->getMembershipStatus($graceId)); + } + + /** + * Test that when including inactive membership types, + * memberships of disabled membership types are considered. + */ + public function testIncludingInactiveMembershipTypesConsidersInactive() { + $oldId = $this->createOldMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'only_active_membership_types' => FALSE, + ]); + + $this->assertEquals('Current', $this->getMembershipStatus($oldId)); + } + + /** + * Test that when including inactive membership types, + * test memberships are still ignored. + */ + public function testIncludingInactiveMembershipTypesStillIgnoresTests() { + $testId = $this->createTestMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'only_active_membership_types' => FALSE, + ]); + + $this->assertEquals('Grace', $this->getMembershipStatus($testId)); + } + + /** + * Test that when including inactive membership types, + * memberships of type deceased are still ignored. + */ + public function testMembershipTypeDeceasedIsExcluded() { + $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired(); + + $this->callAPISuccess('job', 'process_membership', [ + 'only_active_membership_types' => FALSE, + ]); + + $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId)); + } + + /** + * Test that when explicitly setting the status ids to exclude, + * memberships in deceased status are still ignored. + */ + public function testSpecifyingTheStatusIdsToExcludeStillExcludesDeceased() { + $deceasedId = $this->createDeceasedMembershipThatShouldBeExpired(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_membership_status_ids' => [ + array_search('Cancelled', $this->_statuses), + ], + ]); + + $this->assertEquals('Deceased', $this->getMembershipStatus($deceasedId)); + } + + /** + * Test that when explicitly setting the status ids to exclude, + * test memberships are still ignored. + */ + public function testSpecifyingTheStatusIdsToExcludeStillExcludesTests() { + $testId = $this->createTestMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_membership_status_ids' => [ + array_search('Cancelled', $this->_statuses), + ], + ]); + + $this->assertEquals('Grace', $this->getMembershipStatus($testId)); + } + + /** + * Test that when explicitly setting the status ids to exclude, + * memberships of disabled membership types are still ignored. + */ + public function testSpecifyingTheStatusIdsToExcludeStillExcludesInactive() { + $oldId = $this->createOldMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_membership_status_ids' => [ + array_search('Cancelled', $this->_statuses), + ], + ]); + + $this->assertEquals('Grace', $this->getMembershipStatus($oldId)); + } + + /** + * Test that when explicitly setting the status ids to exclude, + * grace memberships are considered by default. + */ + public function testSpecifyingTheStatusIdsToExcludeGraceIsIncludedByDefault() { + $graceId = $this->createGraceMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_membership_status_ids' => [ + array_search('Cancelled', $this->_statuses), + ], + ]); + + $this->assertEquals('Current', $this->getMembershipStatus($graceId)); + } + + /** + * Test that when explicitly setting the status ids to exclude, + * if the specified list doesn't include pending, then pending + * memberships are considered. + */ + public function testSpecifyingTheStatusIdsToExcludePendingIsExcludedByDefault() { + $pendingId = $this->createPendingMembershipThatShouldBeCurrent(); + + $this->callAPISuccess('job', 'process_membership', [ + 'exclude_membership_status_ids' => [ + array_search('Cancelled', $this->_statuses), + ], + ]); + + $this->assertEquals('Current', $this->getMembershipStatus($pendingId)); + } + +}