<?php
/**
 * Class WC_Tests_REST_System_Status file.
 *
 * @package ClassicCommerce/Tests
 */

/**
 * System Status REST Tests.
 *
 * @package ClassicCommerce\Tests\API
 * @since 3.5.0
 */
class WC_Tests_REST_System_Status extends WC_REST_Unit_Test_Case {

	/**
	 * Setup our test server.
	 */
	public function setUp() {
		parent::setUp();
		$this->endpoint = new WC_REST_System_Status_Controller();
		$this->user     = $this->factory->user->create(
			array(
				'role' => 'administrator',
			)
		);

		// Callback used by WP_HTTP_TestCase to decide whether to perform HTTP requests or to provide a mocked response.
		$this->http_responder = array( $this, 'mock_http_responses' );
	}

	/**
	 * Test route registration.
	 */
	public function test_register_routes() {
		$routes = $this->server->get_routes();
		$this->assertArrayHasKey( '/wc/v3/system_status', $routes );
		$this->assertArrayHasKey( '/wc/v3/system_status/tools', $routes );
		$this->assertArrayHasKey( '/wc/v3/system_status/tools/(?P<id>[\w-]+)', $routes );
	}

	/**
	 * Test to make sure system status cannot be accessed without valid creds
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_without_permission() {
		wp_set_current_user( 0 );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$this->assertEquals( 401, $response->get_status() );
	}

	/**
	 * Test to make sure root properties are present.
	 * (environment, theme, database, etc).
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_returns_root_properties() {
		wp_set_current_user( $this->user );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$data     = $response->get_data();

		$this->assertArrayHasKey( 'environment', $data );
		$this->assertArrayHasKey( 'database', $data );
		$this->assertArrayHasKey( 'active_plugins', $data );
		$this->assertArrayHasKey( 'theme', $data );
		$this->assertArrayHasKey( 'settings', $data );
		$this->assertArrayHasKey( 'security', $data );
		$this->assertArrayHasKey( 'pages', $data );
	}

	/**
	 * Test to make sure environment response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_environment() {
		wp_set_current_user( $this->user );
		$response = $this->server->dispatch(
			new WP_REST_Request( 'GET', '/wc/v3/system_status' )
		);
		$data = $response->get_data();

		$environment = (array) $data['environment'];

		// Make sure all expected data is present.
		$this->assertEquals( [
			'home_url',
			'site_url',
			'version',
			'cc_version',
			'log_directory',
			'log_directory_writable',
			'wp_version',
			'wp_multisite',
			'wp_memory_limit',
			'wp_debug_mode',
			'wp_cron',
			'language',
			'external_object_cache',
			'server_info',
			'php_version',
			'php_post_max_size',
			'php_max_execution_time',
			'php_max_input_vars',
			'curl_version',
			'suhosin_installed',
			'max_upload_size',
			'mysql_version',
			'mysql_version_string',
			'default_timezone',
			'fsockopen_or_curl_enabled',
			'soapclient_enabled',
			'domdocument_enabled',
			'gzip_enabled',
			'mbstring_enabled',
			'remote_post_successful',
			'remote_post_response',
			'remote_get_successful',
			'remote_get_response',
		], array_keys( $environment ) );

		// Test some responses to make sure they match up.
		$this->assertEquals( get_option( 'home' ), $environment['home_url'] );
		$this->assertEquals( get_option( 'siteurl' ), $environment['site_url'] );
		$this->assertEquals( WC()->version, $environment['version'] );
	}

	/**
	 * Test to make sure database response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_database() {
		global $wpdb;
		wp_set_current_user( $this->user );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$data     = $response->get_data();
		$database = (array) $data['database'];

		$this->assertEquals( get_option( 'woocommerce_db_version' ), $database['wc_database_version'] );
		$this->assertEquals( $wpdb->prefix, $database['database_prefix'] );
		$this->assertEquals( WC_Geolocation::get_local_database_path(), $database['maxmind_geoip_database'] );
		$this->assertArrayHasKey( 'woocommerce', $database['database_tables'], wc_print_r( $database, true ) );
		$this->assertArrayHasKey( $wpdb->prefix . 'woocommerce_payment_tokens', $database['database_tables']['woocommerce'], wc_print_r( $database, true ) );
	}

	/**
	 * Test to make sure active plugins response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_active_plugins() {
		wp_set_current_user( $this->user );

		$actual_plugins = array( 'hello.php' );
		update_option( 'active_plugins', $actual_plugins );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		update_option( 'active_plugins', array() );

		$data    = $response->get_data();
		$plugins = (array) $data['active_plugins'];

		$this->assertEquals( 1, count( $plugins ) );
		$this->assertEquals( 'Hello Dolly', $plugins[0]['name'] );
	}

	/**
	 * Test to make sure theme response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_theme() {
		wp_set_current_user( $this->user );
		$active_theme = wp_get_theme();

		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$data     = $response->get_data();
		$theme    = (array) $data['theme'];

		$this->assertEquals( 13, count( $theme ) );
		$this->assertEquals( $active_theme->name, $theme['name'] );
	}

	/**
	 * Test to make sure settings response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_settings() {
		wp_set_current_user( $this->user );

		$term_response = array();
		$terms         = get_terms( 'product_type', array( 'hide_empty' => 0 ) );
		foreach ( $terms as $term ) {
			$term_response[ $term->slug ] = strtolower( $term->name );
		}

		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$data     = $response->get_data();
		$settings = (array) $data['settings'];

		$this->assertEquals( 11, count( $settings ) );
		$this->assertEquals( ( 'yes' === get_option( 'woocommerce_api_enabled' ) ), $settings['api_enabled'] );
		$this->assertEquals( get_woocommerce_currency(), $settings['currency'] );
		$this->assertEquals( $term_response, $settings['taxonomies'] );
	}

	/**
	 * Test to make sure security response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_security() {
		wp_set_current_user( $this->user );

		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$data     = $response->get_data();
		$settings = (array) $data['security'];

		$this->assertEquals( 2, count( $settings ) );
		$this->assertEquals( 'https' === substr( get_permalink( wc_get_page_id( 'shop' ) ), 0, 5 ), $settings['secure_connection'] );
		$this->assertEquals( ! ( defined( 'WP_DEBUG' ) && defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG && WP_DEBUG_DISPLAY ) || 0 === intval( ini_get( 'display_errors' ) ), $settings['hide_errors'] );
	}

	/**
	 * Test to make sure pages response is correct.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_info_pages() {
		wp_set_current_user( $this->user );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status' ) );
		$data     = $response->get_data();
		$pages    = $data['pages'];
		$this->assertEquals( 5, count( $pages ) );
	}

	/**
	 * Test system status schema.
	 *
	 * @since 3.5.0
	 */
	public function test_system_status_schema() {
		$request    = new WP_REST_Request( 'OPTIONS', '/wc/v3/system_status' );
		$response   = $this->server->dispatch( $request );
		$data       = $response->get_data();
		$properties = $data['schema']['properties'];
		$this->assertEquals( 7, count( $properties ) );
		$this->assertArrayHasKey( 'environment', $properties );
		$this->assertArrayHasKey( 'database', $properties );
		$this->assertArrayHasKey( 'active_plugins', $properties );
		$this->assertArrayHasKey( 'theme', $properties );
		$this->assertArrayHasKey( 'settings', $properties );
		$this->assertArrayHasKey( 'security', $properties );
		$this->assertArrayHasKey( 'pages', $properties );
	}

	/**
	 * Test to make sure system status tools cannot be accessed without valid creds
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_tools_without_permission() {
		wp_set_current_user( 0 );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status/tools' ) );
		$this->assertEquals( 401, $response->get_status() );
	}

	/**
	 * Test to make sure we can load a single tool correctly.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_tool() {
		wp_set_current_user( $this->user );

		$tools_controller = new WC_REST_System_Status_Tools_Controller();
		$raw_tools        = $tools_controller->get_tools();
		$raw_tool         = $raw_tools['recount_terms'];

		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status/tools/recount_terms' ) );
		$data     = $response->get_data();

		$this->assertEquals( 200, $response->get_status() );

		$this->assertEquals( 'recount_terms', $data['id'] );
		$this->assertEquals( 'Term counts', $data['name'] );
		$this->assertEquals( 'Recount terms', $data['action'] );
		$this->assertEquals( 'This tool will recount product terms - useful when changing your settings in a way which hides products from the catalog.', $data['description'] );

		// Test for _fields query parameter.
		$query_params = array(
			'_fields' => 'id,name,nonexisting',
		);
		$request      = new WP_REST_Request( 'GET', '/wc/v3/system_status/tools/recount_terms' );
		$request->set_query_params( $query_params );
		$response = $this->server->dispatch( $request );
		$data     = $response->get_data();

		$this->assertEquals( 200, $response->get_status() );

		$this->assertEquals( 'recount_terms', $data['id'] );
		$this->assertEquals( 'Term counts', $data['name'] );
		$this->assertArrayNotHasKey( 'action', $data );
		$this->assertArrayNotHasKey( 'description', $data );
		// Links are part of links, not data in single items.
		$this->assertArrayNotHasKey( '_links', $data );

		// Links are part of links, not data in single item response.
		$links = $response->get_links();
		$this->assertEquals( 1, count( $links ) );
	}

	/**
	 * Test to make sure a single system status toolscannot be accessed without valid creds.
	 *
	 * @since 3.5.0
	 */
	public function test_get_system_status_tool_without_permission() {
		wp_set_current_user( 0 );
		$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/system_status/tools/recount_terms' ) );
		$this->assertEquals( 401, $response->get_status() );
	}

	/**
	 * Test to make sure we can RUN a tool correctly.
	 *
	 * @since 3.5.0
	 */
	public function test_execute_system_tool() {
		wp_set_current_user( $this->user );

		$tools_controller = new WC_REST_System_Status_Tools_Controller();
		$raw_tools        = $tools_controller->get_tools();
		$raw_tool         = $raw_tools['recount_terms'];

		$response = $this->server->dispatch( new WP_REST_Request( 'POST', '/wc/v3/system_status/tools/recount_terms' ) );
		$data     = $response->get_data();

		$this->assertEquals( 'recount_terms', $data['id'] );
		$this->assertEquals( 'Term counts', $data['name'] );
		$this->assertEquals( 'Recount terms', $data['action'] );
		$this->assertEquals( 'This tool will recount product terms - useful when changing your settings in a way which hides products from the catalog.', $data['description'] );
		$this->assertTrue( $data['success'] );
		$this->assertEquals( 1, did_action( 'woocommerce_rest_insert_system_status_tool' ) );

		$response = $this->server->dispatch( new WP_REST_Request( 'POST', '/wc/v3/system_status/tools/not_a_real_tool' ) );
		$this->assertEquals( 404, $response->get_status() );

		// Test _fields for execute system tool request.
		$query_params = array(
			'_fields' => 'id,success,nonexisting',
		);
		$request      = new WP_REST_Request( 'PUT', '/wc/v3/system_status/tools/recount_terms' );
		$request->set_query_params( $query_params );
		$response = $this->server->dispatch( $request );
		$data     = $response->get_data();

		$this->assertEquals( 200, $response->get_status() );
		$this->assertEquals( 'recount_terms', $data['id'] );
		$this->assertTrue( $data['success'] );

		// Fields that are not requested are not returned in response.
		$this->assertArrayNotHasKey( 'action', $data );
		$this->assertArrayNotHasKey( 'name', $data );
		$this->assertArrayNotHasKey( 'description', $data );
		// Links are part of links, not data in single item response.
		$this->assertArrayNotHasKey( '_links', $data );
		// Non existing field is ignored.
		$this->assertArrayNotHasKey( 'nonexisting', $data );

		// Links are part of links, not data in single item response.
		$links = $response->get_links();
		$this->assertEquals( 1, count( $links ) );
	}

	/**
	 * Test to make sure a tool cannot be run without valid creds.
	 *
	 * @since 3.5.0
	 */
	public function test_execute_system_status_tool_without_permission() {
		wp_set_current_user( 0 );
		$response = $this->server->dispatch( new WP_REST_Request( 'POST', '/wc/v3/system_status/tools/recount_terms' ) );
		$this->assertEquals( 401, $response->get_status() );
	}

	/**
	 * Test system status schema.
	 *
	 * @since 3.5.0
	 */
	public function test_system_status_tool_schema() {
		$request    = new WP_REST_Request( 'OPTIONS', '/wc/v3/system_status/tools' );
		$response   = $this->server->dispatch( $request );
		$data       = $response->get_data();
		$properties = $data['schema']['properties'];

		$this->assertEquals( 6, count( $properties ) );
		$this->assertArrayHasKey( 'id', $properties );
		$this->assertArrayHasKey( 'name', $properties );
		$this->assertArrayHasKey( 'action', $properties );
		$this->assertArrayHasKey( 'description', $properties );
		$this->assertArrayHasKey( 'success', $properties );
		$this->assertArrayHasKey( 'message', $properties );
	}

	/**
	 * Test execute_tool() with the "add_order_indexes" tool.
	 *
	 * @since 3.5.0
	 */
	public function test_execute_system_tool_add_order_indexes() {
		wp_set_current_user( $this->user );

		$order1 = WC_Helper_Order::create_order();
		$order2 = WC_Helper_Order::create_order();
		delete_post_meta( $order1->get_id(), '_billing_address_index' );
		delete_post_meta( $order2->get_id(), '_billing_address_index' );
		delete_post_meta( $order2->get_id(), '_shipping_address_index' );

		$controller = new WC_REST_System_Status_Tools_Controller();
		$response   = $this->server->dispatch( new WP_REST_Request( 'POST', '/wc/v3/system_status/tools/add_order_indexes' ) );
		$data       = $response->get_data();

		$this->assertTrue( $data['success'] );
		$this->assertNotEmpty( get_post_meta( $order1->get_id(), '_billing_address_index', true ) );
		$this->assertNotEmpty( get_post_meta( $order2->get_id(), '_billing_address_index', true ) );
		$this->assertNotEmpty( get_post_meta( $order2->get_id(), '_shipping_address_index', true ) );

	}

	/**
	 * Provides a mocked response for external requests performed by WC_REST_System_Status_Controller.
	 * This way it is not necessary to perform a regular request to an external server which would
	 * significantly slow down the tests.
	 *
	 * This function is called by WP_HTTP_TestCase::http_request_listner().
	 *
	 * @param array  $request Request arguments.
	 * @param string $url URL of the request.
	 *
	 * @return array|false mocked response or false to let WP perform a regular request.
	 */
	protected function mock_http_responses( $request, $url ) {
		$mocked_response = false;

		if ( in_array( $url, array( 'https://www.paypal.com/cgi-bin/webscr', 'https://woocommerce.com/wc-api/product-key-api?request=ping&network=0' ), true ) ) {
			$mocked_response = array(
				'response' => array( 'code' => 200 ),
			);
		} elseif ( 'https://api.wordpress.org/themes/info/1.0/' === $url ) {
			$mocked_response = array(
				'body'     => 'O:8:"stdClass":12:{s:4:"name";s:7:"Default";s:4:"slug";s:7:"default";s:7:"version";s:5:"1.7.2";s:11:"preview_url";s:29:"https://wp-themes.com/default";s:6:"author";s:15:"wordpressdotorg";s:14:"screenshot_url";s:61:"//ts.w.org/wp-content/themes/default/screenshot.png?ver=1.7.2";s:6:"rating";d:100;s:11:"num_ratings";s:1:"3";s:10:"downloaded";i:296618;s:12:"last_updated";s:10:"2010-06-14";s:8:"homepage";s:37:"https://wordpress.org/themes/default/";s:13:"download_link";s:55:"https://downloads.wordpress.org/theme/default.1.7.2.zip";}',
				'response' => array( 'code' => 200 ),
			);
		}

		return $mocked_response;
	}
}
