@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 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 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 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 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 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 testOnModeratorSetUserNotInChannelDoesntThrowException() throws Exception {
   instance.onModeratorSet(DEFAULT_CHANNEL_NAME, chatUser1.getUsername());
 }
  @Before
  public void setUp() throws Exception {
    instance = new PircBotXChatService();
    instance.fafService = fafService;
    instance.userService = userService;
    instance.taskService = taskService;
    instance.i18n = i18n;
    instance.pircBotXFactory = pircBotXFactory;
    instance.preferencesService = preferencesService;
    instance.executorService = executorService;

    chatUser1 = new ChatUser("chatUser1", null);
    chatUser2 = new ChatUser("chatUser2", null);
    loggedInProperty = new SimpleBooleanProperty();

    botShutdownLatch = new CountDownLatch(1);

    userToColorProperty = new SimpleMapProperty<>(FXCollections.observableHashMap());
    chatColorMode = new SimpleObjectProperty<>(CUSTOM);

    when(userService.getUsername()).thenReturn(CHAT_USER_NAME);
    when(userService.getPassword()).thenReturn(CHAT_PASSWORD);
    when(userService.loggedInProperty()).thenReturn(loggedInProperty);

    when(user1.getNick()).thenReturn(chatUser1.getUsername());
    when(user1.getChannels()).thenReturn(ImmutableSortedSet.of(defaultChannel));
    when(user1.getUserLevels(defaultChannel)).thenReturn(ImmutableSortedSet.of(UserLevel.VOICE));

    when(user2.getNick()).thenReturn(chatUser2.getUsername());
    when(user2.getChannels()).thenReturn(ImmutableSortedSet.of(defaultChannel));
    when(user2.getUserLevels(defaultChannel)).thenReturn(ImmutableSortedSet.of(UserLevel.VOICE));

    when(defaultChannel.getName()).thenReturn(DEFAULT_CHANNEL_NAME);
    when(pircBotX.getConfiguration()).thenReturn(configuration);
    when(pircBotX.sendIRC()).thenReturn(outputIrc);
    when(pircBotX.getUserChannelDao()).thenReturn(userChannelDao);

    doAnswer(
            invocation -> {
              CompletableFuture<Object> future = new CompletableFuture<>();
              WaitForAsyncUtils.async(
                  () -> {
                    invocation.getArgumentAt(0, Task.class).run();
                    future.complete(null);
                  });
              return future;
            })
        .when(executorService)
        .submit(any(Task.class));

    botStartedFuture = new CompletableFuture<>();
    doAnswer(
            invocation -> {
              botStartedFuture.complete(true);
              botShutdownLatch.await();
              return null;
            })
        .when(pircBotX)
        .startBot();

    when(pircBotXFactory.createPircBotX(any())).thenReturn(pircBotX);
    when(configuration.getListenerManager()).thenReturn(listenerManager);

    instance.ircHost = LOOPBACK_ADDRESS.getHostAddress();
    instance.ircPort = IRC_SERVER_PORT;
    instance.defaultChannelName = DEFAULT_CHANNEL_NAME;
    instance.reconnectDelay = 100;

    when(preferencesService.getPreferences()).thenReturn(preferences);
    when(preferences.getChat()).thenReturn(chatPrefs);

    when(chatPrefs.userToColorProperty()).thenReturn(userToColorProperty);
    when(chatPrefs.chatColorModeProperty()).thenReturn(chatColorMode);

    instance.postConstruct();
  }