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; } }