TruUcde Rabbithole 11/12 : Integration Tests (Part 2)

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

In this article we’ll carry on with our examination of the integrations tests of the TruUcde plugin. We’ll follow the same basic order as we did in the unit tests. We’ve already looked at the tests of the key element of the truucde_init() function and here we’ll examine the tests for each of the user_can_add(), e_needs_processing(), and on_loaded() functions in turn below.

It would likely be advantageous to review the previous articles in the series to familiarize yourself with the code and its associated concerns along with the first part of the look at integration tests in the previous article.

user_can_add() tests

When I initially wrote these tests I had already completed unit tests for 100% of the code coverage for this plugin. So, my thinking was that, in the integration tests, testing the result of the function under different contexts would be adequate.

Since then my thinking has evolved, I’m no longer convinced that testing function output in absence of each of the underlying conditions is enough. Also, I have spotted an error in logic/thinking related to the plugin code and associated testing.

I’m going to carry on examining the tests as initially written because I think it will be useful and instructive. This is the way development naturally unfolds, and I’ve encounter mostly tutorials with a finished product with all the bugs worked out. Also, it is easily to get blinded by the message that all your tests have passed without critically examining them to see if what you need to test is actually taken care of. We’ll do this in future articles in this series.

It is also useful to remember that in the unit tests we used mocks to remove WordPress processing but in integration tests we need to ensure that it happens. Let’s have a look at the original user_can_add() tests.

	/*
	 * 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() {

		// 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() );
	}

The code comment succinctly describes my aim here. As we discussed in the previous article the WordPress state is wiped between tests. So we start off with a WordPress that has no users logged in. We confirm this in line 9, when no user is logged in the get_current_user_id() function returns 0. Then in lines 11/12 we test the result of the user_can_add() function expecting it to be false for blog 2. And it is. We do the same for blog 3 on lines 14/15 and the test also passes.

Then, for the super admin test of this function, we use wp_set_current_user() to set the current user to a userId of 1, the super admin user, and test the result of user_can_add() again for both blogs this time expecting the result to be true in each cases. And it is. (Lines 17-24)

So, I proceed to the site admin users checks. In the first set (lines 27-29) I switch the current user to our site2_admin user. An important note is in order here. We created the site2_admin user in our setup() function and the userId is probably 2, but there are situations where it might not be. I spent several frustrating troubleshooting hours discovering this. When we run the $this->factory->user->create() function in setup() we store the resulting user in the site2_admin variable (class attribute). So we’ll use this variable instead of a specific userId number to make sure we are using the right user.

We continue by switching to blog_2 and then check to make sure the result of the user_can_add() function is true. Then we do the same for the site3_admin on blog_3.

The user_can_add() tests conclude with editor-level tests. (Recall that site2_admin and site3_admin have editor roles on each other’s blogs and should not be able to add users.) These tests are similar in form to the admin users tests, but this time we are testing to make sure that the result of user_can_add() for site2_admin is false on the blog_3 and false for site3_admin on blog_2.

Note that for each set we never think, “well we are already on this user or this blog, so we don’t have to switch to them.” Instead, we always establish the context we need for each set of tests. We could well need to add another set in between two existing ones 6 months from now and overlook the fact that we are counting on context from a previous test.

e_needs_processing() tests

Now, here is a case where both testing and blogging about testing show really value. When I first wrote my integration tests the tests for e_needs_processing looked like this:

	/*
	 * Check if error object needs processing
	 */

	// Empty Error object
	public function test_e_needs_processing_empty() {
		$test_error_obj = new WP_Error();
		$result         = e_needs_processing( $test_error_obj );
		$this->assertFalse( $result );

	}

	// Non target error/message
	public function test_e_needs_proc_non_trgt_msg() {
		$test_error_obj = new WP_Error();
		$test_error_obj->add( $this->Another_code, $this->Another_msg, $this->Another_data );
		$result = e_needs_processing( $test_error_obj );
		$this->assertFalse( $result );

	}

	// Black list message
	public function test_e_needs_proc_blk_msg() {
		$test_error_obj = new WP_Error();
		$test_error_obj->add( $this->Another_code, $this->Another_msg, $this->Another_data );
		$test_error_obj->add( $this->Target_code, $this->Black_msg, $this->Target_data );
		$result = e_needs_processing( $test_error_obj );
		$this->assertTrue( $result );

	}

	// White list message
	public function test_e_needs_proc_white_msg() {
		$test_error_obj = new WP_Error();
		$test_error_obj->add( $this->Another_code, $this->Another_msg, $this->Another_data );
		$test_error_obj->add( $this->Target_code, $this->White_msg, $this->Target_data );
		$result = e_needs_processing( $test_error_obj );
		$this->assertTrue( $result );
	}

	// Both messages
	public function test_e_needs_proc_both_msg() {
		$test_error_obj = new WP_Error();
		$test_error_obj->add( $this->Another_code, $this->Another_msg, $this->Another_data );
		$test_error_obj->add( $this->Target_code, $this->Black_msg, $this->Target_data );
		$test_error_obj->add( $this->Target_code, $this->White_msg, $this->Target_data );
		$result = e_needs_processing( $test_error_obj );
		$this->assertTrue( $result );
	}

They are well organized, separated into logical groups and cover all the bases of no errors, non-targeted error and one each of black/white list error and both errors. So what’s the problem?

The problem is that this is not really a set of integration tests in the same way that the tests of the user_can_add() are. Let’s look at the points of integration in the actualuser_can_add() and e_needs_processing() functions to see how a testing context is established. Here’s the user_can_add() function:

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

The user_can_add() function relies on WordPress’s is_user_logged_in() and current_user_can() functions and we needed to mock those functions in our unit tests as they formed part of the WordPress context that we needed to isolate the function from. In the integration tests we don’t use the mocks, we used the actual functions from the test WordPress environment. Now the e_needs_processing() function:

	/**
	 * Tests if an actual error object is passed, if it is empty and
	 * if it contains a black or white list error message. Returns true
	 * if error object needs processing
	 *
	 * @param \WP_Error $original_error Error object passed in the wpmu_validate_user_signup results.
	 *
	 * @return bool
	 */
function e_needs_processing( \WP_Error $original_error ): bool {
	if ( ! empty( $original_error->errors ) // and is not empty.
		&& ( // and contains a black/white list error.
			in_array( BLACK_LIST_MSG, $original_error->get_error_messages( TARGET_ERROR_CODE ), true )
			|| in_array( WHITE_LIST_MSG, $original_error->get_error_messages( TARGET_ERROR_CODE ), true )
		)
	) {
		return true;
	} else {
		return false;
	}

Do you see the problem yet? Have a look at this function and at my first pass at integration testing code and try and figure it out before moving on.

The problem is that the e_needs_processing() function only integrates with WordPress in one way. The only function calls inside the function are in_array() calls which are standard PHP functions not part of the WordPress environment. So e_needs_processing() integrates with WordPress only through the incoming WP_Error object. In my first pass at the tests I create the WP_Error object artificially before each set of tests essentially creating (and duplicating) unit tests rather than integration tests.

By contrast, in the tests for user_can_add() I was setting aspects of the running WordPress environment before running my tests, but the test function still interacted with the WordPress environment.

So, what to do?

Let’s examine the flow through the whole plugin.

The truucde_init() function adds our onloaded() function to the wpmu_validate_user_signup() filter hook.

Only users with the correct permissions use (and see) the add new user interface. (Yes, I know it makes our user_can_add() check function a bit redundant, but it is worth the code in case some other modification enables the add new user interface for non-admin users.)

When an attempt to add a user is initiated, part of the process inside of WordPress is to call the wpmu_validate_user_signup() function which takes the parameters of $user_name and $user_email. It is worth looking at the function in WordPress’s wp-includes/ms-functions.php file. Here is the doc block header for that file.

/**
 * Sanitize and validate data required for a user sign-up.
 *
 * Verifies the validity and uniqueness of user names and user email addresses,
 * and checks email addresses against admin-provided domain whitelists and blacklists.
 *
 * The {@see 'wpmu_validate_user_signup'} hook provides an easy way to modify the sign-up
 * process. The value $result, which is passed to the hook, contains both the user-provided
 * info and the error messages created by the function. {@see 'wpmu_validate_user_signup'}
 * allows you to process the data in any way you'd like, and unset the relevant errors if
 * necessary.
 *
 * @since MU (3.0.0)
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param string $user_name  The login name provided by the user.
 * @param string $user_email The email provided by the user.
 * @return array {
 *     The array of user name, email, and the error messages.
 *
 *     @type string   $user_name     Sanitized and unique username.
 *     @type string   $orig_username Original username.
 *     @type string   $user_email    User email address.
 *     @type WP_Error $errors        WP_Error object containing any errors found.
 * }
 */

In this block are listed the function parameters and also what the function returns, a results array including the sanitized $user_name, the $orig_username, the $user_email and a WP_Error object.

It is this results array that gets passed, via the filter hook, or our plugin’s on_loaded() function. Our on_loaded() function then plucks the WP_Error object out of the results array and passes it to our e_needs_processing() function as part of the guard statement.

After the guard checks our on_loaded() function then processes the WP_Error object if it needs processing otherwise the $result array is returned from our plugin untouched.

So, how do we do integration tests for our e_needs_processing() function? This is where we really need to start thinking about integration. Our e_needs_processing() function is tightly coupled to our on_loaded() function. If e_needs_processing determines that a WP_Error object needs processing then on_loaded() processes it. Maybe we can test them both at the same time.

Or … nah … let’s stick to plan A

Here is another idea that I considered, tried and rejected. Since our plugin is predominantly tied to the wpmu_validate_user_signup() function we could use this function from the active WordPress environment to supply our WP_Error object. This will enable us to test e_needs_processing() and on_loaded() separately which might not be a bad idea (this is actually wrong). e_needs_processing() is strictly concerned with the content of the WP_Error object so we can keep our test focussed just on this without issues of user roles or processing in the mix.

In order to use wpmu_validate_user_signup() to generate various WP_Error objects we will need to feed it username/email address pairings, that include white-listed ones and black-listed ones. We will also need to include an error from our target code that it is not white/black list and another that is not from our error code.

Do you see what the mistake in my thinking is with this second approach. I didn’t until I wrote the first few tests and couldn’t figure out why I wasn’t getting a black list error in the return from wpmu_validate_user_signup() for one of my tests. Well, it’s context again. Our plugin is loaded in the bootstrap file and the on_loaded() function is sitting on the filter hook in wpmu_validate_user_signup() removing the error as it should.

Back to plan A. We’ll test e_needs_processing() and on_loaded() together.

e_needs_processing & on_loaded. Together again at last

Our approach here will be to run some email addresses, some with errors, through wpmu_validate_user_signup() and see if what comes out the other end is what we expect.

I will have to change some of my test class variables (attributes) in preparation for this because the errors will not be supplied as they were in the unit tests, they will be generated by the wpmu_validate_user_signup()function. I originally used fictitious strings for $Another_code, $Another_message and $Another_data. We’ll need to use real codes and messages (that aren’t our targets) from the wpmu_validate_user_signup() function. wpmu_validate_user_signup() doesn’t use data entries so I’ll delete that one. These are are follows:

	public $Target_code_not_black_or_white_message = 'Please enter a valid email address.';
        public $Another_code = 'user_name';
	public $Another_msg  = 'Usernames can only contain lowercase letters (a-z) and numbers.';

The other things we will need for this set of tests are commands to add test domains to each of the black and white lists. Email is not actually sent in these tests, so we can use fictional domains.

	update_network_option( get_current_network_id(), 'limited_email_domains', 'whitelist.com' );
	update_network_option( get_current_network_id(), 'banned_email_domains', 'blacklist.com' );

These don’t interfere with our other tests, and we need them for a few tests here, so I’ll add them to the setup() function. The plan of attack here is to start by running a set of username/email pairings that each meet one of the following conditions and see if the returned WP_Error object is what we expect:

  • Is an email address from white list domain
  • Is an email address from a black list domain
  • Is an email from a domain on neither list
  • An invalid email address (target error code with non-target message)
  • An invalid user name (non-target error code and message)

Here’s a “Henry Ford” inspired way to set up for this first batch.

	/*
	 * Test e_needs_processing and on_load
	 */
	
	// White list domain
	public function test_white_list() {
		$this->markTestSkipped('must be revisited.');
	}
	
	// Black list domain
	public function test_black_list() {
		$this->markTestSkipped('must be revisited.');
	}
	
	// Neither white or black
	public function test_neither_list() {
		$this->markTestSkipped('must be revisited.');
	}
	
	// Invalid email address
	public function test_invalid_email() {
		$this->markTestSkipped('must be revisited.');
	}
	
	// Invalid user name
	public function test_invalid_username() {
		$this->markTestSkipped('must be revisited.');
	}

Basically, I’ve set up comments and function declarations for each test. As you can probably guess, the $this->markTestSkipped() function skips the test, which is fine because they are empty right now. If a function doesn’t contain an assertion a “Risky Test” error will occur. So skipping the empty ones avoids that and speeds processing, in addition to being more accurate.

Then we’ll test out a few combinations of the above. Also, we’ll run all the tests on blog_2 and once for each user type. That should give us a bunch. I’ll send a Thank You card to the inventor of copy and paste. Okay, here’s what we have for the first set:

	/*
	 * Test e_needs_processing and on_load
	 */
	
	// White list domain
	public function test_white_list() {
		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() {
		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() {
		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() {
		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() {
		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 ) );
		
	}

Okay, let’s have a look at this. As I said earlier, we switch to blog_2 for each test (line 7 for the first test, test_white_list() ). Then for each set we set our user (lines 10, 16 and 22 for the first test). Then for each test and user we feed the appropriate username and email into the wpmu_validate_user_signup() function and store the result in a variable (lines 12, 18 and 24 in the first test). Then we assert that the returned WP_Error object matches our expectations. Note that we have to pull the WP_Error object out of the results array: $result['errors']. And we can also use the WP_Error class methods to access error codes and messages: for example, $result['errors']->errors.

A couple of important notes: we are not actually creating these users, we are simply running the validation check on them. Because of this, we get a return WP_Error object even for the editor-level user even though they can’t create users because WordPress itself has not checked for role permissions. However, our plugin determines whether or not to process the WP_Error object based on user capabilities, so we should always get an unprocessed WP_Error object for the editor user, this can be quite handy. Okay, with the basic structure of these tests outlined above, let’s go through each one and briefly note what is interesting.

test_white_list() This is probably the most basic. Our user we want to add is from a white-listed domain and the username and email address conform to requirements, so no errors are generated. Our three assertEmpty() statements test true for super admin, site admin and editor.

test_black_list() This one is interesting. The added user here is on the black list, so that error gets added and is not on the white list, so that error also gets added. For the super admin and site admin our plugin processes those errors out, so these two users have empty WP_Error objects. The editor, on the other hand, doesn’t pass our plugin’s user_can_add() check so the WP_Error object gets passed untouched as part of the original results array. Our assertions for the editor user confirm that we have the Target_code and both the Black_msg and White_msg in the WP_Error object. Happy days, it seems that our plugin is working as expected.

A small note and tip here. This most recent result caught me off guard a little. My thinking was not complete and accurate and I still had an assertEmpty() in place for the editor test, and the test failed. So I wanted to see what the actual WP_Error object was looking like. PHP has a handy command, print_r(), that you can use to print out a variable. So after line 48 I temporarily put in print_r($result); which prints the contents of $result to the console when running the tests. The output looks like this:

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.

.......Array
(
    [user_name] => blistuser
    [orig_username] => blistuser
    [user_email] => blistuser@blacklist.com
    [errors] => WP_Error Object
        (
            [errors] => Array
                (
                    [user_email] => Array
                        (
                            [0] => You cannot use that email address to signup. We are having problems with them blocking some of our email. Please use another email provider.
                            [1] => Sorry, that email address is not allowed!
                        )

                )

            [error_data] => Array
                (
                )

        )

)
...                                                        10 / 10 (100%)

Time: 20.79 seconds, Memory: 40.00 MB

test_neither_list() Here the added user is not on the black list, so no error there. But the added user is also not on the white list. So our results are similar to the previous test without the black list message for the editor. Again, both super admin and site admin have the white list error processed out by our plugin, and the editor does have the error.

test_invalid_email() This time we are testing an error message other than black/white list but on the same error code. This email is also not on the white list. So, for the editor, we have in the WP_Error object the Target_code, Target_code_not_bw message and the White_msg as expected. The super admin and admin user are similar, but with the White_msg removed by our plugin.

test_invalid_username() Similar again to the previous test with one change. This time we are inciting an error_code that our plugin is not targeting. We submit an invalid username and expect all three, super admin, site admin and editor to have the code and message associated the username error. And they do. Also there are no extra errors for the editor this time.

Wrap up

Our coverage report is showing good coverage of e_needs_processing() and on_load(). We could do a “bunch of errors” tests, but I not sure there would be any value in that. So, we’ll conclude our integration tests here. Below is the full code of our integration test file. Note that there are over twice as many lines of code in each of our test files than there is in our original plugin. If you are not convinced of the value of testing I encourage you to search the internet and examine some of the opinions there. Here are couple of things to start with: https://softwareengineering.stackexchange.com/questions/78478/how-to-explain-the-value-of-unit-testing and https://dzone.com/articles/top-8-benefits-of-unit-testing.

It seems a shame to wrap up our article series after only 11 articles, so I’ll add one more. In this one I exam a flaw in my original plugin code, fix the code to remove the flaw and examine how we can use our tests in the process. We add/modify tests to account for the code changes and use the remainder to ensure that we haven’t introduced any bugs. See you there. Oh, and here’s the full code for the integration tests 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 {
// TODO: eliminate need for WP_ERROR object
	/*
	 * 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',
			'TruUcde\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( 'promote_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( 'promote_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( 'promote_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( 'promote_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( 'promote_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( 'promote_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 ) );
	}

}

Comments are closed.