diff options
author | Mario <mario@mariovavti.com> | 2024-03-22 08:37:29 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2024-03-22 08:37:29 +0000 |
commit | 1aeb05628b6a2a069c46980efbe628362c9e3e74 (patch) | |
tree | e9aed15d0cd74e0c23dcb05c7be8fe9541efdf36 /tests/unit | |
parent | 5b7387459cf4de8f7354d81cb0392c4225714d94 (diff) | |
parent | b464fae3bf22585888c5f3def8eded76fd48ed16 (diff) | |
download | volse-hubzilla-1aeb05628b6a2a069c46980efbe628362c9e3e74.tar.gz volse-hubzilla-1aeb05628b6a2a069c46980efbe628362c9e3e74.tar.bz2 volse-hubzilla-1aeb05628b6a2a069c46980efbe628362c9e3e74.zip |
Merge branch '9.0RC'9.0
Diffstat (limited to 'tests/unit')
-rw-r--r-- | tests/unit/GetTagsTest.php | 5 | ||||
-rw-r--r-- | tests/unit/Lib/ActivityTest.php | 39 | ||||
-rw-r--r-- | tests/unit/Lib/ConfigTest.php | 12 | ||||
-rw-r--r-- | tests/unit/Lib/JcsEddsa2022Test.php | 174 | ||||
-rw-r--r-- | tests/unit/Lib/PermissionDescriptionTest.php | 47 | ||||
-rw-r--r-- | tests/unit/UnitTestCase.php | 94 | ||||
-rw-r--r-- | tests/unit/Web/HttpSigTest.php | 7 | ||||
-rw-r--r-- | tests/unit/includes/AccountTest.php | 34 | ||||
-rw-r--r-- | tests/unit/includes/BBCodeTest.php | 220 | ||||
-rw-r--r-- | tests/unit/includes/MarkdownTest.php | 129 | ||||
-rw-r--r-- | tests/unit/includes/NetworkTest.php | 89 | ||||
-rw-r--r-- | tests/unit/includes/PhotodriverTest.php | 28 | ||||
-rw-r--r-- | tests/unit/includes/TextTest.php | 2 | ||||
-rw-r--r-- | tests/unit/includes/dba/TransactionTest.php | 207 | ||||
-rw-r--r-- | tests/unit/includes/dba/_files/config.yml | 23 |
15 files changed, 1010 insertions, 100 deletions
diff --git a/tests/unit/GetTagsTest.php b/tests/unit/GetTagsTest.php index 418d32c47..ccb88080e 100644 --- a/tests/unit/GetTagsTest.php +++ b/tests/unit/GetTagsTest.php @@ -26,6 +26,7 @@ class MockApp { * * @param string $sql */ +/* function q($sql) { $result=array(array('id'=>15, 'attag'=>'', 'network'=>'dfrn', @@ -55,7 +56,7 @@ function q($sql) { return $result; } } - +*/ /** * Replacement for dbesc. * I don't want to test dbesc here, so @@ -68,9 +69,11 @@ function q($sql) { * * @return input */ +/* function dbesc($str) { return $str; } +*/ /** * TestCase for tag handling. diff --git a/tests/unit/Lib/ActivityTest.php b/tests/unit/Lib/ActivityTest.php new file mode 100644 index 000000000..c9ce79d8c --- /dev/null +++ b/tests/unit/Lib/ActivityTest.php @@ -0,0 +1,39 @@ +<?php +namespace Zotlabs\Tests\Unit\Lib; + +error_reporting(E_ALL); + +use Zotlabs\Tests\Unit\UnitTestCase; +use Zotlabs\Lib\Activity; + +class ActivityTest extends UnitTestCase { + /** + * Test get a textfield from an activitystreams object + * + * @dataProvider get_textfield_provider + */ + public function test_get_textfield(array $src, null|string|array $expected): void { + $this->assertEquals($expected, Activity::get_textfield($src, 'content')); + } + + /** + * Dataprovider for test_get_textfield. + */ + private function get_textfield_provider(): array { + return [ + 'get content field' => [ + ['content' => 'Some content'], + 'Some content' + ], + 'get content from map' => [ + ['contentMap' => ['en' => 'Some content']], + ['en' => 'Some content'] + ], + 'get not available content' => [ + ['some_field' => 'Some content'], + null + ] + ]; + } + +} diff --git a/tests/unit/Lib/ConfigTest.php b/tests/unit/Lib/ConfigTest.php index a8ae3631b..c853cdd87 100644 --- a/tests/unit/Lib/ConfigTest.php +++ b/tests/unit/Lib/ConfigTest.php @@ -20,6 +20,7 @@ class ConfigTest extends Zotlabs\Tests\Unit\UnitTestCase { 'php-array' => 'a:3:{i:0;s:3:"one";i:1;s:3:"two";i:2;s:5:"three";}', 'json-array' => 'json:["one","two","three"]', 'object-injection' => 'a:1:{i:0;O:18:"Zotlabs\Lib\Config":0:{}}', + 'unserialized-array' => ['one', 'two', 'three'], 'config_loaded' => true, ), ); @@ -51,6 +52,17 @@ class ConfigTest extends Zotlabs\Tests\Unit\UnitTestCase { } /* + * Test that we can retreive unserialized arrays which are usually + * returned from the existing config cache. + */ + public function testGetPHPUnserializedArray(): void { + $this->assertEquals( + Zotlabs\Lib\Config::Get('test', 'unserialized-array'), + array('one', 'two', 'three') + ); + } + + /* * Make sure we're not vulnerable to PHP Object injection attacks when * using the PHP `unserialize()` function. */ diff --git a/tests/unit/Lib/JcsEddsa2022Test.php b/tests/unit/Lib/JcsEddsa2022Test.php new file mode 100644 index 000000000..d18ad01ce --- /dev/null +++ b/tests/unit/Lib/JcsEddsa2022Test.php @@ -0,0 +1,174 @@ +<?php + +namespace Zotlabs\Tests\Unit\Lib; + +use Zotlabs\Lib\JcsEddsa2022; +use Zotlabs\Tests\Unit\UnitTestCase; + +class JcsEddsa2022Test extends UnitTestCase { + + public function testVerifyFromSpec() { + $publicKey = 'z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2'; + $privateKey = 'z3u2en7t5LR2WtQH5PfFqMqwVHBeXouLzo6haApm8XHqvjxq'; + + $document = '{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "eddsa-jcs-2022", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "https://vc.example/issuers/5678#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2", + "proofPurpose": "assertionMethod", + "proofValue": "z3P6rHMUaWG6e3Ac6xYFht8aEvoVXndgKTtEY8kzWYXzk8dKmAo2GJeZiJw4qoZ2PGp4ugdaHx3oQiLpeFBLDqP2M" + } + }'; + + $verified = (new JcsEddsa2022())->verify(json_decode($document, true), $publicKey); + $this->assertTrue($verified, 'Verify eddsa-jcs-2022 (from specification)'); + + } + + public function testSignAndVerify() { + $publicKey = 'z6MkfpucGTDbMZADwM6vEa8pS3s8Z9xqSEn6HihijZ4fVs9d'; + $channel = [ + 'channel_url' => 'https://example.com/channel/klingon', + 'channel_epubkey' => 'FGdbYgr526Swuyya3e8epCBdHahlWNg9I0sBhMKCzpw', + 'channel_eprvkey' => 'StLRo8xb7VJ5XdR10OUYQM/uooP7D7fMlgvQFa1wrZIUZ1tiCvnbpLC7LJrd7x6kIF0dqGVY2D0jSwGEwoLOnA', + 'channel_address' => 'klingon@example.com', + 'channel_system' => false, + ]; + + $document = '{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/multikey/v1", + { + "nomad": "https://example.com/apschema#", + "toot": "http://joinmastodon.org/ns#", + "litepub": "http://litepub.social/ns#", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "oauthRegistrationEndpoint": "litepub:oauthRegistrationEndpoint", + "sensitive": "as:sensitive", + "movedTo": "as:movedTo", + "discoverable": "toot:discoverable", + "indexable": "toot:indexable", + "capabilities": "litepub:capabilities", + "acceptsJoins": "litepub:acceptsJoins", + "Hashtag": "as:Hashtag", + "canReply": "toot:canReply", + "canSearch": "nomad:canSearch", + "approval": "toot:approval", + "expires": "nomad:expires", + "directMessage": "nomad:directMessage", + "Category": "nomad:Category", + "copiedTo": "nomad:copiedTo", + "searchContent": "nomad:searchContent", + "searchTags": "nomad:searchTags" + } + ], + "type": "Person", + "id": "https://example.com/channel/klingon", + "preferredUsername": "klingon", + "name": "klingon", + "created": "2023-07-13T20:23:32Z", + "updated": "2023-07-13T20:23:32Z", + "icon": { + "type": "Image", + "mediaType": "image/png", + "updated": "2023-07-13T20:23:32Z", + "url": "https://example.com/photo/profile/l/2", + "height": 300, + "width": 300 + }, + "url": "https://example.com/channel/klingon", + "tag": [ + { + "type": "Note", + "name": "Protocol", + "content": "zot6" + }, + { + "type": "Note", + "name": "Protocol", + "content": "nomad" + }, + { + "type": "Note", + "name": "Protocol", + "content": "activitypub" + } + ], + "inbox": "https://example.com/inbox/klingon", + "outbox": "https://example.com/outbox/klingon", + "followers": "https://example.com/followers/klingon", + "following": "https://example.com/following/klingon", + "wall": "https://example.com/outbox/klingon", + "endpoints": { + "sharedInbox": "https://example.com/inbox", + "oauthRegistrationEndpoint": "https://example.com/api/client/register", + "oauthAuthorizationEndpoint": "https://example.com/authorize", + "oauthTokenEndpoint": "https://example.com/token", + "searchContent": "https://example.com/search/klingon?search={}", + "searchTags": "https://example.com/search/klingon?tag={}" + }, + "discoverable": true, + "canSearch": [], + "indexable": false, + "publicKey": { + "id": "https://example.com/channel/klingon?operation=rsakey", + "owner": "https://example.com/channel/klingon", + "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + "publicKeyPem": "-----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+LXyOD/bzzVgM/nUOJ5m + c4WrQPMlhKqWJvKrumdQw9JJYcyaZp/jmMxDx/w/EwVw+wnV5wZcD0yBVhC7NPRa + nYc5OfNhS4MO74xgZrj+VWSTzNo3YooS/dEIIvsu6bhxfooHj17SA6pMRnZkkVpk + ykpPRYwJw+NvKcRwzpF06rxMqjZ+Bp0ea/X37j4cHaosRoQTJiHmMKKnpByKdImF + TR1juJ69ASh6nh8YVGcz6fz1jBQZPMx05tfNdyN5oZRTr8Nug2CiF3V7yKKS14HD + kE9eeFeTMt58Qi+8kprATYxKrlIuTZmI4YdIRgtM+tPQsosKTFmjzbef4dYooutv + T7XfrE+wYVZlx2pkaeFiKrJVacpmmFJe8zCIFXrofq1aOagU1kpwnXgjneCttA+M + OJ3Y+cPamdfRQDtsBcokJUD40RTwux6OGW9zqkJIpniVB+CZu4nTOHCzMJwbxF0p + JmGZd9kc3PR6Uf/IHAb1xeyTi4FyyYTbRDYuJyqRKbe880QUwgCBcogIbNy4xxsH + UTMy0ucWaDSBRahKUIHl3FRglvnI754NJSXBDIQOwC9oRRH27Vmm1Jy8sltmFLFr + ENJCGgOH8Bhpk+y1jtw1jpTig76wIvw+6zQtgNSfPnrNGIHt5mcoy4pFFXLv2lK2 + /u26hUGQAq71Ra0DwgXIWFECAwEAAQ== + -----END PUBLIC KEY----- + " + }, + "assertionMethod": [ + { + "id": "https://example.com/channel/klingon#z6MkfpucGTDbMZADwM6vEa8pS3s8Z9xqSEn6HihijZ4fVs9d", + "type": "Multikey", + "controller": "https://example.com/channel/klingon", + "publicKeyMultibase": "z6MkfpucGTDbMZADwM6vEa8pS3s8Z9xqSEn6HihijZ4fVs9d" + } + ], + "manuallyApprovesFollowers": true + }'; + + $algorithm = new JcsEddsa2022(); + $documentArray = json_decode($document,true); + $documentArray['proof'] = $algorithm->sign($documentArray, $channel); + + $verified = (new JcsEddsa2022())->verify($documentArray, $publicKey); + $this->assertTrue($verified, 'Verify encode and decode eddsa-jcs-2022'); + + } +} diff --git a/tests/unit/Lib/PermissionDescriptionTest.php b/tests/unit/Lib/PermissionDescriptionTest.php index 96c381d0c..fdd676f61 100644 --- a/tests/unit/Lib/PermissionDescriptionTest.php +++ b/tests/unit/Lib/PermissionDescriptionTest.php @@ -63,11 +63,52 @@ class PermissionDescriptionTest extends UnitTestCase { $this->assertNotNull($permDescSelf); } + /** + * Test fetching permission descriptions for the current channel. + */ public function testFromGlobalPermission() { - //$permDesc = PermissionDescription::fromGlobalPermission('view_profile'); + // Initiate the global App with a channel_id + \App::$channel = array( + 'channel_id' => 42, + ); + + // Make sure the requested permission is set for this channel. + \Zotlabs\Access\PermissionLimits::Set( + \App::$channel['channel_id'], + 'view_profile', + PERMS_NETWORK + ); + + \Zotlabs\Access\PermissionLimits::Set( + \App::$channel['channel_id'], + 'write_storage', + PERMS_SPECIFIC + ); + + // Set an invalid(?) permission + \Zotlabs\Access\PermissionLimits::Set( + \App::$channel['channel_id'], + 'view_wiki', + 1337 + ); + + $permDesc = PermissionDescription::fromGlobalPermission('view_profile'); + $this->assertEquals( + 'Anybody in the Hubzilla network', + $permDesc->get_permission_description() + ); + + $permDesc = PermissionDescription::fromGlobalPermission('write_storage'); + $this->assertEquals( + 'Only connections I specifically allow', + $permDesc->get_permission_description() + ); - $this->markTestIncomplete( - 'The method fromGlobalPermission() is not yet testable ...' + // Permissions we don't know about will get the fallback description. + $permDesc = PermissionDescription::fromGlobalPermission('view_wiki'); + $this->assertEquals( + 'Visible to your default audience', + $permDesc->get_permission_description() ); } diff --git a/tests/unit/UnitTestCase.php b/tests/unit/UnitTestCase.php index f6fb28555..a4ea94b13 100644 --- a/tests/unit/UnitTestCase.php +++ b/tests/unit/UnitTestCase.php @@ -23,12 +23,14 @@ namespace Zotlabs\Tests\Unit; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestResult; /* * Make sure global constants and the global App object is available to the * tests. */ require_once __DIR__ . '/../../boot.php'; +require_once 'include/dba/dba_driver.php' ; /** * @brief Base class for our Unit Tests. @@ -39,6 +41,94 @@ require_once __DIR__ . '/../../boot.php'; * * @author Klaus Weidenbach */ -abstract class UnitTestCase extends TestCase { - // when needed we can define functionality here which is used in UnitTests. +class UnitTestCase extends TestCase { + protected array $fixtures = array(); + + /** + * Override the PHPUnit\Framework\TestCase::run method, so we can + * wrap it in a database transaction. + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function run(TestResult $result = null): TestResult { + // $myclass = get_class($this); + // logger("[*] Running test: {$myclass}::{$this->getName(true)}", LOGGER_DEBUG); + + 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->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; + } + + protected function connect_to_test_db() : void { + if ( !\DBA::$dba ) { + \DBA::dba_factory( + getenv('HZ_TEST_DB_HOST') ?: 'localhost', + + // Use default port for db type if none specified + getenv('HZ_TEST_DB_PORT'), + getenv('HZ_TEST_DB_USER') ?: 'test_user', + getenv('HZ_TEST_DB_PASS') ?: 'hubzilla', + getenv('HZ_TEST_DB_DATABASE') ?: 'hubzilla_test_db', + self::dbtype(getenv('HZ_TEST_DB_TYPE')), + getenv('HZ_TEST_DB_CHARSET') ?: 'UTF8', + false); + + if ( !\DBA::$dba->connected ) { + $msg = "Unable to connect to db! "; + if(file_exists('dbfail.out')) { + $msg .= file_get_contents('dbfail.out'); + } + + throw new \Exception($msg); + } + + \DBA::$dba->dbg(true); + } + } + + private static function dbtype(string $type): int { + if (trim(strtolower($type)) === 'postgres') { + return DBTYPE_POSTGRES; + } else { + return DBTYPE_MYSQL; + } + } + + private function loadFixtures() : void { + $files = glob(__DIR__ . '/includes/dba/_files/*.yml'); + if ($files === false || empty($files)) { + error_log('[-] ' . __METHOD__ . ': No fixtures found! :('); + } + array_walk($files, fn($file) => $this->loadFixture($file)); + } + + private function loadFixture($file) : void { + $table_name = basename($file, '.yml'); + $this->fixtures[$table_name] = yaml_parse_file($file)[$table_name]; + + foreach ($this->fixtures[$table_name] as $entry) { + $query = 'INSERT INTO ' . dbesc($table_name) . '(' + . implode(',', array_keys($entry)) + . ') VALUES(' + . implode(',', array_map(fn($val) => "'{$val}'", array_values($entry))) + . ')'; + + q($query); + } + } } diff --git a/tests/unit/Web/HttpSigTest.php b/tests/unit/Web/HttpSigTest.php index 5524e0510..0a22b543a 100644 --- a/tests/unit/Web/HttpSigTest.php +++ b/tests/unit/Web/HttpSigTest.php @@ -70,9 +70,6 @@ class HttpSigTest extends UnitTestCase { ); } - /** - * @uses ::Crypto::unencapsulate - */ function testDecrypt_sigheader() { $header = 'Header: iv="value_iv" key="value_key" alg="value_alg" data="value_data"'; $result = [ @@ -85,9 +82,7 @@ class HttpSigTest extends UnitTestCase { $this->assertSame($result, HTTPSig::decrypt_sigheader($header, 'site private key')); } - /** - * @uses ::Crypto::unencapsulate - */ + function testDecrypt_sigheaderUseSitePrivateKey() { // Create a stub for global function get_config() with expectation $t = $this->getFunctionMock('Zotlabs\Web', 'get_config'); diff --git a/tests/unit/includes/AccountTest.php b/tests/unit/includes/AccountTest.php new file mode 100644 index 000000000..af5bcd3c1 --- /dev/null +++ b/tests/unit/includes/AccountTest.php @@ -0,0 +1,34 @@ +<?php +/** + * Tests for account handling helper functions. + */ + +class AccountTest extends Zotlabs\Tests\Unit\UnitTestCase { + public function test_get_account_by_id_returns_existing_account() { + $account = get_account_by_id(42); + $this->assertNotFalse($account); + $this->assertEquals($this->fixtures['account'][0]['account_email'], $account['account_email']); + } + + /** + * Test the `check_account_email` function. + * + * @dataProvider check_account_email_provider + */ + public function test_check_account_email(string $email, array $expected) { + $this->assertEquals($expected, check_account_email($email)); + } + + function check_account_email_provider() : array { + return [ + // Empty and valid emails return the same result + ['', ['error' => false, 'message' => '']], + ['newuser@example.com', ['error' => false, 'message' => '']], + + // Check emails not valid for various readons + ['not_an_email', ['error' => true, 'message' => 'The provided email address is not valid']], + ['baduser@example.com', ['error' => true, 'message' => 'The provided email domain is not among those allowed on this site']], + ['hubzilla@example.com', ['error' => true, 'message' => 'The provided email address is already registered at this site']], + ]; + } +} diff --git a/tests/unit/includes/BBCodeTest.php b/tests/unit/includes/BBCodeTest.php new file mode 100644 index 000000000..fedd2df06 --- /dev/null +++ b/tests/unit/includes/BBCodeTest.php @@ -0,0 +1,220 @@ +<?php +/* + * Copyright (c) 2024 Hubzilla + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Zotlabs\Tests\Unit\includes; + +use Zotlabs\Tests\Unit\UnitTestCase; + +class BBCodeTest extends UnitTestCase { + /** + * Test converting BBCode to HTML + * + * @dataProvider bbcode_to_html_provider + */ + public function test_parsing_bbcode_to_html(string $src, string $expected): void { + $this->assertBBCode($expected, $src); + } + + /** + * Test the `[observer]` BBCode tags. + * + * @dataProvider bbcode_observer_provider + */ + public function test_bbcode_observer(string $src, bool $logged_in, string $lang, string $expected): void { + if ($logged_in) { + \App::$observer = [ + 'xchan_addr' => '', + 'xchan_name' => '', + 'xchan_connurl' => '', + 'xchan_photo_l' => '', + + // port required in xchan url due to bug in get_rpost_path + 'xchan_url' => 'https://example.com:666', + ]; + } else { + \App::$observer = null; + } + + \App::$language = $lang; + + $this->assertBBCode($expected, $src); + } + + /** + * Test parsing the `[channel]` tag. + */ + public function test_bbcode_channel(): void { + $src = '[channel=1]This is only for channels[/channel][channel=0]This is for everyone else[/channel]'; + + // Verify that the right part is shown to users _not_ in a channel + \App::$channel = null; + $this->assertBBCode('This is for everyone else', $src); + + // Now verify that the right part is shown to users _in_ a channel + \App::$channel = [42]; + $this->assertBBCode('This is only for channels', $src); + } + + /** + * Test converting html to BBCode. + * + * @dataProvider html2bbcode_provider + */ + public function test_html2bbcode(string $src, string $expected): void { + $this->assertEquals($expected, html2bbcode($src)); + } + + /** + * Helper method to validate BBCode conversions. + * + * @param string $expected The expected result of the conversion. + * @param string $src The BBCode to be converted. + */ + private function assertBBCode(string $expected, string $src): void { + // Note! We turn off trying to create oembeds, as that will trigger a + // network request when running the test. + $this->assertEquals($expected, bbcode($src, ['tryoembed' => false])); + } + + /** + * Dataprovider for test_parsing_bbcode_to_html. + * + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + private function bbcode_to_html_provider(): array { + return [ + 'code block' => [ + "[code]\ntestvar = \"this is a test\"\necho \"the message is \$testvar\"\n[/code]", + '<pre><code>testvar = "this is a test"<br />echo "the message is $testvar"</code></pre>', + ], + 'list with linebreaks \n' => [ + "some text\n[list]\n[*] item1\n[*] item2\n[/list]\nsome more text", + 'some text<br /><ul class="listbullet"><li> item1<li> item2</ul>some more text' + ], + 'list with linebreaks \r' => [ + "some text\r[list]\r[*] item1\r[*] item2\r[/list]\rsome more text", + 'some text<br /><ul class="listbullet"><li> item1<li> item2</ul>some more text' + ], + 'list with linebreaks \r\n' => [ + "some text\r\n[list]\r\n[*] item1\r\n[*] item2\r\n[/list]\r\nsome more text", + 'some text<br /><ul class="listbullet"><li> item1<li> item2</ul>some more text' + ] + ]; + } + + /** + * Dataprovider for test_bbcode_observer + * + * @returns an array of arrays with the following fields: + * * `string $src` - The source string to convert + * * `bool $logged_in` - Whether we should test with a logged in user + * * `string $lang` - The language code of the simulated user + * * `string $expected` - The expecte result of the conversion. + * + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + private function bbcode_observer_provider(): array { + return [ + 'authenticated observer' => [ + '[observer=1]This should be visible[/observer][observer=0]but not this[/observer]', + true, + 'en', + 'This should be visible', + ], + 'unauthenticated observer' => [ + '[observer=1]This should not be visible[/observer][observer=0]but this should be![/observer]', + false, + 'en', + 'but this should be!', + ], + 'authenticated observer.language matching' => [ + '[observer.language=nb]Kun på norsk[/observer][observer.language!=nb]To everybody else[/observer]', + true, + 'nb', + 'Kun på norsk', + ], + 'authenticated observer.language no match' => [ + '[observer.language=nb]Kun på norsk[/observer][observer.language!=nb]To everybody else[/observer]', + true, + 'en', + 'To everybody else', + ], + 'multiple observer blocks' => [ + '[observer=1]This should be visible,[/observer][observer=0] but not this,[/observer][observer=1] and this as well.[/observer]', + true, + 'en', + 'This should be visible, and this as well.', + ], + 'authenticated observer rpost' => [ + '[rpost=a title]This is the body[/rpost]', + true, + 'en', + '<a href="https://example.com:666/rpost?f=&title=a+title&body=This+is+the+body" target="_blank" rel="nofollow noopener">https://example.com:666/rpost?f=&title=a+title&body=This+is+the+body</a>', + ], + 'unauthenticated observer rpost' => [ + '[rpost=a title]This is the body[/rpost]', + false, + 'en', + '', + ], + ]; + } + + /** + * Dataprovider for test_html2bbcode. + * + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + private function html2bbcode_provider(): array { + return [ + 'paragraph over multiple lines' => [ + "<p>A paragraph over\nmultiple lines\nshould be unwrapped</p>", + 'A paragraph over multiple lines should be unwrapped' + ], + 'image with alt text' => [ + '<img src="https://example.com/image.jpg" alt="Alt text">', + '[img=https://example.com/image.jpg]Alt text[/img]' + ], + 'code block' => [ + "<pre><code>some\ncode</code></pre>", + "[code]some\ncode[/code]" + ], + 'code block with indentation' => [ + "<pre><code>some\n indented\ncode</code></pre>", + "[code]some\n indented\ncode[/code]" + ], + 'paragraph with a mention and some text' => [ + '<p><span class="h-card" translate="no"><a href="https://example.org/@profile" class="u-url mention">@<span>profile</span></a></span> some content</p>', + '[url=https://example.org/@profile]@profile[/url] some content' + ], + 'nested tags with ampersand and new line' => [ + "<b>\n<i>foo & bar</i></b>", + '[b] [i]foo & bar[/i][/b]' + ], + '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' + ] + ]; + } +} diff --git a/tests/unit/includes/MarkdownTest.php b/tests/unit/includes/MarkdownTest.php index 953305074..960c15139 100644 --- a/tests/unit/includes/MarkdownTest.php +++ b/tests/unit/includes/MarkdownTest.php @@ -1,30 +1,29 @@ <?php /* * Copyright (c) 2017 Hubzilla -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ namespace Zotlabs\Tests\Unit\includes; use Zotlabs\Tests\Unit\UnitTestCase; -use phpmock\phpunit\PHPMock; require_once 'include/markdown.php'; @@ -32,17 +31,80 @@ require_once 'include/markdown.php'; * @brief Unit Test case for markdown functions. */ class MarkdownTest extends UnitTestCase { - use PHPMock; + + /** + * @dataProvider markdown_to_bbcode_provider + */ + public function test_markdown_to_bbcode(string $expected, string $src): void { + $this->assertEquals($expected, markdown_to_bb($src)); + } + + private function markdown_to_bbcode_provider(): array { + return [ + 'empty text' => [ + '', + '' + ], + 'plain text' => [ + 'This is a test', + 'This is a test' + ], + 'bold and italic' => [ + 'This is a test of [b]bold text[/b], [i]italic text[/i] and [b][i]bold and italic text[/i][/b]', + 'This is a test of **bold text**, *italic text* and ***bold and italic text***' + ], + 'multiline text' => [ + 'This text is text wrapped over multiple lines.', + "This text is\ntext wrapped\nover multiple\nlines." + ], + 'text with hard linebreak' => [ + "Line one\nLine two", + "Line one \nLine two" + ], + 'paragraphs' => [ + "Paragraph one\n\nParagraph two", + "Paragraph one\n\nParagraph two", + ], + 'inline image' => [ + '[img=https://example.com/image.jpg]https://example.com/image.jpg[/img]', + '![](https://example.com/image.jpg)' + ], + 'inline image with alt text' => [ + '[img=https://example.com/image.jpg]Alt text[/img]', + '![Alt text](https://example.com/image.jpg)' + ], + 'inline code' => [ + '[code]some code[/code]', + '`some code`' + ], + 'inline code with wrapped text' => [ + '[code]some code unwrapped[/code]', + "`some code\n unwrapped`" + ], + 'code block no language' => [ + "[code]some code\nover multiple lines[/code]", + "```\nsome code\nover multiple lines\n```" + ], + 'code block no language indented' => [ + "[code]some code\n over multiple lines\n with indentation[/code]", + "```\nsome code\n over multiple lines\n with indentation\n```" + ], + 'code block with language' => [ + "[code=php]<?php\necho phpinfo();[/code]", + "```php\n<?php\necho phpinfo();\n```" + ], + ]; + } /** * @covers ::html2markdown * @dataProvider html2markdownProvider */ - public function testHtml2markdown($html, $markdown) { + public function testHtml2markdown(string $html, string $markdown): void { $this->assertEquals($markdown, html2markdown($html)); } - public function html2markdownProvider() { + public function html2markdownProvider(): array { return [ 'empty text' => [ '', @@ -125,23 +187,10 @@ class MarkdownTest extends UnitTestCase { ]; } - /*public function testHtml2markdownException() { - //$this->expectException(\InvalidArgumentException::class); - // need to stub logger() for this to work - $this->assertEquals('', html2markdown('<<invalid')); - }*/ - -/* public function testBB2diasporaMardown() { - //stub bbcode() and return our HTML, we just need to test the HTML2Markdown library. - $html1 = 'test<b>bold</b><br><i>i</i><ul><li>li1</li><li>li2</li></ul><br>'; - $bb1 = 'test'; - - // php-mock can not mock global functions which is called by a global function. - // If the calling function is in a namespace it does work. - $bbc = $this->getFunctionMock(__NAMESPACE__, "bbcode"); - $bbc->expects($this->once())->willReturn('test<b>bold</b><br><i>i</i><ul><li>li1</li><li>li2</li></ul><br>'); + public function test_bb_to_markdown(): void { + $input = "test[b]bold[/b]\n[i]i[/i][ul][li]li1[/li][li]li2[/li][/ul]\n"; + $expected = "test**bold** \n*i*\n\n- li1\n- li2"; - $this->assertEquals($bb1, bb2diaspora($html1)); + $this->assertEquals($expected, bb_to_markdown($input)); } -*/ } diff --git a/tests/unit/includes/NetworkTest.php b/tests/unit/includes/NetworkTest.php index 0b9b42e00..9fb00e9d3 100644 --- a/tests/unit/includes/NetworkTest.php +++ b/tests/unit/includes/NetworkTest.php @@ -5,29 +5,68 @@ * @package test.util */ -use PHPUnit\Framework\TestCase; - -require_once('include/network.php'); - -class NetworkTest extends TestCase { - - public function setup() : void { - \App::set_baseurl("https://mytest.org"); - } - - /** - * @dataProvider localUrlTestProvider - */ - public function testIsLocalURL($url, $expected) { - $this->assertEquals($expected, is_local_url($url)); - } - - public function localUrlTestProvider() : array { - return [ - [ '/some/path', true ], - [ 'https://mytest.org/some/path', true ], - [ 'https://other.site/some/path', false ], - ]; - } -} +class NetworkTest extends Zotlabs\Tests\Unit\UnitTestCase { + + public function setUp() : void { + parent::setUp(); + + \App::set_baseurl("https://mytest.org"); + } + + /** + * @dataProvider localUrlTestProvider + */ + public function testIsLocalURL($url, $expected) { + $this->assertEquals($expected, is_local_url($url)); + } + + public function localUrlTestProvider() : array { + return [ + [ '/some/path', true ], + [ 'https://mytest.org/some/path', true ], + [ 'https://other.site/some/path', false ], + ]; + } + + /** + * Test the validate_email function. + * + * @dataProvider validate_email_provider + */ + public function test_validate_email(string $email, bool $expected) : void { + $this->assertEquals($expected, validate_email($email)); + } + /** + * Test that the validate_email function is disabled when configured to. + * + * @dataProvider validate_email_provider + */ + public function test_disable_validate_email(string $email) : void { + \Zotlabs\Lib\Config::Set('system', 'disable_email_validation', true); + $this->assertTrue(validate_email($email)); + } + + function validate_email_provider() : array { + return [ + // First some invalid email addresses + ['', false], + ['not_an_email', false], + ['@not_an_email', false], + ['not@an@email', false], + ['not@an@email.com', false], + + // then test valid addresses too + ['test@example.com', true], + + // Should also work with international domains + ['some.email@dømain.net', true], + + // Should also work with the new top-level domains + ['some.email@example.cancerresearch', true], + + // And internationalized TLD's + ['some.email@example.شبكة', true] + ]; + } +} diff --git a/tests/unit/includes/PhotodriverTest.php b/tests/unit/includes/PhotodriverTest.php index 6f6ad0ffe..34dc058b7 100644 --- a/tests/unit/includes/PhotodriverTest.php +++ b/tests/unit/includes/PhotodriverTest.php @@ -2,38 +2,22 @@ namespace Zotlabs\Tests\Unit\includes; -//use Zotlabs\Photo\PhotoGd; use Zotlabs\Tests\Unit\UnitTestCase; -//use phpmock\phpunit\PHPMock; /** * @brief Unit Test cases for include/photo/photo_driver.php file. */ class PhotodriverTest extends UnitTestCase { - //use PHPMock; public function testPhotofactoryReturnsNullForUnsupportedType() { - // 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. - //$logger = $this->getFunctionMock(__NAMESPACE__, 'logger'); - //$logger->expects($this->once()); - - //$ph = \photo_factory('', 'image/bmp'); - //$this->assertNull($ph); - - $this->markTestIncomplete('Need to mock logger(), otherwise not unit testable.'); + $photo = \photo_factory('', 'image/bmp'); + $this->assertNull($photo); } public function testPhotofactoryReturnsPhotogdIfConfigIgnore_imagickIsSet() { - // 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'); - // simulate get_config('system', 'ignore_imagick') configured - //$gc->expects($this->once())->willReturn(1) - - //$ph = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png'); - //$this->assertInstanceOf(PhotoGd::class, $ph); + \Zotlabs\Lib\Config::Set('system', 'ignore_imagick', true); - $this->markTestIncomplete('Need to mock get_config(), otherwise not unit testable.'); + $photo = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png'); + $this->assertInstanceOf('Zotlabs\Photo\PhotoGd', $photo); } -}
\ No newline at end of file +} diff --git a/tests/unit/includes/TextTest.php b/tests/unit/includes/TextTest.php index 97fa64895..b76b15dcf 100644 --- a/tests/unit/includes/TextTest.php +++ b/tests/unit/includes/TextTest.php @@ -30,7 +30,7 @@ empty line above'; $this->assertEquals('Your HTML parser does not support HTML5 audio.', purify_html('<audio controls><source src="movie.ogg" "type="audio/ogg">Your HTML parser does not support HTML5 audio.</audio>')); // preserve f6 and bootstrap additional data attributes from our own configuration - $this->assertEquals('<div data-title="title">text</div>', purify_html('<div data-title="title">text</div>')); + $this->assertEquals('<div data-bs-title="title">text</div>', purify_html('<div data-bs-title="title">text</div>')); $this->assertEquals('<ul data-accordion-menu=""><li>item1</li></ul>', purify_html('<ul data-accordion-menu><li>item1</li></ul>')); $this->assertEquals('<ul><li>item1</li></ul>', purify_html('<ul data-accordion-menu-unknown><li>item1</li></ul>')); } diff --git a/tests/unit/includes/dba/TransactionTest.php b/tests/unit/includes/dba/TransactionTest.php new file mode 100644 index 000000000..99e3f459d --- /dev/null +++ b/tests/unit/includes/dba/TransactionTest.php @@ -0,0 +1,207 @@ +<?php +/* + * Copyright (c) 2024 Hubzilla + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +require_once 'tests/fakes/fake_dba.php'; +require_once 'include/dba/dba_transaction.php'; + +use \PHPUnit\Framework\TestCase; +use \Zotlabs\Tests\Fakes\FakeDba; + +/** + * Test database transactions. + * + * This class subclass the base PHPUnit TestCase class, since we do _not_ + * want a real database connection for these tests. We're testing functionality + * of the database adapter itself, so we choose to stub the underlying db driver + * to be able to assert that the adapter behaves as it should. + */ +class DbaTransactionTest extends TestCase { + private $pdo_stub; + + public function setUp(): void { + $this->pdo_stub = $this->createStub(PDO::class); + } + + + /** + * Test that creating a DbaTransaction object initiates a database transaction. + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function test_transaction_initialized_on_construction(): void { + // Stub PDO::inTransaction() + // Expect that it's called once, and return false to simulate that no + // transactions are active. + $this->pdo_stub + ->expects($this->once()) + ->method('inTransaction') + ->willReturn(false); + + // Stub PDO::beginTransaction to ensure that it is being called. + $this->pdo_stub + ->expects($this->once()) + ->method('beginTransaction') + ->willReturn(true); + + $dba = new FakeDba($this->pdo_stub); + + $transaction = new DbaTransaction($dba); + } + + /** + * Test that a transaction is rolled back when the DbaTransaction object + * is destroyed. + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function test_uncommitted_transaction_is_rolled_back_on_destruction(): void { + // Stub PDO::inTransaction() + // Expect that it's called once, and return false to simulate that no + // transactions are active. + $this->pdo_stub + ->expects($this->once()) + ->method('inTransaction') + ->willReturn(false); + + // Stub PDO::beginTransaction to ensure that it is being called. + $this->pdo_stub + ->expects($this->once()) + ->method('beginTransaction') + ->willReturn(true); + + // Stub PDO::rollBack to make sure we test it is being called. + $this->pdo_stub + ->expects($this->once()) + ->method('rollBack') + ->willReturn(true); + + $dba = new FakeDba($this->pdo_stub); + + $transaction = new DbaTransaction($dba); + } + + /** + * Test that a committed transaction is not rolled back when the + * DbaTransaction object goes out of scope. + */ + public function test_committed_transaction_is_not_rolled_back(): void { + // Stub PDO::inTransaction() + // Return false to simulate that no transaction is active when called. + $this->pdo_stub + ->expects($this->once()) + ->method('inTransaction') + ->willReturn(false); + + // Stub PDO::beginTransaction to ensure that it is being called. + $this->pdo_stub + ->expects($this->once()) + ->method('beginTransaction') + ->willReturn(true); + + // Stub PDO::rollBack to ensure it is _not_ called + $this->pdo_stub + ->expects($this->never()) + ->method('rollBack'); + + // Stub PDO::commit to make the test check that it is being called + $this->pdo_stub + ->expects($this->once()) + ->method('commit') + ->willReturn(true); + + $dba = new FakeDba($this->pdo_stub); + + $transaction = new DbaTransaction($dba); + $transaction->commit(); + } + + /** + * Test that commiting a transaction more than once is a no-op. + */ + public function test_that_committing_an_already_committed_transaction_does_nothing(): void { + // Stub PDO::inTransaction() + // Return false to simulate that no transaction is active when called. + $this->pdo_stub + ->expects($this->once()) + ->method('inTransaction') + ->willReturn(false); + + // Stub PDO::beginTransaction to ensure that it is being called. + $this->pdo_stub + ->expects($this->once()) + ->method('beginTransaction') + ->willReturn(true); + + // Stub PDO::rollBack to ensure it is _not_ called + $this->pdo_stub + ->expects($this->never()) + ->method('rollBack'); + + // Stub PDO::commit to make the test check that it is being called + $this->pdo_stub + ->expects($this->once()) + ->method('commit') + ->willReturn(true); + + $dba = new FakeDba($this->pdo_stub); + + $transaction = new DbaTransaction($dba); + $transaction->commit(); + $transaction->commit(); + } + + /** + * Test simulating constructing a DbaTransaction object when a transaction + * is already active. + * + * This should _not_ initiate an actual DB transaction, not call the rollBack + * method on destruction. + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function test_that_nesting_a_transaction_does_not_create_a_new_transaction_in_db(): void { + // Stub PDO::inTransaction() + // We simulate that a transaction is already active, by returning true from + // this method. + $this->pdo_stub + ->expects($this->once()) + ->method('inTransaction') + ->willReturn(true); + + // Stub PDO::beginTransaction + // Since a transaction is already active, we should _not_ initiate + // a new transaction when the DbaTransaction object is constructed. + $this->pdo_stub + ->expects($this->never()) + ->method('beginTransaction'); + + // Stub PDO::rollBack to ensure it is _not_ called + $this->pdo_stub + ->expects($this->never()) + ->method('rollBack'); + + $dba = new FakeDba($this->pdo_stub); + + $transaction = new DbaTransaction($dba); + } +} diff --git a/tests/unit/includes/dba/_files/config.yml b/tests/unit/includes/dba/_files/config.yml new file mode 100644 index 000000000..e93486857 --- /dev/null +++ b/tests/unit/includes/dba/_files/config.yml @@ -0,0 +1,23 @@ +--- +config: + - + cat: system + k: do_not_check_dns + v: true + - + cat: system + k: not_allowed_email + v: 'baduser@example.com,baddomain.com,*.evil.org' + - + cat: system + k: debugging + v: true + - + cat: system + k: loglevel + v: 2 + - + cat: system + k: logfile + v: tests/results/unit_test.log + |