aboutsummaryrefslogtreecommitdiffstats
path: root/tests/unit/Module
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/Module')
-rw-r--r--tests/unit/Module/HelpTest.php179
-rw-r--r--tests/unit/Module/RbmarkTest.php80
-rw-r--r--tests/unit/Module/RpostTest.php81
-rw-r--r--tests/unit/Module/SetupTest.php76
-rw-r--r--tests/unit/Module/TestCase.php192
5 files changed, 608 insertions, 0 deletions
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&quot; onload=&quot;alert(/boom/)');
+ $this->assertPageContains('value="My bookmark&quot;&gt;&lt;script alert(/boom/);&lt;/script&gt;');
+ }
+
+ 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);
+ }
+}