2 // This file is part of GNU social - https://www.gnu.org/software/social
4 // GNU social is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // GNU social is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
19 if (!defined('INSTALLDIR')) {
20 define('INSTALLDIR', dirname(dirname(__DIR__)));
22 if (!defined('PUBLICDIR')) {
23 define('PUBLICDIR', INSTALLDIR . DIRECTORY_SEPARATOR . 'public');
25 if (!defined('GNUSOCIAL')) {
26 define('GNUSOCIAL', true);
28 if (!defined('STATUSNET')) { // Compatibility
29 define('STATUSNET', true);
40 use PHPUnit\Framework\TestCase;
44 require_once INSTALLDIR . '/lib/common.php';
46 final class ActivityGenerationTests extends TestCase
48 static $author1 = null;
49 static $author2 = null;
51 static $targetUser1 = null;
52 static $targetUser2 = null;
54 static $targetGroup1 = null;
55 static $targetGroup2 = null;
57 public static function setUpBeforeClass()
59 $authorNick1 = 'activitygenerationtestsuser' . common_random_hexstr(4);
60 $authorNick2 = 'activitygenerationtestsuser' . common_random_hexstr(4);
62 $targetNick1 = 'activitygenerationteststarget' . common_random_hexstr(4);
63 $targetNick2 = 'activitygenerationteststarget' . common_random_hexstr(4);
65 $groupNick1 = 'activitygenerationtestsgroup' . common_random_hexstr(4);
66 $groupNick2 = 'activitygenerationtestsgroup' . common_random_hexstr(4);
69 self::$author1 = User::register(array('nickname' => $authorNick1,
70 'email' => $authorNick1 . '@example.net',
71 'email_confirmed' => true));
73 self::$author2 = User::register(array('nickname' => $authorNick2,
74 'email' => $authorNick2 . '@example.net',
75 'email_confirmed' => true));
77 self::$targetUser1 = User::register(array('nickname' => $targetNick1,
78 'email' => $targetNick1 . '@example.net',
79 'email_confirmed' => true));
81 self::$targetUser2 = User::register(array('nickname' => $targetNick2,
82 'email' => $targetNick2 . '@example.net',
83 'email_confirmed' => true));
85 self::$targetGroup1 = User_group::register(array('nickname' => $groupNick1,
86 'userid' => self::$author1->id,
90 'description' => null,
94 self::$targetGroup2 = User_group::register(array('nickname' => $groupNick2,
95 'userid' => self::$author1->id,
99 'description' => null,
102 'mainpage' => null));
103 } catch (Exception $e) {
104 self::tearDownAfterClass();
109 public function testBasicNoticeActivity()
111 $notice = $this->_fakeNotice();
113 $entry = $notice->asAtomEntry(true);
115 $element = $this->_entryToElement($entry, false);
117 $this->assertEquals($notice->getUri(), ActivityUtils::childContent($element, 'id'));
118 $this->assertEquals('New note by ' . self::$author1->nickname, ActivityUtils::childContent($element, 'title'));
119 $this->assertEquals($notice->rendered, ActivityUtils::childContent($element, 'content'));
120 $this->assertEquals(strtotime($notice->created), strtotime(ActivityUtils::childContent($element, 'published')));
121 $this->assertEquals(strtotime($notice->created), strtotime(ActivityUtils::childContent($element, 'updated')));
122 $this->assertEquals(ActivityVerb::POST, ActivityUtils::childContent($element, 'verb', Activity::SPEC));
123 $this->assertEquals(ActivityObject::NOTE, ActivityUtils::childContent($element, 'object-type', Activity::SPEC));
126 public function testNamespaceFlag()
128 $notice = $this->_fakeNotice();
130 $entry = $notice->asAtomEntry(true);
132 $element = $this->_entryToElement($entry, false);
134 $this->assertTrue($element->hasAttribute('xmlns'));
135 $this->assertTrue($element->hasAttribute('xmlns:thr'));
136 $this->assertTrue($element->hasAttribute('xmlns:georss'));
137 $this->assertTrue($element->hasAttribute('xmlns:activity'));
138 $this->assertTrue($element->hasAttribute('xmlns:media'));
139 $this->assertTrue($element->hasAttribute('xmlns:poco'));
140 $this->assertTrue($element->hasAttribute('xmlns:ostatus'));
141 $this->assertTrue($element->hasAttribute('xmlns:statusnet'));
143 $entry = $notice->asAtomEntry(false);
145 $element = $this->_entryToElement($entry, true);
147 $this->assertFalse($element->hasAttribute('xmlns'));
148 $this->assertFalse($element->hasAttribute('xmlns:thr'));
149 $this->assertFalse($element->hasAttribute('xmlns:georss'));
150 $this->assertFalse($element->hasAttribute('xmlns:activity'));
151 $this->assertFalse($element->hasAttribute('xmlns:media'));
152 $this->assertFalse($element->hasAttribute('xmlns:poco'));
153 $this->assertFalse($element->hasAttribute('xmlns:ostatus'));
154 $this->assertFalse($element->hasAttribute('xmlns:statusnet'));
157 public function testSourceFlag()
159 $notice = $this->_fakeNotice();
161 // Test with no source
163 $entry = $notice->asAtomEntry(false, false);
165 $element = $this->_entryToElement($entry, true);
167 $source = ActivityUtils::child($element, 'source');
169 $this->assertNull($source);
173 $entry = $notice->asAtomEntry(false, true);
175 $element = $this->_entryToElement($entry, true);
177 $source = ActivityUtils::child($element, 'source');
179 $this->assertNotNull($source);
182 public function testSourceContent()
184 $notice = $this->_fakeNotice();
185 // make a time difference!
187 $notice2 = $this->_fakeNotice();
189 $entry = $notice->asAtomEntry(false, true);
191 $element = $this->_entryToElement($entry, true);
193 $source = ActivityUtils::child($element, 'source');
195 $atomUrl = common_local_url('ApiTimelineUser', array('id' => self::$author1->id, 'format' => 'atom'));
197 $profile = self::$author1->getProfile();
199 $this->assertEquals($atomUrl, ActivityUtils::childContent($source, 'id'));
200 $this->assertEquals($atomUrl, ActivityUtils::getLink($source, 'self', 'application/atom+xml'));
201 $this->assertEquals($profile->profileurl, ActivityUtils::getPermalink($source));
202 $this->assertEquals(strtotime($notice2->created), strtotime(ActivityUtils::childContent($source, 'updated')));
203 // XXX: do we care here?
204 $this->assertFalse(is_null(ActivityUtils::childContent($source, 'title')));
205 $this->assertEquals(common_config('license', 'url'), ActivityUtils::getLink($source, 'license'));
208 public function testAuthorFlag()
210 $notice = $this->_fakeNotice();
212 // Test with no author
214 $entry = $notice->asAtomEntry(false, false, false);
216 $element = $this->_entryToElement($entry, true);
218 $this->assertNull(ActivityUtils::child($element, 'author'));
219 $this->assertNull(ActivityUtils::child($element, 'actor', Activity::SPEC));
223 $entry = $notice->asAtomEntry(false, false, true);
225 $element = $this->_entryToElement($entry, true);
227 $author = ActivityUtils::child($element, 'author');
228 $actor = ActivityUtils::child($element, 'actor', Activity::SPEC);
230 $this->assertFalse(is_null($author));
231 $this->assertTrue(is_null($actor)); // <activity:actor> is obsolete, no longer added
234 public function testAuthorContent()
236 $notice = $this->_fakeNotice();
240 $entry = $notice->asAtomEntry(false, false, true);
242 $element = $this->_entryToElement($entry, true);
244 $author = ActivityUtils::child($element, 'author');
246 $this->assertEquals(self::$author1->getNickname(), ActivityUtils::childContent($author, 'name'));
247 $this->assertEquals(self::$author1->getUri(), ActivityUtils::childContent($author, 'uri'));
251 * We no longer create <activity:actor> entries, they have merged to <atom:author>
253 public function testActorContent()
255 $notice = $this->_fakeNotice();
259 $entry = $notice->asAtomEntry(false, false, true);
261 $element = $this->_entryToElement($entry, true);
263 $actor = ActivityUtils::child($element, 'actor', Activity::SPEC);
265 $this->assertEquals($actor, null);
268 public function testReplyLink()
270 $orig = $this->_fakeNotice(self::$targetUser1);
272 $text = "@" . self::$targetUser1->nickname . " reply text " . common_random_hexstr(4);
274 $reply = Notice::saveNew(self::$author1->id, $text, 'test', array('uri' => null, 'reply_to' => $orig->id));
276 $entry = $reply->asAtomEntry();
278 $element = $this->_entryToElement($entry, true);
280 $irt = ActivityUtils::child($element, 'in-reply-to', 'http://purl.org/syndication/thread/1.0');
282 $this->assertNotNull($irt);
283 $this->assertEquals($orig->getUri(), $irt->getAttribute('ref'));
284 $this->assertEquals($orig->getUrl(), $irt->getAttribute('href'));
287 public function testReplyAttention()
289 $orig = $this->_fakeNotice(self::$targetUser1);
291 $text = "@" . self::$targetUser1->nickname . " reply text " . common_random_hexstr(4);
293 $reply = Notice::saveNew(self::$author1->id, $text, 'test', array('uri' => null, 'reply_to' => $orig->id));
295 $entry = $reply->asAtomEntry();
297 $element = $this->_entryToElement($entry, true);
299 $this->assertEquals(self::$targetUser1->getUri(), ActivityUtils::getLink($element, 'mentioned'));
302 public function testMultipleReplyAttention()
304 $orig = $this->_fakeNotice(self::$targetUser1);
306 $text = "@" . self::$targetUser1->nickname . " reply text " . common_random_hexstr(4);
308 $reply = Notice::saveNew(self::$targetUser2->id, $text, 'test', array('uri' => null, 'reply_to' => $orig->id));
310 $text = "@" . self::$targetUser1->nickname . " @" . self::$targetUser2->nickname . " reply text " . common_random_hexstr(4);
312 $reply2 = Notice::saveNew(self::$author1->id, $text, 'test', array('uri' => null, 'reply_to' => $reply->id));
314 $entry = $reply2->asAtomEntry();
316 $element = $this->_entryToElement($entry, true);
318 $links = ActivityUtils::getLinks($element, 'mentioned');
322 foreach ($links as $link) {
323 $hrefs[] = $link->getAttribute('href');
326 $this->assertTrue(in_array(self::$targetUser1->getUri(), $hrefs));
327 $this->assertTrue(in_array(self::$targetUser2->getUri(), $hrefs));
330 public function testGroupPostAttention()
332 $text = "!" . self::$targetGroup1->nickname . " reply text " . common_random_hexstr(4);
334 $notice = Notice::saveNew(self::$author1->id, $text, 'test', array('uri' => null));
336 $entry = $notice->asAtomEntry();
338 $element = $this->_entryToElement($entry, true);
340 $this->assertEquals(self::$targetGroup1->getUri(), ActivityUtils::getLink($element, 'mentioned'));
343 public function testMultipleGroupPostAttention()
345 $text = "!" . self::$targetGroup1->nickname . " !" . self::$targetGroup2->nickname . " reply text " . common_random_hexstr(4);
347 $notice = Notice::saveNew(self::$author1->id, $text, 'test', array('uri' => null));
349 $entry = $notice->asAtomEntry();
351 $element = $this->_entryToElement($entry, true);
353 $links = ActivityUtils::getLinks($element, 'mentioned');
357 foreach ($links as $link) {
358 $hrefs[] = $link->getAttribute('href');
361 $this->assertTrue(in_array(self::$targetGroup1->getUri(), $hrefs));
362 $this->assertTrue(in_array(self::$targetGroup2->getUri(), $hrefs));
366 public function testRepeatLink()
368 $notice = $this->_fakeNotice(self::$author1);
369 $repeat = $notice->repeat(self::$author2->getProfile(), 'test');
371 $entry = $repeat->asAtomEntry();
373 $element = $this->_entryToElement($entry, true);
375 $noticeInfo = ActivityUtils::child($element, 'notice_info', 'http://status.net/schema/api/1/');
377 $this->assertNotNull($noticeInfo);
378 $this->assertEquals($notice->id, $noticeInfo->getAttribute('repeat_of'));
379 $this->assertEquals($repeat->id, $noticeInfo->getAttribute('local_id'));
382 public function testTag()
384 $tag1 = common_random_hexstr(4);
386 $notice = $this->_fakeNotice(self::$author1, '#' . $tag1);
388 $entry = $notice->asAtomEntry();
390 $element = $this->_entryToElement($entry, true);
392 $category = ActivityUtils::child($element, 'category');
394 $this->assertNotNull($category);
395 $this->assertEquals($tag1, $category->getAttribute('term'));
398 public function testMultiTag()
400 $tag1 = common_random_hexstr(4);
401 $tag2 = common_random_hexstr(4);
403 $notice = $this->_fakeNotice(self::$author1, '#' . $tag1 . ' #' . $tag2);
405 $entry = $notice->asAtomEntry();
407 $element = $this->_entryToElement($entry, true);
409 $categories = $element->getElementsByTagName('category');
411 $this->assertNotNull($categories);
412 $this->assertEquals(2, $categories->length);
416 for ($i = 0; $i < $categories->length; $i++) {
417 $cat = $categories->item($i);
418 $terms[] = $cat->getAttribute('term');
421 $this->assertTrue(in_array($tag1, $terms));
422 $this->assertTrue(in_array($tag2, $terms));
425 public function testGeotaggedActivity()
427 $notice = Notice::saveNew(self::$author1->id, common_random_hexstr(4), 'test', array('uri' => null, 'lat' => 45.5, 'lon' => -73.6));
429 $entry = $notice->asAtomEntry();
431 $element = $this->_entryToElement($entry, true);
433 $this->assertEquals('45.5000000 -73.6000000', ActivityUtils::childContent($element, 'point', "http://www.georss.org/georss"));
436 public function testNoticeInfo()
438 $notice = $this->_fakeNotice();
440 $entry = $notice->asAtomEntry();
442 $element = $this->_entryToElement($entry, true);
444 $noticeInfo = ActivityUtils::child($element, 'notice_info', "http://status.net/schema/api/1/");
446 $this->assertEquals($notice->id, $noticeInfo->getAttribute('local_id'));
447 $this->assertEquals($notice->source, $noticeInfo->getAttribute('source'));
448 $this->assertEquals('', $noticeInfo->getAttribute('repeat_of'));
449 $this->assertEquals('', $noticeInfo->getAttribute('repeated'));
450 // $this->assertEquals('', $noticeInfo->getAttribute('favorite'));
451 $this->assertEquals('', $noticeInfo->getAttribute('source_link'));
454 public function testNoticeInfoRepeatOf()
456 $notice = $this->_fakeNotice();
458 $repeat = $notice->repeat(self::$author2->getProfile(), 'test');
460 $entry = $repeat->asAtomEntry();
462 $element = $this->_entryToElement($entry, true);
464 $noticeInfo = ActivityUtils::child($element, 'notice_info', "http://status.net/schema/api/1/");
466 $this->assertEquals($notice->id, $noticeInfo->getAttribute('repeat_of'));
469 public function testNoticeInfoRepeated()
471 $notice = $this->_fakeNotice();
473 $repeat = $notice->repeat(self::$author2->getProfile(), 'test');
475 $entry = $notice->asAtomEntry(false, false, false, self::$author2->getProfile());
477 $element = $this->_entryToElement($entry, true);
479 $noticeInfo = ActivityUtils::child($element, 'notice_info', "http://status.net/schema/api/1/");
481 $this->assertEquals('true', $noticeInfo->getAttribute('repeated'));
483 $entry = $notice->asAtomEntry(false, false, false, self::$targetUser1->getProfile());
485 $element = $this->_entryToElement($entry, true);
487 $noticeInfo = ActivityUtils::child($element, 'notice_info', "http://status.net/schema/api/1/");
489 $this->assertEquals('false', $noticeInfo->getAttribute('repeated'));
492 /* public function testNoticeInfoFave()
494 $notice = $this->_fakeNotice();
496 $fave = Fave::addNew(self::$author2->getProfile(), $notice);
498 // Should be set if user has faved
500 $entry = $notice->asAtomEntry(false, false, false, self::$author2);
502 $element = $this->_entryToElement($entry, true);
504 $noticeInfo = ActivityUtils::child($element, 'notice_info', "http://status.net/schema/api/1/");
506 $this->assertEquals('true', $noticeInfo->getAttribute('favorite'));
508 // Shouldn't be set if user has not faved
510 $entry = $notice->asAtomEntry(false, false, false, self::$targetUser1);
512 $element = $this->_entryToElement($entry, true);
514 $noticeInfo = ActivityUtils::child($element, 'notice_info', "http://status.net/schema/api/1/");
516 $this->assertEquals('false', $noticeInfo->getAttribute('favorite'));
519 public function testConversationLink()
521 $orig = $this->_fakeNotice(self::$targetUser1);
523 $text = "@" . self::$targetUser1->nickname . " reply text " . common_random_hexstr(4);
525 $reply = Notice::saveNew(self::$author1->id, $text, 'test', array('uri' => null, 'reply_to' => $orig->id));
527 $conv = Conversation::getKV('id', $reply->conversation);
529 $entry = $reply->asAtomEntry();
531 $element = $this->_entryToElement($entry, true);
533 $this->assertEquals($conv->getUrl(), ActivityUtils::getLink($element, 'ostatus:conversation'));
536 public static function tearDownAfterClass()
538 if (!is_null(self::$author1)) {
539 self::$author1->getProfile()->delete();
542 if (!is_null(self::$author2)) {
543 self::$author2->getProfile()->delete();
546 if (!is_null(self::$targetUser1)) {
547 self::$targetUser1->getProfile()->delete();
550 if (!is_null(self::$targetUser2)) {
551 self::$targetUser2->getProfile()->delete();
554 if (!is_null(self::$targetGroup1)) {
555 self::$targetGroup1->delete();
558 if (!is_null(self::$targetGroup2)) {
559 self::$targetGroup2->delete();
563 private function _fakeNotice($user = null, $text = null)
566 $user = self::$author1;
570 $text = "fake-o text-o " . common_random_hexstr(32);
573 return Notice::saveNew($user->id, $text, 'test', array('uri' => null));
576 private function _entryToElement($entry, $namespace = false)
578 $xml = '<?xml version="1.0" encoding="utf-8"?>' . "\n\n";
581 $xml .= ' xmlns="http://www.w3.org/2005/Atom"';
582 $xml .= ' xmlns:thr="http://purl.org/syndication/thread/1.0"';
583 $xml .= ' xmlns:georss="http://www.georss.org/georss"';
584 $xml .= ' xmlns:activity="http://activitystrea.ms/spec/1.0/"';
585 $xml .= ' xmlns:media="http://purl.org/syndication/atommedia"';
586 $xml .= ' xmlns:poco="http://portablecontacts.net/spec/1.0"';
587 $xml .= ' xmlns:ostatus="http://ostatus.org/schema/1.0"';
588 $xml .= ' xmlns:statusnet="http://status.net/schema/api/1/"';
590 $xml .= '>' . "\n" . $entry . "\n" . '</feed>' . "\n";
591 $doc = new DOMDocument();
593 $feed = $doc->documentElement;
594 $entries = $feed->getElementsByTagName('entry');
596 return $entries->item(0);