+
+
+ {% for msg in messages %}
+ {% if msg.user_id == other_user.id %}
+
+
+
+
{{ msg.text | nl2br }}
+
+ {{ msg.created_at }}
+
+ {% if msg.read == false %}
+
+ New alerts
+
+ {% endif %}
+
+
+
+ {% else %}
+
+
+
+
{{ msg.text | nl2br }}
+
+
+
+
+
+
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+{% endblock %}
diff --git a/resources/views/pages/messages/overview.twig b/resources/views/pages/messages/overview.twig
new file mode 100644
index 00000000..9e1fdb71
--- /dev/null
+++ b/resources/views/pages/messages/overview.twig
@@ -0,0 +1,64 @@
+{% extends "layouts/app.twig" %}
+{% import 'macros/base.twig' as m %}
+{% import 'macros/form.twig' as f %}
+
+{% block title %}{{ __('messages.title') }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/src/Controllers/MessagesController.php b/src/Controllers/MessagesController.php
new file mode 100644
index 00000000..2265f6fb
--- /dev/null
+++ b/src/Controllers/MessagesController.php
@@ -0,0 +1,285 @@
+auth = $auth;
+ $this->log = $log;
+ $this->redirect = $redirect;
+ $this->response = $response;
+ $this->request = $request;
+ $this->db = $db;
+ $this->message = $message;
+ $this->user = $user;
+ }
+
+ public function index(): Response
+ {
+ return $this->listConversations();
+ }
+
+ /**
+ * Returns a list of conversations of the current user, each containing the other participant,
+ * the most recent message, and the number of unread messages.
+ */
+ public function listConversations(): Response
+ {
+ $current_user = $this->auth->user();
+
+ $latest_messages = $this->latestMessagePerConversation($current_user);
+ $numberOfUnreadMessages = $this->numberOfUnreadMessagesPerConversation($current_user);
+
+ $conversations = [];
+ foreach ($latest_messages as $msg) {
+ $other_user = $msg->user_id == $current_user->id ? $msg->receiver : $msg->sender;
+ $unread_messages = $numberOfUnreadMessages[$other_user->id] ?? 0;
+ array_push($conversations, [
+ 'other_user' => $other_user,
+ 'latest_message' => $msg,
+ 'unread_messages' => $unread_messages
+ ]);
+ }
+
+ $users = $this->user->orderBy('name')->get()
+ ->except($current_user->id)
+ ->mapWithKeys(function ($u) {
+ return [ $u->id => $u->nameWithPronoun() ];
+ });
+
+ return $this->response->withView(
+ 'pages/messages/overview.twig',
+ [
+ 'conversations' => $conversations,
+ 'users' => $users
+ ]
+ );
+ }
+
+ /**
+ * Forwards to the conversation with the user of the given id.
+ */
+ public function toConversation(Request $request): Response
+ {
+ $data = $this->validate($request, [ 'user_id' => 'required|int' ]);
+ return $this->redirect->to('/messages/' . $data['user_id']);
+ }
+
+ /**
+ * Returns a list of messages between the current user and a user with the given id. The ids shall not be the same.
+ * Unread messages will be marked as read during this call. Still, they will be shown as unread in the frontend to
+ * highlight them to the user as new.
+ */
+ public function conversation(Request $request): Response
+ {
+ $current_user = $this->auth->user();
+ $other_user = $this->user->findOrFail($request->getAttribute('user_id'));
+
+ if ($current_user->id == $other_user->id) {
+ throw new HttpForbidden('You can not start a conversation with yourself.');
+ }
+
+ $messages = $this->message
+ ->where(function ($q) use ($current_user, $other_user) {
+ $q->whereUserId($current_user->id)
+ ->whereReceiverId($other_user->id);
+ })
+ ->orWhere(function ($q) use ($current_user, $other_user) {
+ $q->whereUserId($other_user->id)
+ ->whereReceiverId($current_user->id);
+ })
+ ->orderBy('created_at')
+ ->get();
+
+ $unread_messages = $messages->filter(function ($m) use ($other_user) {
+ return $m->user_id == $other_user->id && !$m->read;
+ });
+
+ foreach ($unread_messages as $msg) {
+ $msg->read = true;
+ $msg->save();
+ $msg->read = false; // change back to true to display it to the frontend one more time.
+ }
+
+ return $this->response->withView(
+ 'pages/messages/conversation.twig',
+ ['messages' => $messages, 'other_user' => $other_user]
+ );
+ }
+
+ /**
+ * Sends a message to another user.
+ */
+ public function send(Request $request): Response
+ {
+ $current_user = $this->auth->user();
+
+ $data = $this->validate($request, [ 'text' => 'required' ]);
+
+ $other_user = $this->user->findOrFail($request->getAttribute('user_id'));
+
+ if ($other_user->id == $current_user->id) {
+ throw new HttpForbidden('You can not send a message to yourself.');
+ }
+
+ $new_message = new Message();
+ $new_message->sender()->associate($current_user);
+ $new_message->receiver()->associate($other_user);
+ $new_message->text = $data['text'];
+ $new_message->read = false;
+ $new_message->save();
+
+ $this->log->info(
+ 'User {from} has written a message to user {to}',
+ [
+ 'from' => $current_user->id,
+ 'to' => $other_user->id
+ ]
+ );
+ return $this->redirect->to('/messages/' . $other_user->id);
+ }
+
+ /**
+ * Deletes a message from a given id, as long as this message was send by the current user. The given user_id
+ * The given user_id is used to redirect back to the conversation with that user.
+ */
+ public function delete(Request $request): Response
+ {
+ $current_user = $this->auth->user();
+ $other_user_id = $request->getAttribute('user_id');
+ $msg_id = $request->getAttribute('msg_id');
+ $msg = $this->message->findOrFail($msg_id);
+
+ if ($msg->user_id == $current_user->id) {
+ $msg->delete();
+
+ $this->log->info(
+ 'User {from} deleted message {msg} in a conversation with user {to}',
+ [
+ 'from' => $current_user->id,
+ 'to' => $other_user_id,
+ 'msg' => $msg_id
+ ]
+ );
+ } else {
+ $this->log->warning(
+ 'User {from} tried to delete message {msg} which was not written by them, ' .
+ 'in a conversation with user {to}',
+ [
+ 'from' => $current_user->id,
+ 'to' => $other_user_id,
+ 'msg' => $msg_id
+ ]
+ );
+
+ throw new HttpForbidden('You can not delete a message you haven\'t send');
+ }
+
+ return $this->redirect->to('/messages/' . $other_user_id);
+ }
+
+ public function numberOfUnreadMessages(): int
+ {
+ return $this->auth->user()
+ ->messagesReceived()
+ ->where('read', false)
+ ->count();
+ }
+
+ protected function numberOfUnreadMessagesPerConversation($current_user): Collection
+ {
+ return $current_user->messagesReceived()
+ ->select('user_id', $this->raw('count(*) as amount'))
+ ->where('read', false)
+ ->groupBy('user_id')
+ ->get(['user_id', 'amount'])
+ ->mapWithKeys(function ($unread) {
+ return [ $unread->user_id => $unread->amount ];
+ });
+ }
+
+ protected function latestMessagePerConversation($current_user): Collection
+ {
+ $latest_message_ids = $this->message
+ ->select($this->raw('max(id) as last_id'))
+ ->where('user_id', "=", $current_user->id)
+ ->orWhere('receiver_id', "=", $current_user->id)
+ ->groupBy($this->raw(
+ '(CASE WHEN user_id = ' . $current_user->id .
+ ' THEN receiver_id ELSE user_id END)'
+ ));
+
+ return $this->message
+ ->joinSub($latest_message_ids, 'conversations', function ($join) {
+ $join->on('messages.id', '=', 'conversations.last_id');
+ })
+ ->orderBy('created_at', 'DESC')
+ ->get();
+ }
+
+ protected function raw($value): QueryExpression
+ {
+ return $this->db->getConnection()->raw($value);
+ }
+}
diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php
index 9edba882..4ed9e122 100644
--- a/src/Middleware/LegacyMiddleware.php
+++ b/src/Middleware/LegacyMiddleware.php
@@ -140,10 +140,6 @@ class LegacyMiddleware implements MiddlewareInterface
return [$title, $content];
case 'user_worklog':
return user_worklog_controller();
- case 'user_messages':
- $title = messages_title();
- $content = user_messages();
- return [$title, $content];
case 'user_settings':
$title = settings_title();
$content = user_settings();
diff --git a/src/Models/Message.php b/src/Models/Message.php
index 55780fc3..3e433c22 100644
--- a/src/Models/Message.php
+++ b/src/Models/Message.php
@@ -20,8 +20,10 @@ use Illuminate\Support\Carbon;
* @property string $text
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
+ * @property-read User $sender
* @property-read User $receiver
* @method static Builder|Message whereId($value)
+ * @method static Builder|Message whereUserId($value)
* @method static Builder|Message whereReceiverId($value)
* @method static Builder|Message whereRead($value)
* @method static Builder|Message whereText($value)
@@ -56,6 +58,14 @@ class Message extends BaseModel
'read' => false,
];
+ /**
+ * @return BelongsTo
+ */
+ public function sender(): BelongsTo
+ {
+ return $this->belongsTo(User::class, 'user_id');
+ }
+
/**
* @return BelongsTo
*/
diff --git a/src/Models/User/User.php b/src/Models/User/User.php
index 2d06468d..62363b45 100644
--- a/src/Models/User/User.php
+++ b/src/Models/User/User.php
@@ -225,4 +225,18 @@ class User extends BaseModel
->orderBy('read')
->orderBy('id', 'DESC');
}
+
+ /**
+ * Return either just the user name or the name alongside with the pronoun.
+ * @return string
+ */
+ public function nameWithPronoun(): string
+ {
+ if (config('enable_pronoun')) {
+ $pronoun = $this->personalData->pronoun;
+ return $pronoun ? $this->name . ' (' . $pronoun . ')' : $this->name;
+ } else {
+ return $this->name;
+ }
+ }
}
diff --git a/tests/Unit/Controllers/MessagesControllerTest.php b/tests/Unit/Controllers/MessagesControllerTest.php
new file mode 100644
index 00000000..d16354ea
--- /dev/null
+++ b/tests/Unit/Controllers/MessagesControllerTest.php
@@ -0,0 +1,585 @@
+ 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;
+ }
+}
diff --git a/tests/Unit/Models/User/UserTest.php b/tests/Unit/Models/User/UserTest.php
index 664b1332..c2365078 100644
--- a/tests/Unit/Models/User/UserTest.php
+++ b/tests/Unit/Models/User/UserTest.php
@@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Models\User;
use Carbon\Carbon;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
+use Engelsystem\Config\Config;
use Engelsystem\Models\BaseModel;
use Engelsystem\Models\News;
use Engelsystem\Models\NewsComment;
@@ -276,4 +277,45 @@ class UserTest extends ModelTest
$this->assertContains($question1->id, $answers);
$this->assertContains($question2->id, $answers);
}
+
+ /**
+ * @testdox nameWithPronoun: pronounsDeactivated -> returnNameOnly
+ */
+ public function testNameWithPronounPronounsDeactivatedReturnNameOnly()
+ {
+ $user_with_pronoun = User::factory(['name' => 'x'])
+ ->has(PersonalData::factory(['pronoun' => 'X']))->create();
+ $user_without_pronoun = User::factory(['name' => 'y'])->create();
+
+ $this->assertEquals('x', $user_with_pronoun->nameWithPronoun());
+ $this->assertEquals('y', $user_without_pronoun->nameWithPronoun());
+ }
+
+ /**
+ * @testdox nameWithPronoun: pronounsActivated -> returnNameAndPronoun
+ */
+ public function testNameWithPronounPronounsActivatedReturnNameAndPronoun()
+ {
+ config(['enable_pronoun' => true]);
+
+ $user_with_pronoun = User::factory(['name' => 'x'])
+ ->has(PersonalData::factory(['pronoun' => 'X']))->create();
+ $user_without_pronoun = User::factory(['name' => 'y'])->create();
+
+ $this->assertEquals('x (X)', $user_with_pronoun->nameWithPronoun());
+ $this->assertEquals('y', $user_without_pronoun->nameWithPronoun());
+ }
+
+ /**
+ * Prepare test
+ */
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ // config needed for checking if pronouns are activated.
+ $config = new Config();
+ $this->app->instance('config', $config);
+ $this->app->instance(Config::class, $config);
+ }
}