<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[44909] trunk/tests/phpunit: Privacy: Add unit tests for exporting and erasing personal data.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/44909">44909</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/44909","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>desrosj</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2019-03-15 18:07:09 +0000 (Fri, 15 Mar 2019)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Privacy: Add unit tests for exporting and erasing personal data.
Props birgire, garrett-eclipse, desrosj.
Fixes <a href="https://core.trac.wordpress.org/ticket/43438">#43438</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunktestsphpunitincludestestcaseajaxphp">trunk/tests/phpunit/includes/testcase-ajax.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsajaxPrivacyErasePersonalDataphp">trunk/tests/phpunit/tests/ajax/PrivacyErasePersonalData.php</a></li>
<li><a href="#trunktestsphpunittestsajaxPrivacyExportPersonalDataphp">trunk/tests/phpunit/tests/ajax/PrivacyExportPersonalData.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunktestsphpunitincludestestcaseajaxphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/includes/testcase-ajax.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/includes/testcase-ajax.php 2019-03-15 18:06:08 UTC (rev 44908)
+++ trunk/tests/phpunit/includes/testcase-ajax.php 2019-03-15 18:07:09 UTC (rev 44909)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -119,6 +119,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'delete-theme',
</span><span class="cx" style="display: block; padding: 0 10px"> 'install-theme',
</span><span class="cx" style="display: block; padding: 0 10px"> 'get-post-thumbnail-html',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'wp-privacy-export-personal-data',
+ 'wp-privacy-erase-personal-data',
</ins><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> public static function setUpBeforeClass() {
</span></span></pre></div>
<a id="trunktestsphpunittestsajaxPrivacyErasePersonalDataphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/ajax/PrivacyErasePersonalData.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/ajax/PrivacyErasePersonalData.php (rev 0)
+++ trunk/tests/phpunit/tests/ajax/PrivacyErasePersonalData.php 2019-03-15 18:07:09 UTC (rev 44909)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,814 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Testing Ajax handler for erasing personal data.
+ *
+ * @package WordPress\UnitTests
+ * @since 5.2.0
+ */
+
+/**
+ * Tests_Ajax_PrivacyExportPersonalData class.
+ *
+ * @since 5.2.0
+ *
+ * @group ajax
+ * @group privacy
+ *
+ * @covers ::wp_ajax_wp_privacy_erase_personal_data
+ */
+class Tests_Ajax_PrivacyErasePersonalData extends WP_Ajax_UnitTestCase {
+
+ /**
+ * User Request ID.
+ *
+ * @since 5.2.0
+ *
+ * @var int $request_id
+ */
+ protected static $request_id;
+
+ /**
+ * User Request Email.
+ *
+ * @since 5.2.0
+ *
+ * @var string $request_email
+ */
+ protected static $request_email;
+
+ /**
+ * Ajax Action.
+ *
+ * @since 5.2.0
+ *
+ * @var string $action
+ */
+ protected static $action;
+
+ /**
+ * Eraser Index.
+ *
+ * @since 5.2.0
+ *
+ * @var int $eraser
+ */
+ protected static $eraser;
+
+ /**
+ * Eraser Key.
+ *
+ * @since 5.2.0
+ *
+ * @var string $eraser_key
+ */
+ protected static $eraser_key;
+
+ /**
+ * Eraser Friendly Name.
+ *
+ * @since 5.2.0
+ *
+ * @var string $eraser_friendly_name
+ */
+ protected static $eraser_friendly_name;
+
+ /**
+ * Page Index.
+ *
+ * @since 5.2.0
+ *
+ * @var int $page
+ */
+ protected static $page;
+
+ /**
+ * Last response parsed.
+ *
+ * @since 5.2.0
+ *
+ * @var array $_last_response_parsed
+ */
+ protected $_last_response_parsed;
+
+ /**
+ * An array key in the test eraser to unset.
+ *
+ * @since 5.2.0
+ *
+ * @var string $key_to_unset
+ */
+ protected $key_to_unset;
+
+ /**
+ * A value to change the test eraser callback to.
+ *
+ * @since 5.2.0
+ *
+ * @var string $new_callback_value
+ */
+ protected $new_callback_value;
+
+ /**
+ * Create user erase request fixtures.
+ *
+ * @param WP_UnitTest_Factory $factory Factory.
+ */
+ public static function wpSetUpBeforeClass( $factory ) {
+ self::$request_email = 'requester@example.com';
+ self::$request_id = wp_create_user_request( self::$request_email, 'remove_personal_data' );
+ self::$action = 'wp-privacy-erase-personal-data';
+ self::$eraser = 1;
+ self::$eraser_key = 'custom-eraser';
+ self::$eraser_friendly_name = 'Custom Eraser';
+ self::$page = 1;
+ }
+
+ /**
+ * Register a custom personal data eraser.
+ */
+ public function setUp() {
+ parent::setUp();
+
+ $this->key_to_unset = '';
+
+ // Make sure the erasers response is not modified and avoid sending emails.
+ remove_all_filters( 'wp_privacy_personal_data_erasure_page' );
+ remove_all_actions( 'wp_privacy_personal_data_erased' );
+
+ // Only use our custom privacy personal data eraser.
+ remove_all_filters( 'wp_privacy_personal_data_erasers' );
+ add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) );
+
+ $this->_setRole( 'administrator' );
+ }
+
+ /**
+ * Clean up after each test method.
+ */
+ public function tearDown() {
+ remove_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_custom_personal_data_eraser' ) );
+ $this->new_callback_value = '';
+
+ parent::tearDown();
+ }
+
+ /**
+ * Helper method for changing the test eraser's callback function.
+ *
+ * @param string|array $callback New test eraser callback index value.
+ */
+ protected function _set_eraser_callback( $callback ) {
+ $this->new_callback_value = $callback;
+ add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_eraser_callback_value' ), 20 );
+ }
+
+ /**
+ * Change the test eraser callback to a specified value.
+ *
+ * @since 5.2.0
+ *
+ * @param array $erasers List of data erasers.
+ *
+ * @return array $erasersList of data erasers.
+ */
+ public function filter_eraser_callback_value( $erasers ) {
+ $erasers[ self::$eraser_key ]['callback'] = $this->new_callback_value;
+
+ return $erasers;
+ }
+
+ /**
+ * Helper method for unsetting an array index in the test eraser.
+ *
+ * @param string|bool $key Test eraser key to unset.
+ */
+ protected function _unset_eraser_key( $key ) {
+ $this->key_to_unset = $key;
+ add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'filter_unset_eraser_index' ), 20 );
+ }
+
+ /**
+ * Unsets an array key in the test eraser.
+ *
+ * If the key is false, the eraser is set to false.
+ *
+ * @since 5.2.0
+ *
+ * @param array $erasers Erasers.
+ *
+ * @return array $erasers Erasers.
+ */
+ public function filter_unset_eraser_index( $erasers ) {
+ if ( false === $this->key_to_unset ) {
+ $erasers[ self::$eraser_key ] = false;
+ } elseif ( ! empty( $this->key_to_unset ) ) {
+ unset( $erasers[ self::$eraser_key ][ $this->key_to_unset ] );
+ }
+
+ return $erasers;
+ }
+
+ /**
+ * Helper method for erasing a key from the eraser response.
+ *
+ * @since 5.2.0
+ *
+ * @param array $key Response key to unset.
+ */
+ protected function _unset_response_key( $key ) {
+ $this->key_to_unset = $key;
+ $this->_set_eraser_callback( array( $this, 'filter_unset_response_index' ) );
+ }
+
+ /**
+ * Unsets an array index in a response.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ *
+ * @return array $return Export data.
+ */
+ public function filter_unset_response_index( $email_address, $page = 1 ) {
+ $response = $this->callback_personal_data_eraser( $email_address, $page );
+
+ if ( ! empty( $this->key_to_unset ) ) {
+ unset( $response[ $this->key_to_unset ] );
+ }
+
+ return $response;
+ }
+
+ /**
+ * The function should send an error when the request ID is missing.
+ *
+ * @since 5.2.0
+ *
+ * @ticket 43438
+ */
+ public function test_error_when_missing_request_id() {
+ $this->assertNotWPError( self::$request_id );
+
+ // Set up a request.
+ $this->_make_ajax_call(
+ array(
+ 'id' => null, // Missing request ID.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the request ID is less than 1.
+ *
+ * @since 5.2.0
+ *
+ * @ticket 43438
+ */
+ public function test_error_when_request_id_invalid() {
+ $this->assertNotWPError( self::$request_id );
+
+ // Set up a request.
+ $this->_make_ajax_call(
+ array(
+ 'id' => -1, // Invalid request ID.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the current user is missing required capabilities.
+ *
+ * @since 5.2.0
+ *
+ * @ticket 43438
+ */
+ public function test_error_when_current_user_missing_required_capabilities() {
+ $this->_setRole( 'author' );
+
+ $this->assertFalse( current_user_can( 'erase_others_personal_data' ) );
+ $this->assertFalse( current_user_can( 'delete_users' ) );
+
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Sorry, you are not allowed to perform this action.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the nonce does not validate.
+ *
+ * @since 5.2.0
+ */
+ public function test_failure_with_invalid_nonce() {
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+
+ $this->_make_ajax_call(
+ array(
+ 'security' => 'invalid-nonce',
+ )
+ );
+ }
+
+ /**
+ * The function should send an error when the request type is incorrect.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_incorrect_request_type() {
+ $request_id = wp_create_user_request(
+ 'export-request@example.com',
+ 'export_personal_data' // Incorrect request type, expects 'remove_personal_data'.
+ );
+
+ $this->_make_ajax_call(
+ array(
+ 'security' => wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ),
+ 'id' => $request_id,
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the request email is invalid.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_invalid_email() {
+ wp_update_post(
+ array(
+ 'ID' => self::$request_id,
+ 'post_title' => '', // Invalid requester's email address.
+ )
+ );
+
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Invalid email address in request.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the eraser index is missing.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_missing_eraser_index() {
+ $this->_make_ajax_call(
+ array(
+ 'eraser' => null, // Missing eraser index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Missing eraser index.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the page index is missing.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_missing_page_index() {
+ $this->_make_ajax_call(
+ array(
+ 'page' => null, // Missing page index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the eraser index is negative.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_negative_eraser_index() {
+ $this->_make_ajax_call(
+ array(
+ 'eraser' => -1, // Negative eraser index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Eraser index cannot be less than one.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the eraser index is out of range.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_index_out_of_range() {
+ $this->_make_ajax_call(
+ array(
+ 'eraser' => PHP_INT_MAX, // Out of range eraser index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Eraser index is out of range.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the page index is less than one.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_page_index_less_than_one() {
+ $this->_make_ajax_call(
+ array(
+ 'page' => 0, // Page index less than one.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when an eraser is not an array.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_not_array() {
+ $this->_unset_eraser_key( false );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected an array describing the eraser at index %s.',
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an eraser is missing a friendly name.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_missing_friendly_name() {
+ $this->_unset_eraser_key( 'eraser_friendly_name' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Eraser array at index %s does not include a friendly name.',
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an eraser is missing a callback.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_missing_callback() {
+ $this->_unset_eraser_key( 'callback' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Eraser does not include a callback: %s.',
+ self::$eraser_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an eraser, at a given index, has an invalid callback.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_index_invalid_callback() {
+ $this->_set_eraser_callback( false );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Eraser callback is not valid: %s.',
+ self::$eraser_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an eraser, at a given index, is missing an array response.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_index_invalid_response() {
+ $this->_set_eraser_callback( '__return_null' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Did not receive array from %1$s eraser (index %2$d).',
+ self::$eraser_friendly_name,
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when missing an items_removed index.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_items_removed_missing() {
+ $this->_unset_response_key( 'items_removed' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected items_removed key in response array from %1$s eraser (index %2$d).',
+ self::$eraser_friendly_name,
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when missing an items_retained index.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_items_retained_missing() {
+ $this->_unset_response_key( 'items_retained' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected items_retained key in response array from %1$s eraser (index %2$d).',
+ self::$eraser_friendly_name,
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when missing a messages index.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_messages_missing() {
+ $this->_unset_response_key( 'messages' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected messages key in response array from %1$s eraser (index %2$d).',
+ self::$eraser_friendly_name,
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when the messages index is not an array.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_messages_not_array() {
+ $this->_set_eraser_callback( array( $this, 'filter_response_messages_invalid' ) );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).',
+ self::$eraser_friendly_name,
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * Change the messages index to an invalid value (not an array).
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ *
+ * @return array $return Export data.
+ */
+ public function filter_response_messages_invalid( $email_address, $page = 1 ) {
+ $response = $this->callback_personal_data_eraser( $email_address, $page );
+ $response['messages'] = true;
+
+ return $response;
+ }
+
+ /**
+ * The function should send an error when an eraser is missing 'done' in array response.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_eraser_missing_done_response() {
+ $this->_unset_response_key( 'done' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected done flag in response array from %1$s eraser (index %2$d).',
+ self::$eraser_friendly_name,
+ self::$eraser
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should successfully send erasers response data when the current user has the required
+ * capabilities.
+ *
+ * @since 5.2.0
+ *
+ * @ticket 43438
+ */
+ public function test_success_when_current_user_has_required_capabilities() {
+ $this->assertTrue( current_user_can( 'erase_others_personal_data' ) );
+ $this->assertTrue( current_user_can( 'delete_users' ) );
+
+ $this->_make_ajax_call();
+
+ $this->assertSame(
+ sprintf( 'A message regarding retained data for %s.', self::$request_email ),
+ $this->_last_response_parsed['data']['messages'][0]
+ );
+ $this->assertTrue( $this->_last_response_parsed['success'] );
+ $this->assertTrue( $this->_last_response_parsed['data']['items_removed'] );
+ $this->assertTrue( $this->_last_response_parsed['data']['items_retained'] );
+ $this->assertTrue( $this->_last_response_parsed['data']['done'] );
+ }
+
+ /**
+ * The function should successfully send erasers response data when no items to erase.
+ *
+ * @since 5.2.0
+ *
+ * @ticket 43438
+ */
+ public function test_success_when_no_items_to_erase() {
+
+ $this->_make_ajax_call( array( 'page' => 2 ) );
+
+ $this->assertTrue( $this->_last_response_parsed['success'] );
+ $this->assertFalse( $this->_last_response_parsed['data']['items_removed'] );
+ $this->assertFalse( $this->_last_response_parsed['data']['items_retained'] );
+ $this->assertEmpty( $this->_last_response_parsed['data']['messages'] );
+ $this->assertTrue( $this->_last_response_parsed['data']['done'] );
+ }
+
+ /**
+ * Test that the function's output should be filterable with the `wp_privacy_personal_data_erasure_page` filter.
+ *
+ * @since 5.2.0
+ */
+ public function test_output_should_be_filterable() {
+ add_filter( 'wp_privacy_personal_data_erasure_page', array( $this, 'filter_eraser_data_response' ), 20, 6 );
+ $this->_make_ajax_call();
+
+ $expected_new_index = self::$request_email . '-' . self::$request_id . '-' . self::$eraser_key;
+
+ $this->assertTrue( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'filtered removed', $this->_last_response_parsed['data']['items_removed'] );
+ $this->assertSame( 'filtered retained', $this->_last_response_parsed['data']['items_retained'] );
+ $this->assertSame( array( 'filtered messages' ), $this->_last_response_parsed['data']['messages'] );
+ $this->assertSame( 'filtered done', $this->_last_response_parsed['data']['done'] );
+ $this->assertSame( $expected_new_index, $this->_last_response_parsed['data']['new_index'] );
+ }
+
+ /**
+ * Filters the eraser response.
+ *
+ * @since 5.2.0
+ *
+ * @param array $response The personal data for the given eraser and page.
+ * @param int $eraser_index The index of the eraser that provided this data.
+ * @param string $email_address The email address associated with this personal data.
+ * @param int $page The page for this response.
+ * @param int $request_id The privacy request post ID associated with this request.
+ * @param string $eraser_key The key (slug) of the eraser that provided this data.
+ *
+ * @return array Filtered erase response.
+ */
+ public function filter_eraser_data_response( $response, $eraser_index, $email_address, $page, $request_id, $eraser_key ) {
+ $response['items_removed'] = 'filtered removed';
+ $response['items_retained'] = 'filtered retained';
+ $response['messages'] = array( 'filtered messages' );
+ $response['done'] = 'filtered done';
+ $response['new_index'] = $email_address . '-' . $request_id . '-' . $eraser_key;
+
+ return $response;
+ }
+
+ /**
+ * Register handler for a custom personal data eraser.
+ *
+ * @since 5.2.0
+ *
+ * @param array $erasers An array of personal data erasers.
+ *
+ * @return array $erasers An array of personal data erasers.
+ */
+ public function register_custom_personal_data_eraser( $erasers ) {
+ $erasers[ self::$eraser_key ] = array(
+ 'eraser_friendly_name' => self::$eraser_friendly_name,
+ 'callback' => array( $this, 'callback_personal_data_eraser' ),
+ );
+ return $erasers;
+ }
+
+ /**
+ * Custom Personal Data Eraser.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The comment author email address.
+ * @param int $page Page number.
+ *
+ * @return array $return Erase data.
+ */
+ public function callback_personal_data_eraser( $email_address, $page = 1 ) {
+ if ( 1 === $page ) {
+ return array(
+ 'items_removed' => true,
+ 'items_retained' => true,
+ 'messages' => array( sprintf( 'A message regarding retained data for %s.', $email_address ) ),
+ 'done' => true,
+ );
+ }
+
+ return array(
+ 'items_removed' => false,
+ 'items_retained' => false,
+ 'messages' => array(),
+ 'done' => true,
+ );
+ }
+
+ /**
+ * Helper function for ajax handler.
+ *
+ * @since 5.2.0
+ *
+ * @param array $args Ajax request arguments.
+ */
+ protected function _make_ajax_call( $args = array() ) {
+ $this->_last_response_parsed = null;
+ $this->_last_response = '';
+
+ $defaults = array(
+ 'action' => self::$action,
+ 'security' => wp_create_nonce( self::$action . '-' . self::$request_id ),
+ 'page' => self::$page,
+ 'id' => self::$request_id,
+ 'eraser' => self::$eraser,
+ );
+
+ $_POST = wp_parse_args( $args, $defaults );
+
+ try {
+ $this->_handleAjax( self::$action );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ if ( $this->_last_response ) {
+ $this->_last_response_parsed = json_decode( $this->_last_response, true );
+ }
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/ajax/PrivacyErasePersonalData.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svnexecutable"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:executable</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+*
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsajaxPrivacyExportPersonalDataphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/ajax/PrivacyExportPersonalData.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/ajax/PrivacyExportPersonalData.php (rev 0)
+++ trunk/tests/phpunit/tests/ajax/PrivacyExportPersonalData.php 2019-03-15 18:07:09 UTC (rev 44909)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,818 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Testing Ajax handler for exporting personal data.
+ *
+ * @package WordPress\UnitTests
+ * @since 5.2.0
+ */
+
+/**
+ * Tests_Ajax_PrivacyExportPersonalData class.
+ *
+ * @since 5.2.0
+ *
+ * @group ajax
+ * @group privacy
+ *
+ * @covers ::wp_ajax_wp_privacy_export_personal_data
+ */
+class Tests_Ajax_PrivacyExportPersonalData extends WP_Ajax_UnitTestCase {
+
+ /**
+ * User Request ID.
+ *
+ * @since 5.2.0
+ *
+ * @var int $request_id
+ */
+ protected static $request_id;
+
+ /**
+ * User Request Email.
+ *
+ * @since 5.2.0
+ *
+ * @var string $request_email
+ */
+ protected static $request_email;
+
+ /**
+ * Ajax Action.
+ *
+ * @since 5.2.0
+ *
+ * @var string $action
+ */
+ protected static $action;
+
+ /**
+ * Exporter Index.
+ *
+ * @since 5.2.0
+ *
+ * @var int $exporter
+ */
+ protected static $exporter;
+
+ /**
+ * Exporter Key.
+ *
+ * @since 5.2.0
+ *
+ * @var string $exporter_key
+ */
+ protected static $exporter_key;
+
+ /**
+ * Exporter Friendly Name.
+ *
+ * @since 5.2.0
+ *
+ * @var string $exporter_friendly_name
+ */
+ protected static $exporter_friendly_name;
+
+ /**
+ * Page Index.
+ *
+ * @since 5.2.0
+ *
+ * @var int $page
+ */
+ protected static $page;
+
+ /**
+ * Send As Email.
+ *
+ * @since 5.2.0
+ *
+ * @var bool $send_as_email
+ */
+ protected static $send_as_email;
+
+ /**
+ * Last response parsed.
+ *
+ * @since 5.2.0
+ *
+ * @var array $_last_response_parsed
+ */
+ protected $_last_response_parsed;
+
+ /**
+ * An array key in the test exporter to unset.
+ *
+ * @since 5.2.0
+ *
+ * @var string $key_to_unset
+ */
+ protected $key_to_unset;
+
+ /**
+ * A value to change the test exporter callback to.
+ *
+ * @since 5.2.0
+ *
+ * @var string $new_callback_value
+ */
+ protected $new_callback_value;
+
+ /**
+ * Create user export request fixtures.
+ *
+ * @since 5.2.0
+ *
+ * @param WP_UnitTest_Factory $factory Factory.
+ */
+ public static function wpSetUpBeforeClass( $factory ) {
+ self::$request_email = 'requester@example.com';
+ self::$request_id = wp_create_user_request( self::$request_email, 'export_personal_data' );
+ self::$action = 'wp-privacy-export-personal-data';
+ self::$exporter = 1;
+ self::$exporter_key = 'custom-exporter';
+ self::$exporter_friendly_name = 'Custom Exporter';
+ self::$page = 1;
+ self::$send_as_email = false;
+ }
+
+ /**
+ * Setup before each test method.
+ *
+ * @since 5.2.0
+ */
+ public function setUp() {
+ parent::setUp();
+
+ $this->key_to_unset = '';
+ $this->new_callback_value = '';
+
+ // Make sure the exporter response is not modified and avoid e.g. writing export file to disk.
+ remove_all_filters( 'wp_privacy_personal_data_export_page' );
+
+ // Only use our custom privacy personal data exporter.
+ remove_all_filters( 'wp_privacy_personal_data_exporters' );
+ add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) );
+
+ $this->_setRole( 'administrator' );
+ }
+
+ /**
+ * Clean up after each test method.
+ */
+ public function tearDown() {
+ remove_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_register_custom_personal_data_exporter' ) );
+
+ parent::tearDown();
+ }
+
+ /**
+ * Helper method for changing the test exporter's callback function.
+ *
+ * @param string|array $callback New test exporter callback function.
+ */
+ protected function _set_exporter_callback( $callback ) {
+ $this->new_callback_value = $callback;
+ add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_exporter_callback_value' ), 20 );
+ }
+
+ /**
+ * Change the test exporter callback to a specified value.
+ *
+ * @since 5.2.0
+ *
+ * @param array $exporters List of data exporters.
+ * @return array $exporters List of data exporters.
+ */
+ public function filter_exporter_callback_value( $exporters ) {
+ $exporters[ self::$exporter_key ]['callback'] = $this->new_callback_value;
+
+ return $exporters;
+ }
+
+ /**
+ * Helper method for unsetting an array index in the test exporter.
+ *
+ * @param string $key Test exporter key to unset.
+ */
+ protected function _unset_exporter_key( $key ) {
+ $this->key_to_unset = $key;
+ add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_unset_exporter_key' ), 20 );
+ }
+
+ /**
+ * Unset a specified key in the test exporter array.
+ *
+ * @param array $exporters List of data exporters.
+ *
+ * @return array $exporters List of data exporters.
+ */
+ public function filter_unset_exporter_key( $exporters ) {
+ if ( false === $this->key_to_unset ) {
+ $exporters[ self::$exporter_key ] = false;
+ } elseif ( ! empty( $this->key_to_unset ) ) {
+ unset( $exporters[ self::$exporter_key ][ $this->key_to_unset ] );
+ }
+
+ return $exporters;
+ }
+
+ /**
+ * The function should send an error when the request ID is missing.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_missing_request_id() {
+ $this->_make_ajax_call(
+ array(
+ 'id' => null, // Missing request ID.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Missing request ID.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the request ID is less than 1.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_invalid_id() {
+ $this->_make_ajax_call(
+ array(
+ 'id' => -1, // Invalid request ID, less than 1.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Invalid request ID.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the current user is missing the required capability.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_current_user_missing_required_capability() {
+ $this->_setRole( 'author' );
+
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertFalse( current_user_can( 'export_others_personal_data' ) );
+ $this->assertSame( 'Sorry, you are not allowed to perform this action.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the nonce does not validate.
+ *
+ * @since 5.2.0
+ */
+ public function test_failure_with_invalid_nonce() {
+ $this->setExpectedException( 'WPAjaxDieStopException', '-1' );
+
+ $this->_make_ajax_call(
+ array(
+ 'security' => 'invalid-nonce',
+ )
+ );
+ }
+
+ /**
+ * The function should send an error when the request type is incorrect.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_incorrect_request_type() {
+ $request_id = wp_create_user_request(
+ 'erase-request@example.com',
+ 'remove_personal_data' // Incorrect request type, expects 'export_personal_data'.
+ );
+
+ $this->_make_ajax_call(
+ array(
+ 'security' => wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ),
+ 'id' => $request_id,
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Invalid request type.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the requester's email address is invalid.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_invalid_email_address() {
+ wp_update_post(
+ array(
+ 'ID' => self::$request_id,
+ 'post_title' => '', // Invalid requester's email address.
+ )
+ );
+
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'A valid email address must be given.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the exporter index is missing.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_missing_exporter_index() {
+ $this->_make_ajax_call(
+ array(
+ 'exporter' => null, // Missing exporter index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Missing exporter index.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the page index is missing.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_missing_page_index() {
+ $this->_make_ajax_call(
+ array(
+ 'page' => null, // Missing page index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Missing page index.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when an exporter has improperly used the `wp_privacy_personal_data_exporters` filter.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_has_improperly_used_exporters_filter() {
+ // Improper filter usage: returns false instead of an expected array.
+ add_filter( 'wp_privacy_personal_data_exporters', '__return_false', 999 );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'An exporter has improperly used the registration filter.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the exporter index is negative.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_negative_exporter_index() {
+ $this->_make_ajax_call(
+ array(
+ 'exporter' => -1, // Negative exporter index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Exporter index cannot be negative.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the exporter index is out of range.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_index_out_of_range() {
+ $this->_make_ajax_call(
+ array(
+ 'exporter' => PHP_INT_MAX, // Out of range exporter index.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Exporter index is out of range.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when the page index is less than one.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_page_index_less_than_one() {
+ $this->_make_ajax_call(
+ array(
+ 'page' => 0, // Page index less than one.
+ )
+ );
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'Page index cannot be less than one.', $this->_last_response_parsed['data'] );
+ }
+
+ /**
+ * The function should send an error when an exporter is not an array.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_not_array() {
+ $this->_unset_exporter_key( false );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected an array describing the exporter at index %s.',
+ self::$exporter_key
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an exporter is missing a friendly name.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_missing_friendly_name() {
+ $this->_unset_exporter_key( 'exporter_friendly_name' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Exporter array at index %s does not include a friendly name.',
+ self::$exporter_key
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an exporter is missing a callback.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_missing_callback() {
+ $this->_unset_exporter_key( 'callback' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Exporter does not include a callback: %s.',
+ self::$exporter_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an exporter, at a given index, has an invalid callback.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_index_invalid_callback() {
+ $this->_set_exporter_callback( false );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Exporter callback is not a valid callback: %s.',
+ self::$exporter_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * When an exporter callback returns a WP_Error, it should be passed as the error.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_callback_returns_wp_error() {
+ $this->_set_exporter_callback( array( $this, 'callback_return_wp_error' ) );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'passed_message', $this->_last_response_parsed['data'][0]['code'] );
+ $this->assertSame( 'This is a WP_Error message.', $this->_last_response_parsed['data'][0]['message'] );
+ }
+
+ /**
+ * Callback for exporter's response.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ * @return WP_Error WP_Error instance.
+ */
+ public function callback_return_wp_error( $email_address, $page = 1 ) {
+ return new WP_Error( 'passed_message', 'This is a WP_Error message.' );
+ }
+
+ /**
+ * The function should send an error when an exporter, at a given index, is missing an array response.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_index_invalid_response() {
+ $this->_set_exporter_callback( '__return_null' );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected response as an array from exporter: %s.',
+ self::$exporter_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * The function should send an error when an exporter is missing data in array response.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_missing_data_response() {
+ $this->_set_exporter_callback( array( $this, 'callback_missing_data_response' ) );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected data in response array from exporter: %s.',
+ self::$exporter_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * Callback for exporter's response.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ *
+ * @return array $return Export data.
+ */
+ public function callback_missing_data_response( $email_address, $page = 1 ) {
+ $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
+ unset( $response['data'] ); // Missing data part of response.
+
+ return $response;
+ }
+
+ /**
+ * The function should send an error when an exporter is missing 'data' array in array response.
+ *
+ * @since 5.2.0
+ */
+ public function test_function_should_error_when_exporter_missing_data_array_response() {
+ $this->_set_exporter_callback( array( $this, 'callback_missing_data_array_response' ) );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected data array in response array from exporter: %s.',
+ self::$exporter_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * Callback for exporter's response.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ *
+ * @return array $return Export data.
+ */
+ public function callback_missing_data_array_response( $email_address, $page = 1 ) {
+ $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
+ $response['data'] = false; // Not an array.
+ return $response;
+ }
+
+ /**
+ * The function should send an error when an exporter is missing 'done' in array response.
+ *
+ * @since 5.2.0
+ */
+ public function test_error_when_exporter_missing_done_response() {
+ $this->_set_exporter_callback( array( $this, 'callback_missing_done_response' ) );
+ $this->_make_ajax_call();
+
+ $this->assertFalse( $this->_last_response_parsed['success'] );
+ $this->assertSame(
+ sprintf(
+ 'Expected done (boolean) in response array from exporter: %s.',
+ self::$exporter_friendly_name
+ ),
+ $this->_last_response_parsed['data']
+ );
+ }
+
+ /**
+ * Remove the response's done flag.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ *
+ * @return array $return Export data.
+ */
+ public function callback_missing_done_response( $email_address, $page = 1 ) {
+ $response = $this->callback_custom_personal_data_exporter( $email_address, $page );
+ unset( $response['done'] );
+
+ return $response;
+ }
+
+ /**
+ * The function should successfully send exporter data response when the current user has the required capability.
+ *
+ * @since 5.2.0
+ */
+ public function test_succeeds_when_current_user_has_required_capability() {
+ $this->assertTrue( current_user_can( 'export_others_personal_data' ) );
+
+ $this->_make_ajax_call();
+
+ $this->assertTrue( $this->_last_response_parsed['success'] );
+ $this->assertSame( 'custom-exporter-item-id', $this->_last_response_parsed['data']['data']['item_id'] );
+ $this->assertSame( 'Email', $this->_last_response_parsed['data']['data']['data'][0]['name'] );
+ $this->assertSame( self::$request_email, $this->_last_response_parsed['data']['data']['data'][0]['value'] );
+ }
+
+ /**
+ * The function should successfully send exporter data response when no items to export.
+ *
+ * @since 5.2.0
+ */
+ public function test_success_when_no_items_to_export() {
+
+ $this->_make_ajax_call( array( 'page' => 2 ) );
+
+ $this->assertTrue( $this->_last_response_parsed['success'] );
+ $this->assertEmpty( $this->_last_response_parsed['data']['data'] );
+ $this->assertTrue( $this->_last_response_parsed['data']['done'] );
+ }
+
+ /**
+ * The function's output should be filterable with the `wp_privacy_personal_data_export_page` filter.
+ *
+ * @since 5.2.0
+ */
+ public function test_output_should_be_filterable() {
+ add_filter( 'wp_privacy_personal_data_export_page', array( $this, 'filter_exporter_data_response' ), 20, 7 );
+ $this->_make_ajax_call();
+
+ $expected_group_label = sprintf(
+ '%s-%s-%s-%s-%s-%s',
+ self::$exporter,
+ self::$page,
+ self::$request_email,
+ self::$request_id,
+ self::$send_as_email,
+ self::$exporter_key
+ );
+
+ $this->assertTrue( $this->_last_response_parsed['success'] );
+ $this->assertSame( $expected_group_label, $this->_last_response_parsed['data']['group_label'] );
+ $this->assertSame( 'filtered_group_id', $this->_last_response_parsed['data']['group_id'] );
+ $this->assertSame( 'filtered_item_id', $this->_last_response_parsed['data']['item_id'] );
+ $this->assertSame( 'filtered_name', $this->_last_response_parsed['data']['data'][0]['name'] );
+ $this->assertSame( 'filtered_value', $this->_last_response_parsed['data']['data'][0]['value'] );
+ }
+
+ /**
+ * Filter exporter's data response.
+ *
+ * @since 5.2.0
+ *
+ * @param array $response The personal data for the given exporter and page.
+ * @param int $exporter_index The index of the exporter that provided this data.
+ * @param string $email_address The email address associated with this personal data.
+ * @param int $page The page for this response.
+ * @param int $request_id The privacy request post ID associated with this request.
+ * @param bool $send_as_email Whether the final results of the export should be emailed to the user.
+ * @param string $exporter_key The key (slug) of the exporter that provided this data.
+ *
+ * @return array $response The personal data for the given exporter and page.
+ */
+ public function filter_exporter_data_response( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) {
+ $group_label = sprintf(
+ '%s-%s-%s-%s-%s-%s',
+ $exporter_index,
+ $page,
+ $email_address,
+ $request_id,
+ $send_as_email,
+ $exporter_key
+ );
+ $response['group_label'] = $group_label;
+ $response['group_id'] = 'filtered_group_id';
+ $response['item_id'] = 'filtered_item_id';
+ $response['data'][0]['name'] = 'filtered_name';
+ $response['data'][0]['value'] = 'filtered_value';
+
+ return $response;
+ }
+
+ /**
+ * Filter to register a custom personal data exporter.
+ *
+ * @since 5.2.0
+ *
+ * @param array $exporters An array of personal data exporters.
+ *
+ * @return array $exporters An array of personal data exporters.
+ */
+ public function filter_register_custom_personal_data_exporter( $exporters ) {
+ $exporters[ self::$exporter_key ] = array(
+ 'exporter_friendly_name' => self::$exporter_friendly_name,
+ 'callback' => array( $this, 'callback_custom_personal_data_exporter' ),
+ );
+ return $exporters;
+ }
+
+ /**
+ * Callback for a custom personal data exporter.
+ *
+ * @since 5.2.0
+ *
+ * @param string $email_address The requester's email address.
+ * @param int $page Page number.
+ *
+ * @return array $response Export data response.
+ */
+ public function callback_custom_personal_data_exporter( $email_address, $page = 1 ) {
+ $data_to_export = array();
+
+ if ( 1 === $page ) {
+ $data_to_export = array(
+ 'group_id' => self::$exporter_key . '-group-id',
+ 'group_label' => self::$exporter_key . '-group-label',
+ 'item_id' => self::$exporter_key . '-item-id',
+ 'data' => array(
+ array(
+ 'name' => 'Email',
+ 'value' => $email_address,
+ ),
+ ),
+ );
+ }
+
+ return array(
+ 'data' => $data_to_export,
+ 'done' => true,
+ );
+ }
+
+ /**
+ * Helper function for ajax handler.
+ *
+ * @since 5.2.0
+ *
+ * @param array $args Ajax request arguments.
+ */
+ protected function _make_ajax_call( $args = array() ) {
+ $this->_last_response_parsed = null;
+ $this->_last_response = '';
+
+ $defaults = array(
+ 'action' => self::$action,
+ 'security' => wp_create_nonce( self::$action . '-' . self::$request_id ),
+ 'exporter' => self::$exporter,
+ 'page' => self::$page,
+ 'sendAsEmail' => self::$send_as_email,
+ 'id' => self::$request_id,
+ );
+
+ $_POST = wp_parse_args( $args, $defaults );
+
+ try {
+ $this->_handleAjax( self::$action );
+ } catch ( WPAjaxDieContinueException $e ) {
+ unset( $e );
+ }
+
+ if ( $this->_last_response ) {
+ $this->_last_response_parsed = json_decode( $this->_last_response, true );
+ }
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/ajax/PrivacyExportPersonalData.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svnexecutable"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:executable</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+*
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>
</body>
</html>