Added post favoriting

This commit is contained in:
Marcin Kurczewski 2014-09-27 21:33:31 +02:00
parent 6d4a4f11d1
commit 6e22efdd6e
21 changed files with 636 additions and 86 deletions

3
TODO
View file

@ -3,7 +3,6 @@ first major release.
everything related to posts:
- single post view
- fav
- score
- editing
- ability to loop video posts
@ -104,8 +103,6 @@ refactors:
with query decorated by PostDao::decorateQueryFromFilter, call
PostDao::decorateEntitiesWithFilter that will optimally load users and
inject them using posts' lazy loaders.
- make view proxies less greedy, e.g. favs should be fetched only in post
view, not for each of 40 posts in post list. (same goes to other things)
- reduce dependencies
miscellaneous:

View file

@ -56,6 +56,10 @@
}
}
#post-view-wrapper .favorites li {
display: inline-block;
margin: 0 0.25em 0.25em 0;
}
#sidebar ul {
list-style-type: none;

View file

@ -22,6 +22,7 @@ App.Presenters.PostPresenter = function(
var historyTemplate;
var post;
var postFavorites;
var postHistory;
var postNameOrId;
@ -74,11 +75,16 @@ App.Presenters.PostPresenter = function(
promise.waitAll(
api.get('/posts/' + postNameOrId),
api.get('/posts/' + postNameOrId + '/favorites'),
privileges.canViewHistory ?
api.get('/posts/' + postNameOrId + '/history') :
null)
.then(function(postResponse, postHistoryResponse) {
.then(function(
postResponse,
postFavoritesResponse,
postHistoryResponse) {
post = postResponse.json;
postFavorites = postFavoritesResponse && postFavoritesResponse.json && postFavoritesResponse.json.data;
postHistory = postHistoryResponse && postHistoryResponse.json && postHistoryResponse.json.data;
topNavigationPresenter.changeTitle('@' + post.id);
render();
@ -112,30 +118,43 @@ App.Presenters.PostPresenter = function(
}
$el.find('.post-edit-wrapper form').submit(editFormSubmitted);
$el.find('#sidebar .delete').click(deleteButtonClicked);
$el.find('#sidebar .feature').click(featureButtonClicked);
$el.find('#sidebar .edit').click(editButtonClicked);
$el.find('#sidebar .history').click(historyButtonClicked);
attachSidebarEvents();
}
function renderSidebar() {
$el.find('#sidebar').html(jQuery(renderPostTemplate()).find('#sidebar').html());
attachSidebarEvents();
}
function renderPostTemplate() {
return postTemplate({
post: post,
postFavorites: postFavorites,
postHistory: postHistory,
formatRelativeTime: util.formatRelativeTime,
formatFileSize: util.formatFileSize,
postContentTemplate: postContentTemplate,
postEditTemplate: postEditTemplate,
historyTemplate: historyTemplate,
hasFav: _.any(postFavorites, function(favUser) { return favUser.id === auth.getCurrentUser().id; }),
isLoggedIn: auth.isLoggedIn(),
privileges: privileges,
editPrivileges: editPrivileges,
});
}
function attachSidebarEvents() {
$el.find('#sidebar .delete').click(deleteButtonClicked);
$el.find('#sidebar .feature').click(featureButtonClicked);
$el.find('#sidebar .edit').click(editButtonClicked);
$el.find('#sidebar .history').click(historyButtonClicked);
$el.find('#sidebar .add-favorite').click(addFavoriteButtonClicked);
$el.find('#sidebar .delete-favorite').click(deleteFavoriteButtonClicked);
}
function deleteButtonClicked(e) {
e.preventDefault();
messagePresenter.hideMessages($messages);
@ -268,6 +287,34 @@ App.Presenters.PostPresenter = function(
$el.find('.post-history-wrapper').slideDown('slow');
}
function addFavoriteButtonClicked(e) {
e.preventDefault();
addFavorite();
}
function deleteFavoriteButtonClicked(e) {
e.preventDefault();
deleteFavorite();
}
function addFavorite() {
api.post('/posts/' + post.id + '/favorites')
.then(function(response) {
postFavorites = response.json.data;
renderSidebar();
})
.fail(showGenericError);
}
function deleteFavorite() {
api.delete('/posts/' + post.id + '/favorites')
.then(function(response) {
postFavorites = response.json.data;
renderSidebar();
})
.fail(showGenericError);
}
function showEditError(response) {
window.alert(response.json && response.json.error || response);
}

View file

@ -10,6 +10,20 @@
<%= post.contentExtension + ', ' + formatFileSize(post.originalFileSize) %>
</a>
</li>
<% if (isLoggedIn) { %>
<li>
<% if (hasFav) { %>
<a href="#" class="delete-favorite">
<i class="fa fa-heart"></i>
</a>
<% } else { %>
<a href="#" class="add-favorite">
<i class="fa fa-heart-o"></i>
</a>
<% } %>
</li>
<% } %>
</ul>
<h1>Tags (<%= _.size(post.tags) %>)</h1>
@ -98,9 +112,24 @@
--><% } %><!--
--></li>
<% } %>
</ul>
<% if (_.any(postFavorites)) { %>
<p>Favorites:</p>
<ul class="favorites">
<% _.each(postFavorites, function(user) { %>
<li>
<a href="#/user/<%= user.name %>">
<img class="fav-avatar"
src="/data/thumbnails/25x25/avatars/<%= user.name || '!' %>"
alt="<%= user.name || 'Anonymous user' %>"/>
</a>
</li>
<% }) %>
</ul>
<% } %>
<% if (_.any(post.relations)) { %>
<h1>Related posts</h1>
<ul class="related">

View file

@ -0,0 +1,57 @@
<?php
namespace Szurubooru\Controllers;
class FavoritesController extends AbstractController
{
private $privilegeService;
private $authService;
private $postService;
private $favoritesService;
private $userViewProxy;
public function __construct(
\Szurubooru\Services\PrivilegeService $privilegeService,
\Szurubooru\Services\AuthService $authService,
\Szurubooru\Services\PostService $postService,
\Szurubooru\Services\FavoritesService $favoritesService,
\Szurubooru\Controllers\ViewProxies\UserViewProxy $userViewProxy)
{
$this->privilegeService = $privilegeService;
$this->authService = $authService;
$this->postService = $postService;
$this->favoritesService = $favoritesService;
$this->userViewProxy = $userViewProxy;
}
public function registerRoutes(\Szurubooru\Router $router)
{
$router->get('/api/posts/:postNameOrId/favorites', [$this, 'getFavoriteUsers']);
$router->post('/api/posts/:postNameOrId/favorites', [$this, 'addFavorite']);
$router->delete('/api/posts/:postNameOrId/favorites', [$this, 'deleteFavorite']);
}
public function getFavoriteUsers($postNameOrId)
{
$post = $this->postService->getByNameOrId($postNameOrId);
$users = $this->favoritesService->getFavoriteUsers($post);
return ['data' => $this->userViewProxy->fromArray($users)];
}
public function addFavorite($postNameOrId)
{
$this->privilegeService->assertLoggedIn();
$user = $this->authService->getLoggedInUser();
$post = $this->postService->getByNameOrId($postNameOrId);
$this->favoritesService->addFavorite($user, $post);
return $this->getFavoriteUsers($postNameOrId);
}
public function deleteFavorite($postNameOrId)
{
$this->privilegeService->assertLoggedIn();
$user = $this->authService->getLoggedInUser();
$post = $this->postService->getByNameOrId($postNameOrId);
$this->favoritesService->deleteFavorite($user, $post);
return $this->getFavoriteUsers($postNameOrId);
}
}

View file

@ -133,14 +133,9 @@ abstract class AbstractDao implements ICrudDao
protected function findBy($columnName, $value)
{
$entities = [];
$query = $this->fpdo->from($this->tableName)->where($columnName, $value);
foreach ($query as $arrayEntity)
{
$entity = $this->entityConverter->toEntity($arrayEntity);
$entities[$entity->getId()] = $entity;
}
return $entities;
$arrayEntities = iterator_to_array($query);
return $this->arrayToEntities($arrayEntities);
}
protected function findOneBy($columnName, $value)

View file

@ -0,0 +1,23 @@
<?php
namespace Szurubooru\Dao\EntityConverters;
class FavoriteEntityConverter extends AbstractEntityConverter implements IEntityConverter
{
public function toArray(\Szurubooru\Entities\Entity $entity)
{
return
[
'id' => $entity->getId(),
'userId' => $entity->getUserId(),
'postId' => $entity->getPostId(),
];
}
public function toBasicEntity(array $array)
{
$entity = new \Szurubooru\Entities\Favorite($array['id']);
$entity->setUserId($array['userId']);
$entity->setPostId($array['postId']);
return $entity;
}
}

View file

@ -45,6 +45,7 @@ class PostEntityConverter extends AbstractEntityConverter implements IEntityConv
$entity->setFeatureCount(intval($array['featureCount']));
$entity->setLastFeatureTime($array['lastFeatureTime']);
$entity->setMeta(\Szurubooru\Entities\Post::META_TAG_COUNT, intval($array['tagCount']));
$entity->setMeta(\Szurubooru\Entities\Post::META_FAV_COUNT, intval($array['favCount']));
return $entity;
}
}

54
src/Dao/FavoritesDao.php Normal file
View file

@ -0,0 +1,54 @@
<?php
namespace Szurubooru\Dao;
class FavoritesDao extends AbstractDao implements ICrudDao
{
private $userDao;
private $postDao;
public function __construct(
\Szurubooru\DatabaseConnection $databaseConnection,
\Szurubooru\Dao\UserDao $userDao,
\Szurubooru\Dao\PostDao $postDao)
{
parent::__construct(
$databaseConnection,
'favorites',
new \Szurubooru\Dao\EntityConverters\FavoriteEntityConverter());
$this->userDao = $userDao;
$this->postDao = $postDao;
}
public function findByUserAndPost(\Szurubooru\Entities\User $user, \Szurubooru\Entities\Post $post)
{
$query = $this->fpdo->from($this->tableName)
->where('userId', $user->getId())
->where('postId', $post->getId());
$arrayEntities = iterator_to_array($query);
$entities = $this->arrayToEntities($arrayEntities);
return array_shift($entities);
}
public function findByPost(\Szurubooru\Entities\Post $post)
{
return $this->findBy('postId', $post->getId());
}
protected function afterLoad(\Szurubooru\Entities\Entity $favorite)
{
$favorite->setLazyLoader(
\Szurubooru\Entities\Favorite::LAZY_LOADER_USER,
function (\Szurubooru\Entities\Favorite $favorite)
{
return $this->userDao->findById($favorite->getUserId());
});
$favorite->setLazyLoader(
\Szurubooru\Entities\Favorite::LAZY_LOADER_POST,
function (\Szurubooru\Entities\Favorite $favorite)
{
return $this->postDao->findById($favorite->getPostId());
});
}
}

View file

@ -51,35 +51,35 @@ class PostDao extends AbstractDao implements ICrudDao
{
$post->setLazyLoader(
\Szurubooru\Entities\Post::LAZY_LOADER_CONTENT,
function(\Szurubooru\Entities\Post $post)
function (\Szurubooru\Entities\Post $post)
{
return $this->fileService->load($post->getContentPath());
});
$post->setLazyLoader(
\Szurubooru\Entities\Post::LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT,
function(\Szurubooru\Entities\Post $post)
function (\Szurubooru\Entities\Post $post)
{
return $this->fileService->load($post->getThumbnailSourceContentPath());
});
$post->setLazyLoader(
\Szurubooru\Entities\Post::LAZY_LOADER_USER,
function(\Szurubooru\Entities\Post $post)
function (\Szurubooru\Entities\Post $post)
{
return $this->getUser($post);
});
$post->setLazyLoader(
\Szurubooru\Entities\Post::LAZY_LOADER_TAGS,
function(\Szurubooru\Entities\Post $post)
function (\Szurubooru\Entities\Post $post)
{
return $this->getTags($post);
});
$post->setLazyLoader(
\Szurubooru\Entities\Post::LAZY_LOADER_RELATED_POSTS,
function(\Szurubooru\Entities\Post $post)
function (\Szurubooru\Entities\Post $post)
{
return $this->getRelatedPosts($post);
});
@ -179,14 +179,14 @@ class PostDao extends AbstractDao implements ICrudDao
$this->tagDao->createMissingTags($tagNames);
$tagIds = array_map(
function($tag)
function ($tag)
{
return $tag->getId();
},
$this->tagDao->findByNames($tagNames));
$existingTagRelationIds = array_map(
function($arrayEntity)
function ($arrayEntity)
{
return $arrayEntity['tagId'];
},

View file

@ -43,6 +43,7 @@ abstract class Entity
public function resetLazyLoaders()
{
$this->lazyLoaders = [];
$this->lazyContainers = [];
}
public function setLazyLoader($lazyContainerName, $getter)

53
src/Entities/Favorite.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace Szurubooru\Entities;
class Favorite extends Entity
{
private $postId;
private $userId;
const LAZY_LOADER_USER = 'user';
const LAZY_LOADER_POST = 'post';
public function getUserId()
{
return $this->userId;
}
public function setUserId($userId)
{
$this->userId = $userId;
}
public function getPostId()
{
return $this->postId;
}
public function setPostId($postId)
{
$this->postId = $postId;
}
public function getUser()
{
return $this->lazyLoad(self::LAZY_LOADER_USER, null);
}
public function setUser(\Szurubooru\Entities\User $user)
{
$this->lazySave(self::LAZY_LOADER_USER, $user);
$this->userId = $user->getId();
}
public function getPost()
{
return $this->lazyLoad(self::LAZY_LOADER_POST, null);
}
public function setPost(\Szurubooru\Entities\Post $post)
{
$this->lazySave(self::LAZY_LOADER_POST, $post);
$this->postId = $post->getId();
}
}

View file

@ -19,6 +19,7 @@ final class Post extends Entity
const LAZY_LOADER_RELATED_POSTS = 'relatedPosts';
const META_TAG_COUNT = 'tagCount';
const META_FAV_COUNT = 'favCount';
protected $name;
protected $userId;
@ -257,4 +258,9 @@ final class Post extends Entity
{
return $this->getMeta(self::META_TAG_COUNT, 0);
}
public function getFavoriteCount()
{
return $this->getMeta(self::META_FAV_COUNT, 0);
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Szurubooru\Services;
class FavoritesService
{
private $favoritesDao;
private $userDao;
private $transactionManager;
public function __construct(
\Szurubooru\Dao\FavoritesDao $favoritesDao,
\Szurubooru\Dao\UserDao $userDao,
\Szurubooru\Dao\TransactionManager $transactionManager)
{
$this->favoritesDao = $favoritesDao;
$this->userDao = $userDao;
$this->transactionManager = $transactionManager;
}
public function getFavoriteUsers(\Szurubooru\Entities\Post $post)
{
$transactionFunc = function() use ($post)
{
$favorites = $this->favoritesDao->findByPost($post);
$userIds = [];
foreach ($favorites as $favorite)
{
$userIds[] = $favorite->getUserId();
}
return $this->userDao->findByIds($userIds);
};
return $this->transactionManager->rollback($transactionFunc);
}
public function addFavorite(\Szurubooru\Entities\User $user, \Szurubooru\Entities\Post $post)
{
$transactionFunc = function() use ($user, $post)
{
$favorite = $this->favoritesDao->findByUserAndPost($user, $post);
if (!$favorite)
{
$favorite = new \Szurubooru\Entities\Favorite();
$favorite->setUser($user);
$favorite->setPost($post);
$this->favoritesDao->save($favorite);
}
};
return $this->transactionManager->commit($transactionFunc);
}
public function deleteFavorite(\Szurubooru\Entities\User $user, \Szurubooru\Entities\Post $post)
{
$transactionFunc = function() use ($user, $post)
{
$favorite = $this->favoritesDao->findByUserAndPost($user, $post);
$this->favoritesDao->deleteById($favorite->getId());
};
return $this->transactionManager->commit($transactionFunc);
}
}

View file

@ -44,13 +44,21 @@ class PrivilegeService
public function assertPrivilege($privilege)
{
if (!$this->hasPrivilege($privilege))
throw new \DomainException('Unprivileged operation');
$this->fail();
}
public function assertLoggedIn($userIdentifier)
public function assertLoggedIn($userIdentifier = null)
{
if (!$this->isLoggedIn($userIdentifier))
throw new \DomainException('Unprivileged operation');
if ($userIdentifier)
{
if (!$this->isLoggedIn($userIdentifier))
$this->fail();
}
else
{
if (!$this->authService->getLoggedInUser())
$this->fail();
}
}
public function isLoggedIn($userIdentifier)
@ -74,4 +82,9 @@ class PrivilegeService
throw new \InvalidArgumentException('Invalid user identifier.');
}
}
private function fail()
{
throw new \DomainException('Unprivileged operation');
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Szurubooru\Upgrades;
class Upgrade10 implements IUpgrade
{
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
{
$pdo = $databaseConnection->getPDO();
$pdo->exec('ALTER TABLE posts ADD COLUMN favCount INTEGER NOT NULL DEFAULT 0');
$pdo->exec('CREATE TABLE favorites
(
id INTEGER PRIMARY KEY NOT NULL,
userId INTEGER NOT NULL,
postId INTEGER NOT NULL,
UNIQUE (userId, postId)
)');
$pdo->exec('
CREATE TRIGGER favoritesDelete BEFORE DELETE ON favorites
FOR EACH ROW
BEGIN
UPDATE posts SET favCount = favCount - 1 WHERE posts.id = OLD.postId;
END');
$pdo->exec('
CREATE TRIGGER favoritesInsert AFTER INSERT ON favorites
FOR EACH ROW
BEGIN
UPDATE posts SET favCount = favCount + 1 WHERE posts.id = NEW.postId;
END');
$pdo->exec('
CREATE TRIGGER favoritesUpdate AFTER UPDATE ON favorites
FOR EACH ROW
BEGIN
UPDATE posts SET favCount = favCount + 1 WHERE posts.id = NEW.postId;
UPDATE posts SET favCount = favCount - 1 WHERE posts.id = NEW.postId;
END');
}
}

View file

@ -25,6 +25,7 @@ return [
$container->get(\Szurubooru\Upgrades\Upgrade07::class),
$container->get(\Szurubooru\Upgrades\Upgrade08::class),
$container->get(\Szurubooru\Upgrades\Upgrade09::class),
$container->get(\Szurubooru\Upgrades\Upgrade10::class),
];
}),
@ -37,6 +38,7 @@ return [
$container->get(\Szurubooru\Controllers\PostContentController::class),
$container->get(\Szurubooru\Controllers\GlobalParamController::class),
$container->get(\Szurubooru\Controllers\HistoryController::class),
$container->get(\Szurubooru\Controllers\FavoritesController::class),
];
}),
];

View file

@ -25,30 +25,4 @@ abstract class AbstractDatabaseTestCase extends \Szurubooru\Tests\AbstractTestCa
if ($this->databaseConnection)
$this->databaseConnection->close();
}
protected function assertEntitiesEqual($expected, $actual)
{
if (!is_array($expected))
{
$expected = [$expected];
$actual = [$actual];
}
$this->assertEquals(count($expected), count($actual), 'Unmatching array sizes');
$this->assertEquals(array_keys($expected), array_keys($actual), 'Unmatching array keys');
foreach (array_keys($expected) as $key)
{
if ($expected[$key] === null)
{
$this->assertNull($actual[$key]);
}
else
{
$expected[$key]->resetLazyLoaders();
$expected[$key]->resetMeta();
$actual[$key]->resetLazyLoaders();
$actual[$key]->resetMeta();
$this->assertEquals($expected[$key], $actual[$key]);
}
}
}
}

View file

@ -3,39 +3,6 @@ namespace Szurubooru\Tests;
abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
{
public function mock($className)
{
return $this->getMockBuilder($className)->disableOriginalConstructor()->getMock();
}
public function mockConfig($dataPath = null, $publicDataPath = null)
{
return new ConfigMock($dataPath, $publicDataPath);
}
public function mockTransactionManager()
{
return new TransactionManagerMock($this->mock(\Szurubooru\DatabaseConnection::class));
}
public function createTestDirectory()
{
$path = $this->getTestDirectoryPath();
if (!file_exists($path))
mkdir($path, 0777, true);
return $path;
}
public function getTestFile($fileName)
{
return file_get_contents($this->getTestFilePath($fileName));
}
public function getTestFilePath($fileName)
{
return __DIR__ . DIRECTORY_SEPARATOR . 'test_files' . DIRECTORY_SEPARATOR . $fileName;
}
protected function setUp()
{
\Szurubooru\Injector::init();
@ -46,6 +13,66 @@ abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
$this->cleanTestDirectory();
}
protected function mock($className)
{
return $this->getMockBuilder($className)->disableOriginalConstructor()->getMock();
}
protected function mockConfig($dataPath = null, $publicDataPath = null)
{
return new ConfigMock($dataPath, $publicDataPath);
}
protected function mockTransactionManager()
{
return new TransactionManagerMock($this->mock(\Szurubooru\DatabaseConnection::class));
}
protected function createTestDirectory()
{
$path = $this->getTestDirectoryPath();
if (!file_exists($path))
mkdir($path, 0777, true);
return $path;
}
protected function getTestFile($fileName)
{
return file_get_contents($this->getTestFilePath($fileName));
}
protected function getTestFilePath($fileName)
{
return __DIR__ . DIRECTORY_SEPARATOR . 'test_files' . DIRECTORY_SEPARATOR . $fileName;
}
protected function assertEntitiesEqual($expected, $actual)
{
if (!is_array($expected))
{
$expected = [$expected];
$actual = [$actual];
}
$this->assertEquals(count($expected), count($actual), 'Unmatching array sizes');
$this->assertEquals(array_keys($expected), array_keys($actual), 'Unmatching array keys');
foreach (array_keys($expected) as $key)
{
if ($expected[$key] === null)
{
$this->assertNull($actual[$key]);
}
else
{
$this->assertNotNull($actual[$key]);
$expected[$key]->resetLazyLoaders();
$expected[$key]->resetMeta();
$actual[$key]->resetLazyLoaders();
$actual[$key]->resetMeta();
$this->assertEquals($expected[$key], $actual[$key]);
}
}
}
private function getTestDirectoryPath()
{
return __DIR__ . DIRECTORY_SEPARATOR . 'files';

View file

@ -0,0 +1,82 @@
<?php
namespace Szurubooru\Tests\Dao;
class FavoritesDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
{
private $userDaoMock;
private $postDaoMock;
public function setUp()
{
parent::setUp();
$this->userDaoMock = $this->mock(\Szurubooru\Dao\UserDao::class);
$this->postDaoMock = $this->mock(\Szurubooru\Dao\PostDao::class);
}
public function testSaving()
{
$user = new \Szurubooru\Entities\User(1);
$user->setName('olivia');
$post = new \Szurubooru\Entities\Post(2);
$post->setName('sword');
$favorite = new \Szurubooru\Entities\Favorite();
$favorite->setUser($user);
$favorite->setPost($post);
$favoritesDao = $this->getFavoritesDao();
$favoritesDao->save($favorite);
$this->userDaoMock->expects($this->once())->method('findById')->with(1)->willReturn($user);
$this->postDaoMock->expects($this->once())->method('findById')->with(2)->willReturn($post);
$savedFavorite = $favoritesDao->findById($favorite->getId());
$this->assertEquals(1, $savedFavorite->getUserId());
$this->assertEquals(2, $savedFavorite->getPostId());
$this->assertEntitiesEqual($user, $savedFavorite->getUser());
$this->assertEntitiesEqual($post, $savedFavorite->getPost());
}
public function testFindingByUserAndPost()
{
$post1 = new \Szurubooru\Entities\Post(1);
$post2 = new \Szurubooru\Entities\Post(2);
$user1 = new \Szurubooru\Entities\User(3);
$user2 = new \Szurubooru\Entities\User(4);
$fav1 = new \Szurubooru\Entities\Favorite();
$fav1->setUser($user1);
$fav1->setPost($post1);
$fav2 = new \Szurubooru\Entities\Favorite();
$fav2->setUser($user2);
$fav2->setPost($post2);
$fav3 = new \Szurubooru\Entities\Favorite();
$fav3->setUser($user1);
$fav3->setPost($post2);
$favoritesDao = $this->getFavoritesDao();
$favoritesDao->save($fav1);
$favoritesDao->save($fav2);
$favoritesDao->save($fav3);
$this->assertEntitiesEqual($fav1, $favoritesDao->findByUserAndPost($user1, $post1));
$this->assertEntitiesEqual($fav2, $favoritesDao->findByUserAndPost($user2, $post2));
$this->assertEntitiesEqual($fav3, $favoritesDao->findByUserAndPost($user1, $post2));
$this->assertNull($favoritesDao->findByUserAndPost($user2, $post1));
}
public function findByPost(\Szurubooru\Entities\Post $post)
{
return $this->findOneBy('postId', $post->getId());
}
private function getFavoritesDao()
{
return new \Szurubooru\Dao\FavoritesDao(
$this->databaseConnection,
$this->userDaoMock,
$this->postDaoMock);
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Szurubooru\Tests\Services;
final class FavoritesServiceTest extends \Szurubooru\Tests\AbstractTestCase
{
private $favoritesDaoMock;
private $userDaoMock;
private $transactionManagerMock;
public function setUp()
{
parent::setUp();
$this->favoritesDaoMock = $this->mock(\Szurubooru\Dao\FavoritesDao::class);
$this->userDaoMock = $this->mock(\Szurubooru\Dao\UserDao::class);
$this->transactionManagerMock = $this->mockTransactionManager();
}
public function testAddingExisting()
{
$user = new \Szurubooru\Entities\User();
$post = new \Szurubooru\Entities\Post();
$fav = new \Szurubooru\Entities\Favorite(3);
$this->favoritesDaoMock->expects($this->once())->method('findByUserAndPost')->with($user, $post)->willReturn($fav);
$this->favoritesDaoMock->expects($this->never())->method('save');
$favoritesService = $this->getFavoritesService();
$favoritesService->addFavorite($user, $post);
}
public function testAdding()
{
$user = new \Szurubooru\Entities\User(1);
$post = new \Szurubooru\Entities\Post(2);
$fav = new \Szurubooru\Entities\Favorite();
$fav->setUserId($user->getId());
$fav->setPostId($post->getId());
$this->favoritesDaoMock->expects($this->once())->method('findByUserAndPost')->with($user, $post)->willReturn(null);
$this->favoritesDaoMock->expects($this->once())->method('save')->with($this->callback(
function($subject) use ($fav)
{
$this->assertEntitiesEqual($fav, $subject);
return true;
}));
$favoritesService = $this->getFavoritesService();
$favoritesService->addFavorite($user, $post);
}
public function testDeleting()
{
$user = new \Szurubooru\Entities\User();
$post = new \Szurubooru\Entities\Post();
$fav = new \Szurubooru\Entities\Favorite(3);
$this->favoritesDaoMock->expects($this->once())->method('findByUserAndPost')->with($user, $post)->willReturn($fav);
$this->favoritesDaoMock->expects($this->once())->method('deleteById')->with($fav->getId());
$favoritesService = $this->getFavoritesService();
$favoritesService->deleteFavorite($user, $post);
}
public function testGettingByPost()
{
$post = new \Szurubooru\Entities\Post();
$fav1 = new \Szurubooru\Entities\Favorite();
$fav2 = new \Szurubooru\Entities\Favorite();
$fav1->setUser(new \Szurubooru\Entities\User(1));
$fav2->setUser(new \Szurubooru\Entities\User(2));
$this->favoritesDaoMock->expects($this->once())->method('findByPost')->with($post)->willReturn([$fav1, $fav2]);
$this->userDaoMock->expects($this->once())->method('findByIds')->with([1, 2]);
$favoritesService = $this->getFavoritesService();
$favoritesService->getFavoriteUsers($post);
}
private function getFavoritesService()
{
return new \Szurubooru\Services\FavoritesService(
$this->favoritesDaoMock,
$this->userDaoMock,
$this->transactionManagerMock);
}
}