@Test
  public void testOnConnected() throws Exception {
    String password = "******";

    when(userService.getPassword()).thenReturn(password);
    instance.connect();

    botStartedFuture.get(TIMEOUT, TIMEOUT_UNIT);

    CountDownLatch latch = new CountDownLatch(1);
    doAnswer(
            invocation -> {
              latch.countDown();
              return null;
            })
        .when(outputIrc)
        .joinChannel(DEFAULT_CHANNEL_NAME);

    mockTaskService();
    instance.connectionStateProperty().set(ConnectionState.CONNECTED);

    String md5Password = Hashing.md5().hashString(password, StandardCharsets.UTF_8).toString();
    verify(outputIrc).message("NICKSERV", String.format("IDENTIFY %s", md5Password));

    assertTrue("Channel has not been joined within timeout", latch.await(TIMEOUT, TIMEOUT_UNIT));
  }
  @Ignore("hangs when run with other tests")
  @Test
  public void testJoinChannel() throws Exception {
    CountDownLatch connectedLatch = new CountDownLatch(1);
    String channelToJoin = "#anotherChannel";
    doAnswer(
            invocation -> {
              connectedLatch.countDown();
              return null;
            })
        .when(outputIrc)
        .joinChannel(DEFAULT_CHANNEL_NAME);

    when(taskService.submitTask(any())).thenReturn(CompletableFuture.completedFuture(null));

    instance.connect();
    instance.connectionStateProperty().set(ConnectionState.CONNECTED);

    assertTrue(connectedLatch.await(TIMEOUT, TIMEOUT_UNIT));

    instance.joinChannel(channelToJoin);

    verify(outputIrc).joinChannel(DEFAULT_CHANNEL_NAME);
    verify(outputIrc).joinChannel(channelToJoin);
  }
  @Test
  public void testAddOnPrivateChatMessageListener() throws Exception {
    CompletableFuture<String> usernameFuture = new CompletableFuture<>();
    CompletableFuture<ChatMessage> chatMessageFuture = new CompletableFuture<>();
    instance.addOnPrivateChatMessageListener(
        (username, chatMessage) -> {
          usernameFuture.complete(username);
          chatMessageFuture.complete(chatMessage);
        });

    String message = "private message";

    User user = mock(User.class);
    when(user.getNick()).thenReturn(chatUser1.getUsername());

    Channel channel = mock(Channel.class);
    when(channel.getName()).thenReturn(DEFAULT_CHANNEL_NAME);

    UserHostmask userHostMask = mock(UserHostmask.class);
    instance.onEvent(new PrivateMessageEvent(pircBotX, userHostMask, user, message));

    assertThat(chatMessageFuture.get().getMessage(), is(message));
    assertThat(chatMessageFuture.get().getUsername(), is(chatUser1.getUsername()));
    assertThat(
        chatMessageFuture.get().getTime(),
        is(greaterThan(Instant.ofEpochMilli(System.currentTimeMillis() - 1000))));
    assertThat(chatMessageFuture.get().isAction(), is(false));
  }
  @Test
  public void testOnDisconnected() throws Exception {
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);
    assertThat(instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME).values(), hasSize(1));

    instance.connectionStateProperty().set(ConnectionState.DISCONNECTED);

    assertThat(instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME).values(), empty());
  }
  @Test
  public void testAddOnChatUserQuitListener() throws Exception {
    CompletableFuture<Boolean> quitFuture = new CompletableFuture<>();
    instance.addOnChatUserQuitListener((username) -> quitFuture.complete(true));

    instance.onEvent(new QuitEvent(pircBotX, daoSnapshot, userHostMask, userSnapshot, "reason"));

    assertThat(quitFuture.get(TIMEOUT, TIMEOUT_UNIT), is(true));
  }
  @Test
  public void testGetChatUsersForChannelTwoUsersInSameChannel() throws Exception {
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser2);

    ObservableMap<String, ChatUser> chatUsersForDefaultChannel =
        instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME);

    assertThat(chatUsersForDefaultChannel.values(), hasSize(2));
    assertThat(chatUsersForDefaultChannel.values(), containsInAnyOrder(chatUser1, chatUser2));
  }
  @Test
  public void testAddChannelUserListListener() throws Exception {
    @SuppressWarnings("unchecked")
    MapChangeListener<String, ChatUser> listener = mock(MapChangeListener.class);

    instance.addChannelUserListListener(DEFAULT_CHANNEL_NAME, listener);
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser2);

    verify(listener, times(2)).onChanged(any());
  }
  @Test
  public void testCreateOrGetChatUserUserObjectPopulatedMap() throws Exception {
    when(chatPrefs.getChatColorMode()).thenReturn(chatColorMode.get());
    when(chatPrefs.getUserToColor()).thenReturn(userToColorProperty);

    ChatUser addedUser = instance.createOrGetChatUser("chatUser1");
    ChatUser returnedUser = instance.createOrGetChatUser("chatUser1");

    assert returnedUser == addedUser;
    assertEquals(returnedUser, addedUser);
  }
  @Test
  public void testLeaveChannel() throws Exception {
    OutputChannel outputChannel = mock(OutputChannel.class);

    when(userChannelDao.getChannel(DEFAULT_CHANNEL_NAME)).thenReturn(defaultChannel);
    when(defaultChannel.send()).thenReturn(outputChannel);

    instance.connect();
    instance.leaveChannel(DEFAULT_CHANNEL_NAME);

    verify(outputChannel).part();
  }
  @Test
  public void testOnChatUserQuit() throws Exception {
    ObservableMap<String, ChatUser> usersForChannel =
        instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME);
    assertThat(usersForChannel.values(), empty());

    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser2);
    instance.onChatUserQuit(chatUser1.getUsername());

    assertThat(usersForChannel.values(), hasSize(1));
    assertThat(usersForChannel.get(chatUser2.getUsername()), sameInstance(chatUser2));
  }
  @Test
  public void testGetChatUsersForChannelTwoUsersInDifferentChannel() throws Exception {
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);
    instance.onUserJoinedChannel(OTHER_CHANNEL_NAME, chatUser2);

    ObservableMap<String, ChatUser> chatUsersForDefaultChannel =
        instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME);
    ObservableMap<String, ChatUser> chatUsersForOtherChannel =
        instance.getChatUsersForChannel(OTHER_CHANNEL_NAME);

    assertThat(chatUsersForDefaultChannel.values(), hasSize(1));
    assertThat(chatUsersForDefaultChannel.values().iterator().next(), sameInstance(chatUser1));
    assertThat(chatUsersForOtherChannel.values(), hasSize(1));
    assertThat(chatUsersForOtherChannel.values().iterator().next(), sameInstance(chatUser2));
  }
  @Test
  public void testOnModeratorSet() throws Exception {
    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);
    ObservableSet<String> moderatorInChannels =
        instance
            .getChatUsersForChannel(DEFAULT_CHANNEL_NAME)
            .get(chatUser1.getUsername())
            .getModeratorInChannels();

    assertThat(moderatorInChannels, empty());

    instance.onModeratorSet(DEFAULT_CHANNEL_NAME, chatUser1.getUsername());

    assertThat(moderatorInChannels, contains(DEFAULT_CHANNEL_NAME));
  }
  @Test
  public void testReconnect() throws Exception {
    CompletableFuture<Boolean> firstStartFuture = new CompletableFuture<>();
    CompletableFuture<Boolean> secondStartFuture = new CompletableFuture<>();
    doAnswer(
            invocation -> {
              if (!firstStartFuture.isDone()) {
                firstStartFuture.complete(true);
                throw new IOException("test exception");
              }

              secondStartFuture.complete(true);
              botShutdownLatch.await();
              return null;
            })
        .when(pircBotX)
        .startBot();

    instance.connect();
    firstStartFuture.get(TIMEOUT, TIMEOUT_UNIT);

    secondStartFuture.get(TIMEOUT, TIMEOUT_UNIT);

    verify(pircBotX, times(2)).startBot();
  }
  @Test
  public void testOnChatUserList() throws Exception {
    ObservableMap<String, ChatUser> usersForChannel =
        instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME);
    assertThat(usersForChannel.values(), empty());

    Map<String, ChatUser> users = new HashMap<>();
    users.put(chatUser1.getUsername(), chatUser1);
    users.put(chatUser2.getUsername(), chatUser2);

    instance.onChatUserList(DEFAULT_CHANNEL_NAME, users);

    assertThat(usersForChannel.values(), hasSize(2));
    assertThat(usersForChannel.get(chatUser1.getUsername()), sameInstance(chatUser1));
    assertThat(usersForChannel.get(chatUser2.getUsername()), sameInstance(chatUser2));
  }
  @Test
  public void testSendActionInBackground() throws Exception {
    instance.connect();

    String action = "test action";

    when(taskService.submitTask(any())).thenReturn(CompletableFuture.completedFuture(action));
    mockTaskService();

    CompletableFuture<String> future =
        instance.sendActionInBackground(DEFAULT_CHANNEL_NAME, action);

    verify(pircBotX).sendIRC();
    verify(outputIrc).action(DEFAULT_CHANNEL_NAME, action);
    assertThat(future.get(TIMEOUT, TIMEOUT_UNIT), is(action));
  }
  @Test
  @SuppressWarnings("unchecked")
  public void testSendMessageInBackground() throws Exception {
    instance.connect();

    String message = "test message";

    mockTaskService();

    CompletableFuture<String> future =
        instance.sendMessageInBackground(DEFAULT_CHANNEL_NAME, message);

    assertThat(future.get(TIMEOUT, TIMEOUT_UNIT), is(message));
    verify(pircBotX).sendIRC();
    verify(outputIrc).message(DEFAULT_CHANNEL_NAME, message);
  }
  @Test
  @SuppressWarnings("unchecked")
  public void testAddOnJoinChannelsRequestListener() throws Exception {
    Consumer<List<String>> listener = mock(Consumer.class);

    instance.addOnJoinChannelsRequestListener(listener);

    verify(fafService).addOnMessageListener(eq(SocialMessage.class), any());
  }
  @Test
  public void testAddOnChatUserJoinedChannelListener() throws Exception {
    when(chatPrefs.getChatColorMode()).thenReturn(chatColorMode.get());
    when(chatPrefs.getUserToColor()).thenReturn(userToColorProperty);

    CompletableFuture<String> channelNameFuture = new CompletableFuture<>();
    CompletableFuture<ChatUser> userFuture = new CompletableFuture<>();
    instance.addOnChatUserJoinedChannelListener(
        (channelName, chatUser) -> {
          channelNameFuture.complete(channelName);
          userFuture.complete(chatUser);
        });

    instance.onEvent(new JoinEvent(pircBotX, defaultChannel, userHostMask, user1));

    assertThat(channelNameFuture.get(TIMEOUT, TIMEOUT_UNIT), is(DEFAULT_CHANNEL_NAME));
    assertThat(userFuture.get(TIMEOUT, TIMEOUT_UNIT), is(chatUser1));
  }
  @Test
  public void testAddOnModeratorSetListener() throws Exception {

    instance.onUserJoinedChannel(DEFAULT_CHANNEL_NAME, chatUser1);

    ObservableSet<String> moderatorInChannels =
        instance
            .getChatUsersForChannel(DEFAULT_CHANNEL_NAME)
            .get(chatUser1.getUsername())
            .getModeratorInChannels();

    assertThat(moderatorInChannels, empty());

    instance.onModeratorSet(DEFAULT_CHANNEL_NAME, chatUser1.getUsername());

    assertThat(moderatorInChannels, hasSize(1));
    assertThat(moderatorInChannels.iterator().next(), is(DEFAULT_CHANNEL_NAME));
  }
  @Test
  public void testAddOnChatDisconnectedListener() throws Exception {
    CompletableFuture<Void> onChatDisconnectedFuture = new CompletableFuture<>();
    instance
        .connectionStateProperty()
        .addListener(
            (observable, oldValue, newValue) -> {
              switch (newValue) {
                case DISCONNECTED:
                  onChatDisconnectedFuture.complete(null);
                  break;
              }
            });

    instance.onEvent(new DisconnectEvent(pircBotX, daoSnapshot, null));

    onChatDisconnectedFuture.get(TIMEOUT, TIMEOUT_UNIT);
  }
  @Test
  public void testCreateOrGetChatUserStringEmptyMap() throws Exception {
    when(chatPrefs.getChatColorMode()).thenReturn(chatColorMode.get());
    when(chatPrefs.getUserToColor()).thenReturn(userToColorProperty);

    ChatUser returnedUser = instance.createOrGetChatUser("chatUser1");

    assert returnedUser != chatUser1;
    assertEquals(returnedUser, chatUser1);
  }
  @Test
  public void testAddOnChatUserLeftChannelListener() throws Exception {
    CompletableFuture<String> usernameFuture = new CompletableFuture<>();
    CompletableFuture<String> channelNameFuture = new CompletableFuture<>();
    instance.addOnChatUserLeftChannelListener(
        (username, channelName) -> {
          usernameFuture.complete(username);
          channelNameFuture.complete(channelName);
        });

    when(channelSnapshot.getName()).thenReturn(DEFAULT_CHANNEL_NAME);
    when(userSnapshot.getNick()).thenReturn(chatUser1.getUsername());

    String reason = "Part reason";
    instance.onEvent(
        new PartEvent(pircBotX, daoSnapshot, channelSnapshot, userHostMask, userSnapshot, reason));

    assertThat(channelNameFuture.get(TIMEOUT, TIMEOUT_UNIT), is(DEFAULT_CHANNEL_NAME));
    assertThat(usernameFuture.get(TIMEOUT, TIMEOUT_UNIT), is(chatUser1.getUsername()));
  }
  @Test
  public void testAddOnUserListListener() throws Exception {
    CompletableFuture<String> channelNameFuture = new CompletableFuture<>();
    CompletableFuture<Map<String, ChatUser>> usersFuture = new CompletableFuture<>();
    instance.addOnUserListListener(
        (channelName, users) -> {
          channelNameFuture.complete(channelName);
          usersFuture.complete(users);
        });
    when(chatPrefs.getChatColorMode()).thenReturn(chatColorMode.get());
    when(chatPrefs.getUserToColor()).thenReturn(userToColorProperty);

    when(user2.compareTo(user1)).thenReturn(1);
    ImmutableSortedSet<User> users = ImmutableSortedSet.of(user1, user2);
    instance.onEvent(new UserListEvent(pircBotX, defaultChannel, users, true));

    assertThat(channelNameFuture.get(TIMEOUT, TIMEOUT_UNIT), is(DEFAULT_CHANNEL_NAME));

    Map<String, ChatUser> userMap = usersFuture.get(TIMEOUT, TIMEOUT_UNIT);
    assertThat(userMap.values(), hasSize(2));
    assertThat(userMap.get(chatUser1.getUsername()), is(chatUser1));
    assertThat(userMap.get(chatUser2.getUsername()), is(chatUser2));
  }
  @Test
  public void testAddOnChatConnectedListener() throws Exception {
    CompletableFuture<Boolean> onChatConnectedFuture = new CompletableFuture<>();

    instance
        .connectionStateProperty()
        .addListener(
            (observable, oldValue, newValue) -> {
              switch (newValue) {
                case CONNECTED:
                  onChatConnectedFuture.complete(null);
                  break;
              }
            });

    String password = "******";
    when(userService.getPassword()).thenReturn(password);

    mockTaskService();

    instance.onEvent(new ConnectEvent(pircBotX));

    assertThat(onChatConnectedFuture.get(TIMEOUT, TIMEOUT_UNIT), is(nullValue()));
  }
  @Test
  @SuppressWarnings("unchecked")
  public void testConnect() throws Exception {
    ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);

    instance.connect();
    botStartedFuture.get(TIMEOUT, TIMEOUT_UNIT);

    verify(pircBotX).startBot();
    verify(pircBotXFactory).createPircBotX(captor.capture());
    Configuration configuration = captor.getValue();

    assertThat(configuration.getName(), is(CHAT_USER_NAME));
    assertThat(configuration.getLogin(), is(CHAT_USER_NAME));
    assertThat(configuration.getRealName(), is(CHAT_USER_NAME));
    assertThat(
        configuration.getServers().get(0).getHostname(), is(LOOPBACK_ADDRESS.getHostAddress()));
    assertThat(configuration.getServers().get(0).getPort(), is(IRC_SERVER_PORT));
  }
 @After
 public void tearDown() {
   instance.close();
   botShutdownLatch.countDown();
 }
 @Test
 public void testGetChatUsersForChannelEmpty() throws Exception {
   ObservableMap<String, ChatUser> chatUsersForChannel =
       instance.getChatUsersForChannel(DEFAULT_CHANNEL_NAME);
   assertThat(chatUsersForChannel.values(), empty());
 }
 @Test
 public void testOnModeratorSetUserNotInChannelDoesntThrowException() throws Exception {
   instance.onModeratorSet(DEFAULT_CHANNEL_NAME, chatUser1.getUsername());
 }
 @Test
 public void testClose() {
   instance.close();
 }
 @Test
 public void testIsDefaultChannel() throws Exception {
   assertTrue(instance.isDefaultChannel(DEFAULT_CHANNEL_NAME));
 }