diff --git a/AUTHORS.md b/AUTHORS.md index 56f3aceb1c..3d3d07094a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -157,4 +157,10 @@ * [waffshappen](mailto:44290023+waffshappen@users.noreply.github.com) * [wizdude](mailto:wizdude@users.noreply.github.com) * [zero77](mailto:zero77@users.noreply.github.com) -* [Éloi Rivard](mailto:eloi.rivard@aquilenet.fr) \ No newline at end of file +* [Éloi Rivard](mailto:eloi.rivard@aquilenet.fr) +* [Marco Nassabain](mailto:marco.nassabain@hotmail.com) +* [Nicolas Wendling](mailto:nicolas.wendling1011@gmail.com) +* [Jimmy Huynh](mailto:natorisaki@gmail.com) +* [Aurélien David](mailto:dav.aurelien@gmail.com) +* [Hamza Elhaddad](mailto:elhaddadhamza49@gmail.com) +* [Ilyes Chergui-Malih](mailto:ilyes.chergui.malih@outlook.fr) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 358a199cbf..5fff081c88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ The format is almost based on [Keep a Changelog](https://keepachangelog.com/en/1 - Search: Add folder search (#1215) - Improve test coverage (#1263) - Allow directly adding a feed without going through the discovery process (#1265) +- Implemented sharing news items between nextcloud users (#1191) +- Updated the news items table in DB to include sharer data (#1191) +- Added route for sharing news items (#1191) +- Added share data in news items serialization (#1191) +- Added tests for the news items share feature (#1191) ### Fixed - Do not show deleted feeds in item list (#1214) diff --git a/appinfo/routes.php b/appinfo/routes.php index a50f4a5b7e..b938d4fb51 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -52,6 +52,7 @@ ['name' => 'item#read', 'url' => '/items/{itemId}/read', 'verb' => 'POST'], ['name' => 'item#read_multiple', 'url' => '/items/read/multiple', 'verb' => 'POST'], ['name' => 'item#star', 'url' => '/items/{feedId}/{guidHash}/star', 'verb' => 'POST'], +['name' => 'item#share', 'url' => '/items/{itemId}/share/{shareRecipientId}', 'verb' => 'POST'], // export ['name' => 'export#opml', 'url' => '/export/opml', 'verb' => 'GET'], diff --git a/lib/Controller/ItemController.php b/lib/Controller/ItemController.php index 85f67ef7a9..4d2050e63e 100644 --- a/lib/Controller/ItemController.php +++ b/lib/Controller/ItemController.php @@ -24,6 +24,7 @@ use \OCA\News\Service\Exceptions\ServiceException; use \OCA\News\Service\Exceptions\ServiceNotFoundException; use \OCA\News\Service\ItemServiceV2; +use \OCA\News\Service\ShareService; use OCP\IUserSession; /** @@ -43,6 +44,10 @@ class ItemController extends Controller * @var FeedServiceV2 */ private $feedService; + /** + * @var ShareService + */ + private $shareService; /** * @var IConfig */ @@ -52,12 +57,14 @@ public function __construct( IRequest $request, FeedServiceV2 $feedService, ItemServiceV2 $itemService, + ShareService $shareService, IConfig $settings, ?IUserSession $userSession ) { parent::__construct($request, $userSession); $this->itemService = $itemService; $this->feedService = $feedService; + $this->shareService = $shareService; $this->settings = $settings; } @@ -169,7 +176,8 @@ public function index( ); break; } - $return['items'] = $items; + // Map sharer display names onto shared items + $return['items'] = $this->shareService->mapSharedByDisplayNames($items); // this gets thrown if there are no items // in that case just return an empty array @@ -229,7 +237,8 @@ public function newItems(int $type, int $id, $lastModified = 0): array $return['newestItemId'] = $this->itemService->newest($this->getUserId())->getId(); $return['feeds'] = $this->feedService->findAllForUser($this->getUserId()); $return['starred'] = count($this->itemService->starred($this->getUserId())); - $return['items'] = $items; + // Map sharer display names onto shared items + $return['items'] = $this->shareService->mapSharedByDisplayNames($items); // this gets thrown if there are no items // in that case just return an empty array @@ -318,4 +327,26 @@ public function readMultiple(array $itemIds): void } } } + + + /** + * @NoAdminRequired + * + * @param int $itemId Item to share + * @param string $shareRecipientId User to share the item with + */ + public function share(int $itemId, string $shareRecipientId) + { + try { + $this->shareService->shareItemWithUser( + $this->getUserId(), + $itemId, + $shareRecipientId + ); + } catch (ServiceNotFoundException $ex) { + return $this->error($ex, Http::STATUS_NOT_FOUND); + } + + return []; + } } diff --git a/lib/Db/Item.php b/lib/Db/Item.php index 221fb8627d..2ead77066c 100644 --- a/lib/Db/Item.php +++ b/lib/Db/Item.php @@ -67,6 +67,10 @@ class Item extends Entity implements IAPI, \JsonSerializable protected $starred = false; /** @var string|null */ protected $categoriesJson; + /** @var string|null */ + protected $sharedBy; + /** @var string|null */ + protected $sharedByDisplayName; public function __construct() { @@ -90,6 +94,33 @@ public function __construct() $this->addType('unread', 'boolean'); $this->addType('starred', 'boolean'); $this->addType('categoriesJson', 'string'); + $this->addType('sharedBy', 'string'); + } + + public function __clone() + { + $this->resetUpdatedFields(); + $this->markFieldUpdated('contentHash'); + $this->markFieldUpdated('guidHash'); + $this->markFieldUpdated('guid'); + $this->markFieldUpdated('url'); + $this->markFieldUpdated('title'); + $this->markFieldUpdated('author'); + $this->markFieldUpdated('pubDate'); + $this->markFieldUpdated('body'); + $this->markFieldUpdated('enclosureMime'); + $this->markFieldUpdated('enclosureLink'); + $this->markFieldUpdated('mediaThumbnail'); + $this->markFieldUpdated('mediaDescription'); + $this->markFieldUpdated('feedId'); + $this->markFieldUpdated('lastModified'); + $this->markFieldUpdated('searchIndex'); + $this->markFieldUpdated('rtl'); + $this->markFieldUpdated('fingerprint'); + $this->markFieldUpdated('unread'); + $this->markFieldUpdated('starred'); + $this->markFieldUpdated('categoriesJson'); + $this->markFieldUpdated('sharedBy'); } /** @@ -285,6 +316,16 @@ public function isUnread(): bool return $this->unread; } + public function getSharedBy(): ?string + { + return $this->sharedBy; + } + + public function getSharedByDisplayName(): ?string + { + return $this->sharedByDisplayName; + } + /** * @return null|string */ @@ -327,7 +368,9 @@ public function jsonSerialize(): array 'rtl' => $this->getRtl(), 'intro' => $this->getIntro(), 'fingerprint' => $this->getFingerprint(), - 'categories' => $this->getCategories() + 'categories' => $this->getCategories(), + 'sharedBy' => $this->getSharedBy(), + 'sharedByDisplayName' => $this->getSharedByDisplayName() ]; } @@ -509,6 +552,25 @@ public function setTitle(string $title = null): self return $this; } + public function setSharedBy(string $sharedBy = null): self + { + if ($this->sharedBy !== $sharedBy) { + $this->sharedBy = $sharedBy; + $this->markFieldUpdated('sharedBy'); + } + + return $this; + } + + public function setSharedByDisplayName(string $sharedByDisplayName = null): self + { + if ($this->sharedByDisplayName !== $sharedByDisplayName) { + $this->sharedByDisplayName = $sharedByDisplayName; + } + + return $this; + } + public function setUnread(bool $unread): self { if ($this->unread !== $unread) { diff --git a/lib/Migration/Version150400Date20210318215425.php b/lib/Migration/Version150400Date20210318215425.php new file mode 100644 index 0000000000..dbe5618bd5 --- /dev/null +++ b/lib/Migration/Version150400Date20210318215425.php @@ -0,0 +1,53 @@ +hasTable('news_items')) { + $table = $schema->getTable('news_items'); + $table->addColumn('shared_by', 'string', [ + 'notnull' => false, + 'length' => 64 + ]); + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php new file mode 100644 index 0000000000..17db4e53bb --- /dev/null +++ b/lib/Service/ShareService.php @@ -0,0 +1,163 @@ + + */ + +namespace OCA\News\Service; + +use \OCA\News\Db\Item; +use \OCA\News\Db\Feed; + +use \Psr\Log\LoggerInterface; +use OCP\IURLGenerator; +use OCP\IUserManager; +use \OCP\IL10N; + +use OCA\News\Service\Exceptions\ServiceNotFoundException; +use OCP\AppFramework\Db\DoesNotExistException; + +/** + * Class ShareService + * + * @package OCA\News\Service + */ +class ShareService +{ + /** + * Items service. + * + * @var ItemServiceV2 + */ + protected $itemService; + + /** + * Feeds service. + * + * @var FeedServiceV2 + */ + protected $feedService; + + /** + * @var LoggerInterface + */ + protected $logger; + + /** + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * @var IUserManager + */ + private $userManager; + + /** + * @var IL10N + */ + private $l10n; + + /** + * ShareService constructor + * + * @param FeedServiceV2 $feedService Service for feeds + * @param ItemServiceV2 $itemService Service to manage items + * @param IURLGenerator $urlGenerator URL Generator + * @param IUserManager $userManager User Manager + * @param IL10N $l10n Localization interface + * @param LoggerInterface $logger Logger + */ + public function __construct( + FeedServiceV2 $feedService, + ItemServiceV2 $itemService, + IURLGenerator $urlGenerator, + IUserManager $userManager, + IL10N $l10n, + LoggerInterface $logger + ) { + $this->itemService = $itemService; + $this->feedService = $feedService; + $this->urlGenerator = $urlGenerator; + $this->userManager = $userManager; + $this->l10n = $l10n; + $this->logger = $logger; + } + + /** + * Share an item with a user + * + * @param string $userId ID of user sharing the item + * @param int $itemId Item ID + * @param string $shareRecipientId ID of user to share with + * + * Sharing by copying - the item is duplicated, and the 'sharedBy' + * field is filled accordingly. + * The item is then placed in a dummy feed reserved for items + * shared with the user + * + * @return Item Shared item + * @throws ServiceNotFoundException|ServiceConflictException + */ + public function shareItemWithUser(string $userId, int $itemId, string $shareRecipientId) + { + // Find item to share + $item = $this->itemService->find($userId, $itemId); + + // Duplicate item & initialize fields + $sharedItem = clone $item; + $sharedItem->setId(null); + $sharedItem->setUnread(true); + $sharedItem->setStarred(false); + $sharedItem->setSharedBy($userId); + + // Get 'shared with me' dummy feed + $feedUrl = $this->urlGenerator->getBaseUrl() . '/news/sharedwithme'; + $feed = $this->feedService->findByUrl($shareRecipientId, $feedUrl); + if (is_null($feed)) { + $feed = new Feed(); + $feed->setUserId($shareRecipientId) + ->setUrlHash(md5($feedUrl)) + ->setLink($feedUrl) + ->setUrl($feedUrl) + ->setTitle($this->l10n->t('Shared with me')) + ->setAdded(time()) + ->setFolderId(null) + ->setPreventUpdate(true); + + $feed = $this->feedService->insert($feed); + } + + $sharedItem->setFeedId($feed->getId()); + + return $this->itemService->insertOrUpdate($sharedItem); + } + + /** + * Map sharers display names to shared items. + * + * Loops through an array of news items. For all shared items, the function + * fetches the sharers display name and adds it into the item. + * + * @param Item[] Array containing items that are shared or not + * @return Item[] + */ + public function mapSharedByDisplayNames(array $items): array + { + foreach ($items as $item) { + $sharedBy = $item->getSharedBy(); + if (!is_null($sharedBy)) { + $user = $this->userManager->get($sharedBy); + if (!is_null($user)) { + $item->setSharedByDisplayName($user->getDisplayName()); + } + } + } + + return $items; + } +} diff --git a/tests/Unit/Controller/ItemControllerTest.php b/tests/Unit/Controller/ItemControllerTest.php index c64cd93300..33488a338e 100644 --- a/tests/Unit/Controller/ItemControllerTest.php +++ b/tests/Unit/Controller/ItemControllerTest.php @@ -16,6 +16,7 @@ use OCA\News\Controller\ItemController; use OCA\News\Service\FeedServiceV2; use OCA\News\Service\ItemServiceV2; +use OCA\News\Service\ShareService; use \OCP\AppFramework\Http; use \OCA\News\Db\Item; @@ -46,6 +47,10 @@ class ItemControllerTest extends TestCase * @var \PHPUnit\Framework\MockObject\MockObject|FeedServiceV2 */ private $feedService; + /** + * @var \PHPUnit\Framework\MockObject\MockObject|ShareService + */ + private $shareService; /** * @var \PHPUnit\Framework\MockObject\MockObject|IRequest */ @@ -79,6 +84,10 @@ public function setUp(): void $this->getMockBuilder(FeedServiceV2::class) ->disableOriginalConstructor() ->getMock(); + $this->shareService = + $this->getMockBuilder(ShareService::class) + ->disableOriginalConstructor() + ->getMock(); $this->request = $this->getMockBuilder(IRequest::class) ->disableOriginalConstructor() ->getMock(); @@ -95,6 +104,7 @@ public function setUp(): void $this->request, $this->feedService, $this->itemService, + $this->shareService, $this->settings, $this->userSession ); @@ -128,6 +138,33 @@ public function testReadDoesNotExist() } + public function testShare() + { + $this->shareService->expects($this->once()) + ->method('shareItemWithUser') + ->with('user', 4, 'test'); + + $this->controller->share(4, 'test'); + } + + + public function testShareDoesNotExist() + { + $msg = 'hi'; + + $this->shareService->expects($this->once()) + ->method('shareItemWithUser') + ->with('user', 4, 'test') + ->will($this->throwException(new ServiceNotFoundException($msg))); + + $response = $this->controller->share(4, 'test'); + $params = json_decode($response->render(), true); + + $this->assertEquals($response->getStatus(), Http::STATUS_NOT_FOUND); + $this->assertEquals($msg, $params['message']); + } + + public function testReadMultiple() { $this->itemService->expects($this->exactly(2)) @@ -256,6 +293,11 @@ public function testIndexForFeed() ->with('user', 2, 3, 0, false, false, []) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->index(ListType::FEED, 2, 3); $this->assertEquals($result, $response); } @@ -293,6 +335,11 @@ public function testIndexForFolder() ->with('user', 2, 3, 0, false, false, []) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->index(ListType::FOLDER, 2, 3); $this->assertEquals($result, $response); } @@ -330,6 +377,11 @@ public function testIndexForOther() ->with('user', 2, 3, 0, false, []) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->index(ListType::STARRED, 2, 3); $this->assertEquals($result, $response); } @@ -367,6 +419,11 @@ public function testIndexSearchFeed() ->with('user', 2, 3, 0, false, false, ['test', 'search']) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->index(ListType::FEED, 2, 3, 0, null, null, 'test%20%20search%20'); $this->assertEquals($result, $response); } @@ -383,6 +440,11 @@ public function testItemsOffsetNotZero() ->with('user', 2, 3, 10, false, true) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $this->feedService->expects($this->never()) ->method('findAllForUser'); @@ -440,6 +502,11 @@ public function testNewItemsFeed() ->with('user', 2, 3, false) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->newItems(ListType::FEED, 2, 3); $this->assertEquals($result, $response); } @@ -480,6 +547,11 @@ public function testNewItemsFolder() ->with('user', 2, 3, false) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->newItems(ListType::FOLDER, 2, 3); $this->assertEquals($result, $response); } @@ -520,6 +592,11 @@ public function testNewItemsOther() ->with('user', 6, 3) ->will($this->returnValue($result['items'])); + $this->shareService->expects($this->once()) + ->method('mapSharedByDisplayNames') + ->with($result['items']) + ->will($this->returnValue($result['items'])); + $response = $this->controller->newItems(ListType::UNREAD, 2, 3); $this->assertEquals($result, $response); } @@ -542,4 +619,4 @@ public function testGetNewItemsNoNewestItemsId() } -} \ No newline at end of file +} diff --git a/tests/Unit/Db/ItemTest.php b/tests/Unit/Db/ItemTest.php index 4f9de59d3a..68b7b5ab8e 100644 --- a/tests/Unit/Db/ItemTest.php +++ b/tests/Unit/Db/ItemTest.php @@ -200,6 +200,8 @@ public function testJSONSerialize() $item->setStarred(true); $item->setLastModified(321); $item->setCategories(['food']); + $item->setSharedBy('jack'); + $item->setSharedByDisplayName('Jack'); $this->assertEquals( [ @@ -223,7 +225,9 @@ public function testJSONSerialize() 'rtl' => true, 'intro' => 'this is a test', 'fingerprint' => 'fingerprint', - 'categories' => ['food'] + 'categories' => ['food'], + 'sharedBy' => 'jack', + 'sharedByDisplayName' => 'Jack' ], $item->jsonSerialize() ); } @@ -448,4 +452,19 @@ public function testSetCategoriesJson() $this->assertEquals(json_encode(['podcast', 'blog']), $item->getCategoriesJson()); $this->assertArrayHasKey('categoriesJson', $item->getUpdatedFields()); } + + public function testSetSharedBy() + { + $item = new Item(); + $item->setSharedBy('Hector'); + $this->assertEquals('Hector', $item->getSharedBy()); + $this->assertArrayHasKey('sharedBy', $item->getUpdatedFields()); + } + + public function testSetSharedByDisplayName() + { + $item = new Item(); + $item->setSharedByDisplayName('Hector'); + $this->assertEquals('Hector', $item->getSharedByDisplayName()); + } } diff --git a/tests/Unit/Service/ShareServiceTest.php b/tests/Unit/Service/ShareServiceTest.php new file mode 100644 index 0000000000..d7c26a9181 --- /dev/null +++ b/tests/Unit/Service/ShareServiceTest.php @@ -0,0 +1,291 @@ + + */ + + +namespace OCA\News\Tests\Unit\Service; + +use FeedIo\Explorer; +use FeedIo\Reader\ReadErrorException; + +use OC\L10N\L10N; +use OCA\News\Service\Exceptions\ServiceConflictException; +use OCA\News\Service\Exceptions\ServiceNotFoundException; +use OCP\AppFramework\Db\DoesNotExistException; +use OCA\News\Service\FeedServiceV2; +use OCA\News\Service\ItemServiceV2; +use OCA\News\Service\ShareService; +use OCA\News\Utility\Time; + +use OCA\News\Db\Feed; +use OCA\News\Db\Item; + +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IUser; + +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + + +class ShareServiceTest extends TestCase +{ + /** + * @var MockObject|ItemServiceV2 + */ + private $itemService; + + /** + * @var MockObject|FeedServiceV2 + */ + private $feedService; + + /** + * @var MockObject|IURLGenerator + */ + private $urlGenerator; + + /** + * @var MockObject|IUserManager + */ + private $userManager; + + /** + * @var MockObject|IL10N + */ + private $l10n; + + /** + * @var MockObject|LoggerInterface + */ + private $logger; + + /** @var ShareService */ + private $class; + + /** + * @var string + */ + private $uid; + + /** + * @var string + */ + private $recipient; + + protected function setUp(): void + { + $this->logger = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->itemService = $this + ->getMockBuilder(ItemServiceV2::class) + ->disableOriginalConstructor() + ->getMock(); + $this->feedService = $this + ->getMockBuilder(FeedServiceV2::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlGenerator = $this + ->getMockBuilder(IURLGenerator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->userManager = $this + ->getMockBuilder(IUserManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->l10n = $this->getMockBuilder(IL10N::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->time = 333333; + + $this->class = new ShareService( + $this->feedService, + $this->itemService, + $this->urlGenerator, + $this->userManager, + $this->l10n, + $this->logger + ); + + $this->uid = 'sender'; + $this->recipient = 'recipient'; + } + + public function testShareItemWithUser() + { + $feedUrl = 'http://serverurl/news/sharedwithme'; + $itemId = 3; + + // Item to be shared + $item = new Item(); + $item->setGuid('_guid_') + ->setGuidHash(md5('_guid_')) + ->setUrl('_url_') + ->setTitle('_title_') + ->setAuthor('_author_') + ->setUnread(0) + ->setStarred(1) + ->setFeedId(10); + + // Shared item + $sharedItem = clone $item; + $sharedItem->setUnread(1) // A newly shared item is unread, ... + ->setStarred(0) // ... not starred, ... + ->setFeedId(100) // ... placed in the 'Shared with me' feed, ... + ->setSharedBy($this->uid); // ... and contains the senders user ID + + // Dummy feed 'Shared with me' + $feed = new Feed(); + $feed->setId(100); + $feed->setUserId($this->recipient) + ->setUrl($feedUrl) + ->setLink($feedUrl) + ->setTitle('Shared with me') + ->setAdded($this->time) + ->setFolderId(null) + ->setPreventUpdate(true); + + + $this->itemService->expects($this->once()) + ->method('find') + ->with($this->uid, $itemId) + ->will($this->returnValue($item)); + + $this->urlGenerator->expects($this->once()) + ->method('getBaseUrl') + ->will($this->returnValue('http://serverurl')); + + $this->feedService->expects($this->once()) + ->method('findByUrl') + ->with($this->recipient, $feedUrl) + ->will($this->returnValue($feed)); + + // Here we test if the setters worked properly using 'with()' + $this->itemService->expects($this->once()) + ->method('insertOrUpdate') + ->with($sharedItem) + ->will($this->returnValue($sharedItem)); + + + $this->class->shareItemWithUser($this->uid, $itemId, $this->recipient); + } + + public function testShareItemWithUserCreatesOwnFeedWhenNotFound() + { + $feedUrl = 'http://serverurl/news/sharedwithme'; + $itemId = 3; + + // Item to be shared + $item = new Item(); + $item->setGuid('_guid_') + ->setGuidHash(md5('_guid_')) + ->setUrl('_url_') + ->setTitle('_title_') + ->setAuthor('_author_') + ->setUnread(0) + ->setStarred(1) + ->setFeedId(10); + + // Shared item + $sharedItem = clone $item; + $sharedItem->setUnread(1) // A newly shared item is unread, ... + ->setStarred(0) // ... not starred, ... + ->setFeedId(100) // ... placed in the 'Shared with me' feed, ... + ->setSharedBy($this->uid); // ... and contains the senders user ID + + // Dummy feed 'Shared with me' + $feed = new Feed(); + $feed->setId(100); + $feed->setUserId($this->recipient) + ->setUrl($feedUrl) + ->setLink($feedUrl) + ->setTitle('Shared with me') + ->setAdded($this->time) + ->setFolderId(null) + ->setPreventUpdate(true); + + + $this->itemService->expects($this->once()) + ->method('find') + ->with($this->uid, $itemId) + ->will($this->returnValue($item)); + + $this->urlGenerator->expects($this->once()) + ->method('getBaseUrl') + ->will($this->returnValue('http://serverurl')); + + $this->feedService->expects($this->once()) + ->method('findByUrl') + ->with($this->recipient, $feedUrl) + ->will($this->returnValue(null)); + + $this->l10n->expects($this->once()) + ->method('t') + ->with('Shared with me') + ->will($this->returnValue('Shared with me')); + + $this->feedService->expects($this->once()) + ->method('insert') + ->will($this->returnValue($feed)); + + // Here we test if the setters worked properly using 'with()' + $this->itemService->expects($this->once()) + ->method('insertOrUpdate') + ->with($sharedItem) + ->will($this->returnValue($sharedItem)); + + + $this->class->shareItemWithUser($this->uid, $itemId, $this->recipient); + } + + + public function testShareItemWithUserItemDoesNotExist() + { + $this->expectException(ServiceNotFoundException::class); + $this->itemService->expects($this->once()) + ->method('find') + ->will($this->throwException(new ServiceNotFoundException(''))); + + $this->class->shareItemWithUser('sender', 1, 'recipient'); + } + + + public function testMapSharedByDisplayNames() + { + $item1 = new Item(); + $item1->setTitle('Item 1') + ->setSharedBy('sender'); + $item2 = new Item(); + $item2->setTitle('Item 2') + ->setSharedBy(null); + + $items = [$item1, $item2]; + $user = $this->getMockBuilder(IUser::class) + ->getMock(); + + $this->userManager->expects($this->once()) + ->method('get') + ->with('sender') + ->will($this->returnValue($user)); + + $user->expects($this->once()) + ->method('getDisplayName') + ->will($this->returnValue('Mr. Sender')); + + $result = $this->class->mapSharedByDisplayNames($items); + + $this->assertEquals('Mr. Sender', $result[0]->getSharedByDisplayName()); + $this->assertEquals(null, $result[1]->getSharedByDisplayName()); + } +}