aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/unit/Lib/ActivityTest.php245
-rw-r--r--tests/unit/Lib/JcsEddsa2022Test.php8
-rw-r--r--tests/unit/Lib/MessageFilterTest.php207
-rw-r--r--tests/unit/Module/HelpTest.php4
-rw-r--r--tests/unit/Module/MagicTest.php8
-rw-r--r--tests/unit/Module/TestCase.php2
-rw-r--r--tests/unit/Widget/MessagesWidgetTest.php83
-rw-r--r--tests/unit/includes/AccountTest.php21
-rw-r--r--tests/unit/includes/BBCodeTest.php31
-rw-r--r--tests/unit/includes/PhotodriverTest.php58
10 files changed, 653 insertions, 14 deletions
diff --git a/tests/unit/Lib/ActivityTest.php b/tests/unit/Lib/ActivityTest.php
index 0e2703f2b..46f53ecd9 100644
--- a/tests/unit/Lib/ActivityTest.php
+++ b/tests/unit/Lib/ActivityTest.php
@@ -5,8 +5,13 @@ error_reporting(E_ALL);
use Zotlabs\Tests\Unit\UnitTestCase;
use Zotlabs\Lib\Activity;
+use Zotlabs\Lib\ActivityStreams;
+use phpmock\phpunit\PHPMock;
class ActivityTest extends UnitTestCase {
+ // Import PHPMock methods into this class
+ use PHPMock;
+
/**
* Test get a textfield from an activitystreams object
*
@@ -36,4 +41,244 @@ class ActivityTest extends UnitTestCase {
];
}
+
+ /**
+ *
+ * @dataProvider get_mid_and_uuid_provider
+ */
+ public function test_get_mid_and_uuid(string $payload, string $mid, string $uuid): void {
+
+
+ //
+ // Mock z_fetch_url to prevent us from spamming real servers during test runs
+ //
+ // We just create some sample ActivityStreams objects to return for the various
+ // URL's to make it a somewhat realistic test. Each object will have it's URL as
+ // it's id and only specify the object type as laid out in the $urlmap below.
+
+ $item_json = '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://purl.archive.org/socialweb/webfinger",{"zot":"https://hub.somaton.com/apschema#","contextHistory":"https://w3id.org/fep/171b/contextHistory","schema":"http://schema.org#","ostatus":"http://ostatus.org#","diaspora":"https://diasporafoundation.org/ns/","litepub":"http://litepub.social/ns#","toot":"http://joinmastodon.org/ns#","commentPolicy":"zot:commentPolicy","Bookmark":"zot:Bookmark","Category":"zot:Category","Emoji":"toot:Emoji","directMessage":"litepub:directMessage","PropertyValue":"schema:PropertyValue","value":"schema:value","uuid":"schema:identifier","conversation":"ostatus:conversation","guid":"diaspora:guid","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","Hashtag":"as:Hashtag"}],"type":"Note","id":"https://hub.somaton.com/item/7bb5da01-f97b-408f-853a-eb4d09079e5a","diaspora:guid":"7bb5da01-f97b-408f-853a-eb4d09079e5a","published":"2025-04-14T05:43:00Z","attributedTo":"https://hub.somaton.com/channel/mario","inReplyTo":"https://social.wake.st/users/liaizon/statuses/114332208953644534","context":"https://social.wake.st/users/liaizon/statuses/114332208953644534","content":"@<a href=\"https://social.wake.st/users/liaizon\" target=\"_blank\" rel=\"nofollow noopener\" >wakest \u2042</a>,<br />@<a class=\"zrl\" href=\"https://hubzilla.org/channel/info\" target=\"_blank\" rel=\"nofollow noopener\" >Hubzilla</a> has had <span style=\"color: magenta;\">colors</span> <span style=\"color: lightgreen;\">since</span> <span style=\"color: blue;\">10+</span> <span style=\"color: orange;\">years</span> :grinning_face_with_smiling_eyes:","source":{"content":"@[url=https://social.wake.st/users/liaizon]wakest \u2042[/url],\r\n@[zrl=https://hubzilla.org/channel/info]Hubzilla[/zrl] has had [color=magenta]colors[/color] [color=lightgreen]since[/color] [color=blue]10+[/color] [color=orange]years[/color] :grinning_face_with_smiling_eyes:","mediaType":"text/bbcode"},"actor":"https://hub.somaton.com/channel/mario","tag":[{"type":"Mention","href":"https://social.wake.st/users/liaizon","name":"@wakest \u2042"},{"type":"Mention","href":"https://hubzilla.org/channel/info","name":"@Hubzilla"},{"type":"Emoji","id":"https://hub.somaton.com/emoji/grinning_face_with_smiling_eyes","name":":grinning_face_with_smiling_eyes:","icon":{"type":"Image","url":"https://hub.somaton.com/addon/emoji/emojitwo/1f604.png"}}],"to":["https://www.w3.org/ns/activitystreams#Public","https://social.wake.st/users/liaizon","https://hubzilla.org/channel/info"],"cc":["https://social.wake.st/users/liaizon/followers","https://app.wafrn.net/fediverse/blog/wafrn"],"proof":{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2025-04-14T15:18:46Z","verificationMethod":"https://hub.somaton.com/channel/mario#z6MkfHv7DiVBDs7qZJVfbLUHLbKFYBxdhDBeqHRmhpWq8Pj9","proofPurpose":"assertionMethod","proofValue":"z5bxnZPhccvxtKZAMPxPexeqHgfzmL6U9jX6mSDbUCi3xmtLgsDbjMtAvMS2f8qw9rHBsFHyo2999xWmfPDZsCZ8U"},"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"2fd1920c5b8906ce4d3bfbdd9ff967c10e5e37202e227fb40dc0a2a14e887093","creator":"https://hub.somaton.com/channel/mario","created":"2025-04-14T15:18:46Z","signatureValue":"Xlg0UISgRoeTfB2Qpy/UqZ4jQeNeGUVx0Gztp7uhqtj8lbQNmWyZqUzXdXf7jGjiVcy87rmdakSfcQG9Zvbak/illLePj8pkXixLWdquoyJ5v/MhDtfgEoKikGSP3mkunabNNL1yFm5uZ6dYjS4ea0lB/1YPIyWjP7NhLbv0+HD/02a9P87Nlwufh1PUFoL9Y4XPIJpparz5Wax5fIfqzmVSMa0QLN+d/zQb+/jdOszhdiTZdUgpRK4Yb8xKeRBO1kOngtOfD0I7szUdRlTmFIpi83HKRNTAJrGyFsCwTnZmIy0dHhxmyvarHyz2kuEcf8nz3z5BV8amo7edAx9wWizsRiYaiMQ65mgl6wfZapHzkUqGH7mT9Kp3YmTOTgCy9OyP7yXyUUx5MRnyqQnjzoEw6stQwNb+IuhfwRcoLwJ/sIY5db29FK3QrbMCKNvxxJUjBqq+rdUjXnpvpdm9i8X1oJ1dHtkqSNoOBleykNudxyDRjvy+uI9z6OLt3LyNorOQ+3RUxxSxONoAsb+DVuLldMfD8ASVZWMzPr2CnyAuf8EFHccCoHibiNbMRuovk+kcLd+47B+v/tOq+rk6bPQ+np323nyUYZDGrH7KYgkQuXA83E2bLd3pOFfVQjDGEJlwrSx3U7wj+DDQohN1DqIkoK7WBpU1cFI6nn0r6ak="}}';
+
+ $urlmap = [
+ 'https://hub.somaton.com/channel/mario' => [ 'type' => 'Person' ],
+ 'https://mitra.social/users/silverpill' => [ 'type' => 'Person' ],
+ 'https://hub.somaton.com/item/7bb5da01-f97b-408f-853a-eb4d09079e5a' => json_decode($item_json, true)
+ ];
+
+ $z_fetch_url_stub = $this->getFunctionMock('Zotlabs\Lib', 'z_fetch_url');
+ $z_fetch_url_stub
+ ->expects($this->any())
+ ->willReturnCallback(function ($url) use ($urlmap) {
+ if (isset($urlmap[$url])) {
+ $body = json_encode(
+ array_merge([ 'id' => $url ], $urlmap[$url]),
+ JSON_FORCE_OBJECT,
+ );
+
+ return [
+ 'success' => true,
+ 'body' => $body,
+ ];
+ } else {
+ // We should perhaps throw an error here to fail the test,
+ // as we're receiving an unexpected URL.
+ return [
+ 'success' => false,
+ ];
+ }
+ });
+
+ // Make sure we have a sys channel before we start
+ create_sys_channel();
+
+ $as = new ActivityStreams($payload);
+
+ $this->assertEquals($mid, Activity::getMessageID($as));
+ $this->assertEquals($uuid, Activity::getUUID($as));
+ }
+
+ /**
+ * Dataprovider for test_get_mid_and_uuid.
+ */
+ public static function get_mid_and_uuid_provider(): array {
+ return [
+ 'Note from hubzilla with diaspora:guid' => [
+ '{
+ "@context":[
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ "https://purl.archive.org/socialweb/webfinger",
+ {
+ "zot":"https://hub.somaton.com/apschema#",
+ "contextHistory":"https://w3id.org/fep/171b/contextHistory",
+ "schema":"http://schema.org#",
+ "ostatus":"http://ostatus.org#",
+ "diaspora":"https://diasporafoundation.org/ns/",
+ "litepub":"http://litepub.social/ns#",
+ "toot":"http://joinmastodon.org/ns#",
+ "commentPolicy":"zot:commentPolicy",
+ "Bookmark":"zot:Bookmark",
+ "Category":"zot:Category",
+ "Emoji":"toot:Emoji",
+ "directMessage":"litepub:directMessage",
+ "PropertyValue":"schema:PropertyValue",
+ "value":"schema:value",
+ "uuid":"schema:identifier",
+ "conversation":"ostatus:conversation",
+ "guid":"diaspora:guid",
+ "manuallyApprovesFollowers":"as:manuallyApprovesFollowers",
+ "Hashtag":"as:Hashtag"
+ }
+ ],
+ "type":"Note",
+ "id":"https://hub.somaton.com/item/2e4b2cfa-7c20-49c2-b192-ae54f43a211a",
+ "diaspora:guid":"2e4b2cfa-7c20-49c2-b192-ae54f43a211a",
+ "published":"2025-04-03T17:45:41Z",
+ "commentPolicy":"contacts",
+ "attributedTo":"https://hub.somaton.com/channel/mario",
+ "contextHistory":"https://hub.somaton.com/conversation/2e4b2cfa-7c20-49c2-b192-ae54f43a211a",
+ "context":"https://hub.somaton.com/conversation/2e4b2cfa-7c20-49c2-b192-ae54f43a211a",
+ "content":"Looks like we have a :hubzilla: emoji now :slightly_smiling_face:",
+ "source":{
+ "content":"Looks like we have a :hubzilla: emoji now :slightly_smiling_face:",
+ "mediaType":"text/bbcode"
+ },
+ "actor":"https://hub.somaton.com/channel/mario",
+ "tag":[
+ {
+ "type":"Emoji",
+ "id":"https://hub.somaton.com/emoji/hubzilla",
+ "name":":hubzilla:",
+ "icon":{
+ "type":"Image",
+ "url":"https://hub.somaton.com/images/hubzilla.svg"
+ }
+ },
+ {
+ "type":"Emoji",
+ "id":"https://hub.somaton.com/emoji/slightly_smiling_face",
+ "name":":slightly_smiling_face:",
+ "icon":{
+ "type":"Image",
+ "url":"https://hub.somaton.com/images/emoji/slightly_smiling_face.png"
+ }
+ }
+ ],
+ "to":[
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc":[
+ "https://hub.somaton.com/followers/mario"
+ ],
+ "proof":{
+ "type":"DataIntegrityProof",
+ "cryptosuite":"eddsa-jcs-2022",
+ "created":"2025-04-14T15:25:17Z",
+ "verificationMethod":"https://hub.somaton.com/channel/mario#z6MkfHv7DiVBDs7qZJVfbLUHLbKFYBxdhDBeqHRmhpWq8Pj9",
+ "proofPurpose":"assertionMethod",
+ "proofValue":"zJf7xXBtD6ZTG27171X7X1BSkw7kijw4MCvzhowL7giB5s3mUKbo9yF1wq29E3bvHc3Q7HbDzUdbFE8cpCYYH9uJ"
+ },
+ "signature":{
+ "@context":[
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1"
+ ],
+ "type":"RsaSignature2017",
+ "nonce":"3cda8c90fdbe708d625a017d1c946c8144fa288c9a04124b40b27f2f6a429e94",
+ "creator":"https://hub.somaton.com/channel/mario",
+ "created":"2025-04-14T15:25:17Z",
+ "signatureValue":"d01N0pMca7I9/dCdYbwuY3/SUe0xCZfwRSPxA7w9Pj4fFYDwhCNVYLKWz66K7RP7KfDD7DQ3oS8wLxn4qSX7wjFDJhwy7PkbGUzawBc9eti+8wHbiMGD2JgZCbzGaXmR/k5zyOykKhqglUOr0BcvAfqM1g3+7UxtYdxMNFlJ9nAJTObmd5jR8RyPe9b7Tbgi5XJDJ4U4qLsb8tAK54Sr2208fuCs7T+baErMPNj4eVprWoJObnr6sQX4YJH3404eJpExMLSu+y9taWLXxg6qDv+EY/RjgbKh/cdYNB5ERDFVK4WxgrrJCTv5t7mdVxjDN3sHUsfeT1aF2JYbS5ISSdtdHZnEMNIw3uwXLm5zG76fk17nUdDfXm1Pyu2uAuwRYIBOMQWeFZvdvo1Lf457kCQQN0DgUv3t89JD7Y5fZAzOlKiqXb52cxlsNUQFw8vQnWLGZNdqpDU0np6IhABrsMo+QoYrQepwKzxnmy8cwB6KKyD8W75H49l79DslDvg71nue3MuLtIfaI+d1GhYIul9o0ttJnzTbvg6L+pLLtzwgsDCqVhkXgQmk7J8RUuux9gmqYMe0pCoDlrVcR0Jhte57JqgqYZck3BPupLuu+Pg4n5/SIAogsCCWOu4ygV7jwAOcPmze7XozBuP7CVFVpgfooo9rU3kPKeEETkcljKg="
+ }
+ }',
+ 'https://hub.somaton.com/item/2e4b2cfa-7c20-49c2-b192-ae54f43a211a',
+ '2e4b2cfa-7c20-49c2-b192-ae54f43a211a'
+ ],
+
+ 'Like(Note) from mitra without uuid' => [
+ '{
+ "@context":[
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ "https://w3id.org/security/data-integrity/v1",
+ {
+ "Emoji":"toot:Emoji",
+ "EmojiReact":"litepub:EmojiReact",
+ "Hashtag":"as:Hashtag",
+ "litepub":"http://litepub.social/ns#",
+ "sensitive":"as:sensitive",
+ "toot":"http://joinmastodon.org/ns#"
+ }
+ ],
+ "actor":"https://mitra.social/users/silverpill",
+ "cc":[
+
+ ],
+ "id":"https://mitra.social/activities/like/01963430-e998-4bef-c903-50903dde06dc",
+ "object":"https://hub.somaton.com/item/7bb5da01-f97b-408f-853a-eb4d09079e5a",
+ "proof":{
+ "created":"2025-04-14T12:05:42.945286724Z",
+ "cryptosuite":"eddsa-jcs-2022",
+ "proofPurpose":"assertionMethod",
+ "proofValue":"z4XrZRkUBoxmAn1xYXwmhaJTXb9Mog9C3cjrPenRhqWQbfXv6QJmMyGydsQ3LqN61uVfRvis8RoTymyUPqy76k9mg",
+ "type":"DataIntegrityProof",
+ "verificationMethod":"https://mitra.social/users/silverpill#ed25519-key"
+ },
+ "to":[
+ "https://hub.somaton.com/channel/mario",
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type":"Like"
+ }',
+ 'https://mitra.social/activities/like/01963430-e998-4bef-c903-50903dde06dc',
+ ''
+ ],
+
+ 'Like(Note) from mitra with manually added uuid' => [
+ '{
+ "@context":[
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ "https://w3id.org/security/data-integrity/v1",
+ {
+ "Emoji":"toot:Emoji",
+ "EmojiReact":"litepub:EmojiReact",
+ "Hashtag":"as:Hashtag",
+ "litepub":"http://litepub.social/ns#",
+ "sensitive":"as:sensitive",
+ "toot":"http://joinmastodon.org/ns#"
+ }
+ ],
+ "actor":"https://mitra.social/users/silverpill",
+ "cc":[
+
+ ],
+ "id":"https://mitra.social/activities/like/01963430-e998-4bef-c903-50903dde06dc",
+ "uuid":"01963430-e998-4bef-c903-50903dde06dc",
+ "object":"https://hub.somaton.com/item/7bb5da01-f97b-408f-853a-eb4d09079e5a",
+ "proof":{
+ "created":"2025-04-14T12:05:42.945286724Z",
+ "cryptosuite":"eddsa-jcs-2022",
+ "proofPurpose":"assertionMethod",
+ "proofValue":"z4XrZRkUBoxmAn1xYXwmhaJTXb9Mog9C3cjrPenRhqWQbfXv6QJmMyGydsQ3LqN61uVfRvis8RoTymyUPqy76k9mg",
+ "type":"DataIntegrityProof",
+ "verificationMethod":"https://mitra.social/users/silverpill#ed25519-key"
+ },
+ "to":[
+ "https://hub.somaton.com/channel/mario",
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type":"Like"
+ }',
+ 'https://mitra.social/activities/like/01963430-e998-4bef-c903-50903dde06dc',
+ '01963430-e998-4bef-c903-50903dde06dc'
+ ]
+ ];
+ }
+
+ public function testBuildPacketWithEmptyChannel(): void {
+ $data = [ 'aKey' => 'aValue' ];
+ $packet = json_decode(Activity::build_packet($data, []), true);
+
+ $this->assertArrayHasKey('aKey', $packet);
+ $this->assertEquals('aValue', $packet['aKey']);
+ }
}
diff --git a/tests/unit/Lib/JcsEddsa2022Test.php b/tests/unit/Lib/JcsEddsa2022Test.php
index d18ad01ce..7cdc655f8 100644
--- a/tests/unit/Lib/JcsEddsa2022Test.php
+++ b/tests/unit/Lib/JcsEddsa2022Test.php
@@ -3,6 +3,7 @@
namespace Zotlabs\Tests\Unit\Lib;
use Zotlabs\Lib\JcsEddsa2022;
+use Zotlabs\Lib\JcsEddsa2022SignException;
use Zotlabs\Tests\Unit\UnitTestCase;
class JcsEddsa2022Test extends UnitTestCase {
@@ -171,4 +172,11 @@ class JcsEddsa2022Test extends UnitTestCase {
$this->assertTrue($verified, 'Verify encode and decode eddsa-jcs-2022');
}
+
+ public function testSignWithInvalidChannelShouldBeRejected(): void {
+ $this->expectException(JcsEddsa2022SignException::class);
+
+ $alg = new JcsEddsa2022();
+ $res = $alg->sign([], []);
+ }
}
diff --git a/tests/unit/Lib/MessageFilterTest.php b/tests/unit/Lib/MessageFilterTest.php
new file mode 100644
index 000000000..0a2aea0c6
--- /dev/null
+++ b/tests/unit/Lib/MessageFilterTest.php
@@ -0,0 +1,207 @@
+<?php
+namespace Zotlabs\Tests\Unit\Lib;
+
+use Zotlabs\Tests\Unit\UnitTestCase;
+use Zotlabs\Lib\MessageFilter;
+
+class MessageFilterTest extends UnitTestCase {
+
+ /**
+ * Test the `evaluate` function.
+ *
+ * @dataProvider evaluate_provider
+ */
+ public function test_evaluate(string $incl, string $excl, bool $result) : void {
+ // This is for simpler handling of the timestamps
+ date_default_timezone_set('UTC');
+
+ $item = [
+ 'title' => '',
+ 'body' => "A grasshopper spent the summer hopping about in the sun and singing to his heart's content. One day, an ant went hurrying by, looking very hot and weary.\r\n#story #grasshopper #ant",
+ 'term' => [
+ ['ttype' => TERM_HASHTAG, 'term' => 'story'],
+ ['ttype' => TERM_HASHTAG, 'term' => 'grasshopper'],
+ ['ttype' => TERM_HASHTAG, 'term' => 'ant']
+ ],
+ 'verb' => 'Create',
+ 'obj_type' => 'Note',
+ 'obj' => [
+ 'type' => 'Note',
+ 'attributedTo' => 'https://example.com/users/test',
+ 'summary' => null,
+ 'content' => "A grasshopper spent the summer hopping about in the sun and singing to his heart's content. One day, an ant went hurrying by, looking very hot and weary.\r\n#story #grasshopper #ant",
+ 'sensitive' => false
+ ],
+ 'item_private' => 0,
+ 'item_thread_top' => 1,
+ 'created' => '2025-04-18 20:50:00'
+ ];
+
+ $this->assertEquals($result, MessageFilter::evaluate($item, $incl, $excl));
+ }
+
+ public static function evaluate_provider() : array {
+ return [
+ 'body contains incl' => [
+ 'summer',
+ '',
+ true
+ ],
+ 'body contains excl' => [
+ '',
+ 'summer',
+ false
+ ],
+ 'body contains word hopper (starting with a space) in excl using regex' => [
+ '',
+ '/ hopper/',
+ true
+ ],
+ 'lang=en in incl' => [
+ 'lang=en',
+ '',
+ true
+ ],
+ 'lang=en in excl' => [
+ '',
+ 'lang=en',
+ false
+ ],
+ 'lang=de in incl' => [
+ 'lang=de',
+ '',
+ false
+ ],
+ 'lang=de in excl' => [
+ '',
+ 'lang=de',
+ true
+ ],
+ 'until=2025-04-18 20:49:00 in excl' => [
+ '',
+ 'until=2025-04-18 20:49:00',
+ true
+ ],
+ 'until=2025-04-18 20:51:00 in excl' => [
+ '',
+ 'until=2025-04-18 20:51:00',
+ false
+ ],
+ 'until=2025-04-18 20:49:00 in incl' => [
+ 'until=2025-04-18 20:49:00',
+ '',
+ false
+ ],
+ 'until=2025-04-18 20:51:00 in incl' => [
+ 'until=2025-04-18 20:51:00',
+ '',
+ true
+ ],
+ 'hashtag in incl' => [
+ '#grasshopper',
+ '',
+ true
+ ],
+ 'hashtag in excl' => [
+ '',
+ '#grasshopper',
+ false
+ ],
+ 'any hashtag in excl' => [
+ '',
+ '#*',
+ false
+ ],
+ 'item.body contains substring hopper in excl' => [
+ '',
+ '?body ~= hopper',
+ false
+ ],
+ 'item.verb == Announce in excl' => [
+ '',
+ '?verb == Announce',
+ true
+ ],
+ 'item.verb != Announce in incl' => [
+ '?verb != Announce',
+ '',
+ true
+ ],
+ 'combined body contains word and item.verb == Announce in excl' => [
+ '',
+ "summer\r\n?verb == Announce",
+ false
+ ],
+ 'item.item_thread_top == 1 in excl' => [
+ '',
+ "?item_thread_top == 1",
+ false
+ ],
+ 'combined item_private == 0 and item.item_thread_top == 1 in excl' => [
+ '',
+ "?item_private == 0\r\n?item_thread_top == 1",
+ false
+ ],
+ 'item.item_private < 1 in excl' => [
+ '',
+ "?item_private < 1",
+ false
+ ],
+ 'item.item_thread_top = 1 and item.item_private > 0 in excl' => [
+ '',
+ "?item_thread_top == 1 && ?item_private > 0 ",
+ true
+ ],
+ 'item.item_thread_top = 1 and item.item_private < 1 in excl' => [
+ '',
+ "?item_thread_top == 1 && ?item_private < 1 ",
+ false
+ ],
+ 'item.item_thread_top = 1 or item.item_private = 0 in excl' => [
+ '',
+ "?item_thread_top == 1 && ?item_private == 0",
+ false
+ ],
+ 'item.item_private < 1 and item.item_thread_top = 1 in excl' => [
+ '',
+ "?item_private < 1 && ?item_thread_top == 1",
+ false
+ ],
+ 'item.item_private < 1 and item.item_thread_top = 0 in excl' => [
+ '',
+ "?item_private < 1 && ?item_thread_top == 0",
+ true
+ ],
+ 'combined item.verb = Create, item.item_private < 1 and item.item_thread_top = 0 in excl' => [
+ '',
+ "?verb == Create\r\n?item_private < 1 && ?item_thread_top == 1",
+ false
+ ],
+ 'item.obj contains value Note in incl' => [
+ '?obj {} Note',
+ '',
+ true
+ ],
+ 'item.obj contains key type in incl' => [
+ '?obj {*} type',
+ '',
+ true
+ ],
+ 'obj.type = Note in incl' => [
+ '?+type == Note',
+ '',
+ true
+ ],
+ 'obj.sensitive = true in incl' => [
+ '?+sensitive',
+ '',
+ false
+ ],
+ 'obj.sensitive != false in incl' => [
+ '?+!sensitive',
+ '',
+ true
+ ],
+ ];
+ }
+}
diff --git a/tests/unit/Module/HelpTest.php b/tests/unit/Module/HelpTest.php
index 1feef26a8..e1aea1a06 100644
--- a/tests/unit/Module/HelpTest.php
+++ b/tests/unit/Module/HelpTest.php
@@ -76,13 +76,13 @@ class HelpTest extends \Zotlabs\Tests\Unit\Module\TestCase {
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->expectExceptionMessage('about');
$this->get('help');
}
public function test_getting_locale_with_no_topic_should_redirect_to_about_page(): void {
- $this->expectRedirectTo('help/about/about');
+ $this->expectRedirectTo('help/about');
$this->get('help/de');
}
diff --git a/tests/unit/Module/MagicTest.php b/tests/unit/Module/MagicTest.php
index 4a03d9d57..2c426bf76 100644
--- a/tests/unit/Module/MagicTest.php
+++ b/tests/unit/Module/MagicTest.php
@@ -46,9 +46,9 @@ class MagicTest extends TestCase {
App::set_baseurl($baseurl);
- App::$observer = [
+ App::set_observer([
'xchan_hash' => 'the hash',
- ];
+ ]);
// We pass a local URL, and have a valid observer, but as the
// delegate param is not passed, nothing will be done except
@@ -72,9 +72,9 @@ class MagicTest extends TestCase {
App::$timezone = 'UTC';
// Simulate a foreign (to this hub) observer,
- App::$observer = [
+ App::set_observer([
'xchan_hash' => 'foreign hash',
- ];
+ ]);
// Create the channel the foreign observer wants to access
$result = create_identity([
diff --git a/tests/unit/Module/TestCase.php b/tests/unit/Module/TestCase.php
index 1a4cf52fc..dd88a5a3b 100644
--- a/tests/unit/Module/TestCase.php
+++ b/tests/unit/Module/TestCase.php
@@ -43,6 +43,8 @@ class TestCase extends UnitTestCase {
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['QUERY_STRING'] = "q={$uri}";
+ $_SERVER['REQUEST_URI'] = $uri;
+
// phpcs:disable Generic.PHP.DisallowRequestSuperglobal.Found
$_REQUEST = array_merge($_GET, $_POST);
// phpcs::enable
diff --git a/tests/unit/Widget/MessagesWidgetTest.php b/tests/unit/Widget/MessagesWidgetTest.php
new file mode 100644
index 000000000..ca025ff43
--- /dev/null
+++ b/tests/unit/Widget/MessagesWidgetTest.php
@@ -0,0 +1,83 @@
+<?php
+/*
+ * SPDX-FileCopyrightText: 2025 The Hubzilla Community
+ * SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+namespace Zotlabs\Tests\Unit\Widget;
+
+use App;
+use Zotlabs\Widget\Messages;
+use Zotlabs\Tests\Unit\Module\TestCase;
+
+class MessagesWidgetTest extends TestCase
+{
+ use \phpmock\phpunit\PHPMock;
+
+ /**
+ * List of file tags should be empty if there are no file tags.
+ */
+ public function testNoFileTags(): void
+ {
+ $local_channe_stub = $this->getFunctionMock('Zotlabs\Widget', 'local_channel')
+ ->expects($this->any())
+ ->willReturn(42);
+
+ $feature_enabled_stub = $this->getFunctionMock('Zotlabs\Widget', 'feature_enabled')
+ ->expects($this->any())
+ ->willReturn(true);
+
+ $this->renderWidget();
+ $this->assertOutputMatches('|<datalist\s+id="data_filetags">\s+</datalist>|');
+ }
+
+ /**
+ * The widget lists file tags that are defined for the channel.
+ */
+ public function testFileTagsAreListed(): void
+ {
+ $local_channe_stub = $this->getFunctionMock('Zotlabs\Widget', 'local_channel')
+ ->expects($this->any())
+ ->willReturn(42);
+
+ $feature_enabled_stub = $this->getFunctionMock('Zotlabs\Widget', 'feature_enabled')
+ ->expects($this->any())
+ ->willReturn(true);
+
+ /*
+ * Add a few tags.
+ */
+ store_item_tag(42, 1, TERM_OBJ_POST, TERM_FILE, 'test_file_tag', '');
+ store_item_tag(42, 1, TERM_OBJ_POST, TERM_FILE, 'test_file_tag2', '');
+
+ $this->renderWidget();
+ $this->assertOutputMatches('|<option\s+value="test_file_tag">|');
+ $this->assertOutputMatches('|<option\s+value="test_file_tag2">|');
+ }
+
+ /**
+ * Initializes the app and calls the widget code.
+ */
+ private function renderWidget(): void {
+ $_GET['q'] = 'hq';
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+
+ App::init();
+
+ $widget = new Messages();
+ $this->output = $widget->widget([]);
+ }
+
+ /**
+ * Asserts that the output matches a given regex pattern.
+ *
+ * If the pattern does not match, the test will be marked as failed.
+ *
+ * @param string $pattern The regex that should be matched.
+ */
+ private function assertOutputMatches(string $pattern): void {
+ $this->assertMatchesRegularExpression($pattern, $this->output);
+ }
+}
diff --git a/tests/unit/includes/AccountTest.php b/tests/unit/includes/AccountTest.php
index 3978f9d04..66c761ef5 100644
--- a/tests/unit/includes/AccountTest.php
+++ b/tests/unit/includes/AccountTest.php
@@ -1,9 +1,28 @@
<?php
+
+use Zotlabs\Tests\Unit\UnitTestCase;
+
/**
* Tests for account handling helper functions.
*/
+class AccountTest extends UnitTestCase {
+
+ /**
+ * Test the `get_account_id()` function.
+ */
+ public function test_get_account_id() {
+ App::set_account(null);
+ unset($_SESSION['account_id']);
+
+ $this->assertEquals(false, get_account_id(), 'get_account_id() should return false if not authenticated');
+
+ App::set_account(['account_id' => 36]);
+ $this->assertEquals(36, get_account_id(), 'get_account_id() should return account from global App object');
+
+ $_SESSION['account_id'] = 42;
+ $this->assertEquals(42, get_account_id(), 'get_account_id() should return the account from the session');
+ }
-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);
diff --git a/tests/unit/includes/BBCodeTest.php b/tests/unit/includes/BBCodeTest.php
index 136fc6e0e..982ef4eb9 100644
--- a/tests/unit/includes/BBCodeTest.php
+++ b/tests/unit/includes/BBCodeTest.php
@@ -23,6 +23,7 @@
namespace Zotlabs\Tests\Unit\includes;
+use App;
use Zotlabs\Tests\Unit\UnitTestCase;
class BBCodeTest extends UnitTestCase {
@@ -42,7 +43,7 @@ class BBCodeTest extends UnitTestCase {
*/
public function test_bbcode_observer(string $src, bool $logged_in, string $lang, string $expected): void {
if ($logged_in) {
- \App::$observer = [
+ App::set_observer([
'xchan_addr' => '',
'xchan_name' => '',
'xchan_connurl' => '',
@@ -50,9 +51,9 @@ class BBCodeTest extends UnitTestCase {
// port required in xchan url due to bug in get_rpost_path
'xchan_url' => 'https://example.com:666',
- ];
+ ]);
} else {
- \App::$observer = null;
+ App::set_observer(null);
}
\App::$language = $lang;
@@ -141,20 +142,36 @@ class BBCodeTest extends UnitTestCase {
],
'naked url is converted to link' => [
'example url: https://example.com',
- 'example url: <a href="https://example.com" target="_blank" rel="nofollow noopener">https://example.com</a>'
+ 'example url: <a href="https://example.com" target="_blank" rel="nofollow noopener">https://example.com</a>'
],
'naked url followed by newline' => [
"https://www.example.com\nhave a great day.",
- '<a href="https://www.example.com" target="_blank" rel="nofollow noopener">https://www.example.com</a><br />have a great day.',
+ '<a href="https://www.example.com" target="_blank" rel="nofollow noopener">https://www.example.com</a><br />have a great day.',
],
'inline naked url' => [
"This is a link https://example.com/some/path more info.",
- 'This is a link <a href="https://example.com/some/path" target="_blank" rel="nofollow noopener">https://example.com/some/path</a> more info.',
+ 'This is a link <a href="https://example.com/some/path" target="_blank" rel="nofollow noopener">https://example.com/some/path</a> more info.',
],
'naked url within code block is not converted to link' => [
"[code]\nhttp://example.com\n[/code]",
"<pre><code>http://example.com</code></pre>"
],
+ 'geo uri is converted to link' => [
+ 'example url: [url]geo:37.786971,-122.399677;u=35[/url]',
+ 'example url: <a href="geo:37.786971,-122.399677;u=35" target="_blank" rel="nofollow noopener">geo:37.786971,-122.399677;u=35</a>'
+ ],
+ 'geo uri with label is converted to link' => [
+ 'example url: [url=geo:37.786971,-122.399677;u=35(Wikimedia+Foundation)]Wikimedia Foundation[/url]',
+ 'example url: <a href="geo:37.786971,-122.399677;u=35(Wikimedia+Foundation)" target="_blank" rel="nofollow noopener">Wikimedia Foundation</a>'
+ ],
+ 'naked geo uri is converted to link' => [
+ 'example url: geo:37.786971,-122.399677;u=35',
+ 'example url: <a href="geo:37.786971,-122.399677;u=35" target="_blank" rel="nofollow noopener">geo:37.786971,-122.399677;u=35</a>'
+ ],
+ 'naked geo uri with label is converted to link' => [
+ 'example url: geo:37.78918,-122.40335(Wikimedia+Foundation)',
+ 'example url: <a href="geo:37.78918,-122.40335(Wikimedia+Foundation)" target="_blank" rel="nofollow noopener">📍Wikimedia Foundation</a>'
+ ],
];
}
@@ -205,7 +222,7 @@ class BBCodeTest extends UnitTestCase {
'[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>',
+ '<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]',
diff --git a/tests/unit/includes/PhotodriverTest.php b/tests/unit/includes/PhotodriverTest.php
index 34dc058b7..db9883589 100644
--- a/tests/unit/includes/PhotodriverTest.php
+++ b/tests/unit/includes/PhotodriverTest.php
@@ -20,4 +20,62 @@ class PhotodriverTest extends UnitTestCase {
$photo = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png');
$this->assertInstanceOf('Zotlabs\Photo\PhotoGd', $photo);
}
+
+ // Helper to create a temporary image file
+ private function createTempImage($type = 'jpeg'): string
+ {
+ $tmp = tempnam(sys_get_temp_dir(), 'img');
+ switch ($type) {
+ case 'png':
+ $im = imagecreatetruecolor(10, 10);
+ imagepng($im, $tmp);
+ imagedestroy($im);
+ break;
+ case 'jpeg':
+ default:
+ $im = imagecreatetruecolor(10, 10);
+ imagejpeg($im, $tmp);
+ imagedestroy($im);
+ break;
+ }
+ return $tmp;
+ }
+
+ public function testGuessImageTypeFromRawData()
+ {
+ $filename = 'irrelevant';
+ $data = [
+ 'body' => file_get_contents($this->createTempImage('jpeg'))
+ ];
+ $result = guess_image_type($filename, $data);
+ $this->assertEquals('image/jpeg', $result);
+ }
+
+ public function testGuessImageTypeFromLocalFile()
+ {
+ $file = $this->createTempImage('png');
+ $result = guess_image_type($file);
+ $this->assertEquals('image/png', $result);
+ unlink($file);
+ }
+
+ public function testGuessImageTypeFromHeaders()
+ {
+ $filename = 'irrelevant';
+ $data = [
+ 'header' => "Content-Type: image/jpeg\nOther: value"
+ ];
+ $result = guess_image_type($filename, $data);
+ $this->assertEquals('image/jpeg', $result);
+ }
+
+ public function testGuessImageTypeUnknownTypeReturnsNull()
+ {
+ $filename = 'not_an_image.txt';
+ $data = [
+ 'body' => 'not an image'
+ ];
+ $result = guess_image_type($filename, $data);
+ $this->assertNull($result);
+ }
}