diff options
Diffstat (limited to 'tests/unit')
29 files changed, 1143 insertions, 161 deletions
diff --git a/tests/unit/Access/AccessListTest.php b/tests/unit/Access/AccessListTest.php index 635f09930..feb9efebd 100644 --- a/tests/unit/Access/AccessListTest.php +++ b/tests/unit/Access/AccessListTest.php @@ -185,7 +185,7 @@ class AccessListTest extends UnitTestCase { $this->assertTrue($accessListPrivate->is_private()); } - public function isprivateProvider() { + public static function isprivateProvider() { return [ 'all set' => [[ 'channel_allow_cid' => '<acid>', diff --git a/tests/unit/Access/PermissionRolesTest.php b/tests/unit/Access/PermissionRolesTest.php index 3d3cbbae5..ccd6df205 100644 --- a/tests/unit/Access/PermissionRolesTest.php +++ b/tests/unit/Access/PermissionRolesTest.php @@ -23,6 +23,7 @@ namespace Zotlabs\Tests\Unit\Access; +use PHPUnit\Framwork\Attributes\IgnoreDeprecations; use Zotlabs\Tests\Unit\UnitTestCase; use Zotlabs\Access\PermissionRoles; use phpmock\phpunit\PHPMock; @@ -35,6 +36,7 @@ use DMS\PHPUnitExtensions\ArraySubset\Assert; * * @covers Zotlabs\Access\PermissionRoles */ +#[IgnoreDeprecations] class PermissionRolesTest extends UnitTestCase { use PHPMock; diff --git a/tests/unit/Access/PermissionsTest.php b/tests/unit/Access/PermissionsTest.php index 3ab567ad0..f9cb25b64 100644 --- a/tests/unit/Access/PermissionsTest.php +++ b/tests/unit/Access/PermissionsTest.php @@ -63,14 +63,6 @@ class PermissionsTest extends UnitTestCase { // There are 17 default perms $permsCount = 17; - // Create a stub for global function t() with expectation - $t = $this->getFunctionMock('Zotlabs\Access', 't'); - $t->expects($this->exactly(2*$permsCount))->willReturnCallback( - function ($string) { - return $string; - } - ); - // static method Perms() $perms = Permissions::Perms(); @@ -97,14 +89,6 @@ class PermissionsTest extends UnitTestCase { // There are 17 default perms $permsCount = 17; - // Create a stub for global function t() with expectation - $t = $this->getFunctionMock('Zotlabs\Access', 't'); - $t->expects($this->exactly(2*$permsCount))->willReturnCallback( - function ($string) { - return $string; - } - ); - $perms = Permissions::Perms('view_'); $this->assertEquals($permsCount, count($perms)); @@ -125,9 +109,6 @@ class PermissionsTest extends UnitTestCase { * @param array $expected The expected result perms array */ public function testFilledPerms($permarr, $expected) { - // Create a stub for global function t() - $t = $this->getFunctionMock('Zotlabs\Access', 't'); - $this->assertEquals($expected, Permissions::FilledPerms($permarr)); } /** @@ -135,7 +116,7 @@ class PermissionsTest extends UnitTestCase { * * \e array Indexed array which is passed as parameter to FilledPerms() * * \e array Expected associative result array with filled perms */ - public function FilledPermsProvider() { + public static function FilledPermsProvider() { return [ 'Empty param array' => [ [], @@ -209,13 +190,6 @@ class PermissionsTest extends UnitTestCase { * @uses ::call_hooks */ public function testFilledPermsNull() { - // Create a stub for global function t() with expectation - $t = $this->getFunctionMock('Zotlabs\Access', 't'); - $t->expects($this->atLeastOnce()); - // Create a stub for global function bt() with expectations - $bt = $this->getFunctionMock('Zotlabs\Access', 'btlogger'); - $bt->expects($this->once())->with($this->equalTo('FilledPerms: null')); - $result = [ 'view_stream' => 0, 'send_stream' => 0, @@ -253,7 +227,7 @@ class PermissionsTest extends UnitTestCase { * * \e array Array with perms to test * * \e array Expected result array */ - public function OPermsProvider() { + public static function OPermsProvider() { return [ 'empty' => [ [], @@ -286,7 +260,7 @@ class PermissionsTest extends UnitTestCase { * * \e array 2nd array with perms * * \e boolean expected result for the perms comparison */ - public function permsCompareProvider() { + public static function permsCompareProvider() { return [ 'equal' => [ ['perm1' => 1, 'perm2' => 0], diff --git a/tests/unit/AntiXSSTest.php b/tests/unit/AntiXSSTest.php index 1de9d54cd..642224f60 100644 --- a/tests/unit/AntiXSSTest.php +++ b/tests/unit/AntiXSSTest.php @@ -31,7 +31,7 @@ class AntiXSSTest extends TestCase { $this->assertEquals($expected, escape_url($url)); } - public function urlTestProvider() : array { + public static function urlTestProvider() : array { return [ [ "https://example.com/settings/calendar/?f=&rpath=https://example.com/cdav/calendar'><script>alert('boom')</script>", diff --git a/tests/unit/CallHooksTest.php b/tests/unit/CallHooksTest.php new file mode 100644 index 000000000..0170f31d0 --- /dev/null +++ b/tests/unit/CallHooksTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Unit tests for the `call_hooks` function, located in include/plugin.php. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit; + +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use App; + +#[BackupStaticProperties(App::class)] +class CallHooksTest extends UnitTestCase { + + /** + * Test using a freestanding function as callback. + * + * @SuppressWarnings(PHPMD.EvalExpression) + */ + public function test_freestanding_function_as_string(): void { + eval('function hook_test_function(array &$args): void { $args["called"] = true; }'); + insert_hook('test_hook', 'hook_test_function'); + $this->assertHookInvoked(); + } + + public function test_static_class_function_as_string(): void { + insert_hook('test_hook', 'Zotlabs\Tests\Unit\CallHooksTest::static_test_hook'); + $this->assertHookInvoked(); + } + + public function test_static_class_function_as_array(): void { + insert_hook('test_hook', ['Zotlabs\Tests\Unit\CallHooksTest', 'static_test_hook']); + $this->assertHookInvoked(); + } + + public function test_static_class_function_as_serialized_array(): void { + insert_hook('test_hook', serialize(['Zotlabs\Tests\Unit\CallHooksTest', 'static_test_hook'])); + $this->assertHookInvoked(); + } + + public function test_instance_function_as_array(): void { + insert_hook('test_hook', [$this, 'instance_test_hook']); + $this->assertHookInvoked(); + } + + + public function assertHookInvoked(): void { + $test_hook_args = ['called' => false]; + call_hooks('test_hook', $test_hook_args); + + $this->assertTrue($test_hook_args['called']); + } + + public function instance_test_hook(array &$args): void { + $args['called'] = true; + } + public static function static_test_hook(array &$args): void { + $args['called'] = true; + } +} + diff --git a/tests/unit/CreateIdentityTest.php b/tests/unit/CreateIdentityTest.php new file mode 100644 index 000000000..a5e0f278a --- /dev/null +++ b/tests/unit/CreateIdentityTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Unit tests for the `create_identity` function. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit; + +class CreateIdentityTest extends UnitTestCase { + + private bool $queueworker_started = false; + + public function test_empty_args() { + insert_hook('proc_run', [$this, 'proc_run_hook']); + $result = create_identity([]); + $this->assertEquals( + ['success' => false, 'message' => 'No account identifier'], + $result); + + $this->assertFalse($this->queueworker_started); + } + + public function test_create_new_channel_with_valid_account_id(): void { + insert_hook('proc_run', [$this, 'proc_run_hook']); + $result = create_identity([ + 'account_id' => $this->fixtures['account'][0]['account_id'], + 'nickname' => 'testuser', + 'name' => 'Olga Testuser', + ]); + + $this->assertTrue($result['success']); + $this->assertTrue($this->queueworker_started); + } + + public function test_create_new_channel_with_nnexistant_account_id(): void { + insert_hook('proc_run', [$this, 'proc_run_hook']); + $result = create_identity([ + 'account_id' => 666, + 'nickname' => 'testuser', + 'name' => 'Olga Testuser', + ]); + + /* + * We would expect this fo fail, but... + * + * The create_identity function will happily create a new channel with an + * non-existent account_id. The New_channel module will perform a check + * to ensure that only valid (and logged in) accounts can create a new channel. + * + * This is a bit weak, but for now we let it pass... + */ + $this->assertTrue($result['success']); + $this->assertTrue($this->queueworker_started); + } + + public function proc_run_hook(array &$args): void { + $args['run_cmd'] = false; + $this->queueworker_started = + $args['args'] === ['php', 'Zotlabs/Daemon/Master.php', 'Queueworker']; + } +} diff --git a/tests/unit/Lib/ActivityTest.php b/tests/unit/Lib/ActivityTest.php index c9ce79d8c..0e2703f2b 100644 --- a/tests/unit/Lib/ActivityTest.php +++ b/tests/unit/Lib/ActivityTest.php @@ -19,7 +19,7 @@ class ActivityTest extends UnitTestCase { /** * Dataprovider for test_get_textfield. */ - private function get_textfield_provider(): array { + public static function get_textfield_provider(): array { return [ 'get content field' => [ ['content' => 'Some content'], diff --git a/tests/unit/Lib/PermissionDescriptionTest.php b/tests/unit/Lib/PermissionDescriptionTest.php index fdd676f61..1e4b5292c 100644 --- a/tests/unit/Lib/PermissionDescriptionTest.php +++ b/tests/unit/Lib/PermissionDescriptionTest.php @@ -46,16 +46,6 @@ class PermissionDescriptionTest extends UnitTestCase { } public function testFromStandalonePermission() { - // Create a stub for global function t() - $t = $this->getFunctionMock('Zotlabs\Lib', 't'); - $t->expects($this->atLeastOnce())->willReturnCallback( - function ($string) { - return $string; - } - ); - // Create a mock for global function logger() - $this->getFunctionMock('Zotlabs\Lib', 'logger'); - $permDescUnknown = PermissionDescription::fromStandalonePermission(-1); $permDescSelf = PermissionDescription::fromStandalonePermission(0); @@ -113,16 +103,6 @@ class PermissionDescriptionTest extends UnitTestCase { } public function testGetPermissionDescription() { - // Create a stub for global function t() - $t = $this->getFunctionMock('Zotlabs\Lib', 't'); - $t->expects($this->atLeastOnce())->willReturnCallback( - function ($string) { - return $string; - } - ); - // Create a mock for global function logger() - $this->getFunctionMock('Zotlabs\Lib', 'logger'); - // Create a stub for the PermissionDescription class $stub = $this->createMock(PermissionDescription::class); $stub->method('get_permission_description') diff --git a/tests/unit/Lib/ZotlibTest.php b/tests/unit/Lib/ZotlibTest.php new file mode 100644 index 000000000..0ab89dc2f --- /dev/null +++ b/tests/unit/Lib/ZotlibTest.php @@ -0,0 +1,38 @@ +<?php +namespace Zotlabs\Tests\Unit\Lib; + +use Zotlabs\Tests\Unit\UnitTestCase; + +class ZotlibTest extends UnitTestCase { + /** + * Test the `get_rpost_path` function. + * + * @dataProvider get_rpost_path_provider + */ + public function test_get_rpost_path(string $expected, string $xchan_url) : void { + $observer = [ 'xchan_url' => $xchan_url ]; + + $this->assertEquals($expected, \Zotlabs\Lib\Libzot::get_rpost_path($observer)); + } + + public static function get_rpost_path_provider() : array { + return [ + 'xchan_url without port' => [ + 'https://example.com/rpost?f=', + 'https://example.com' + ], + 'xchan_url with port' => [ + 'https://example.com:666/rpost?f=', + 'https://example.com:666' + ], + 'xchan_url ignores path and args' => [ + 'https://example.com/rpost?f=', + 'https://example.com/path?arg1=balle' + ], + 'xchan_url with no scheme should default to https' => [ + 'https://example.com/rpost?f=', + 'example.com', + ], + ]; + } +} diff --git a/tests/unit/Module/HelpTest.php b/tests/unit/Module/HelpTest.php new file mode 100644 index 000000000..c345d5e52 --- /dev/null +++ b/tests/unit/Module/HelpTest.php @@ -0,0 +1,179 @@ +<?php +/** + * SPDX-FileCopyrightText: 2024 Harald Eilertsen + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * + * SPDX-License-Identifier: MIT + */ + +class HelpTest extends \Zotlabs\Tests\Unit\Module\TestCase { + + use \phpmock\phpunit\PHPMock; + + /** + * Define the stubs to make sure they work later in the test. + * + * @see https://php-mock.github.io/php-mock-phpunit/api/class-phpmock.phpunit.PHPMock.html#_defineFunctionMock + * + * @beforeClass + */ + public static function define_stubs(): void { + self::defineFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + self::defineFunctionMock('Zotlabs\Module', 'file_get_contents'); + } + + /** + * Test getting a help page when the underlying file exists. + * + * @testWith + * ["md"] + * ["bb"] + * ["html"] + */ + public function test_get_request_when_help_file_exists(string $ext): void { + // Stub file exists, to only retur true for the file with the current + // extension + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->any()) + ->willReturnCallback( + fn (string $path) => $path === "doc/en/about/help_topic.{$ext}" + ); + + // Use a value map to make the `file_get_contents` stub return the + // correct content for the file types. + $file_content_map = [ + [ 'doc/en/about/help_topic.md', "### Help heading\n\$Projectname help content" ], + [ 'doc/en/about/help_topic.bb', "[h3]Help heading[/h3]\n\n\$Projectname help content" ], + [ 'doc/en/about/help_topic.html', "<h3>Help heading</h3><p>\$Projectname help content</p>" ], + ]; + + // Stub `file_get_contents` to plant our own content. + $fgc_stub = $this->getFunctionMock('Zotlabs\Module', 'file_get_contents'); + $fgc_stub + ->expects($this->once()) + ->willReturnMap($file_content_map); + + + $this->get("help/about/help_topic"); + + // Check that markdown content was correctly rendered + $this->assertPageContains('<h3>Help heading</h3>'); + + // Check that `$Projectname` has been translated properly + $this->assertPageContains('Hubzilla help content'); + + // Check that heading has been set + $this->assertPageContains('Hubzilla Documentation: About'); + + // Check that page title has been set + $this->assertTrue(isset(\App::$page['title']), 'Page title not set'); + $this->assertStringContainsString('Help', \App::$page['title']); + $this->assertStringContainsStringIgnoringCase('Help Topic', \App::$page['title']); + + // Check that nav selection has been set + $this->assertTrue(isset(\App::$nav_sel['raw_name']), 'Nav selection raw name not set'); + $this->assertEquals('Help', \App::$nav_sel['raw_name']); + + $this->assertTrue(isset(\App::$nav_sel['name']), 'Navselection name not set'); + $this->assertEquals('Help', \App::$nav_sel['name']); + } + + public function test_get_request_should_return_404_when_help_file_does_not_exist(): void { + // Stub file exists, to only retur true for the file with the current + // extension + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->any()) + ->willReturn(false); + + // Make sure `file_get_contents` is never called by the code. + $fgc_stub = $this->getFunctionMock('Zotlabs\Module', 'file_get_contents'); + $fgc_stub->expects($this->never()); + + $this->get("help/this_topic_does_not_exist"); + + $this->assertPageContains('not found'); + } + + public function test_get_request_without_args_redirects_to_about_page(): void { + $this->stub_goaway(); + $this->expectException(\Zotlabs\Tests\Unit\Module\RedirectException::class); + $this->expectExceptionMessage('about/about'); + + $this->get('help'); + } + + public function test_find_help_file_returns_first_match(): void { + // Stub file exists, to always return true + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->once()) + ->willReturn(true); + + // Stub `file_get_contents` to plant our own content. + $fgc_stub = $this->getFunctionMock('Zotlabs\Module', 'file_get_contents'); + $fgc_stub + ->expects($this->once()) + ->with('doc/en/first.md') + ->willReturn('found'); + + $this->get('help/first'); + } + + public function test_includes(): void { + // Stub `file_get_contents` to plant our own content. + $fgc_stub = $this->getFunctionMock('Zotlabs\Module', 'file_get_contents'); + $fgc_stub + ->expects($this->any()) + ->willReturnCallback( + function (string $path): string { + if ($path === 'doc/en/sub.md') { + return "### This is the included file."; + } else { + return "### Main topic\n\n#include doc/en/sub.md;"; + } + } + ); + + // Stub file exists, to always return true + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->any()) + ->willReturn(true); + + $this->get('help/main'); + + $this->assertPageContains('<h3>This is the included file.</h3>'); + } + + public function test_include_file_of_different_type_than_main_file(): void { + // Stub `file_get_contents` to plant our own content. + $fgc_stub = $this->getFunctionMock('Zotlabs\Module', 'file_get_contents'); + $fgc_stub + ->expects($this->any()) + ->willReturnCallback( + function (string $path): string { + if ($path === 'doc/en/sub.md') { + return "### This is the included file."; + } else { + return "[h3]Main topic[/h3]\n\n#include doc/en/sub.md;"; + } + } + ); + + // Stub file exists, only return true for main.bb and sub.md + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->any()) + ->willReturnCallback( + fn (string $path) => ( + $path === 'doc/en/main.bb' || $path === 'doc/en/sub.md' + ) + ); + + $this->get('help/main'); + + $this->assertPageContains('<h3>This is the included file.</h3>'); + } +} diff --git a/tests/unit/Module/RbmarkTest.php b/tests/unit/Module/RbmarkTest.php new file mode 100644 index 000000000..594e7369b --- /dev/null +++ b/tests/unit/Module/RbmarkTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Unit/integration tests for the Rbmark module. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * + * SPDX-License-Identifier: MIT + */ + +class RbmarkTest extends \Zotlabs\Tests\Unit\Module\TestCase { + public function test_unauthenticated_get_request_return_login_form(): void { + $lc_stub = $this->getFunctionMock('Zotlabs\Module', 'local_channel'); + $lc_stub + ->expects($this->once()) + ->willReturn(false); + + $this->get('rbmark', ['url' => 'https://bookmarked.url']); + + $this->assertPageContains('value="login" />'); + + // also check that the original query is saved in the session + $this->assertEquals('https://bookmarked.url', $_SESSION['bookmark']['url']); + $this->assertEquals('rbmark', $_SESSION['bookmark']['q']); + } + + public function test_authenticated_get_request_returns_save_bookmark_form(): void { + $lc_stub = $this->getFunctionMock('Zotlabs\Module', 'local_channel'); + $lc_stub + ->expects($this->once()) + ->willReturn(42); + + $this->get('rbmark', [ + 'url' => 'https://bookmarked.url', + 'title' => 'My bookmark', + ]); + + $this->assertPageContains('<form action="rbmark" method="post"'); + $this->assertPageContains('URL of bookmark'); + $this->assertPageContains('value="https://bookmarked.url"'); + $this->assertPageContains('value="My bookmark"'); + } + + public function test_that_params_are_escaped_in_save_bookmark_form(): void { + $lc_stub = $this->getFunctionMock('Zotlabs\Module', 'local_channel'); + $lc_stub + ->expects($this->once()) + ->willReturn(42); + + $this->get('rbmark', [ + 'url' => 'https://bookmarked.url" onload="alert(/boom/)', + 'title' => 'My bookmark"><script alert(/boom/);</script>', + ]); + + $this->assertPageContains('value="https://bookmarked.url" onload="alert(/boom/)'); + $this->assertPageContains('value="My bookmark"><script alert(/boom/);</script>'); + } + + public function test_that_existing_bookmark_folders_are_listed(): void { + $lc_stub = $this->getFunctionMock('Zotlabs\Module', 'local_channel'); + $lc_stub + ->expects($this->once()) + ->willReturn(42); + + $menu_id = menu_create([ + 'menu_name' => 'My bookmarks', + 'menu_desc' => 'A collection of my bookmarks', + 'menu_flags' => MENU_BOOKMARK, + 'menu_channel_id' => 42, + ]); + + $this->get('rbmark', [ + 'url' => 'https://bookmarked.url', + 'title' => 'My bookmark', + ]); + + $this->assertPageContains( + "<option value=\"{$menu_id}\" >My bookmarks</option>" + ); + } +} diff --git a/tests/unit/Module/RpostTest.php b/tests/unit/Module/RpostTest.php new file mode 100644 index 000000000..ad94f2f06 --- /dev/null +++ b/tests/unit/Module/RpostTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Tests for Rpost module. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +class RpostTest extends \Zotlabs\Tests\Unit\Module\TestCase { + + /** + * Basic test of a get request with no args as an authenticated user. + */ + public function test_get_with_no_args(): void { + $this->get_authenticated(); + + $this->assertPageContains('<form id="profile-jot-form"'); + $this->assertPageContains('<input type="hidden" name="profile_uid" value="42"'); + } + + /** + * Display login form if session is not authenticated. + */ + public function test_display_login_form_if_not_logged_in(): void { + $lc_mock = $this->getFunctionMock('Zotlabs\Module', 'local_channel') + ->expects($this->any()) + ->willReturn(false); + + $this->get('rpost'); + + $this->assertPageContains('<form action="https://hubzilla.test/rpost" id="main_login"'); + } + + public function test_populates_form_from_query_params(): void { + $this->get_authenticated([ + 'title' => 'This is my title', + 'body' => 'The body of the post', + 'source' => 'The temple of the Dagon', + ]); + + $this->assertPageContains('value="This is my title"'); + $this->assertPageContains('>The body of the post</textarea>'); + $this->assertPageContains('value="The temple of the Dagon"'); + } + + public function test_convert_body_from_html_to_bbcode(): void { + $this->get_authenticated([ + 'body' => "<h1>Awesome page</h1>\r\n<p>Awesome content!</p>", + 'type' => 'html', + ]); + + $this->assertPageContains(">[h1]Awesome page[/h1]\n\nAwesome content!</textarea>"); + } + + /** + * Private helper method to perform an authenticated GET request. + * + * @param array $query An associative array of query parameters. + */ + private function get_authenticated(array $query = []): void { + // Mock `local_chanel()` to emulate a valid logged in channel + $lc_mock = $this->getFunctionMock('Zotlabs\Module', 'local_channel') + ->expects($this->any()) + ->willReturn(42); + + // Set basic access controls to keep AccessList happy. + \App::$channel = [ + 'channel_id' => 42, + 'channel_location' => null, + 'channel_address' => '', + 'channel_allow_cid' => '', + 'channel_allow_gid' => '', + 'channel_deny_cid' => '', + 'channel_deny_gid' => '', + ]; + + $this->get('rpost', $query); + } +} diff --git a/tests/unit/Module/SetupTest.php b/tests/unit/Module/SetupTest.php new file mode 100644 index 000000000..3575dd477 --- /dev/null +++ b/tests/unit/Module/SetupTest.php @@ -0,0 +1,76 @@ +<?php +/** + * SPDX-FileCopyrightText: 2024 Harald Eilertsen + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit\Module; + +/** + * SetupModuleTest + * + * The Setup module should only be available during site installation. This is + * determined by whether there are any accounts present in the database, or + * not. + * + * This is a complex module, so expect the tests to grow as more of it will be + * covered. + */ +class SetupTest extends TestCase { + + public function test_that_setup_is_available_if_no_accounts_in_db(): void { + $this->with_no_accounts_in_db(); + $this->get('setup'); + + $this->assertEquals('setup', \App::$page['page_title']); + + // Assert that result _don't_ match "Permission denied" + $this->assertThat( + \App::$page['content'], + $this->logicalNot( + $this->matchesRegularExpression('/Permission denied/') + ) + ); + } + + public function test_that_setup_is_not_available_if_accounts_in_db(): void { + // The fixtures loaded by default add a couple of accounts. + $this->get('setup'); + + $this->assertEquals('setup', \App::$page['page_title']); + $this->assertMatchesRegularExpression('/Permission denied/', \App::$page['content']); + } + + public function test_that_setup_testrewrite_returns_ok(): void { + // We need to stub the `killme` function, as it is called directly from + // the code under test. + $this->stub_killme(); + + $output = ''; + + // Turn on output buffering, as code under test echoes + // directly to the output + ob_start(); + try { + $this->get('setup/testrewrite'); + } catch (\Zotlabs\Tests\Unit\Module\KillmeException) { + $output = ob_get_contents(); + } + + $this->assertEquals('ok', $output); + + ob_end_clean(); + } + + /** + * Delete all accounts from the database. + * + * This is currently needed because we automatically import the database + * fixtures on test start, which contains a couple of accounts already. + */ + private function with_no_accounts_in_db(): void { + q('DELETE FROM account;'); + } +} diff --git a/tests/unit/Module/TestCase.php b/tests/unit/Module/TestCase.php new file mode 100644 index 000000000..e92bc7083 --- /dev/null +++ b/tests/unit/Module/TestCase.php @@ -0,0 +1,192 @@ +<?php +/** + * This file contains the base class for module test cases. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit\Module; + +use Zotlabs\Tests\Unit\UnitTestCase; +use App; + +/** + * Base class for writing module tests. + * + * This test class adds a number of helper methods to the base from + * the UnitTestCase class, useful when testing Modules (Controllers) + * that handle incoming requests. + */ +class TestCase extends UnitTestCase { + + // Import PHPMock methods into this class + use \phpmock\phpunit\PHPMock; + + /** + * Emulate a GET request. + * + * @param string $uri The URI to request. Typically this will be the module + * name, followed by any req args separated by slashes. + * @param array $query Assciative array of query args, with the parameters + * as keys. + */ + protected function get(string $uri, array $query = []): void { + $_GET['q'] = $uri; + + if (!empty($query)) { + $_GET = array_merge($_GET, $query); + } + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; + $_SERVER['QUERY_STRING'] = "q={$uri}"; + // phpcs:disable Generic.PHP.DisallowRequestSuperglobal.Found + $_REQUEST = $_GET; + // phpcs::enable + + \App::init(); + \App::$page['content'] = ''; + + $router = new \Zotlabs\Web\Router(); + $router->Dispatch(); + } + + /** + * Helper to simplify asserting contents in the rendered page. + * + * @param string $needle The expected string to find. + */ + protected function assertPageContains(string $needle): void { + $this->assertStringContainsString($needle, App::$page['content']); + } + + /** + * Stub out the `killme` function. + * + * Useful for testing modules that call this function directly. + * + * Instead of calling exit, the stub will throw a `KillmeException`, + * that can be caught by the test code to regain control after request + * processing is terminated. + * + * **Example:** + * + * public function test_something(): void { + * $this->stub_killme(); + * + * try { + * killme(); + * } catch (KillmeException $e) { + * $this->assertSomething(...); + * } + * } + * + * It's also possible to use the builting PHPUnit expecations to verify + * that the function was called. + * + * public function test_something(): void { + * $this->stub_killme(); + * $this->expectException(KillmeException::class); + * + * killme(); + * } + * + * This is useful if you only want to check that processing was terminated + * with the `killme()` function. + * + * @throws KillmeException + */ + protected function stub_killme(): void { + $killme_stub = $this->getFunctionMock('Zotlabs\Module', 'killme'); + $killme_stub + ->expects($this->once()) + ->willReturnCallback( + function () { + throw new KillmeException(); + } + ); + } + + /** + * Stub out the `goaway` function. + * + * Useful for testing modules that calls this function directly. + * + * Instead of calling `killme()`, the stub will throw a RedirectException + * with the target URL as the exception message. This allows the test code + * to regain control after request processing is terminated. + * + * **Example:** + * + * public function test_redirect(): void { + * $this->stub_goaway(); + * + * try { + * goaway('https://example.com/some_uri'); + * } catch (RedirectException $e) { + * $this->assertEquals('https://example.com/some_uri', $e->getMessage()); + * $this->assertSomethingElse(...); + * } + * } + * It's also possible to use the builting PHPUnit expecations to verify + * that the function was called. + * + * public function test_something(): void { + * $this->stub_goaway(); + * $this->expectException(RedirectException::class); + * $this->expectExceptionMessage('https://example.com/some_uri'); + * + * goaway('https://example.com/some_uri'); + * } + * + * This is useful if you only want to check that the request was redirected. + * + * @throws RedirectException + */ + protected function stub_goaway(): void { + $goaway_stub = $this->getFunctionMock('Zotlabs\Module', 'goaway'); + $goaway_stub + ->expects($this->once()) + ->willReturnCallback( + function (string $uri) { + throw new RedirectException($uri); + } + ); + } + + /** + * Shorthand function to expect a redirect to a given URL. + * + * **Example:** + * + * public function test_redirect(): void { + * $this->expectRedirectTo('https://example.com/some_uri'); + * goaway('https://example.com/some_uri'); + * } + */ + protected function expectRedirectTo(string $destination): void { + $this->stub_goaway(); + $this->expectException(RedirectException::class); + $this->expectExceptionMessage($destination); + } +} + +/** + * Exception class for killme stub + */ +class KillmeException extends \Exception {} + +/** + * Exception class for goaway stub. + * + * Takes the goaway uri as an arg, and makes it available to the catch + * site via the `getMessage()` method. + */ +class RedirectException extends \Exception { + function __construct(string $uri) { + parent::__construct($uri); + } +} diff --git a/tests/unit/Photo/PhotoGdTest.php b/tests/unit/Photo/PhotoGdTest.php index 1324043c4..7ef07864c 100644 --- a/tests/unit/Photo/PhotoGdTest.php +++ b/tests/unit/Photo/PhotoGdTest.php @@ -130,21 +130,8 @@ class PhotoGdTest extends UnitTestCase { * Tests PhotoGd->imageString() */ public function testImagestringReturnsABinaryString() { - // Create a stub for global function get_config() - // get_config('system', 'png_quality') - // get_config('system', 'jpeg_quality'); - $gc = $this->getFunctionMock('Zotlabs\Photo', 'get_config'); - $gc->expects($this->once())->willReturnCallback( - function() { - switch($this->photoGd->getType()){ - case 'image/png': - return 7; - case 'image/jpeg': - default: - return 70; - } - } - ); + // Init config with a known value for the test + \Zotlabs\Lib\Config::Set('system', 'png_quality', 7); $this->assertIsString($this->photoGd->imageString()); } diff --git a/tests/unit/UnitTestCase.php b/tests/unit/UnitTestCase.php index a4ea94b13..afc309205 100644 --- a/tests/unit/UnitTestCase.php +++ b/tests/unit/UnitTestCase.php @@ -22,8 +22,8 @@ namespace Zotlabs\Tests\Unit; +use PHPUnit\Framework\Attributes\{Before, After}; use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestResult; /* * Make sure global constants and the global App object is available to the @@ -31,49 +31,78 @@ use PHPUnit\Framework\TestResult; */ require_once __DIR__ . '/../../boot.php'; require_once 'include/dba/dba_driver.php' ; +require_once 'include/dba/dba_transaction.php'; /** - * @brief Base class for our Unit Tests. + * Base class for our Unit Tests. * - * Empty class at the moment, but you should extend this class for unit test - * cases, so we could and for sure we will need to implement basic behaviour - * for all of our unit tests. + * Base class for Hubzilla unit/integration tests. This extends the base + * TestCase class from PHPUnit by connecting to a test database, and making the + * database connection available to the code under test via the normal Hubzilla + * mechanisms, i.e the \DBA::$dba global variable. * - * @author Klaus Weidenbach + * It also automatically loads database fixtures from yaml files in the + * tests/unit/includes/dba/_files directory. And wraps each test run in it's + * own database transaction. */ class UnitTestCase extends TestCase { protected array $fixtures = array(); + protected ?\DbaTransaction $db_transacton = null; /** - * Override the PHPUnit\Framework\TestCase::run method, so we can - * wrap it in a database transaction. + * Connect to the test db, load fixtures and global config. * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * This function is executed before every test. + * + * The transaction is rolled back in the `cleanup_test_db()` function + * that's executed after every test. */ - public function run(TestResult $result = null): TestResult { - // $myclass = get_class($this); - // logger("[*] Running test: {$myclass}::{$this->getName(true)}", LOGGER_DEBUG); - + #[Before] + protected function setup_test_db(): void { if (! \DBA::$dba) { - //logger('[*] Connecting to test db...'); $this->connect_to_test_db(); } // The $transactuion variable is needed to hold the transaction until the // function returns. - $transaction = new \DbaTransaction(\DBA::$dba); + $this->db_transaction = new \DbaTransaction(\DBA::$dba); $this->loadFixtures(); // Make sure app config is reset and loaded from fixtures \App::$config = array(); \Zotlabs\Lib\Config::Load('system'); + } - $result = parent::run($result); - - return $result; + /** + * Roll back test database to it's original state, cleaning up + * any changes from the test. + * + * This function is executes after evert tests. + */ + #[After] + protected function cleanup_test_db(): void { + // Setting the transaction to `null`, runs the destructor + // which rolls backk the transacton. + $this->db_transaction = null; } + /** + * Connect to the test database, + * + * By default it will connect to a MySQL database with the following settings: + * + * - HZ_TEST_DB_HOST: db + * - HZ_TEST_DB_PORT: default + * - HZ_TEST_DB_USER: test_user + * - HZ_TEST_DB_PASS: hubzilla + * - HZ_TEST_DB_DATABASE: hubzilla_test_db + * - HZ_TEST_DB_TYPE: mysql (can also be "postgres") + * - HZ_TEST_DB_CHARSET: UTF8 + * + * All of these settings can be overridden by the test runner by setting ENV vars + * named as above with the values you want to override. + */ protected function connect_to_test_db() : void { if ( !\DBA::$dba ) { \DBA::dba_factory( @@ -101,6 +130,14 @@ class UnitTestCase extends TestCase { } } + /** + * Return the database type from a string. + * + * @param string $type The database type, can be either mysql or postgres. + * + * @return The database type constant matching the passed in type, or DBTYPE_MYSQL + * if $type is empty or invalid. + */ private static function dbtype(string $type): int { if (trim(strtolower($type)) === 'postgres') { return DBTYPE_POSTGRES; @@ -109,6 +146,9 @@ class UnitTestCase extends TestCase { } } + /** + * Load database fixtures from the fixture path. + */ private function loadFixtures() : void { $files = glob(__DIR__ . '/includes/dba/_files/*.yml'); if ($files === false || empty($files)) { @@ -117,6 +157,21 @@ class UnitTestCase extends TestCase { array_walk($files, fn($file) => $this->loadFixture($file)); } + /** + * Load database fixtures from a specific file. + * + * The file must be a yaml file named the same as the table in the database + * it should populate. + * + * The file also need to have a root key with the same name as the table. + * Under which it contains an array of rows that should be inserted into + * the db table. + * + * @param string $file The path and filename of the fixture to load. + * The path name is relative to the current working + * directory of the process, which should normally + * be the Hubzilla root directory. + */ private function loadFixture($file) : void { $table_name = basename($file, '.yml'); $this->fixtures[$table_name] = yaml_parse_file($file)[$table_name]; diff --git a/tests/unit/Web/HttpSigTest.php b/tests/unit/Web/HttpSigTest.php index 0a22b543a..d705be4ff 100644 --- a/tests/unit/Web/HttpSigTest.php +++ b/tests/unit/Web/HttpSigTest.php @@ -46,7 +46,7 @@ class HttpSigTest extends UnitTestCase { HTTPSig::generate_digest_header($text) ); } - public function generate_digestProvider() { + public static function generate_digestProvider() { return [ 'empty body text' => [ '', @@ -84,9 +84,8 @@ class HttpSigTest extends UnitTestCase { } function testDecrypt_sigheaderUseSitePrivateKey() { - // Create a stub for global function get_config() with expectation - $t = $this->getFunctionMock('Zotlabs\Web', 'get_config'); - $t->expects($this->once())->willReturn('system.prvkey'); + // Initialize config with a known value for test + \Zotlabs\Lib\Config::Set('system', 'prvkey', 'system.prvkey'); $header = 'Header: iv="value_iv" key="value_key" alg="value_alg" data="value_data"'; $result = [ diff --git a/tests/unit/Widget/HelpindexTest.php b/tests/unit/Widget/HelpindexTest.php new file mode 100644 index 000000000..26aa34104 --- /dev/null +++ b/tests/unit/Widget/HelpindexTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Tests for the Helpindex widget. + * + * SPDX-FileCopyrightText: 2024 Harald Eilertsen + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * + * SPDX-License-Identifier: MIT + */ + +/** + * Test class for testing the Helpindex widget. + */ +class HelpindexTest extends \Zotlabs\Tests\Unit\Module\TestCase { + + use \phpmock\phpunit\PHPMock; + + /** + * Define the stubs to make sure they work later in the test. + * + * @see https://php-mock.github.io/php-mock-phpunit/api/class-phpmock.phpunit.PHPMock.html#_defineFunctionMock + * + * @beforeClass + */ + public static function define_stubs(): void { + self::defineFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + self::defineFunctionMock('Zotlabs\Widget', 'file_get_contents'); + } + + public function test_loading_toc(): void { + // Stub `file_get_contents` to plant our own content. + $fgc_stub = $this->getFunctionMock('Zotlabs\Widget', 'file_get_contents'); + $fgc_stub + ->expects($this->once()) + ->with($this->equalTo('doc/en/toc.html')) + ->willReturn('toc'); + + // Stub `file_exists` to only return true for the english toc file + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->any()) + ->willReturnCallback(fn (string $path) => $path === 'doc/en/toc.html' ); + + $this->render_widget(); + $this->assertOutputContains('toc'); + //$this->assertOutputContains('Help Content'); + } + + public function test_that_result_is_empty_when_toc_not_present(): void { + // Ensure `file_get_contents` is not called during the test. + $fgc_stub = $this->getFunctionMock('Zotlabs\Widget', 'file_get_contents'); + $fgc_stub->expects($this->never()); + + // Stub `file_exists` to always return false to simulate that we + // can't find the toc file. + $fe_stub = $this->getFunctionMock('Zotlabs\Lib\Traits', 'file_exists'); + $fe_stub + ->expects($this->any()) + ->willReturn(false); + + $this->render_widget(); + } + + /** + * Initializes the app and calls the widget code. + */ + private function render_widget(): void { + $_GET['q'] = 'help/en/about/about'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + + \App::init(); + + $widget = new \Zotlabs\Widget\Helpindex(); + $this->output = $widget->widget([]); + } + + private function assertOutputContains(string $needle): void { + $this->assertStringContainsString($needle, $this->output); + } +} diff --git a/tests/unit/includes/AccountTest.php b/tests/unit/includes/AccountTest.php index af5bcd3c1..3978f9d04 100644 --- a/tests/unit/includes/AccountTest.php +++ b/tests/unit/includes/AccountTest.php @@ -19,7 +19,7 @@ class AccountTest extends Zotlabs\Tests\Unit\UnitTestCase { $this->assertEquals($expected, check_account_email($email)); } - function check_account_email_provider() : array { + public static function check_account_email_provider() : array { return [ // Empty and valid emails return the same result ['', ['error' => false, 'message' => '']], diff --git a/tests/unit/includes/AuthTest.php b/tests/unit/includes/AuthTest.php new file mode 100644 index 000000000..fa9726fe8 --- /dev/null +++ b/tests/unit/includes/AuthTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Tests for the authentication code used in Hubzilla. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit\Includes; + +use App; +use Zotlabs\Lib\AConfig; +use Zotlabs\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\{DataProvider, RunTestsInSeparateProcesses}; + +/** + * Test class containing the test for the Hubzilla authentication code. + * + * Since the main authentication code is executed in the global scope on + * inclusion of the `includes/auth.php` file, we need to run each test in a + * separate process to make sure we can excersize the code as we need. + */ +#[RunTestsInSeparateProcesses] +class AuthTest extends UnitTestCase { + + /** + * Check that mfa status is not checked for certain modules. + * + * This causes issues with things like WebDAV and CardDAV, as there's + * currently no way for these modules to signal that a TOTP code is needed + * back to the connecting client. + */ + #[DataProvider('modules_excluded_from_mfa')] + public function test_mfa_is_not_checked_for_excluded_modules(string $module, array $args): void { + $account_id = $this->fixtures['account']['0']['account_id']; + + $_SESSION = [ + 'authenticated' => true, + 'account_id' => $account_id, + + // Trick the code to not warn that $_SESSION['uid'] is not set, + // but also not trigger the code that tries to change to the + // given channel. *Remove when code is fixed!* + 'uid' => 0, + ]; + + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + + App::$session = $this->create_session_stub(); + App::$module = $module; + App::$argv = $args; + App::$argc = count($args); + + // Enable multi factor authentication for this account + AConfig::Set($account_id, 'system', 'mfa_enabled', true); + + require 'include/auth.php'; + + $this->assertEquals(1, $_SESSION['authenticated']); + } + + /** + * Data provider for testing modules excluded from mfa + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + public static function modules_excluded_from_mfa(): array { + return [ + ['totp_check', []], + ['cdav', []], + ['cdav', ['calendar']], + ['cdav', ['addressbook']], + ['dav', []], + ]; + } + + private function create_session_stub(): \Zotlabs\Web\Session { + return $this->createStub('Zotlabs\Web\Session'); + } +} diff --git a/tests/unit/includes/BBCodeTest.php b/tests/unit/includes/BBCodeTest.php index a8aa8ee2b..daa66bf72 100644 --- a/tests/unit/includes/BBCodeTest.php +++ b/tests/unit/includes/BBCodeTest.php @@ -101,7 +101,7 @@ class BBCodeTest extends UnitTestCase { * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function bbcode_to_html_provider(): array { + public static function bbcode_to_html_provider(): array { return [ 'code block' => [ "[code]\ntestvar = \"this is a test\"\necho \"the message is \$testvar\"\n[/code]", @@ -134,6 +134,10 @@ class BBCodeTest extends UnitTestCase { 'list with linebreaks \r\n in text' => [ "some text\r\n[list]\r\n[*] item1\r\nsome text[*] item2\r\nsome text[/list]\r\nsome more text", 'some text<br /><ul class="listbullet"><li> item1<br />some text<li> item2<br />some text</ul>some more text' + ], + 'del tag' => [ + 'some [s]strike through[/s] text', + 'some <del>strike through</del> text' ] ]; } @@ -149,7 +153,7 @@ class BBCodeTest extends UnitTestCase { * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function bbcode_observer_provider(): array { + public static function bbcode_observer_provider(): array { return [ 'authenticated observer' => [ '[observer=1]This should be visible[/observer][observer=0]but not this[/observer]', @@ -201,7 +205,7 @@ class BBCodeTest extends UnitTestCase { * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function html2bbcode_provider(): array { + public static function html2bbcode_provider(): array { return [ 'paragraph over multiple lines' => [ "<p>A paragraph over\nmultiple lines\nshould be unwrapped</p>", @@ -230,6 +234,38 @@ class BBCodeTest extends UnitTestCase { 'html reshares from streams' => [ '<div><div><a href="https://example.com"><img src="https://example.com/image.jpg" alt="image/photo"></a> shared something</div>something</div>', '[url=https://example.com][img=https://example.com/image.jpg]image/photo[/img][/url] shared something' . "\n" . 'something' + ], + 'list' => [ + '<ul><li>list 1</li><li>list 2</li><li>list 3</li></ul>', + '[list][*]list 1[*]list 2[*]list 3[/list]' + ], + 'list with paragraph' => [ + '<ul><li><p>list 1</p></li><li><p>list 2</p></li><li><p>list 3</p></li></ul>', + '[list][*]list 1[*]list 2[*]list 3[/list]' + ], + 'nested list' => [ + '<ul><li>list 1</li><li>list 2</li><li>list 3</li><ul><li>list 1</li><li>list 2</li><li>list 3</li></ul></ul>', + '[list][*]list 1[*]list 2[*]list 3[list][*]list 1[*]list 2[*]list 3[/list][/list]' + ], + 'double nested list' => [ + '<ul><li>list 1</li><li>list 2</li><li>list 3</li><ul><li>list 1</li><li>list 2</li><li>list 3</li><ul><li>list 1</li><li>list 2</li><li>list 3</li></ul></ul></ul>', + '[list][*]list 1[*]list 2[*]list 3[list][*]list 1[*]list 2[*]list 3[list][*]list 1[*]list 2[*]list 3[/list][/list][/list]' + ], + 'list without closing li' => [ + '<ul><li>list 1<li>list 2<li>list 3</ul>', + '[list][*]list 1[*]list 2[*]list 3[/list]' + ], + 'nested list without closing li' => [ + '<ul><li>list 1<li>list 2<li>list 3<ul><li>list 1<li>list 2<li>list 3</ul></ul>', + '[list][*]list 1[*]list 2[*]list 3[list][*]list 1[*]list 2[*]list 3[/list][/list]' + ], + 'double nested list without closing li' => [ + '<ul><li>list 1<li>list 2<li>list 3<ul><li>list 1<li>list 2<li>list 3<ul><li>list 1<li>list 2<li>list 3</ul></ul></ul>', + '[list][*]list 1[*]list 2[*]list 3[list][*]list 1[*]list 2[*]list 3[list][*]list 1[*]list 2[*]list 3[/list][/list][/list]' + ], + 'del tag' => [ + 'some <del>strike through</del> text', + 'some [s]strike through[/s] text' ] ]; } diff --git a/tests/unit/includes/FeedutilsTest.php b/tests/unit/includes/FeedutilsTest.php index bda0bf425..05bff06a3 100644 --- a/tests/unit/includes/FeedutilsTest.php +++ b/tests/unit/includes/FeedutilsTest.php @@ -47,7 +47,7 @@ class FeedutilsTest extends UnitTestCase { public function test_atom_author() { $this->assertEquals('', atom_author('', 'nick', 'name', 'uri', 72, 72, 'png', 'photourl')); - $a = '<tag> + $expected = '<tag> <id>uri</id> <name>nick</name> <uri>uri</uri> @@ -57,7 +57,10 @@ class FeedutilsTest extends UnitTestCase { <poco:displayName>name</poco:displayName> </tag>'; - $this->assertXmlStringEqualsXmlString($a, atom_author('tag', 'nick', 'name', 'uri', 72, 72, 'png', 'http://photourl')); + $this->assertAtomAuthorMatches( + $expected, + atom_author('tag', 'nick', 'name', 'uri', 72, 72, 'png', 'http://photourl') + ); } /** @@ -86,6 +89,63 @@ class FeedutilsTest extends UnitTestCase { <poco:displayName>Chan</poco:displayName> </tag>'; - $this->assertXmlStringEqualsXmlString($a, atom_render_author('tag', $xchan)); + $this->assertAtomAuthorMatches($a, atom_render_author('tag', $xchan)); + } + + /** + * Helper method to assert that the generated author tag matches + * what we expect. + * + * Calling `assertXmlStringEqualsXmlString` directly on the fragments + * does not work anymore in PHPUnit >= 10.x, as t will throw an XMLException + * because of undefined namespaces. + * + * To overcome that we wrap the generated tags in the proper template, + * and compare the fully generated XML from the template instead. + * + * @param string $expected The expected author XML fragment. + * @param string $actual The actually generated authr XML fragment. + */ + private function assertAtomAuthorMatches(string $expected, string $actual): void { + + // Make sure the template engine is initialized before we try to render + // the template. + // + // This may be problematic, as it will compile the template into the same + // directory as the site. Assuming that nobody is crazy enough to run the + // test suite in a production server, it should probably be fine for now. + $smarty = new \Zotlabs\Render\SmartyTemplate(); + \App::register_template_engine(get_class($smarty)); + + $feed_template = get_markup_template('atom_feed.tpl'); + $expected_xml = replace_macros($feed_template, [ + '$version' => 42, + '$generator' => 'Hubzilla test', + '$generator_uri' => 'https://hubzilla.test', + '$feed_id' => 'test_channel', + '$feed_title' => 'Test channel', + '$feed_updated' => 'Sometime', + '$author' => $expected, + '$owner' => $expected, + '$profile_page' => xmlify('https://hubzilla.test/channel/test'), + ]); + + $expected_xml .= '</feed>'; + + $actual_xml = replace_macros($feed_template, [ + '$version' => 42, + '$generator' => 'Hubzilla test', + '$generator_uri' => 'https://hubzilla.test', + '$feed_id' => 'test_channel', + '$feed_title' => 'Test channel', + '$feed_updated' => 'Sometime', + '$author' => $actual, + '$owner' => $actual, + '$profile_page' => xmlify('https://hubzilla.test/channel/test'), + ]); + + $actual_xml .= '</feed>'; + + $this->assertXmlStringEqualsXmlString($expected_xml, $actual_xml); } } diff --git a/tests/unit/includes/LanguageTest.php b/tests/unit/includes/LanguageTest.php index 9525c783d..8f62e71e2 100644 --- a/tests/unit/includes/LanguageTest.php +++ b/tests/unit/includes/LanguageTest.php @@ -33,54 +33,6 @@ use Text_LanguageDetect; * @author Klaus Weidenbach */ class LanguageTest extends UnitTestCase { - //use PHPMock; - - /** - * @dataProvider languageExamplesProvider - * @coversNothing - */ - public function testDetectLanguage($text, $langCode, $confidence) { - - // php-mock can not mock global functions which is called by a global function. - // If the calling function is in a namespace it would work. - //$gc = $this->getFunctionMock(__NAMESPACE__, 'get_config'); - //$gc->expects($this->once())->willReturn(10) - //$cg = $this->getFunctionMock('Zotlabs\Lib\Config', 'Get'); - //$cg->expects($this->once())->willReturn(10); - //$this->assertEquals($langCode, detect_language($text)); - - - // Can not unit test detect_language(), therefore test the used library - // only for now to find regressions on library updates. - $l = new Text_LanguageDetect; - // return 2-letter ISO 639-1 (en) language code - $l->setNameMode(2); - $lng = $l->detectConfidence($text); - - $this->assertEquals($langCode, $lng['language']); - $this->assertEquals($confidence, round($lng['confidence'], 6)); - } - - public function languageExamplesProvider() { - return [ - 'English' => [ - 'English is a West Germanic language that was first spoken in early medieval England and is now a global lingua franca.[4][5] Named after the Angles, one of the Germanic tribes that migrated to England, it ultimately derives its name from the Anglia (Angeln) peninsula in the Baltic Sea. It is closely related to the Frisian languages, but its vocabulary has been significantly influenced by other Germanic languages, particularly Norse (a North Germanic language), as well as by Latin and Romance languages, especially French.', - 'en', - 0.078422 - ], - 'German' => [ - 'Deutschland ist ein Bundesstaat in Mitteleuropa. Er besteht aus 16 Ländern und ist als freiheitlich-demokratischer und sozialer Rechtsstaat verfasst. Die Bundesrepublik Deutschland stellt die jüngste Ausprägung des deutschen Nationalstaates dar. Mit rund 82,8 Millionen Einwohnern (31. Dezember 2016) zählt Deutschland zu den dicht besiedelten Flächenstaaten.', - 'de', - 0.134339 - ], - 'Norwegian' => [ - 'Kongeriket Norge er et nordisk, europeisk land og en selvstendig stat vest på Den skandinaviske halvøy. Landet er langt og smalt, og kysten strekker seg langs Nord-Atlanteren, hvor også Norges kjente fjorder befinner seg. Totalt dekker det relativt tynt befolkede landet 385 000 kvadratkilometer med litt over fem millioner innbyggere (2016).', - 'no', - 0.007076 - ] - ]; - } - /** * @covers ::get_language_name @@ -94,7 +46,7 @@ class LanguageTest extends UnitTestCase { } } - public function getLanguageNameProvider() { + public static function getLanguageNameProvider() { return [ 'empty language code' => [ '', diff --git a/tests/unit/includes/MarkdownTest.php b/tests/unit/includes/MarkdownTest.php index 960c15139..310130bf1 100644 --- a/tests/unit/includes/MarkdownTest.php +++ b/tests/unit/includes/MarkdownTest.php @@ -39,7 +39,7 @@ class MarkdownTest extends UnitTestCase { $this->assertEquals($expected, markdown_to_bb($src)); } - private function markdown_to_bbcode_provider(): array { + public static function markdown_to_bbcode_provider(): array { return [ 'empty text' => [ '', @@ -104,7 +104,7 @@ class MarkdownTest extends UnitTestCase { $this->assertEquals($markdown, html2markdown($html)); } - public function html2markdownProvider(): array { + public static function html2markdownProvider(): array { return [ 'empty text' => [ '', diff --git a/tests/unit/includes/NetworkTest.php b/tests/unit/includes/NetworkTest.php index 9fb00e9d3..a41075f25 100644 --- a/tests/unit/includes/NetworkTest.php +++ b/tests/unit/includes/NetworkTest.php @@ -7,12 +7,6 @@ class NetworkTest extends Zotlabs\Tests\Unit\UnitTestCase { - public function setUp() : void { - parent::setUp(); - - \App::set_baseurl("https://mytest.org"); - } - /** * @dataProvider localUrlTestProvider */ @@ -20,10 +14,10 @@ class NetworkTest extends Zotlabs\Tests\Unit\UnitTestCase { $this->assertEquals($expected, is_local_url($url)); } - public function localUrlTestProvider() : array { + public static function localUrlTestProvider() : array { return [ [ '/some/path', true ], - [ 'https://mytest.org/some/path', true ], + [ 'https://hubzilla.test/some/path', true ], [ 'https://other.site/some/path', false ], ]; } @@ -47,7 +41,7 @@ class NetworkTest extends Zotlabs\Tests\Unit\UnitTestCase { $this->assertTrue(validate_email($email)); } - function validate_email_provider() : array { + public static function validate_email_provider() : array { return [ // First some invalid email addresses ['', false], diff --git a/tests/unit/includes/TextTest.php b/tests/unit/includes/TextTest.php index b76b15dcf..1e80d71d8 100644 --- a/tests/unit/includes/TextTest.php +++ b/tests/unit/includes/TextTest.php @@ -85,7 +85,7 @@ empty line above'; public function testNotags($string, $expected) { $this->assertEquals($expected, notags($string)); } - public function notagsProvider() { + public static function notagsProvider() { return [ 'empty string' => ['', ''], 'simple tag' => ['<value>', '[value]'], @@ -102,7 +102,7 @@ empty line above'; sanitise_acl($string); $this->assertEquals($expected, $string); } - public function sanitise_aclProvider() { + public static function sanitise_aclProvider() { return [ 'text' => ['value', '<value>'], 'text with angle bracket' => ['<value>', '<[value]>'], diff --git a/tests/unit/includes/dba/TransactionTest.php b/tests/unit/includes/dba/TransactionTest.php index 99e3f459d..0b986c6aa 100644 --- a/tests/unit/includes/dba/TransactionTest.php +++ b/tests/unit/includes/dba/TransactionTest.php @@ -24,8 +24,8 @@ require_once 'tests/fakes/fake_dba.php'; require_once 'include/dba/dba_transaction.php'; -use \PHPUnit\Framework\TestCase; -use \Zotlabs\Tests\Fakes\FakeDba; +use PHPUnit\Framework\TestCase; +use Zotlabs\Tests\Fakes\FakeDba; /** * Test database transactions. @@ -39,7 +39,7 @@ class DbaTransactionTest extends TestCase { private $pdo_stub; public function setUp(): void { - $this->pdo_stub = $this->createStub(PDO::class); + $this->pdo_stub = $this->createMock(PDO::class); } diff --git a/tests/unit/includes/dba/_files/account.yml b/tests/unit/includes/dba/_files/account.yml index 344bdb799..88e59056e 100644 --- a/tests/unit/includes/dba/_files/account.yml +++ b/tests/unit/includes/dba/_files/account.yml @@ -3,7 +3,9 @@ account: account_id: 42 account_email: "hubzilla@example.com" account_language: "no" + account_flags: 0 - account_id: 43 account_email: "hubzilla@example.org" account_language: "de" + account_flags: 1 diff --git a/tests/unit/includes/dba/_files/config.yml b/tests/unit/includes/dba/_files/config.yml index e93486857..ac3c8acb0 100644 --- a/tests/unit/includes/dba/_files/config.yml +++ b/tests/unit/includes/dba/_files/config.yml @@ -2,6 +2,10 @@ config: - cat: system + k: baseurl + v: https://hubzilla.test + - + cat: system k: do_not_check_dns v: true - |