TruUcde Rabbithole 12/12 : I’m fixing a hole

posted in: Development, Series, Testing, truucde, Wordpress | 0

Here we are in the final article of this series, I promise. As I was doing my plugin testing and research for this article series I encountered something in passing on the internet that got me thinking. It soon became apparent that I had a flaw in my TruUcde plugin. See article 1 if you want to review the code and try and spot my error.

The error arose this way. I was looking for a way in code to ensure that both super admins and site admins could do something in a WordPress multisite context. In this case it was add new users to subsites and by extension to the network. The wisdom I found was that using a capability that both super admins and site admins have is a sound way and further that many developers use ‘promote_users‘ as the capability. Here’s the function where this comes up in the truucde plugin:

function user_can_add(): bool {
	if ( is_user_logged_in() && current_user_can( 'promote_users' ) ) {
		return true;
	} else {
		return false;
	}
}

The problem is that while super admins and site admins can always promote users, site admins cannot always add new users to their sites and the network. They only can if their super admin has checked the box below found on the /wp-admin/network/settings.php page (Network settings).

What this box does is add a meta-capability to site admin users, which can be thought of as a configurable capability. So in the case of my plugin, whatever else happens the site admins can only create new users if this box is checked. So I started digging through the WordPress code and scouring the internet to find our what magic is unleashed when this checkbox is checked. It was the digging through the code that helped.

I spent a lot of time on this page which has flaws of its own: https://wordpress.org/support/article/roles-and-capabilities/ . Specifically what is circled below:

Lo the many times I pondered and thought, “what a shame, the add_users and create_users capabilities sound like just the ticket.” After doing some primary research with the Query Monitor plugin, one of the best developer plugins out there for WordPress, I concluded that the sentence above circled in red should be amended to read, “… only the Super Admin has these abilities unless the fucking checkbox is checked in which case the site admins have some of them too.”

Alrighty then. What my plugin needs is a change in the capability check from promote_users to create_users. Also, I need to add an instruction to my readme file to make sure the box is checked if you want things to work.

What next

Okay, so the change to the plugin code is relatively minor, but we also will need to review the test code for necessary changes and run the tests again. First the code changes. All we have to do is change this:

	if ( is_user_logged_in() && current_user_can( 'promote_users' ) )

to:

	if ( is_user_logged_in() && current_user_can( 'create_users' ) ) {

Initially it was tempting to check the network option somehow to see if the box is checked, but really the create_users check will fail if the box is not checked. False either way, so let’s keep it simple. It is conceivable that at some point I might gussy things up and add a message to warn users to bug their super admin if the box isn’t checked, but that is not on the roadmap at the moment.

The Unit Tests

So, the questions now are, how does this change affect our tests and what do we need to do to test this change? We’ll start with the unit tests and just to see what happens we’ll run the test suite and see what happens. Refer to the article 8 of this series on unit testing to review the test code.

vendor/bin/phpunit -c phpunit_unit.xml
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.EE....EE                                                           9 / 9 (100%)

Time: 167 ms, Memory: 6.00 MB

There were 4 errors:

1) TruUcdeBlog\TruUcdeTest::test_user_can_add_TT
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_1__wp_api::current_user_can('create_users'). Either the method was unexpected or its arguments matched no expected argument list for this method



/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:92
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Handler.php:43
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/truucde.php:102
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/unitTests/tests/truucdeTest.php:81
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Tools/TestCase.php:307

2) TruUcdeBlog\TruUcdeTest::test_user_can_add_TF
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_1__wp_api::current_user_can('create_users'). Either the method was unexpected or its arguments matched no expected argument list for this method



/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:92
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Handler.php:43
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/truucde.php:102
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/unitTests/tests/truucdeTest.php:109
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Tools/TestCase.php:307

3) TruUcdeBlog\TruUcdeTest::test_on_load_conditions_bail
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_1__wp_api::current_user_can('create_users'). Either the method was unexpected or its arguments matched no expected argument list for this method



/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:92
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Handler.php:43
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/truucde.php:102
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/truucde.php:67
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/unitTests/tests/truucdeTest.php:253
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Tools/TestCase.php:307

4) TruUcdeBlog\TruUcdeTest::test_on_load_processing
Mockery\Exception\NoMatchingExpectationException: No matching handler found for Mockery_1__wp_api::current_user_can('create_users'). Either the method was unexpected or its arguments matched no expected argument list for this method



/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:92
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Handler.php:43
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/truucde.php:102
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/truucde.php:67
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/unitTests/tests/truucdeTest.php:291
/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/vendor/10up/wp_mock/php/WP_Mock/Tools/TestCase.php:307

ERRORS!
Tests: 9, Assertions: 8, Errors: 4.

Generating code coverage report in HTML format ... done

Okay, we have some errors. This is good, we did expect them after all. In our unit test file we refer to 'promote_users' in several mocks of the current_user_can() function, six in total. Hey, wait a minute, if we have 'promote_users' in six mocks, then why do we only have 4 errors when we run the test? This is because on two of the user_can_add() ‘truth table’ tests the condition fails when is_logged_in() is false and the second part of the condition on the right side of && (and it’s corresponding mock) is never called. So, we need to change the 'promote_users' to 'create_users' in 6 places in our test file.

Once we do that our tests all pass. We changed 4 cases in the user_can_add() tests and 2 cases in the on_loaded() tests. The e_needs_processing() function is not influenced by the change, neither is the truucde_init() function. This is a very simple plugin with very simple tests, but we can see how the tests were useful in directing us to what we needed to fix. But not in all cases. None-the-less, the larger the code base the more time we will save ourselves. Here’s the completed code for the unit test file:

<?php /** @noinspection DuplicatedCode */

/**
 * TruUcde
 *
 * @package Truucde
 */

namespace TruUcdeBlog;

require_once 'class-wp-error.php';
use \WP_Error; // need to create empty error objects.

/**
 * TruUcde test case
 */
class TruUcdeTest extends \WP_Mock\Tools\TestCase {
	/*
	 * Define all the things
	 */
	public $Target_code  = 'user_email';
	public $Black_msg    = 'You cannot use that email address to signup. We are having problems with them blocking some of our email. Please use another email provider.';
	public $White_msg    = 'Sorry, that email address is not allowed!';
	public $Target_data  = 'User email things.';
	public $Another_code = 'user_foolishness';
	public $Another_msg  = 'The eagle lands at midnight';
	public $Another_data = 'Spy games';

	/**
	 * Test setup
	 */
	public function setUp(): void {
		\WP_Mock::setUp();
	}

	/**
	 * Test teardown
	 */
	public function tearDown(): void {
		\WP_Mock::tearDown();
	}

	/**
	 * Test that hooks are being initialized.
	 */
	public function test_it_adds_hooks(): void {

		// ensure the filter is added.
		\WP_Mock::expectFilterAdded(
			'wpmu_validate_user_signup',
			'TruUcdeBlog\on_loaded'
		);
		// Now test the init hook method of the class to check if the filter is added.
		truucde_init();
		$this->assertConditionsMet();
	}

	/**
	 * Tests that user checks are working as they should
	 * TT
	 */
	public function test_user_can_add_TT() {

		// Mock 'is_user_logged' in and 'current_user_can'.
		\WP_Mock::userFunction(
			'is_user_logged_in',
			array(
				'times'  => 1,
				'return' => true,
			)
		);
		\WP_Mock::userFunction(
			'current_user_can',
			array(
				'times'  => 1,
				'args'   => array( 'create_users' ),
				'return' => true,
			)
		);
		// Run it through the function.
		$result = user_can_add();
		// And assert
		$this->assertSame( true, $result );
	}

	/**
	 * Tests that user checks are working as they should
	 * TF
	 */
	public function test_user_can_add_TF() {

		// Mock 'is_user_logged' in and 'current_user_can'.
		\WP_Mock::userFunction(
			'is_user_logged_in',
			array(
				'times'  => 1,
				'return' => true,
			)
		);
		\WP_Mock::userFunction(
			'current_user_can',
			array(
				'times'  => 1,
				'args'   => array( 'create_users' ),
				'return' => false,
			)
		);
		// Run it through the function.
		$result = user_can_add();
		// And assert.
		$this->assertSame( false, $result );
	}

	/**
	 * Tests that user checks are working as they should
	 * FT
	 */
	public function test_user_can_add_FT() {

		// Mock 'is_user_logged' in and 'current_user_can'.
		\WP_Mock::userFunction(
			'is_user_logged_in',
			array(
				'times'  => 1,
				'return' => false,
			)
		);
		\WP_Mock::userFunction(
			'current_user_can',
			array(
				'times'  => 0,
				'args'   => array( 'create_users' ),
				'return' => true,
			)
		);
		// Run it through the function.
		$result = user_can_add();
		// And assert
		$this->assertSame( false, $result );
	}

	/**
	 * Tests that user checks are working as they should
	 * FF
	 */
	public function test_user_can_add_FF() {
		// Mock 'is_user_logged' in and 'current_user_can'.
		\WP_Mock::userFunction(
			'is_user_logged_in',
			array(
				'times'  => 1,
				'return' => false,
			)
		);
		\WP_Mock::userFunction(
			'current_user_can',
			array(
				'times'  => 0,
				'args'   => array( 'create_users' ),
				'return' => false,
			)
		);
		// Run it through the function.
		$result = user_can_add();
		// And assert
		$this->assertSame( false, $result );
	}

	/**
	 * Tests that the error object is valid, non-empty and
	 * has white/black list errors that need processing
	 *
	 * Tests error object empty (4 cases)
	 */
	public function test_e_needs_processing_empty() {
		// Is error object -> is empty (4 cases).
		$empty_wp_error = new WP_Error();
		$result         = e_needs_processing( $empty_wp_error );
		$this->assertFalse( $result );

	}

	/**
	 * Tests that the error object is valid, non-empty and
	 * has white/black list errors that need processing
	 *
	 * Tests truth table of list msg presence.
	 */
	public function test_e_needs_processing_lists() {
		// TODO: Separate functions
		// Doing this the lazy way, adding and removing as I go
		// to create the various truth table conditions
		$truucde_error = new WP_Error();

		$truucde_error->add( $this->Another_code, $this->Another_msg, $this->Another_data );

		// FF
		$result = e_needs_processing( $truucde_error );
		$this->assertFalse( $result );

		// TF
		$truucde_error->add( $this->Target_code, $this->Black_msg, $this->Target_data );

		$result = e_needs_processing( $truucde_error );
		$this->assertTrue( $result );

		// FT
		$truucde_error->remove( $this->Target_code );

		$truucde_error->add( $this->Target_code, $this->White_msg, $this->Target_data );

		$result = e_needs_processing( $truucde_error );
		$this->assertTrue( $result );

		// TT
		$truucde_error->add( $this->Target_code, $this->Black_msg, $this->Target_data );

		$result = e_needs_processing( $truucde_error );
		$this->assertTrue( $result );
	}

	/**
	 * Testing if on load conditions failure returns
	 * original object
	 */
	// TODO: other bail tests
	public function test_on_load_conditions_bail() {

		\WP_Mock::userFunction(
			'is_user_logged_in',
			array(
				'times'  => 1,
				'return' => true,
			)
		);

		\WP_Mock::userFunction(
			'current_user_can',
			array(
				'times'  => 1,
				'args'   => array( 'create_users' ),
				'return' => true,
			)
		);

		$truucde_error = new WP_Error();

		$truucde_error->add( $this->Another_code, $this->Another_msg, $this->Another_data );

		$orig_result           = array();
		$orig_result['errors'] = $truucde_error;

		$result = on_loaded( $orig_result );

		$this->assertEquals( $orig_result, $result );

	}

	/**
	 * Testing if function properly processes object
	 */
	public function test_on_load_processing() {

		\WP_Mock::userFunction(
			'is_user_logged_in',
			array(
				'times'  => 1,
				'return' => true,
			)
		);

		\WP_Mock::userFunction(
			'current_user_can',
			array(
				'times'  => 1,
				'args'   => array( 'create_users' ),
				'return' => true,
			)
		);

		$truucde_error = new WP_Error();

		$truucde_error->add( $this->Another_code, $this->Another_msg, $this->Another_data );
		$truucde_error->add( $this->Target_code, $this->Another_msg, $this->Another_data );
		$truucde_error->add( $this->Target_code, $this->Black_msg, $this->Target_data );
		$truucde_error->add( $this->Target_code, $this->White_msg, $this->Target_data );

		$orig_result           = array();
		$orig_result['errors'] = $truucde_error;

		$result = on_loaded( $orig_result );

		$error_return = $result['errors'];
		// Object is WP_Error
		$this->assertInstanceOf( WP_Error::class, $error_return );

		// Returns correct error codes
		$return_codes = $error_return->get_error_codes();
		$this->assertContains( $this->Another_code, $return_codes );
		$this->assertContains( $this->Target_code, $return_codes );

		// Returns correct messages
		$return_a_msg = $error_return->get_error_messages( $this->Another_code );
		$return_t_msg = $error_return->get_error_messages( $this->Target_code );

		$this->assertContains( $this->Another_msg, $return_a_msg );
		$this->assertContains( $this->Another_msg, $return_t_msg );

		$this->assertNotContains( $this->Black_msg, $return_t_msg );
		$this->assertNotContains( $this->White_msg, $return_t_msg );

		// Returns data
		$this->assertContains( $this->Another_data, $error_return->get_error_data( $this->Another_code ) );
		$this->assertNotContains( $this->Target_data, $error_return->get_error_data( $this->Another_code ) );
		$this->assertContains( $this->Target_data, $error_return->get_error_data( $this->Target_code ) );
	}

}

Integration tests

Okay, let’s try the same approach with the integration tests and see what happens.

vendor/bin/phpunit -c phpunit_integration.xml
Installing...
Installing network...
Running as multisite...
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.

Using WordPress core : /var/folders/_n/rft3ncgj1r165v0rx7wxtkj1_q1v8f/T/wordpress//

PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

....F.FF..                                                        10 / 10 (100%)

Time: 19.94 seconds, Memory: 40.00 MB

There were 3 failures:

1) TruUcdeBlog\MsTruUcdeTest::test_user_can_add
Failed asserting that false is true.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:140

2) TruUcdeBlog\MsTruUcdeTest::test_black_list
Failed asserting that an array is empty.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:199

3) TruUcdeBlog\MsTruUcdeTest::test_neither_list
Failed asserting that an array is empty.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:224

FAILURES!
Tests: 10, Assertions: 33, Failures: 3.

Generating code coverage report in HTML format ... done

Okay, we have three failed tests here: test_user_can_add(), test_black_list() and test_neither_list(). We for sure expected a test_user_can_add() failure, but what about the other two? They both tell us that an array (from the WP_Error object) that we expected to be empty is not. This means that our error codes are not being stripped out. This will mostly likely be for the site admin because the checkbox in the network settings that enables site admins to add users is not checked and therefore the site admin does not have create_users permissions.

Luckily we have a way to check and uncheck the box in our tests.

update_network_option( get_current_network_id(), 'add_new_users', 1 );

We will want to make sure the checkbox is checked ( the third argument in the above command is 1 for checked, 0 for not checked) for most of our tests, but we will also add a sanity check type test that checks one of our admin users with the box both checked and unchecked to test their create_users permission. Also note, in the above command, that the option name itself is add_new_users. Unlike me, try not the keep mixing up create_users the capability with add_new_users the option.

As we want to use the update_network_option command to both check and uncheck the box, we will not put it in the setup() function, we’ll just call it as needed for our tests. A good place to start is with our test_context() function, which is one of our sanity test functions. It currently looks like this:

	/*
	 * Check context, blogs, users, roles
	 */
	public function test_context() {
		global $current_user;
		global $current_blog;

		// Blog 2
		switch_to_blog( $this->blog_2 );
		set_current_screen( 'user_new.php' );

		wp_set_current_user( $this->site2_admin );
		$this->assertTrue( user_can( $this->site2_admin, 'promote_users' ) );

		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'promote_users' ) );

		// Blog 3
		switch_to_blog( $this->blog_3 );
		set_current_screen( 'user_new.php' );

		wp_set_current_user( $this->site3_admin );
		$this->assertTrue( user_can( $this->site3_admin, 'promote_users' ) );

		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'promote_users' ) );
	}

This is the only place in the integration tests that we specifically check the capability. The other tests use the user_can_add() function to derive capabilities. So the test_context() function will give us a good testing ground for the check box usage. Although this function didn’t fail when we ran our integration tests above, the first order of business is to change the 'promote_users' cap to 'create_users' and we should get test failures then. And we do, just as expected.

vendor/bin/phpunit -c phpunit_integration.xml
Installing...
Installing network...
Running as multisite...
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.

Using WordPress core : /var/folders/_n/rft3ncgj1r165v0rx7wxtkj1_q1v8f/T/wordpress//

PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

...FF.FF..                                                        10 / 10 (100%)

Time: 19.83 seconds, Memory: 40.00 MB

There were 4 failures:

1) TruUcdeBlog\MsTruUcdeTest::test_context
Failed asserting that false is true.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:95

2) TruUcdeBlog\MsTruUcdeTest::test_user_can_add
Failed asserting that false is true.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:140

3) TruUcdeBlog\MsTruUcdeTest::test_black_list
Failed asserting that an array is empty.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:199

4) TruUcdeBlog\MsTruUcdeTest::test_neither_list
Failed asserting that an array is empty.

/Users/twelch/www/sites/multibox/wwwroot/wp-content/plugins/truucde-blog/tests/integrationTests/tests/truucdeTest.php:224

FAILURES!
Tests: 10, Assertions: 30, Failures: 4.

Generating code coverage report in HTML format ... done

Specifically, by changing the capability from 'promote_users' to 'create_users' we are now getting false on line 95 of our complete test file (corresponds to line 13 in the test_context() code listing above) where we have asserted true. This will also, presumably, affect line 105 in our complete test file (line 23 in the code listing above.)

So, what I’m going to do explicitly is turn the add_new_users option off and assert create_users false in one set, then turn the option on and assert true. I have talked about the importance of explicitly setting context on a test by test basis previously in this article series. This will tell me if my understanding of this capability and option are correct and in play for our tests. The new version of the test_context() looks like this:

	public function test_context() {
		global $current_user;
		global $current_blog;

		// Blog 2.
		switch_to_blog( $this->blog_2 );
		set_current_screen( 'user_new.php' );

		// add_new_users option unchecked.
		update_network_option( get_current_network_id(), 'add_new_users', 0 );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'create_users' ) );

		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'create_users' ) );
		
		// add_new_users option checked.
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertTrue( user_can( $this->site2_admin, 'create_users' ) );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'create_users' ) );

		// Blog 3.
		switch_to_blog( $this->blog_3 );
		set_current_screen( 'user_new.php' );
		
		// add_new_users option unchecked.
		update_network_option( get_current_network_id(), 'add_new_users', 0 );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'create_users' ) );

		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'create_users' ) );
		
		// add_new_users option checked.
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertTrue( user_can( $this->site3_admin, 'create_users' ) );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'create_users' ) );
	}

Okay, the test_context() tests now pass and we are left with the original three errors: test_user_can_add(), test_black_list() and test_neither_list(). My thinking now runs as follows:

  • I have tests in place to test the ‘create_users’ capability and the add_new_users option.
  • The remaining tests use our user_can_add() function which draws on that capability and option.
  • As this plugin is intended for an add_new_users checkbox on state, we will turn it on for the failing tests.
  • We may run into a situation as we did in the unit tests where tests were not erroring out due to boolean/conditional shortcutting. So we’ll turn the checkbox on for all remaining tests.

And, all tests are passing. Happy days. Here’s the full complete code for the integration test file.

<?php
namespace TruUcdeBlog;

use WP_Error;
use WP_UnitTestCase;

class MsTruUcdeTest extends WP_UnitTestCase {

	/*
	 * Define all the things
	 */
	public $site2_admin;
	public $site3_admin;
	public $blog_2;
	public $blog_3;

	public $Target_code  = 'user_email';
	public $Black_msg    = 'You cannot use that email address to signup. We are having problems with them blocking some of our email. Please use another email provider.';
	public $White_msg    = 'Sorry, that email address is not allowed!';
	public $Target_code_not_bw = 'Please enter a valid email address.';
	public $Target_data  = 'User email things.';
	public $Another_code = 'user_name';
	public $Another_msg  = 'Usernames can only contain lowercase letters (a-z) and numbers.';


	/*
	 * Test setup
	 */
	public function setUp() {
		parent::setUp();

		// add additional subsites.
		$this->blog_2 = $this->factory->blog->create();
		$this->blog_3 = $this->factory->blog->create();

		// add additional users: user 1 - superadmin already created.
		$this->site2_admin = $this->factory->user->create();
		$this->site3_admin = $this->factory->user->create();

		// add users to blogs with roles.
		add_user_to_blog( $this->blog_2, $this->site2_admin, 'administrator' );
		add_user_to_blog( $this->blog_3, $this->site2_admin, 'editor' );
		add_user_to_blog( $this->blog_2, $this->site3_admin, 'editor' );
		add_user_to_blog( $this->blog_3, $this->site3_admin, 'administrator' );
		
		// add white list and black list domain.
		update_network_option( get_current_network_id(), 'limited_email_domains', 'whitelist.com' );
		update_network_option( get_current_network_id(), 'banned_email_domains', 'blacklist.com' );
	}

	/*
	 * Test Teardown
	 */
	public function tearDown() {
		parent::tearDown();
	}

	/**
	 * Setup test: basic sanity check
	 */
	public function testBasic() {
		$this->assertTrue( true );
	}

	/**
	 * Check for a plugin constant to ensure connection with plugin
	 */
	public function test_plugin_constant() {
		$this->assertSame( 'user_email', TARGET_ERROR_CODE );
	}

	/*
	 * Make sure filter is registered
	 */
	public function test_for_filter() {
		$this->assertEquals(
			10,
			has_filter( 'wpmu_validate_user_signup', 'TruUcdeBlog\on_loaded' )
		);

	}

	/*
	 * Check context, blogs, users, roles
	 */
	public function test_context() {
		global $current_user;
		global $current_blog;

		// Blog 2.
		switch_to_blog( $this->blog_2 );
		set_current_screen( 'user_new.php' );

		// add_new_users option unchecked.
		update_network_option( get_current_network_id(), 'add_new_users', 0 );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'create_users' ) );

		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'create_users' ) );
		
		// add_new_users option checked.
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertTrue( user_can( $this->site2_admin, 'create_users' ) );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'create_users' ) );

		// Blog 3.
		switch_to_blog( $this->blog_3 );
		set_current_screen( 'user_new.php' );
		
		// add_new_users option unchecked.
		update_network_option( get_current_network_id(), 'add_new_users', 0 );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertFalse( user_can( $this->site3_admin, 'create_users' ) );

		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'create_users' ) );
		
		// add_new_users option checked.
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		
		wp_set_current_user( $this->site3_admin );
		$this->assertTrue( user_can( $this->site3_admin, 'create_users' ) );
		
		wp_set_current_user( $this->site2_admin );
		$this->assertFalse( user_can( $this->site2_admin, 'create_users' ) );
	}


	/*
	 * Check responses from user_can_add() for:
	 * No user logged in, superadmin, site2_admin and site3_admin
	 * for blogs 2 and 3.
	 */
	public function test_user_can_add() {
		update_network_option( get_current_network_id(), 'add_new_users', 1 );

		// No user logged in
		$this->assertSame( 0, get_current_user_id() );
		
		switch_to_blog( $this->blog_2 );
		$this->assertFalse( user_can_add() );
		
		switch_to_blog( $this->blog_3 );
		$this->assertFalse( user_can_add() );
		
		// Super admin
		wp_set_current_user( 1 );

		switch_to_blog( $this->blog_2 );
		$this->assertTrue( user_can_add() );

		switch_to_blog( $this->blog_3 );
		$this->assertTrue( user_can_add() );

		// Admin users
		wp_set_current_user( $this->site2_admin );
		switch_to_blog( $this->blog_2 );
		$this->assertTrue( user_can_add() );

		wp_set_current_user( $this->site3_admin );
		switch_to_blog( $this->blog_3 );
		$this->assertTrue( user_can_add() );

		// Editor
		wp_set_current_user( $this->site2_admin );
		switch_to_blog( $this->blog_3 );
		$this->assertFalse( user_can_add() );

		wp_set_current_user( $this->site3_admin );
		switch_to_blog( $this->blog_2 );
		$this->assertFalse( user_can_add() );
	}

	
	/*
	 * Test e_needs_processing and on_load
	 */
	
	// White list domain
	public function test_white_list() {
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		switch_to_blog( $this->blog_2 );
		
		// super admin
		wp_set_current_user( 1 );
		
		$result = wpmu_validate_user_signup( 'wlistuser', 'wlistuser@whitelist.com' );
		$this->assertEmpty( $result['errors']->errors );
		
		// site admin
		wp_set_current_user( $this->site2_admin );
		
		$result = wpmu_validate_user_signup( 'wlistuser', 'wlistuser@whitelist.com' );
		$this->assertEmpty( $result['errors']->errors );
		
		// site editor
		wp_set_current_user( $this->site3_admin );
		
		$result = wpmu_validate_user_signup( 'wlistuser', 'wlistuser@whitelist.com' );
		$this->assertEmpty( $result['errors']->errors );
		
	}
	
	// Black list domain
	public function test_black_list() {
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		switch_to_blog( $this->blog_2 );
		
		// super admin
		wp_set_current_user( 1 );
		
		$result = wpmu_validate_user_signup( 'blistuser', 'blistuser@blacklist.com' );
		$this->assertEmpty( $result['errors']->errors );
		
		// site admin
		wp_set_current_user( $this->site2_admin );
		
		$result = wpmu_validate_user_signup( 'blistuser', 'blistuser@blacklist.com' );
		$this->assertEmpty( $result['errors']->errors );
		
		// site editor
		wp_set_current_user( $this->site3_admin );
		
		$result = wpmu_validate_user_signup( 'blistuser', 'blistuser@blacklist.com' );
		$this->assertContains( $this->Target_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Black_msg, $result['errors']->get_error_messages( $this->Target_code ) );
		$this->assertContains( $this->White_msg, $result['errors']->get_error_messages( $this->Target_code ) );
	}
	
	// Neither white or black
	public function test_neither_list() {
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		switch_to_blog( $this->blog_2 );
		
		// super admin
		wp_set_current_user( 1 );
		
		$result = wpmu_validate_user_signup( 'neither', 'neither@neither.com' );
		$this->assertEmpty( $result['errors']->errors );
		
		// site admin
		wp_set_current_user( $this->site2_admin );
		
		$result = wpmu_validate_user_signup( 'neither', 'neither@neither.com' );
		$this->assertEmpty( $result['errors']->errors );
		
		// site editor
		wp_set_current_user( $this->site3_admin );
		
		$result = wpmu_validate_user_signup( 'neither', 'neither@neither.com' );
		$this->assertContains( $this->Target_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->White_msg, $result['errors']->get_error_messages( $this->Target_code ) );
	}
	
	// Invalid email address: target code, other message
	public function test_invalid_email() {
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		switch_to_blog( $this->blog_2 );
		
		// super admin
		wp_set_current_user( 1 );
		
		$result = wpmu_validate_user_signup( 'neither', 'neither=neither.com' );
		$this->assertContains( $this->Target_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Target_code_not_bw, $result['errors']->get_error_messages( $this->Target_code ) );
		
		// site admin
		wp_set_current_user( $this->site2_admin );
		
		$result = wpmu_validate_user_signup( 'neither', 'neither=neither.com' );
		$this->assertContains( $this->Target_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Target_code_not_bw, $result['errors']->get_error_messages( $this->Target_code ) );
		
		// site editor
		wp_set_current_user( $this->site3_admin );
		
		$result = wpmu_validate_user_signup( 'neither', 'neither=neither.com' );
		$this->assertContains( $this->Target_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Target_code_not_bw, $result['errors']->get_error_messages( $this->Target_code ) );
		$this->assertContains( $this->White_msg, $result['errors']->get_error_messages( $this->Target_code ) );
		
	}
	
	// Invalid user name
	public function test_invalid_username() {
		update_network_option( get_current_network_id(), 'add_new_users', 1 );
		switch_to_blog( $this->blog_2 );
		
		// super admin
		wp_set_current_user( 1 );
		
		$result = wpmu_validate_user_signup( 'white=list', 'whitelist@whitelist.com' );
		$this->assertContains( $this->Another_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Another_msg, $result['errors']->get_error_messages( $this->Another_code ) );
		
		// site admin
		wp_set_current_user( $this->site2_admin );
		
		$result = wpmu_validate_user_signup( 'white=list', 'whitelist@whitelist.com' );
		$this->assertContains( $this->Another_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Another_msg, $result['errors']->get_error_messages( $this->Another_code ) );
		
		// site editor
		wp_set_current_user( $this->site3_admin );
		
		$result = wpmu_validate_user_signup( 'white=list', 'whitelist@whitelist.com' );
		$this->assertContains( $this->Another_code, $result['errors']->get_error_codes() );
		$this->assertContains( $this->Another_msg, $result['errors']->get_error_messages( $this->Another_code ) );
		
	}
}

Wrapping up

We have covered a lot of ground. Thanks for sticking with me, I hope you found this series valuable. I’m going to toss in a ‘modified leprechaun’ bonus. If you catch me in pub I’ll buy you a pint, but you’ll have to give me an honest review of the series. And here’s a link to the github repo of the final code. https://github.com/twelch555/truucde-blog

Happy coding and testing!

Comments are closed.