From 3e938ea7653d82d87207e10e764c6e08a41bc161 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 17 Oct 2024 17:42:13 +0200 Subject: [PATCH] [ticket/17413] Add full test coverage for turnstile class PHPBB-17413 --- tests/captcha/turnstile_test.php | 176 ++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 1 deletion(-) diff --git a/tests/captcha/turnstile_test.php b/tests/captcha/turnstile_test.php index 79e64e1ea0..dd9b1347ed 100644 --- a/tests/captcha/turnstile_test.php +++ b/tests/captcha/turnstile_test.php @@ -15,16 +15,18 @@ use phpbb\captcha\plugins\confirm_type; use phpbb\captcha\plugins\turnstile; use phpbb\config\config; use phpbb\db\driver\driver_interface; +use phpbb\form\form_helper; use phpbb\language\language; use phpbb\log\log_interface; use phpbb\request\request; use phpbb\request\request_interface; use phpbb\template\template; use phpbb\user; -use PHPUnit\Framework\TestCase; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Response; +require_once __DIR__ . '/../../phpBB/includes/functions_acp.php'; + class phpbb_captcha_turnstile_test extends \phpbb_database_test_case { /** @var turnstile */ @@ -160,6 +162,48 @@ class phpbb_captcha_turnstile_test extends \phpbb_database_test_case $this->assertTrue($this->turnstile->validate()); } + public function test_validate_with_guzzle_exception(): void + { + // Mock the request and response from the Turnstile API + $this->request->method('variable')->with('cf-turnstile-response')->willReturn('valid_response'); + $this->request->method('header')->with('CF-Connecting-IP')->willReturn('127.0.0.1'); + + // Mock the GuzzleHttp client and response + $client_mock = $this->createMock(Client::class); + + $request_mock = $this->createMock(\GuzzleHttp\Psr7\Request::class); + $exception = new \GuzzleHttp\Exception\ConnectException('Failed at connecting', $request_mock); + $client_mock->method('request')->willThrowException($exception); + + // Mock config values for secret + $this->config->method('offsetGet')->willReturn('secret_value'); + + // Use reflection to inject the mocked client into the turnstile class + $reflection = new \ReflectionClass($this->turnstile); + $client_property = $reflection->getProperty('client'); + $client_property->setAccessible(true); + $client_property->setValue($this->turnstile, $client_mock); + + // Validatation fails due to guzzle exception + $this->assertFalse($this->turnstile->validate()); + } + + public function test_validate_previous_solve(): void + { + // Use reflection to inject the mocked client into the turnstile class + $reflection = new \ReflectionClass($this->turnstile); + $confirm_id = $reflection->getProperty('confirm_id'); + $confirm_id->setValue($this->turnstile, 'confirm_id'); + $code_property = $reflection->getProperty('code'); + $code_property->setValue($this->turnstile, 'test_code'); + $confirm_code_property = $reflection->getProperty('confirm_code'); + $confirm_code_property->setValue($this->turnstile, 'test_code'); + + // Validate that the CAPTCHA was solved successfully + $this->assertTrue($this->turnstile->validate()); + $this->assertTrue($this->turnstile->is_solved()); + } + public function test_has_config(): void { $this->assertTrue($this->turnstile->has_config()); @@ -250,5 +294,135 @@ class phpbb_captcha_turnstile_test extends \phpbb_database_test_case $this->assertEquals('captcha_turnstile_acp_demo.html', $this->turnstile->get_demo_template()); } + + public function test_acp_page_display(): void + { + global $phpbb_container, $phpbb_dispatcher, $template; + + $phpbb_container = new phpbb_mock_container_builder(); + $form_helper = new form_helper($this->config, $this->request, $this->user); + $phpbb_container->set('form_helper', $form_helper); + $this->user->data['user_id'] = ANONYMOUS; + $this->user->data['user_form_salt'] = 'foobar'; + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $template = $this->template; + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + + $this->request->method('variable')->willReturn(''); + + $expected = [ + 1 => [ + 'TURNSTILE_THEME' => 'light', + 'U_TURNSTILE_SCRIPT' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', + ], + 2 => [ + 'CAPTCHA_PREVIEW' => 'captcha_turnstile_acp_demo.html', + 'CAPTCHA_NAME' => '', + 'CAPTCHA_TURNSTILE_THEME' => 'light', + 'CAPTCHA_TURNSTILE_THEMES' => ['light', 'dark', 'auto'], + 'U_ACTION' => 'test_u_action', + ], + ]; + $matcher = $this->exactly(count($expected)); + $this->template + ->expects($matcher) + ->method('assign_vars') + ->willReturnCallback(function ($template_data) use ($matcher, $expected) { + $callNr = $matcher->getInvocationCount(); + $this->assertEquals($expected[$callNr], $template_data); + }); + + $module_mock = new ModuleMock(); + + $this->turnstile->acp_page('', $module_mock); + } + + public function test_acp_page_submit_without_form(): void + { + global $language, $phpbb_container, $phpbb_dispatcher, $template; + + $language = $this->language; + $phpbb_container = new phpbb_mock_container_builder(); + $form_helper = new form_helper($this->config, $this->request, $this->user); + $phpbb_container->set('form_helper', $form_helper); + $this->user->data['user_id'] = ANONYMOUS; + $this->user->data['user_form_salt'] = 'foobar'; + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $template = $this->template; + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + + $this->request->method('is_set_post')->willReturnMap([ + ['creation_time', ''], + ['submit', true] + ]); + + $this->setExpectedTriggerError(E_USER_NOTICE, 'FORM_INVALID'); + + $module_mock = new ModuleMock(); + + $this->turnstile->acp_page('', $module_mock); + } + + public function test_acp_page_submit_valid(): void + { + global $language, $phpbb_container, $phpbb_dispatcher, $template; + + $language = $this->language; + $phpbb_container = new phpbb_mock_container_builder(); + $form_helper = new form_helper($this->config, $this->request, $this->user); + $phpbb_container->set('form_helper', $form_helper); + $this->user->data['user_id'] = ANONYMOUS; + $this->user->data['user_form_salt'] = 'foobar'; + + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $template = $this->template; + + $form_tokens = $form_helper->get_form_tokens('acp_captcha'); + + // Mock the template assignments + $this->config->method('offsetGet')->willReturnMap([ + ['captcha_turnstile_sitekey', 'sitekey_value'], + ['captcha_turnstile_theme', 'light'], + ]); + $this->config['form_token_lifetime'] = 3600; + + $this->request->method('is_set_post')->willReturnMap([ + ['creation_time', true], + ['form_token', true], + ['submit', true] + ]); + + $this->request->method('variable')->willReturnMap([ + ['creation_time', 0, false, request_interface::REQUEST, $form_tokens['creation_time']], + ['form_token', '', false, request_interface::REQUEST, $form_tokens['form_token']], + ['captcha_turnstile_sitekey', '', false, request_interface::REQUEST, 'newsitekey'], + ['captcha_turnstile_theme', 'light', false, request_interface::REQUEST, 'auto'], + ]); + + $this->setExpectedTriggerError(E_USER_NOTICE, 'CONFIG_UPDATED'); + + $module_mock = new ModuleMock(); + sleep(1); // sleep for a second to ensure form token validation succeeds + + $this->turnstile->acp_page('', $module_mock); + } } +class ModuleMock +{ + public string $tpl_name = ''; + public string $page_title = ''; + public string $u_action = 'test_u_action'; +}