Mat Lipe Dot Com

Mat Lipe Dot Com

Reflections from a Distinguished Software Engineer

Unit Test – A Few Days Later

Another unit test for a similar feature.

This test is for a single incomplete PHP class. Clearly, I’m not messing around with this notification system.

<?php
declare( strict_types=1 );

namespace Lipe\Project\Cron;

use Lipe\Project\Meta\User_Fields;
use Lipe\Project\Taxonomies\Graduation_Year;
use Lipe\Project\User\Grade\Wp_Users;
use Lipe\Project\User\Membership;
use Lipe\Project\User\Membership\Plan;
use Lipe\Project\User\Roles;
use Lipe\Project\User\Student;
use Lipe\Project\User\User;
use Lipe\WP_Unit\Utils\PrivateAccess;
use PHPUnit\Framework\Attributes\DataProvider;

/**
 * @author Mat Lipe
 * @since  November 2027
 *
 */
class Message_NotificationTest extends \WP_UnitTestCase {
	public static \wpdb $wpdb;


	protected function setUp(): void {
		parent::setUp();
		self::$wpdb = $GLOBALS['wpdb'];
	}


	protected function tearDown(): void {
		$GLOBALS['wpdb'] = self::$wpdb;
		parent::tearDown();
	}


	public function test_get_matching_users_incremented(): void {
		// prime caches to avoid conflicting queries.
		Plan::TEAMS_YEARLY->variation_id();
		Plan::TEAMS_MONTHLY->variation_id();
		Student\Join_Table::in();
		$params = [
			[
				Graduation_Year::from_year( new \DateTimeImmutable( '2026-01-01' ) ),
				Graduation_Year::from_year( new \DateTimeImmutable( '2027-01-01' ) ),
			],
			[
				Plan::TEAMS_YEARLY,
				Plan::TEAMS_MONTHLY,
			],
		];

		$mock = $this->getMockBuilder( \wpdb::class )
		             ->setConstructorArgs( [ DB_USER, DB_PASSWORD, DB_NAME, DB_HOST ] )
		             ->onlyMethods( [ 'get_results' ] )
		             ->getMock();

		$mock->expects( $this->exactly( 3 ) )
		     ->method( 'get_results' )
		     ->willReturnCallback( function( $query ) {
			     static $call_count = 0;
			     $call_count ++;

			     if ( 1 === $call_count ) {
				     $this->assertSameIgnoreLeadingWhitespace( "SELECT DISTINCT users.ID as parent_id, students.ID as student_id
FROM `wp_users` AS users
    INNER JOIN `wp_wc_orders` AS orders
        ON orders.customer_id = users.ID
              AND orders.type = 'shop_subscription'
         # Limit to active subscriptions.
              AND orders.status IN ('wc-active', 'wc-pending-cancel')

    INNER JOIN `wp_woocommerce_order_items` as order_items
        ON order_items.order_id = orders.id
              AND order_items.order_item_type = 'line_item'

    INNER JOIN `wp_woocommerce_order_itemmeta` AS itemmeta
        ON order_items.order_item_id = itemmeta.order_item_id
              AND itemmeta.meta_key IN ('_variation_id', '_product_id')
        	# Limit to subscriptions to the provided plans.
              AND itemmeta.meta_value IN ('" . Plan::TEAMS_YEARLY->variation_id() . "','" . Plan::TEAMS_MONTHLY->variation_id() . "')

         # Joins to filter by graduation year.
    INNER JOIN `wp_parent_student_relationships` student_relationship
        ON student_relationship.parent_id = `users`.ID
    
    INNER JOIN `wp_users` students 
        ON student_relationship.student_id = students.ID
         	# Limit to graduation years.
             AND students.graduation_year IN ('2026-01-01','2027-01-01')

         # Parents join to filter by notifications enabled.
    LEFT JOIN `wp_usermeta` as mt0 
        ON users.ID = mt0.user_id
             AND mt0.meta_key = 'lipe/project/meta/user-fields/text-notifications'
    LEFT JOIN `wp_usermeta` AS mt1 
        ON users.ID = mt1.user_id
             AND mt1.meta_key = 'lipe/project/meta/user-fields/email-notifications'

         # Students join to filter by notifications enabled.
    LEFT JOIN `wp_usermeta` AS mt2 
        ON students.ID = mt2.user_id
             AND mt2.meta_key = 'lipe/project/meta/user-fields/text-notifications'
    LEFT JOIN `wp_usermeta` AS mt3 
        ON students.ID = mt3.user_id
             AND mt3.meta_key = 'lipe/project/meta/user-fields/email-notifications'

WHERE 
  # Limit to users with notifications enabled.
  # May include students or parents who don't have notifications enabled
  # if their counterpart has notifications enabled.
  (
    mt0.user_id IS NULL OR
    mt1.user_id IS NULL OR
    mt2.user_id IS NULL OR
    mt3.user_id IS NULL
)

ORDER BY parent_id LIMIT 500 OFFSET 0", $query );

				     return \array_fill( 0, 500, 1 );
			     }
			     if ( 2 === $call_count ) {
				     $this->assertSameIgnoreLeadingWhitespace( "SELECT DISTINCT users.ID as parent_id, students.ID as student_id
FROM `wp_users` AS users
    INNER JOIN `wp_wc_orders` AS orders
        ON orders.customer_id = users.ID
              AND orders.type = 'shop_subscription'
         # Limit to active subscriptions.
              AND orders.status IN ('wc-active', 'wc-pending-cancel')

    INNER JOIN `wp_woocommerce_order_items` as order_items
        ON order_items.order_id = orders.id
              AND order_items.order_item_type = 'line_item'

    INNER JOIN `wp_woocommerce_order_itemmeta` AS itemmeta
        ON order_items.order_item_id = itemmeta.order_item_id
              AND itemmeta.meta_key IN ('_variation_id', '_product_id')
        	# Limit to subscriptions to the provided plans.
              AND itemmeta.meta_value IN ('" . Plan::TEAMS_YEARLY->variation_id() . "','" . Plan::TEAMS_MONTHLY->variation_id() . "')

         # Joins to filter by graduation year.
    INNER JOIN `wp_parent_student_relationships` student_relationship
        ON student_relationship.parent_id = `users`.ID
    
    INNER JOIN `wp_users` students 
        ON student_relationship.student_id = students.ID
         	# Limit to graduation years.
             AND students.graduation_year IN ('2026-01-01','2027-01-01')

         # Parents join to filter by notifications enabled.
    LEFT JOIN `wp_usermeta` as mt0 
        ON users.ID = mt0.user_id
             AND mt0.meta_key = 'lipe/project/meta/user-fields/text-notifications'
    LEFT JOIN `wp_usermeta` AS mt1 
        ON users.ID = mt1.user_id
             AND mt1.meta_key = 'lipe/project/meta/user-fields/email-notifications'

         # Students join to filter by notifications enabled.
    LEFT JOIN `wp_usermeta` AS mt2 
        ON students.ID = mt2.user_id
             AND mt2.meta_key = 'lipe/project/meta/user-fields/text-notifications'
    LEFT JOIN `wp_usermeta` AS mt3 
        ON students.ID = mt3.user_id
             AND mt3.meta_key = 'lipe/project/meta/user-fields/email-notifications'

WHERE 
  # Limit to users with notifications enabled.
  # May include students or parents who don't have notifications enabled
  # if their counterpart has notifications enabled.
  (
    mt0.user_id IS NULL OR
    mt1.user_id IS NULL OR
    mt2.user_id IS NULL OR
    mt3.user_id IS NULL
)

ORDER BY parent_id LIMIT 500 OFFSET 500", $query );
				     return \array_fill( 0, 500, 1 );
			     }

			     if ( 3 === $call_count ) {
				     $this->assertSameIgnoreLeadingWhitespace( "SELECT DISTINCT users.ID as parent_id, students.ID as student_id
FROM `wp_users` AS users
    INNER JOIN `wp_wc_orders` AS orders
        ON orders.customer_id = users.ID
              AND orders.type = 'shop_subscription'
         # Limit to active subscriptions.
              AND orders.status IN ('wc-active', 'wc-pending-cancel')

    INNER JOIN `wp_woocommerce_order_items` as order_items
        ON order_items.order_id = orders.id
              AND order_items.order_item_type = 'line_item'

    INNER JOIN `wp_woocommerce_order_itemmeta` AS itemmeta
        ON order_items.order_item_id = itemmeta.order_item_id
              AND itemmeta.meta_key IN ('_variation_id', '_product_id')
        	# Limit to subscriptions to the provided plans.
              AND itemmeta.meta_value IN ('" . Plan::TEAMS_YEARLY->variation_id() . "','" . Plan::TEAMS_MONTHLY->variation_id() . "')

         # Joins to filter by graduation year.
    INNER JOIN `wp_parent_student_relationships` student_relationship
        ON student_relationship.parent_id = `users`.ID
    
    INNER JOIN `wp_users` students 
        ON student_relationship.student_id = students.ID
         	# Limit to graduation years.
             AND students.graduation_year IN ('2026-01-01','2027-01-01')

         # Parents join to filter by notifications enabled.
    LEFT JOIN `wp_usermeta` as mt0 
        ON users.ID = mt0.user_id
             AND mt0.meta_key = 'lipe/project/meta/user-fields/text-notifications'
    LEFT JOIN `wp_usermeta` AS mt1 
        ON users.ID = mt1.user_id
             AND mt1.meta_key = 'lipe/project/meta/user-fields/email-notifications'

         # Students join to filter by notifications enabled.
    LEFT JOIN `wp_usermeta` AS mt2 
        ON students.ID = mt2.user_id
             AND mt2.meta_key = 'lipe/project/meta/user-fields/text-notifications'
    LEFT JOIN `wp_usermeta` AS mt3 
        ON students.ID = mt3.user_id
             AND mt3.meta_key = 'lipe/project/meta/user-fields/email-notifications'

WHERE 
  # Limit to users with notifications enabled.
  # May include students or parents who don't have notifications enabled
  # if their counterpart has notifications enabled.
  (
    mt0.user_id IS NULL OR
    mt1.user_id IS NULL OR
    mt2.user_id IS NULL OR
    mt3.user_id IS NULL
)

ORDER BY parent_id LIMIT 500 OFFSET 1000", $query );
				     return \array_fill( 0, 26, 1 );
			     }

			     $this->fail( 'Unexpected call to get_results' );
		     } );
		$mock->set_prefix( 'wp_' );
		$GLOBALS['wpdb'] = $mock;

		\iterator_to_array( PrivateAccess::in()->call_private_method( Message_Notification::in(), 'get_users_to_notify', $params ) );
	}


	#[DataProvider( 'provideUsersToNotify' )]
	public function test_get_users_to_notify( Plan $plan, \DateTimeImmutable $year, array $user_keys ): void {
		$users = $this->populate_users();
		/** @var User[] $results */
		$results = \iterator_to_array( PrivateAccess::in()->call_private_method( Message_Notification::in(), 'get_users_to_notify', [ [ Graduation_Year::from_year( $year ) ], [ $plan ] ] ) );

		foreach ( $results as $user ) {
			wp_set_current_user( $user->get_id() );
			Membership::in()->clear_memoize_cache();
			$this->assertSame( $plan, Plan::get_current_user_plan() );
			$years = $user->get_graduation_years();
			$this->assertSame( $year->format( 'Y' ), (string) \reset( $years )->get_year() );
		}

		$user_ids = \array_map( fn( User $user ) => $user->get_id(), $results );
		$from_keys = \array_map( fn( string $key ) => $users[ $key ], $user_keys );

		$this->assertEquals( $from_keys, \array_values( $user_ids ) );
	}


	#[DataProvider( 'provideQueries' )]
	public function test_get_query( array $plans, \DateTimeImmutable $year, string $expected, int $page ): void {
		$grad_year = Graduation_Year::from_year( $year );

		$query = PrivateAccess::in()->call_private_method( Message_Notification::in(), 'get_query', [ [ $grad_year ], $plans, $page ] );

		$replaced = \str_replace( '{plan_id}', \implode( "','", \array_map( fn( Plan $plan ) => (string) $plan->variation_id(), $plans ) ), $expected );

		$this->assertSameIgnoreLeadingWhitespace( $replaced, $query );
	}


	/**
	 * @return array{
	 *     "2019 - 0": int, - both enabled
	 *     "2027 - 1": int, - both disabled
	 *     "2027 - 2": int, - text enabled
	 *     "2027 - 3": int, - email enabled
	 *     "2027 - 4": int, - both disabled
	 *     "2027 - 5": int, - both disabled
	 *     "2028 - 6": int, - both disabled
	 *     "2028 - 7": int, - both enabled
	 *     "2028 - 8": int, - text enabled
	 *     "parent-0": int, - both enabled (Plan::FREE_YEARLY)
	 *     "parent-1": int, - text disabled (Plan::FREE_YEARLY)
	 *     "parent-2": int, - both enabled (No plan)
	 *     "parent-3": int, - email disabled (Plan::TEAMS_YEARLY)
	 *     "parent-4": int, - both enabled (Plan::TEAMS_YEARLY)
	 *     "parent-5": int, - both disabled (Plan::TEAMS_YEARLY)
	 *     "parent-6": int, - both disabled (Plan::PLUS_MONTHLY)
	 *     "parent-7": int, - both enabled (Plan::TEAMS_YEARLY)
	 *     "parent-2028": int, - both enabled (Plan::TEAMS_YEARLY) (parent for all 2028 students)
	 * }
	 */
	private function populate_users(): array {
		$students = [];
		$parents = [];
		for ( $i = 0; $i < 8; $i ++ ) {
			$parents["parent-{$i}"] = self::factory()->user->create( [
				'first_name' => "Parent - {$i}",
				'role'       => Roles\Subscriber::NAME,
			] );
			wp_set_current_user( $parents["parent-{$i}"] );
			switch ( $i ) {
				case 0:
				case 1:
					tests_generate_order_with_plan( Plan::FREE_YEARLY );
					break;
				case 3:
				case 4:
				case 5:
				case 7:
					tests_generate_order_with_plan( Plan::TEAMS_YEARLY );
					break;
				case 6:
					tests_generate_order_with_plan( Plan::PLUS_MONTHLY );
			}
		}
		$parents['parent-2028'] = $parents["parent-7"];

		for ( $i = 0; $i < 1; $i ++ ) {
			$students["2019 - {$i}"] = self::factory()->user->create( [
				'first_name'              => "2019 - {$i}",
				'role'                    => Roles\Student::NAME,
				Wp_Users::GRADUATION_YEAR => '2019-01-01',
			] );
			if ( ! Student\Db::in()->add_student( User::factory( $parents['parent-0'] ), User::factory( $students["2019 - {$i}"] ) ) ) {
				$this->fail( 'Failed to add a student to a parent.' );
			}
		}
		for ( $i = 1; $i < 6; $i ++ ) {
			$students["2027 - {$i}"] = self::factory()->user->create( [
				'first_name'              => "2027 - {$i}",
				'role'                    => Roles\Student::NAME,
				Wp_Users::GRADUATION_YEAR => '2027-01-01',
			] );
			if ( ! Student\Db::in()->add_student( User::factory( $parents["parent-{$i}"] ), User::factory( $students["2027 - {$i}"] ) ) ) {
				$this->fail( 'Failed to add a student to a parent.' );
			}
		}
		for ( $i = 6; $i < 10; $i ++ ) {
			$students["2028 - {$i}"] = self::factory()->user->create( [
				'first_name'              => "2028 - {$i}",
				'role'                    => Roles\Student::NAME,
				Wp_Users::GRADUATION_YEAR => '2028-01-01',
			] );
			if ( ! Student\Db::in()->add_student( User::factory( $parents['parent-2028'] ), User::factory( $students["2028 - {$i}"] ) ) ) {
				$this->fail( 'Failed to add a student to a parent.' );
			}
		}

		$text_disabled = [
			$parents['parent-1'],
			$parents['parent-5'],
			$students['2019 - 0'],
			$students['2027 - 1'],
			$students['2027 - 3'],
			$students['2027 - 4'],
			$students['2027 - 5'],
			$students['2028 - 6'],
		];
		$email_disabled = [
			$parents['parent-3'],
			$parents['parent-5'],
			$students['2019 - 0'],
			$students['2027 - 1'],
			$students['2027 - 2'],
			$students['2027 - 4'],
			$students['2027 - 5'],
			$students['2028 - 6'],
			$students['2028 - 8'],
		];
		foreach ( $text_disabled as $student ) {
			User::factory( $student )[ User_Fields::TEXT_NOTIFICATIONS ] = User_Fields::ONOFF_OFF;
		}
		foreach ( $email_disabled as $student ) {
			User::factory( $student )[ User_Fields::EMAIL_NOTIFICATIONS ] = User_Fields::ONOFF_OFF;
		}
		foreach ( \array_values( [ ...$students, ...$parents ] ) as $i => $student ) {
			$v = \str_pad( (string) $i, 2, '0', STR_PAD_LEFT );
			User::factory( $student )[ User_Fields::PHONE ] = \sprintf( '155555555%s', $v );
		}

		return [ ...$parents, ...$students ];
	}


	public static function provideUsersToNotify(): array {
		return [
			'2027 - FREE_YEARLY'  => [
				'plan'      => Plan::FREE_YEARLY,
				'year'      => new \DateTimeImmutable( '2027-01-01' ),
				'user_keys' => [
					'parent-1',
					'2027 - 1',
				],
			],
			'2027 - TEAMS_YEARLY' => [
				'plan'      => Plan::TEAMS_YEARLY,
				'year'      => new \DateTimeImmutable( '2027-01-01' ),
				'user_keys' => [
					'parent-3',
					'2027 - 3',
					'parent-4',
					'2027 - 4',
				],
			],
		];
	}


	public static function provideQueries(): array {
		return [
			'2027 - TEAMS_YEARLY' => [
				'plans'    => [ Plan::TEAMS_YEARLY, Plan::TEAMS_MONTHLY ],
				'year'     => new \DateTimeImmutable( '2027-01-01' ),
				'page'     => 1,
				'expected' => "SELECT DISTINCT users.ID as parent_id, students.ID as student_id
FROM `z_tests_users` AS users
INNER JOIN `z_tests_wc_orders` AS orders
ON orders.customer_id = users.ID
AND orders.type = 'shop_subscription'
# Limit to active subscriptions.
AND orders.status IN ('wc-active', 'wc-pending-cancel')

INNER JOIN `z_tests_woocommerce_order_items` as order_items
ON order_items.order_id = orders.id
AND order_items.order_item_type = 'line_item'

INNER JOIN `z_tests_woocommerce_order_itemmeta` AS itemmeta
ON order_items.order_item_id = itemmeta.order_item_id
AND itemmeta.meta_key IN ('_variation_id', '_product_id')
# Limit to subscriptions to the provided plans.
AND itemmeta.meta_value IN ('{plan_id}')

# Joins to filter by graduation year.
INNER JOIN `z_tests_parent_student_relationships` student_relationship
ON student_relationship.parent_id = `users`.ID

INNER JOIN `z_tests_users` students
ON student_relationship.student_id = students.ID
# Limit to graduation years.
AND students.graduation_year IN ('2027-01-01')

# Parents join to filter by notifications enabled.
LEFT JOIN `z_tests_usermeta` as mt0
ON users.ID = mt0.user_id
AND mt0.meta_key = 'lipe/project/meta/user-fields/text-notifications'
LEFT JOIN `z_tests_usermeta` AS mt1
ON users.ID = mt1.user_id
AND mt1.meta_key = 'lipe/project/meta/user-fields/email-notifications'

# Students join to filter by notifications enabled.
LEFT JOIN `z_tests_usermeta` AS mt2
ON students.ID = mt2.user_id
AND mt2.meta_key = 'lipe/project/meta/user-fields/text-notifications'
LEFT JOIN `z_tests_usermeta` AS mt3
ON students.ID = mt3.user_id
AND mt3.meta_key = 'lipe/project/meta/user-fields/email-notifications'

WHERE
# Limit to users with notifications enabled.
# May include students or parents who don't have notifications enabled
# if their counterpart has notifications enabled.
(
mt0.user_id IS NULL OR
mt1.user_id IS NULL OR
mt2.user_id IS NULL OR
mt3.user_id IS NULL
)

ORDER BY parent_id LIMIT 500 OFFSET 0",
			],
			'2029 - Plus Monthly' => [
				'plans'    => [ Plan::PLUS_MONTHLY ],
				'year'     => new \DateTimeImmutable( '2029-01-01' ),
				'page'     => 3,
				'expected' => "SELECT DISTINCT users.ID as parent_id, students.ID as student_id
FROM `z_tests_users` AS users
INNER JOIN `z_tests_wc_orders` AS orders
ON orders.customer_id = users.ID
AND orders.type = 'shop_subscription'
# Limit to active subscriptions.
AND orders.status IN ('wc-active', 'wc-pending-cancel')

INNER JOIN `z_tests_woocommerce_order_items` as order_items
ON order_items.order_id = orders.id
AND order_items.order_item_type = 'line_item'

INNER JOIN `z_tests_woocommerce_order_itemmeta` AS itemmeta
ON order_items.order_item_id = itemmeta.order_item_id
AND itemmeta.meta_key IN ('_variation_id', '_product_id')
# Limit to subscriptions to the provided plans.
AND itemmeta.meta_value IN ('{plan_id}')

# Joins to filter by graduation year.
INNER JOIN `z_tests_parent_student_relationships` student_relationship
ON student_relationship.parent_id = `users`.ID

INNER JOIN `z_tests_users` students
ON student_relationship.student_id = students.ID
# Limit to graduation years.
AND students.graduation_year IN ('2029-01-01')

# Parents join to filter by notifications enabled.
LEFT JOIN `z_tests_usermeta` as mt0
ON users.ID = mt0.user_id
AND mt0.meta_key = 'lipe/project/meta/user-fields/text-notifications'
LEFT JOIN `z_tests_usermeta` AS mt1
ON users.ID = mt1.user_id
AND mt1.meta_key = 'lipe/project/meta/user-fields/email-notifications'

# Students join to filter by notifications enabled.
LEFT JOIN `z_tests_usermeta` AS mt2
ON students.ID = mt2.user_id
AND mt2.meta_key = 'lipe/project/meta/user-fields/text-notifications'
LEFT JOIN `z_tests_usermeta` AS mt3
ON students.ID = mt3.user_id
AND mt3.meta_key = 'lipe/project/meta/user-fields/email-notifications'

WHERE
# Limit to users with notifications enabled.
# May include students or parents who don't have notifications enabled
# if their counterpart has notifications enabled.
(
mt0.user_id IS NULL OR
mt1.user_id IS NULL OR
mt2.user_id IS NULL OR
mt3.user_id IS NULL
)

ORDER BY parent_id LIMIT 500 OFFSET 1000",
			],
		];
	}
}
  • About Me
  • Resume
  • My Story
  • Recipes
  • Contact
  • Plugins

Copyright © 2026 ยท Log in