Make WordPress Core

Ticket #44233: 44233.6.diff

File 44233.6.diff, 35.8 KB (added by iandunn, 8 years ago)
  • new file tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php

    diff --git tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php tests/phpunit/tests/privacy/wpPrivacyGeneratePersonalDataExportFile.php
    new file mode 100644
    index 0000000000..c224c26abf
    - +  
     1<?php
     2/**
     3 * Define a class to test `wp_privacy_generate_personal_data_export_file()`.
     4 *
     5 * @package WordPress
     6 * @subpackage UnitTests
     7 * @since 4.9.9
     8 */
     9
     10/**
     11 * Test cases for `wp_privacy_generate_personal_data_export_file()`.
     12 *
     13 * @group privacy
     14 * @covers ::wp_privacy_generate_personal_data_export_file
     15 *
     16 * @since 4.9.9
     17 */
     18class Tests_Privacy_WpPrivacyGeneratePersonalDataExportFile extends WP_UnitTestCase {
     19        /**
     20         * An Export Request ID
     21         *
     22         * @since 4.9.9
     23         *
     24         * @var int $export_request_id
     25         */
     26        protected static $export_request_id;
     27
     28        /**
     29         * The full path to the export file for the current test method.
     30         *
     31         * @since 4.9.9
     32         *
     33         * @var string $export_file_name
     34         */
     35        public $export_file_name = '';
     36
     37        /**
     38         * The full path to the exports directory.
     39         *
     40         * @since 4.9.9
     41         *
     42         * @var string $exports_dir
     43         */
     44        public static $exports_dir;
     45
     46        /**
     47         * Create fixtures that are shared by multiple test cases.
     48         *
     49         * @since 4.9.9
     50         *
     51         * @param WP_UnitTest_Factory $factory The base factory object.
     52         */
     53        public static function wpSetUpBeforeClass( $factory ) {
     54                self::$export_request_id = wp_create_user_request( 'export-requester@example.com', 'export_personal_data' );
     55                update_post_meta( self::$export_request_id, '_export_data_grouped', array() );
     56                self::$exports_dir = wp_privacy_exports_dir();
     57        }
     58
     59        /**
     60         * Set up the test fixture.
     61         *
     62         * Override `wp_die()`, pretend to be Ajax, and suppress `E_WARNING`s.
     63         *
     64         * @since 4.9.9
     65         */
     66        public function setUp() {
     67                parent::setUp();
     68
     69                $this->export_file_name = '';
     70
     71                if ( ! class_exists( 'ZipArchive' ) ) {
     72                        $this->markTestSkipped( 'The ZipArchive class is missing.' );
     73                }
     74
     75                if ( false === strpos( self::$exports_dir, 'wp-personal-data-exports/' ) ) {
     76                        $this->markTestSkipped( 'Exports directory unclear. Skipping test to avoid loss of data.' );
     77                        // todo what problem is caused by a plugin intentionally changing the path to the folder?
     78                        // if we remove this check, there are similar checks at other points in the file that should be removed too
     79                }
     80
     81                if ( ! $this->remove_exports_dir() ) {
     82                        $this->markTestSkipped( 'Existing exports directory could not be removed. Skipping test.' );
     83                }
     84
     85                // We need to override the die handler. Otherwise, the unit tests will die too.
     86                add_filter( 'wp_die_ajax_handler', array( $this, 'get_wp_die_handler' ), 1, 1 );
     87                add_filter( 'wp_doing_ajax', '__return_true' );
     88                add_action( 'wp_privacy_personal_data_export_file_created', array( $this, 'action_wp_privacy_personal_data_export_file_created' ) );
     89
     90                // Suppress warnings from "Cannot modify header information - headers already sent by".
     91                $this->_error_level = error_reporting();
     92                error_reporting( $this->_error_level & ~E_WARNING );
     93        }
     94
     95        /**
     96         * Tear down the test fixture.
     97         *
     98         * Remove the `wp_die()` override, restore error reporting.
     99         *
     100         * @since 4.9.9
     101         */
     102        public function tearDown() {
     103                remove_filter( 'wp_die_ajax_handler', array( $this, 'get_wp_die_handler' ), 1 );
     104                remove_filter( 'wp_doing_ajax', '__return_true' );
     105                remove_action( 'wp_privacy_personal_data_export_file_created', array( $this, 'action_wp_privacy_personal_data_export_file_created' ) );
     106
     107                $this->remove_exports_dir();
     108                error_reporting( $this->_error_level );
     109                parent::tearDown();
     110        }
     111
     112        /**
     113         * Stores the name of the export zip file to check the file is actually created.
     114         *
     115         * @since 4.9.9
     116         *
     117         * @param string $archive_name Created export zip file path.
     118         */
     119        public function action_wp_privacy_personal_data_export_file_created( $archive_name ) {
     120                $this->export_file_name = $archive_name;
     121        }
     122
     123        /**
     124         * Removes the privacy exports directory, including files and subdirectories.
     125         *
     126         * Ignores hidden files and has upper limit of nested levels, because of `list_files()`.
     127         *
     128         * @return bool Whether the privacy exports directory was removed.
     129         */
     130        private function remove_exports_dir() {
     131                // Make sure the export directory path is not corrupted by any filter.
     132                if ( false === strpos( self::$exports_dir, 'wp-personal-data-exports/' ) ) {
     133                        return false;
     134                }
     135
     136                // Remove if the file exists.
     137                        // todo is it unsafe to assume that $exports_dir is a folder? what would cause it to be a file?
     138                        // is_file() should resolve symlinks.
     139                        // if it safe safe to assume it's a dir, then can this section be deleted?
     140                        // if not, it'd be good to make the comment explain what would cause this, since it's not obvious
     141                if ( is_file( untrailingslashit( self::$exports_dir ) ) ) {
     142                        wp_delete_file( untrailingslashit( self::$exports_dir ) );
     143                        return ! is_file( untrailingslashit( self::$exports_dir ) );
     144                }
     145
     146                if ( ! is_dir( self::$exports_dir ) ) {
     147                        return true;
     148                }
     149
     150                chmod( self::$exports_dir, 0755 );
     151
     152                $files = list_files( self::$exports_dir );
     153
     154                // Delete files first, then delete subdirectories.
     155                foreach ( $files as $file ) {
     156                        if ( is_file( $file ) ) {
     157                                wp_delete_file( $file );
     158                        }
     159                }
     160
     161                foreach ( $files as $file ) {
     162                        if ( is_dir( $file ) ) {
     163                                rmdir( $file );
     164                        }
     165                }
     166
     167                rmdir( self::$exports_dir );
     168
     169                return ! is_dir( self::$exports_dir );
     170        }
     171
     172        /**
     173         * When a remove request ID is passed to the export function an error should be displayed.
     174         *
     175         * @since 4.9.9
     176         */
     177        public function test_function_rejects_remove_requests() {
     178                $request_id = wp_create_user_request( 'removal-requester@example.com', 'remove_personal_data' );
     179
     180                $this->setExpectedException( 'WPDieException' );
     181                $this->expectOutputString( '{"success":false,"data":"Invalid request ID when generating export file."}' );
     182                wp_privacy_generate_personal_data_export_file( $request_id );
     183        }
     184
     185        /**
     186         * When an invalid request ID is passed an error should be displayed.
     187         *
     188         * @since 4.9.9
     189         */
     190        public function test_function_invalid_request_id() {
     191                $this->setExpectedException( 'WPDieException' );
     192                $this->expectOutputString( '{"success":false,"data":"Invalid request ID when generating export file."}' );
     193                wp_privacy_generate_personal_data_export_file( 123456789 );
     194        }
     195
     196        /**
     197         * When the request post title is not a valid email an error should be displayed.
     198         *
     199         * @since 4.9.9
     200         */
     201        public function test_function_rejects_requests_with_bad_email_addresses() {
     202                $request_id = wp_create_user_request( 'bad-email-requester@example.com', 'export_personal_data' );
     203
     204                wp_update_post(
     205                        array(
     206                                'ID'         => $request_id,
     207                                'post_title' => 'not-a-valid-email-address',
     208                        )
     209                );
     210
     211                $this->setExpectedException( 'WPDieException' );
     212                $this->expectOutputString( '{"success":false,"data":"Invalid email address when generating export file."}' );
     213                wp_privacy_generate_personal_data_export_file( $request_id );
     214        }
     215
     216        /**
     217         * When the export directory fails to be created an error should be displayed.
     218         *
     219         * @since 4.9.9
     220         */
     221        public function test_function_detects_cannot_create_folder() {
     222                // Create a file with the folder name to ensure the function cannot create a folder.
     223                touch( untrailingslashit( self::$exports_dir ) );
     224
     225                $this->setExpectedException( 'WPDieException' );
     226                $this->expectOutputString( '{"success":false,"data":"Unable to create export folder."}' );
     227                wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     228        }
     229
     230        /**
     231         * When the index.html file cannot be created an error should be displayed.
     232         *
     233         * @since 4.9.9
     234         */
     235        public function test_function_detects_cannot_create_index() {
     236                // Make the export directory read only so the index.html file can't be created.
     237                mkdir( self::$exports_dir );
     238                chmod( self::$exports_dir, 0444 );
     239
     240                if ( '444' !== substr( decoct( fileperms( self::$exports_dir ) ), -3 ) ) {
     241                        $this->markTestSkipped( 'Data export directory permissions were not changed correctly.' );
     242                }
     243
     244                $this->setExpectedException( 'WPDieException' );
     245                $this->expectOutputString( '{"success":false,"data":"Unable to protect export folder from browsing."}' );
     246                wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     247        }
     248
     249        /**
     250         * Test that an index.html file can be added to the export directory.
     251         *
     252         * @since 4.9.9
     253         */
     254        public function test_function_creates_index_in_export_folder() {
     255                $this->expectOutputString( '' );
     256                wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     257
     258                $this->assertTrue( file_exists( self::$exports_dir . 'index.html' ) );
     259        }
     260
     261        /**
     262         * When the export directory is not writable the report should fail to write.
     263         *
     264         * @since 4.9.9
     265         */
     266        public function test_function_detects_cannot_write_html() {
     267                // Make the folder read only so HTML writing will fail.
     268                mkdir( self::$exports_dir );
     269                touch( self::$exports_dir . 'index.html' );
     270                chmod( self::$exports_dir, 0555 );
     271
     272                if ( '555' !== substr( decoct( fileperms( self::$exports_dir ) ), -3 ) ) {
     273                        $this->markTestSkipped( 'Data export directory permissions were not changed correctly.' );
     274                }
     275
     276                $this->setExpectedException( 'WPDieException' );
     277                $this->expectOutputString( '{"success":false,"data":"Unable to open export file (HTML report) for writing."}' );
     278                wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     279
     280                $this->assertEmpty( $this->export_file_name );
     281        }
     282
     283        /**
     284         * Test that an export file is successfully created.
     285         *
     286         * @since 4.9.9
     287         */
     288        public function test_function_can_succeed() {
     289                wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     290
     291                $this->assertTrue( file_exists( $this->export_file_name ) );
     292        }
     293
     294        /**
     295         * Test the export file has all the expected parts.
     296         *
     297         * @since 4.9.9
     298         */
     299        public function test_function_contents() {
     300                $this->expectOutputString( '' );
     301                wp_privacy_generate_personal_data_export_file( self::$export_request_id );
     302                $this->assertTrue( file_exists( $this->export_file_name ) );
     303
     304                $report_dir = trailingslashit( self::$exports_dir . 'test_contents' );
     305                mkdir( $report_dir );
     306
     307                $zip        = new ZipArchive();
     308                $opened_zip = $zip->open( $this->export_file_name );
     309                $this->assertTrue( $opened_zip );
     310
     311                $zip->extractTo( $report_dir );
     312                $zip->close();
     313                $this->assertTrue( file_exists( $report_dir . 'index.html' ) );
     314
     315                $report_contents = file_get_contents( $report_dir . 'index.html' );
     316                $request         = wp_get_user_request_data( self::$export_request_id );
     317
     318                $this->assertContains( '<h1>Personal Data Export</h1>', $report_contents );
     319                $this->assertContains( '<h2>About</h2>', $report_contents );
     320                $this->assertContains( $request->email, $report_contents );
     321        }
     322}
  • new file tests/phpunit/tests/privacy/wpPrivacyProcessPersonalDataExportPage.php

    diff --git tests/phpunit/tests/privacy/wpPrivacyProcessPersonalDataExportPage.php tests/phpunit/tests/privacy/wpPrivacyProcessPersonalDataExportPage.php
    new file mode 100644
    index 0000000000..afea74f03c
    - +  
     1<?php
     2/**
     3 * Test cases for the `wp_privacy_process_personal_data_export_page()` function.
     4 *
     5 * @package WordPress
     6 * @subpackage UnitTests
     7 * @since 4.9.9
     8 */
     9
     10/**
     11 * Tests_Privacy_WpPrivacyProcessPersonalDataExportPage class.
     12 *
     13 * @group privacy
     14 * @covers ::wp_privacy_process_personal_data_export_page
     15 *
     16 * @since 4.9.9
     17 */
     18class Tests_Privacy_WpPrivacyProcessPersonalDataExportPage extends WP_UnitTestCase {
     19        /**
     20         * Request ID.
     21         *
     22         * @since 4.9.9
     23         *
     24         * @var int $request_id
     25         */
     26        protected static $request_id;
     27
     28        /**
     29         * Request Email. todo how is there different from requester_email?
     30         *
     31         * @since 4.9.9
     32         *
     33         * @var int $request_email
     34         */
     35        protected static $request_email;
     36
     37        /**
     38         * Response for the First Page.
     39         *
     40         * @since 4.9.9
     41         *
     42         * @var array $response
     43         */
     44        protected static $response_first_page;
     45
     46        /**
     47         * Response for the Last Page.
     48         *
     49         * @since 4.9.9
     50         *
     51         * @var array $response_last_page
     52         */
     53        protected static $response_last_page;
     54
     55        /**
     56         * Export File Url.
     57         *
     58         * @since 4.9.9
     59         *
     60         * @var string $export_file_url
     61         */
     62        protected static $export_file_url;
     63
     64        /**
     65         * Requester Email. todo how is there different from request_email?
     66         *
     67         * @since 4.9.9
     68         *
     69         * @var string $requester_email
     70         */
     71        protected static $requester_email;
     72
     73        /**
     74         * Whether the final results of the export should be emailed to the user.
     75         *
     76         * @since 4.9.9
     77         *
     78         * @var bool $send_as_email
     79         */
     80        protected static $send_as_email;
     81
     82        /**
     83         * Index Of The First Page.
     84         *
     85         * @since 4.9.9
     86         *
     87         * @var int $page
     88         */
     89        protected static $page_index_first;
     90
     91        /**
     92         * Index Of The Last Page.
     93         *
     94         * @since 4.9.9
     95         *
     96         * @var int $page_index_last
     97         */
     98        protected static $page_index_last;
     99
     100        /**
     101         * Index of the First Exporter.
     102         *
     103         * @since 4.9.9
     104         *
     105         * @var int $exporter_index_first
     106         */
     107        protected static $exporter_index_first;
     108
     109        /**
     110         * Index of the Last Exporter.
     111         *
     112         * @since 4.9.9
     113         *
     114         * @var int $exporter_index_last
     115         */
     116        protected static $exporter_index_last;
     117
     118        /**
     119         * Key of the First Exporter.
     120         *
     121         * @since 4.9.9
     122         *
     123         * @var int $exporter_key_first
     124         */
     125        protected static $exporter_key_first;
     126
     127        /**
     128         * Key of the Last Exporter.
     129         *
     130         * @since 4.9.9
     131         *
     132         * @var int $exporter_key_last
     133         */
     134        protected static $exporter_key_last;
     135
     136        /**
     137         * Export data stored on the `wp_privacy_personal_data_export_file` action hook.
     138         *
     139         * @var string $_export_data_grouped_fetched_within_callback
     140         */
     141        public $_export_data_grouped_fetched_within_callback;
     142
     143        /**
     144         * Create user request fixtures shared by test methods.
     145         *
     146         * @since 4.9.9
     147         *
     148         * @param WP_UnitTest_Factory $factory Factory.
     149         */
     150        public static function wpSetUpBeforeClass( $factory ) {
     151                self::$requester_email      = 'requester@example.com';
     152                self::$export_file_url      = wp_privacy_exports_url() . 'wp-personal-data-file-requester-at-example-com-Wv0RfMnGIkl4CFEDEEkSeIdfLmaUrLsl.zip';
     153                self::$request_id           = wp_create_user_request( self::$requester_email, 'export_personal_data' );
     154                self::$send_as_email        = true;
     155                self::$page_index_first     = 1;
     156                self::$page_index_last      = 2;
     157                self::$exporter_index_first = 1;
     158                self::$exporter_index_last  = 2;
     159                self::$exporter_key_first   = 'custom-exporter-first';
     160                self::$exporter_key_last    = 'custom-exporter-last';
     161
     162                $data = array(
     163                        array(
     164                                'group_id'    => 'custom-exporter-group-id',
     165                                'group_label' => 'custom-exporter-group-label',
     166                                'item_id'     => 'custom-exporter-item-id',
     167                                'data'        => array(
     168                                        array(
     169                                                'name'  => 'Email',
     170                                                'value' => self::$requester_email,
     171                                        ),
     172                                ),
     173                        ),
     174                );
     175
     176                self::$response_first_page = array(
     177                        'done' => false,
     178                        'data' => $data,
     179                );
     180
     181                self::$response_last_page = array(
     182                        'done' => true,
     183                        'data' => $data,
     184                );
     185        }
     186
     187        /**
     188         * Setup before each test method.
     189         *
     190         * @since 4.9.9
     191         */
     192        public function setUp() {
     193                parent::setUp();
     194
     195                // Avoid writing export files to disk. Using `WP_Filesystem_MockFS` is blocked by #44204.
     196                remove_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 );
     197
     198                // Register our custom data exporters, very late, so we can override other unrelated exporters.
     199                add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporters' ), 9999 );
     200
     201                // Set Ajax context for `wp_send_json()` and `wp_die()`.
     202                add_filter( 'wp_doing_ajax', '__return_true' );
     203
     204                // Set up a `wp_die()` ajax handler that throws an exception, to be able to get
     205                // the error message from `wp_send_json_error( 'some message here' )`,
     206                // called by `wp_privacy_process_personal_data_export_page()`.
     207                add_filter( 'wp_die_ajax_handler', array( $this, 'get_wp_die_handler' ), 1, 1 );
     208
     209                // Suppress warnings from "Cannot modify header information - headers already sent by".
     210                $this->_error_level = error_reporting();
     211                error_reporting( $this->_error_level & ~E_WARNING );
     212        }
     213
     214        /**
     215         * Clean up after each test method.
     216         *
     217         * @since 4.9.9
     218         */
     219        public function tearDown() {
     220                add_action( 'wp_privacy_personal_data_export_file', 'wp_privacy_generate_personal_data_export_file', 10 );
     221
     222                remove_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporters' ), 9999 );
     223                remove_filter( 'wp_doing_ajax', '__return_true' );
     224                remove_filter( 'wp_die_ajax_handler', array( $this, 'get_die_handler' ), 1 );
     225                remove_filter( 'wp_mail_from', '__return_empty_string' );   // todo why is this removed here instead of in test_function_should_send_error_on_last_page_of_last_exporter_when_mail_delivery_fails() ?
     226
     227                error_reporting( $this->_error_level );
     228
     229                parent::tearDown();
     230        }
     231
     232        /**
     233         * Filter to register custom personal data exporters.
     234         *
     235         * @since 4.9.9
     236         *
     237         * @param  array $exporters An array of personal data exporters.
     238         * @return array $exporters An array of personal data exporters.
     239         */
     240        public function filter_register_custom_personal_data_exporters( $exporters ) {
     241                // Let's override other unrelated exporters.
     242                $exporters = array();
     243
     244                $exporters[ self::$exporter_key_first ] = array(
     245                        'exporter_friendly_name' => __( 'Custom Exporter #1' ),
     246                        'callback'               => null,
     247                );
     248                $exporters[ self::$exporter_key_last ]  = array(
     249                        'exporter_friendly_name' => __( 'Custom Exporter #2' ),
     250                        'callback'               => null,
     251                );
     252
     253                return $exporters;
     254        }
     255
     256        /*
     257         * todo most of these functions follow a similar pattern, but have minor differences in the arguments passed
     258         * to the function, and the expected response. it's difficult to keep track of the differences between each
     259         * function, because 90% of each is an exact duplicate of all the others. some of the documentation was wrong
     260         * because it was copy/pasted without being updated, and it's possible that some of the values are wrong too,
     261         * but in ways that aren't obvious and don't cause the test to fail.
     262         *
     263         * it seems like it might be better to refactor all of this to use a `@dataProvider` function, similar to
     264         * `test_wp_privacy_anonymize_ip()` / `data_wp_privacy_anonymize_ip()`. that would make the differences between
     265         * the tests much more obvious, and remove a lot of duplicated code.
     266         *
     267         * it probably won't be possible in all cases, because of custom logic that needs to run for some tests, but if
     268         * it can remove 30% or more of the tests, then it's probably worth it. i'm guessing it'll remove about 70% of
     269         * the functions.
     270         */
     271
     272        /**
     273         * The function should return the response when it's not an array.
     274         *
     275         * @since 4.9.9
     276         */
     277        public function test_function_should_return_response_when_response_not_array() {
     278                $expected_response = 'not-an-array';
     279
     280                // Process data, given the last exporter, on the last page and send as email.
     281                $actual_response = wp_privacy_process_personal_data_export_page(
     282                        $expected_response,
     283                        self::$exporter_index_last,
     284                        self::$requester_email,
     285                        self::$page_index_last,
     286                        self::$request_id,
     287                        self::$send_as_email,
     288                        self::$exporter_key_last
     289                );
     290
     291                $this->assertSame( $expected_response, $actual_response );
     292        }
     293
     294        /**
     295         * The function should return the response when it's missing the 'done' array key.
     296         *
     297         * @since 4.9.9
     298         */
     299        public function test_function_should_return_response_when_missing_done_array_key() {
     300                $expected_response = array(
     301                        'missing-done-array-key' => true,
     302                );
     303
     304                // Process data, given the last exporter, on the last page and send as email.
     305                $actual_response = wp_privacy_process_personal_data_export_page(
     306                        $expected_response,
     307                        self::$exporter_index_last,
     308                        self::$requester_email,
     309                        self::$page_index_last,
     310                        self::$request_id,
     311                        self::$send_as_email,
     312                        self::$exporter_key_last
     313                );
     314
     315                $this->assertSame( $expected_response, $actual_response );
     316        }
     317
     318        /**
     319         * The function should return the response when it's missing the 'data' array key.
     320         *
     321         * @since 4.9.9
     322         */
     323        public function test_function_should_return_response_when_missing_data_array_key() {
     324                $expected_response = array(
     325                        'done' => true,
     326                );
     327
     328                // Process data, given the last exporter, on the last page and send as email.
     329                $actual_response = wp_privacy_process_personal_data_export_page(
     330                        $expected_response,
     331                        self::$exporter_index_last,
     332                        self::$requester_email,
     333                        self::$page_index_last,
     334                        self::$request_id,
     335                        self::$send_as_email,
     336                        self::$exporter_key_last
     337                );
     338
     339                $this->assertSame( $expected_response, $actual_response );
     340        }
     341
     342        /**
     343         * The function should return the response when data is not an array.
     344         *
     345         * @since 4.9.9
     346         */
     347        public function test_function_should_return_response_when_data_not_array() {
     348                $response = array(
     349                        'done' => true,
     350                        'data' => 'not-an-array',
     351                );
     352
     353                // Process data, given the last exporter, on the last page and send as email.
     354                $actual_response = wp_privacy_process_personal_data_export_page(
     355                        $response,
     356                        self::$exporter_index_last,
     357                        self::$requester_email,
     358                        self::$page_index_last,
     359                        self::$request_id,
     360                        self::$send_as_email,
     361                        self::$exporter_key_last
     362                );
     363
     364                $this->assertSame( $response, $actual_response );
     365        }
     366
     367        /**
     368         * The function should send a JSON error when receiving an invalid request ID.
     369         *
     370         * @since 4.9.9
     371         */
     372        public function test_function_should_send_error_when_invalid_request_id() {
     373                $response = array(
     374                        'done' => true,
     375                        'data' => array(),
     376                );
     377                $invalid_request_id = 0;
     378
     379                // Process data, given the last exporter, on the last page and send as email.
     380                $this->setExpectedException( 'WPDieException' );
     381                $this->expectOutputString( '{"success":false,"data":"Invalid request ID when merging exporter data."}' );
     382                wp_privacy_process_personal_data_export_page(
     383                        $response,
     384                        self::$exporter_index_last,
     385                        self::$requester_email,
     386                        self::$page_index_last,
     387                        $invalid_request_id,
     388                        self::$send_as_email,
     389                        self::$exporter_key_last
     390                );
     391        }
     392
     393        /**
     394         * The function should send a JSON error when the request has an invalid action name.
     395         *
     396         * @since 4.9.9
     397         */
     398        public function test_function_should_send_error_when_invalid_request_action_name() {
     399                $response = array(
     400                        'done' => true,
     401                        'data' => array(),
     402                );
     403
     404                // Create a valid request ID, but for a different action than the function expects.
     405                $request_id = wp_create_user_request( self::$requester_email, 'remove_personal_data' );
     406
     407                // Process data, given the last exporter, on the last page and send as email.
     408                $this->setExpectedException( 'WPDieException' );
     409                $this->expectOutputString( '{"success":false,"data":"Invalid request ID when merging exporter data."}' );
     410                wp_privacy_process_personal_data_export_page(
     411                        $response,
     412                        self::$exporter_index_last,
     413                        self::$requester_email,
     414                        self::$page_index_last,
     415                        $request_id,
     416                        self::$send_as_email,
     417                        self::$exporter_key_last
     418                );
     419        }
     420
     421        /**
     422         * When mail delivery fails, the function should send a JSON error on the last page of the last exporter.
     423         *
     424         * @since 4.9.9
     425         */
     426        public function test_function_should_send_error_on_last_page_of_last_exporter_when_mail_delivery_fails() {
     427                // Cause `wp_mail()` to return false, to simulate mail delivery failure. Filter removed in tearDown.
     428                add_filter( 'wp_mail_from', '__return_empty_string' );
     429
     430                // Process data, given the last exporter, on the last page and send as email.
     431                $this->setExpectedException( 'WPDieException' );
     432                $this->expectOutputString( '{"success":false,"data":"Unable to send personal data export email."}' );
     433                wp_privacy_process_personal_data_export_page(
     434                        self::$response_last_page,
     435                        self::$exporter_index_last,
     436                        self::$requester_email,
     437                        self::$page_index_last,
     438                        self::$request_id,
     439                        self::$send_as_email,
     440                        self::$exporter_key_last
     441                );
     442        }
     443
     444        /**
     445         * The function should return the response, containing the export file URL, when not sent as email
     446         * for the last exporter on the last page.
     447         *
     448         * @since 4.9.9
     449         */
     450        public function test_function_should_return_response_with_export_file_url_when_not_sent_as_email_for_last_exporter_on_last_page() {
     451                update_post_meta( self::$request_id, '_export_file_url', self::$export_file_url );
     452
     453                // Process data, given the last exporter, on the last page and not send as email.
     454                $actual_response = wp_privacy_process_personal_data_export_page(
     455                        self::$response_last_page,
     456                        self::$exporter_index_last,
     457                        self::$requester_email,
     458                        self::$page_index_last,
     459                        self::$request_id,
     460                        ! self::$send_as_email,
     461                        self::$exporter_key_last
     462                );
     463
     464                $this->assertArrayHasKey( 'url', $actual_response );
     465                $this->assertSame( self::$export_file_url, $actual_response['url'] );
     466                $this->assertSame( self::$response_last_page['done'], $actual_response['done'] );
     467                $this->assertSame( self::$response_last_page['data'], $actual_response['data'] );
     468        }
     469
     470        /**
     471         * The function should return the response, not containing the export file URL, when sent as email
     472         * for the last exporter on the last page.
     473         *
     474         * @since 4.9.9
     475         */
     476        public function test_function_should_return_response_without_export_file_url_when_sent_as_email_for_last_exporter_on_last_page() {
     477                // Process data, given the last exporter, on the last page and send as email.
     478                $actual_response = wp_privacy_process_personal_data_export_page(
     479                        self::$response_last_page,
     480                        self::$exporter_index_last,
     481                        self::$requester_email,
     482                        self::$page_index_last,
     483                        self::$request_id,
     484                        self::$send_as_email,
     485                        self::$exporter_key_last
     486                );
     487
     488                $this->assertArrayNotHasKey( 'url', $actual_response );
     489                $this->assertSame( self::$response_last_page['done'], $actual_response['done'] );
     490                $this->assertSame( self::$response_last_page['data'], $actual_response['data'] );
     491        }
     492
     493        /**
     494         * The function should mark the request as completed for the last exporter on the last page.
     495         *
     496         * @since 4.9.9
     497         */
     498        public function test_function_should_mark_request_as_completed_when_last_exporter_on_last_page() {
     499                // Process data, given the last exporter on the last page and send as email.
     500                wp_privacy_process_personal_data_export_page(
     501                        self::$response_last_page,
     502                        self::$exporter_index_last,
     503                        self::$requester_email,
     504                        self::$page_index_last,
     505                        self::$request_id,
     506                        self::$send_as_email,
     507                        self::$exporter_key_last
     508                );
     509                $this->assertSame( 'request-completed', get_post_status( self::$request_id ) );
     510
     511                // Process data, given the last exporter on the last page and not send as email.
     512                wp_privacy_process_personal_data_export_page(
     513                        self::$response_last_page,
     514                        self::$exporter_index_last,
     515                        self::$requester_email,
     516                        self::$page_index_last,
     517                        self::$request_id,
     518                        ! self::$send_as_email,
     519                        self::$exporter_key_last
     520                );
     521                $this->assertSame( 'request-completed', get_post_status( self::$request_id ) );
     522        }
     523
     524        /**
     525         * The function should leave the request as pending when not the last exporter and not on the last page.
     526         *
     527         * @since 4.9.9
     528         */
     529        public function test_function_should_leave_request_as_pending_when_not_last_exporter_and_not_on_last_page() {
     530                // Process data, given the not last exporter, on the last page and send as email.
     531                wp_privacy_process_personal_data_export_page(
     532                        self::$response_first_page,
     533                        self::$exporter_index_first,
     534                        self::$requester_email,
     535                        self::$page_index_first,
     536                        self::$request_id,
     537                        self::$send_as_email,
     538                        self::$exporter_key_first
     539                );
     540                $this->assertSame( 'request-pending', get_post_status( self::$request_id ) );
     541
     542                // Process data, given the not last exporter, on the last page and not send as email.
     543                wp_privacy_process_personal_data_export_page(
     544                        self::$response_first_page,
     545                        self::$exporter_index_first,
     546                        self::$requester_email,
     547                        self::$page_index_first,
     548                        self::$request_id,
     549                        ! self::$send_as_email,
     550                        self::$exporter_key_first
     551                );
     552                $this->assertSame( 'request-pending', get_post_status( self::$request_id ) );
     553        }
     554
     555        /**
     556         * The function should leave the request as pending when last exporter and not on the last page.
     557         *
     558         * @since 4.9.9
     559         */
     560        public function test_function_should_leave_request_as_pending_when_last_exporter_and_not_on_last_page() {
     561                // Process data, given the last exporter, not on the last page and send as email.
     562                wp_privacy_process_personal_data_export_page(
     563                        self::$response_first_page,
     564                        self::$exporter_index_last,
     565                        self::$requester_email,
     566                        self::$page_index_first,
     567                        self::$request_id,
     568                        self::$send_as_email,
     569                        self::$exporter_key_last
     570                );
     571                $this->assertSame( 'request-pending', get_post_status( self::$request_id ) );
     572
     573                // Process data, given the last exporter, not on the last page and not send as email.
     574                wp_privacy_process_personal_data_export_page(
     575                        self::$response_first_page,
     576                        self::$exporter_index_last,
     577                        self::$requester_email,
     578                        self::$page_index_first,
     579                        self::$request_id,
     580                        ! self::$send_as_email,
     581                        self::$exporter_key_last
     582                );
     583                $this->assertSame( 'request-pending', get_post_status( self::$request_id ) );
     584        }
     585
     586        /**
     587         * The function should leave the request as pending when not last exporter on the last page.
     588         *
     589         * @since 4.9.9
     590         */
     591        public function test_function_should_leave_request_as_pending_when_not_last_exporter_on_last_page() {
     592                // Process data, given not the last exporter on the last page and sending as email.
     593                wp_privacy_process_personal_data_export_page(
     594                        self::$response_last_page,
     595                        self::$exporter_index_first,
     596                        self::$requester_email,
     597                        self::$page_index_last,
     598                        self::$request_id,
     599                        self::$send_as_email,
     600                        self::$exporter_key_first
     601                );
     602                $this->assertSame( 'request-pending', get_post_status( self::$request_id ) );
     603
     604                // Process data, given not the last exporter on the last page and not send as email.
     605                wp_privacy_process_personal_data_export_page(
     606                        self::$response_last_page,
     607                        self::$exporter_index_first,
     608                        self::$requester_email,
     609                        self::$page_index_last,
     610                        self::$request_id,
     611                        ! self::$send_as_email,
     612                        self::$exporter_key_first
     613                );
     614                $this->assertSame( 'request-pending', get_post_status( self::$request_id ) );
     615        }
     616
     617        /**
     618         * The function should add `_export_data_raw` post meta for the request, when sent as email
     619         * for the first exporter on the first page.
     620         *
     621         * @since 4.9.9
     622         */
     623        public function test_function_should_add_post_meta_with_raw_data_when_sent_as_email_for_first_exporter_on_first_page() {
     624                delete_post_meta( self::$request_id, '_export_data_raw' );
     625                $this->assertEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     626
     627                // Process data, given the first exporter on the first page and send as email.
     628                wp_privacy_process_personal_data_export_page(
     629                        self::$response_first_page,
     630                        self::$exporter_index_first,
     631                        self::$requester_email,
     632                        self::$page_index_first,
     633                        self::$request_id,
     634                        self::$send_as_email,
     635                        self::$exporter_key_first
     636                );
     637                $this->assertNotEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     638        }
     639
     640        /**
     641         * The function should delete `_export_data_raw` post meta for the request, when sent as email
     642         * for the last exporter on the last page.
     643         *
     644         * @since 4.9.9
     645         */
     646        public function test_function_should_delete_post_meta_with_raw_data_when_sent_as_email_for_last_exporter_and_last_page() {
     647                // Adds post meta when processing data, given the first exporter on the first page and send as email.
     648                wp_privacy_process_personal_data_export_page(
     649                        self::$response_first_page,
     650                        self::$exporter_index_first,
     651                        self::$requester_email,
     652                        self::$page_index_first,
     653                        self::$request_id,
     654                        self::$send_as_email,
     655                        self::$exporter_key_first
     656                );
     657                $this->assertNotEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     658
     659                // Deletes post meta when processing data, given the last exporter on the last page and send as email.
     660                wp_privacy_process_personal_data_export_page(
     661                        self::$response_last_page,
     662                        self::$exporter_index_last,
     663                        self::$requester_email,
     664                        self::$page_index_last,
     665                        self::$request_id,
     666                        self::$send_as_email,
     667                        self::$exporter_key_last
     668                );
     669                $this->assertEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     670        }
     671
     672        /**
     673         * The function should add `_export_data_raw` post meta for the request, when first exporter and first page and
     674         * email is not sent.
     675         *
     676         * @since 4.9.9
     677         */
     678        public function test_function_should_add_post_meta_with_raw_data_when_not_sent_as_email_for_first_exporter_and_first_page() {
     679                $this->assertEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     680
     681                // Adds post meta when processing data, given the first exporter on the first page and not send as email.
     682                wp_privacy_process_personal_data_export_page(
     683                        self::$response_first_page,
     684                        self::$exporter_index_first,
     685                        self::$requester_email,
     686                        self::$page_index_first,
     687                        self::$request_id,
     688                        ! self::$send_as_email,
     689                        self::$exporter_key_first
     690                );
     691
     692                $this->assertNotEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     693        }
     694
     695        /**
     696         * The function should delete `_export_data_raw` post meta for the request, when sent as email
     697         * for the last exporter on the last page.
     698         *
     699         * @since 4.9.9
     700         */
     701        public function test_function_should_delete_post_meta_with_raw_data_when_not_sent_as_email_for_last_exporter_and_last_page() {
     702                // Adds post meta when processing data, given the first exporter on the first page and not send as email.
     703                wp_privacy_process_personal_data_export_page(
     704                        self::$response_first_page,
     705                        self::$exporter_index_first,
     706                        self::$requester_email,
     707                        self::$page_index_first,
     708                        self::$request_id,
     709                        ! self::$send_as_email,
     710                        self::$exporter_key_first
     711                );
     712                $this->assertNotEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     713
     714                // Deletes post meta when processing data, given the last exporter on the last page and not send as email.
     715                wp_privacy_process_personal_data_export_page(
     716                        self::$response_last_page,
     717                        self::$exporter_index_last,
     718                        self::$requester_email,
     719                        self::$page_index_last,
     720                        self::$request_id,
     721                        ! self::$send_as_email,
     722                        self::$exporter_key_last
     723                );
     724                $this->assertEmpty( get_post_meta( self::$request_id, '_export_data_raw', true ) );
     725        }
     726
     727        /**
     728         * The function should add `_export_data_grouped` post meta for the request, only available
     729         * when personal data export file is generated.
     730         *
     731         * @since 4.9.9
     732         */
     733        public function test_function_should_add_post_meta_with_groups_data_only_available_when_export_file_generated() {
     734                // Adds post meta when processing data, given the first exporter on the first page and send as email.
     735                wp_privacy_process_personal_data_export_page(
     736                        self::$response_first_page,
     737                        self::$exporter_index_first,
     738                        self::$requester_email,
     739                        self::$page_index_first,
     740                        self::$request_id,
     741                        self::$send_as_email,
     742                        self::$exporter_key_first
     743                );
     744                $this->assertEmpty( get_post_meta( self::$request_id, '_export_data_grouped', true ) );
     745
     746                add_action( 'wp_privacy_personal_data_export_file', array( $this, 'action_callback_to_get_export_groups_data' ) );
     747                // Process data, given the last exporter on the last page and send as email.
     748                wp_privacy_process_personal_data_export_page(
     749                        self::$response_last_page,
     750                        self::$exporter_index_last,
     751                        self::$requester_email,
     752                        self::$page_index_last,
     753                        self::$request_id,
     754                        self::$send_as_email,
     755                        self::$exporter_key_last
     756                );
     757                remove_action( 'wp_privacy_personal_data_export_file', array( $this, 'action_callback_to_get_export_groups_data' ) );
     758
     759                $this->assertNotEmpty( $this->_export_data_grouped_fetched_within_callback );
     760                $this->assertEmpty( get_post_meta( self::$request_id, '_export_data_grouped', true ) );
     761        }
     762
     763        /**
     764         * A callback for the `wp_privacy_personal_data_export_file` action that stores the
     765         * `_export_data_grouped` meta data locally for testing.
     766         *
     767         * @since 4.9.9
     768         *
     769         * @param int $request_id Request ID.
     770         */
     771        public function action_callback_to_get_export_groups_data( $request_id ) {
     772                $this->_export_data_grouped_fetched_within_callback = get_post_meta( $request_id, '_export_data_grouped', true );
     773        }
     774}