engelsystem/tests/Unit/Controllers/MessagesControllerTest.php

586 lines
20 KiB
PHP

<?php
namespace Engelsystem\Test\Unit\Controllers;
use Carbon\Carbon;
use Engelsystem\Controllers\MessagesController;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\Message;
use Engelsystem\Models\Question;
use Engelsystem\Models\User\PersonalData;
use Engelsystem\Models\User\Settings;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use PHPUnit\Framework\MockObject\MockObject;
class MessagesControllerTest extends ControllerTest
{
use HasDatabase;
/** @var MessagesController */
protected $controller;
/** @var Authenticator|MockObject */
protected $auth;
/** @var User */
protected $user_a;
/** @var User */
protected $user_b;
/** @var Carbon */
protected $now;
/** @var Carbon */
protected $one_minute_ago;
/** @var Carbon */
protected $two_minutes_ago;
/**
* @testdox index: underNormalConditions -> returnsCorrectViewAndData
*/
public function testIndexUnderNormalConditionsReturnsCorrectViewAndData()
{
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$this->assertEquals('pages/messages/overview.twig', $view);
$this->assertArrayHasKey('conversations', $data);
$this->assertArrayHasKey('users', $data);
$this->assertArrayOrCollection($data['conversations']);
$this->assertArrayOrCollection($data['users']);
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: otherUsersExist -> returnsUsersWithoutMeOrderedByName
*/
public function testIndexOtherUsersExistReturnsUsersWithoutMeOrderedByName()
{
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$users = $data['users'];
$this->assertEquals(1, count($users));
$this->assertEquals('b', $users[$this->user_b->id]);
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: pronounsDeactivated -> userListHasNoPronouns
*/
public function testIndexPronounsDeactivatedUserListHasNoPronouns()
{
$this->user_with_pronoun = User::factory(['name' => 'x'])
->has(PersonalData::factory(['pronoun' => 'X']))->create();
$this->user_without_pronoun = $this->user_b;
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$users = $data['users'];
$this->assertEquals('x', $users[$this->user_with_pronoun->id]);
$this->assertEquals('b', $users[$this->user_without_pronoun->id]);
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: pronounsActivated -> userListHasPronouns
*/
public function testIndexPronounsActivatedUserListHasPronouns()
{
config(['enable_pronoun' => true]);
$this->user_with_pronoun = User::factory(['name' => 'x'])
->has(PersonalData::factory(['pronoun' => 'X']))->create();
$this->user_without_pronoun = $this->user_b;
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$users = $data['users'];
$this->assertEquals('x (X)', $users[$this->user_with_pronoun->id]);
$this->assertEquals('b', $users[$this->user_without_pronoun->id]);
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: withNoConversation -> returnsEmptyConversationList
*/
public function testIndexWithNoConversationReturnsEmptyConversationList()
{
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$this->assertEquals(0, count($data['conversations']));
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: withConversation -> conversationContainsCorrectData
*/
public function testIndexWithConversationConversationContainsCorrectData()
{
// save messages in wrong order to ensure latest message considers creation date, not id.
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->two_minutes_ago);
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->one_minute_ago);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$conversations = $data['conversations'];
$this->assertEquals(1, count($conversations));
$c = $conversations[0];
$this->assertArrayHasKey('other_user', $c);
$this->assertArrayHasKey('latest_message', $c);
$this->assertArrayHasKey('unread_messages', $c);
$this->assertTrue($c['other_user'] instanceof User);
$this->assertTrue($c['latest_message'] instanceof Message);
$this->assertEquals('string', gettype($c['unread_messages']));
$this->assertEquals('b', $c['other_user']->name);
$this->assertEquals('b>a', $c['latest_message']->text);
$this->assertEquals(2, $c['unread_messages']);
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: withConversations -> onlyContainsConversationsWithMe
*/
public function testIndexWithConversationsOnlyContainsConversationsWithMe()
{
$user_c = User::factory(['name' => 'c'])->create();
// save messages in wrong order to ensure latest message considers creation date, not id.
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
$this->createMessage($this->user_b, $user_c, 'b>c', $this->now);
$this->createMessage($user_c, $this->user_a, 'c>a', $this->now);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$conversations = $data['conversations'];
$this->assertEquals(2, count($conversations));
$msg0 = $conversations[0]['latest_message']->text;
$msg1 = $conversations[1]['latest_message']->text;
$this->assertTrue(($msg0 == 'a>b' && $msg1 == 'c>a') || ($msg1 == 'c>a' && $msg0 == 'a>b'));
return $this->response;
});
$this->controller->index();
}
/**
* @testdox index: withConversations -> conversationsOrderedByDate
*/
public function testIndexWithConversationsConversationsOrderedByDate()
{
$user_c = User::factory(['name' => 'c'])->create();
$user_d = User::factory(['name' => 'd'])->create();
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
$this->createMessage($user_d, $this->user_a, 'd>a', $this->two_minutes_ago);
$this->createMessage($this->user_a, $user_c, 'a>c', $this->one_minute_ago);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$conversations = $data['conversations'];
$this->assertEquals('a>b', $conversations[0]['latest_message']->text);
$this->assertEquals('a>c', $conversations[1]['latest_message']->text);
$this->assertEquals('d>a', $conversations[2]['latest_message']->text);
return $this->response;
});
$this->controller->index();
}
/**
* @testdox ToConversation: withNoUserIdGiven -> throwsException
*/
public function testToConversationWithNoUserIdGivenThrowsException()
{
$this->expectException(ValidationException::class);
$this->controller->toConversation($this->request);
}
/**
* @testdox ToConversation: withUserIdGiven -> redirect
*/
public function testToConversationWithUserIdGivenRedirect()
{
$this->request = $this->request->withParsedBody([
'user_id' => '1',
]);
$this->response->expects($this->once())
->method('redirectTo')
->with('http://localhost/messages/1')
->willReturn($this->response);
$this->controller->toConversation($this->request);
}
/**
* @testdox conversation: withNoUserIdGiven -> throwsException
*/
public function testConversationWithNoUserIdGivenThrowsException()
{
$this->expectException(ModelNotFoundException::class);
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: withMyUserIdGiven -> throwsException
*/
public function testConversationWithMyUserIdGivenThrowsException()
{
$this->request->attributes->set('user_id', $this->user_a->id);
$this->expectException(HttpForbidden::class);
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: withUnknownUserIdGiven -> throwsException
*/
public function testConversationWithUnknownUserIdGivenThrowsException()
{
$this->request->attributes->set('user_id', '1234');
$this->expectException(ModelNotFoundException::class);
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: underNormalConditions -> returnsCorrectViewAndData
*/
public function testConversationUnderNormalConditionsReturnsCorrectViewAndData()
{
$this->request->attributes->set('user_id', $this->user_b->id);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$this->assertEquals('pages/messages/conversation.twig', $view);
$this->assertArrayHasKey('messages', $data);
$this->assertArrayHasKey('other_user', $data);
$this->assertArrayOrCollection($data['messages']);
$this->assertTrue($data['other_user'] instanceof User);
return $this->response;
});
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: withNoMessages -> returnsEmptyMessageList
*/
public function testConversationWithNoMessagesReturnsEmptyMessageList()
{
$this->request->attributes->set('user_id', $this->user_b->id);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$this->assertEquals(0, count($data['messages']));
return $this->response;
});
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: withMessages -> messagesOnlyWithThatUserOrderedByDate
*/
public function testConversationWithMessagesMessagesOnlyWithThatUserOrderedByDate()
{
$this->request->attributes->set('user_id', $this->user_b->id);
$user_c = User::factory(['name' => 'c'])->create();
// to be listed
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->two_minutes_ago);
$this->createMessage($this->user_b, $this->user_a, 'b>a2', $this->one_minute_ago);
// not to be listed
$this->createMessage($this->user_a, $user_c, 'a>c', $this->now);
$this->createMessage($user_c, $this->user_b, 'b>c', $this->now);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$messages = $data['messages'];
$this->assertEquals(3, count($messages));
$this->assertEquals('b>a', $messages[0]->text);
$this->assertEquals('b>a2', $messages[1]->text);
$this->assertEquals('a>b', $messages[2]->text);
return $this->response;
});
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: withUnreadMessages -> messagesToMeWillStillBeReturnedAsUnread
*/
public function testConversationWithUnreadMessagesMessagesToMeWillStillBeReturnedAsUnread()
{
$this->request->attributes->set('user_id', $this->user_b->id);
$this->createMessage($this->user_b, $this->user_a, 'b>a', $this->now);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
$this->assertFalse($data['messages'][0]->read);
return $this->response;
});
$this->controller->conversation($this->request);
}
/**
* @testdox conversation: withUnreadMessages -> messagesToMeWillBeMarkedAsRead
*/
public function testConversationWithUnreadMessagesMessagesToMeWillBeMarkedAsRead()
{
$this->request->attributes->set('user_id', $this->user_b->id);
$this->response->expects($this->once())
->method('withView')
->willReturnCallback(function (string $view, array $data) {
return $this->response;
});
$msg = $this->createMessage($this->user_b, $this->user_a, 'b>a', $this->now);
$this->controller->conversation($this->request);
$this->assertTrue(Message::whereId($msg->id)->first()->read);
}
/**
* @testdox send: withNoTextGiven -> throwsException
*/
public function testSendWithNoTextGivenThrowsException()
{
$this->expectException(ValidationException::class);
$this->controller->send($this->request);
}
/**
* @testdox send: withNoUserIdGiven -> throwsException
*/
public function testSendWithNoUserIdGivenThrowsException()
{
$this->request = $this->request->withParsedBody([
'text' => 'a',
]);
$this->expectException(ModelNotFoundException::class);
$this->controller->send($this->request);
}
/**
* @testdox send: withMyUserIdGiven -> throwsException
*/
public function testSendWithMyUserIdGivenThrowsException()
{
$this->request = $this->request->withParsedBody([
'text' => 'a',
]);
$this->request->attributes->set('user_id', $this->user_a->id);
$this->expectException(HttpForbidden::class);
$this->controller->send($this->request);
}
/**
* @testdox send: withUnknownUserIdGiven -> throwsException
*/
public function testSendWithUnknownUserIdGivenThrowsException()
{
$this->request = $this->request->withParsedBody([
'text' => 'a',
]);
$this->request->attributes->set('user_id', '1234');
$this->expectException(ModelNotFoundException::class);
$this->controller->send($this->request);
}
/**
* @testdox send: withUserAndTextGiven -> savesMessage
*/
public function testSendWithUserAndTextGivenSavesMessage()
{
$this->request = $this->request->withParsedBody([
'text' => 'a',
]);
$this->request->attributes->set('user_id', $this->user_b->id);
$this->response->expects($this->once())
->method('redirectTo')
->with('http://localhost/messages/' . $this->user_b->id)
->willReturn($this->response);
$this->controller->send($this->request);
$msg = Message::whereText('a')->first();
$this->assertEquals($this->user_a->id, $msg->user_id);
$this->assertEquals($this->user_b->id, $msg->receiver_id);
$this->assertFalse($msg->read);
}
/**
* @testdox delete: withNoMsgIdGiven -> throwsException
*/
public function testDeleteWithNoMsgIdGivenThrowsException()
{
$this->expectException(ModelNotFoundException::class);
$this->controller->delete($this->request);
}
/**
* @testdox delete: tryingToDeleteSomeonesMessage -> throwsException
*/
public function testDeleteTryingToDeleteSomeonesMessageThrowsException()
{
$this->expectException(HttpForbidden::class);
$msg = $this->createMessage($this->user_b, $this->user_a, 'a>b', $this->now);
$this->request->attributes->set('msg_id', $msg->id);
$this->controller->delete($this->request);
}
/**
* @testdox delete: tryingToDeleteMyMessage -> deletesItAndRedirect
*/
public function testDeleteTryingToDeleteMyMessageDeletesItAndRedirect()
{
$msg = $this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
$this->request->attributes->set('msg_id', $msg->id);
$this->request->attributes->set('user_id', '1');
$this->response->expects($this->once())
->method('redirectTo')
->with('http://localhost/messages/1')
->willReturn($this->response);
$this->controller->delete($this->request);
$this->assertEquals(0, count(Message::whereId($msg->id)->get()));
}
/**
* @testdox NumberOfUnreadMessages: withNoMessages -> returns0
*/
public function testNumberOfUnreadMessagesWithNoMessagesReturns0()
{
$this->assertEquals(0, $this->controller->numberOfUnreadMessages());
}
/**
* @testdox NumberOfUnreadMessages: withMessagesNotToMe -> messagesNotToMeAreIgnored
*/
public function testNumberOfUnreadMessagesWithMessagesNotToMeMessagesNotToMeAreIgnored()
{
$user_c = User::factory(['name' => 'c'])->create();
$this->createMessage($this->user_a, $this->user_b, 'a>b', $this->now);
$this->createMessage($this->user_b, $user_c, 'b>c', $this->now);
$this->assertEquals(0, $this->controller->numberOfUnreadMessages());
}
/**
* @testdox NumberOfUnreadMessages: withMessages -> returnsSumOfUnreadMessagesSentToMe
*/
public function testNumberOfUnreadMessagesWithMessagesReturnsSumOfUnreadMessagesSentToMe()
{
$user_c = User::factory(['name' => 'c'])->create();
$this->createMessage($this->user_b, $this->user_a, 'b>a1', $this->now);
$this->createMessage($this->user_b, $this->user_a, 'b>a2', $this->now);
$this->createMessage($user_c, $this->user_a, 'c>a', $this->now);
$this->assertEquals(3, $this->controller->numberOfUnreadMessages());
}
/**
* Setup environment
*/
public function setUp(): void
{
parent::setUp();
$this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth);
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
$this->user_a = User::factory(['name' => 'a'])->create();
$this->user_b = User::factory(['name' => 'b'])->create();
$this->setExpects($this->auth, 'user', null, $this->user_a, $this->any());
$this->now = Carbon::now();
$this->one_minute_ago = Carbon::now()->subMinute();
$this->two_minutes_ago = Carbon::now()->subMinutes(2);
$this->controller = $this->app->get(MessagesController::class);
$this->controller->setValidator(new Validator());
}
protected function assertArrayOrCollection($obj)
{
$this->assertTrue(gettype($obj) == 'array' || $obj instanceof Collection);
}
protected function createMessage(User $from, User $to, string $text, Carbon $at): Message
{
Message::unguard(); // unguard temporarily to save custom creation dates.
$msg = new Message([
'user_id' => $from->id,
'receiver_id' => $to->id,
'text' => $text,
'created_at' => $at,
'updated_at' => $at,
]);
$msg->save();
Message::reguard();
return $msg;
}
}