### Question:
NodeImpl implements Node { @Override @Nonnull public GroupConfigChangeTaskReference removeNode(@Nonnull NodeId id) { Preconditions.checkNotNull(id); ensureLeader(); GroupConfigChangeTaskResult result = awaitPreviousGroupConfigChangeTask(); if (result != null) { return new FixedResultGroupConfigTaskReference(result); } synchronized (this) { if (!groupConfigChangeTaskHolder.isEmpty()) { throw new IllegalStateException("group config change concurrently"); } RemoveNodeTask task = new RemoveNodeTask(groupConfigChangeTaskContext, id); Future<GroupConfigChangeTaskResult> future = context.groupConfigChangeTaskExecutor().submit(task); GroupConfigChangeTaskReference reference = new FutureGroupConfigChangeTaskReference(future); groupConfigChangeTaskHolder = new GroupConfigChangeTaskHolder(task, reference); return reference; } } NodeImpl(NodeContext context); @Override synchronized void registerStateMachine(@Nonnull StateMachine stateMachine); @Override @Nonnull RoleNameAndLeaderId getRoleNameAndLeaderId(); @Override void addNodeRoleListener(@Nonnull NodeRoleListener listener); @Override synchronized void start(); @Override void appendLog(@Nonnull byte[] commandBytes); @Override @Nonnull GroupConfigChangeTaskReference addNode(@Nonnull NodeEndpoint endpoint); @Override @Nonnull GroupConfigChangeTaskReference removeNode(@Nonnull NodeId id); @Subscribe void onReceiveRequestVoteRpc(RequestVoteRpcMessage rpcMessage); @Subscribe void onReceiveRequestVoteResult(RequestVoteResult result); @Subscribe void onReceiveAppendEntriesRpc(AppendEntriesRpcMessage rpcMessage); @Subscribe void onReceiveAppendEntriesResult(AppendEntriesResultMessage resultMessage); @Subscribe void onReceiveInstallSnapshotRpc(InstallSnapshotRpcMessage rpcMessage); @Subscribe void onReceiveInstallSnapshotResult(InstallSnapshotResultMessage resultMessage); @Subscribe void onGroupConfigEntryFromLeaderAppend(GroupConfigEntryFromLeaderAppendEvent event); @Subscribe void onGroupConfigEntryCommitted(GroupConfigEntryCommittedEvent event); @Subscribe void onGroupConfigEntryBatchRemoved(GroupConfigEntryBatchRemovedEvent event); @Subscribe void onGenerateSnapshot(SnapshotGenerateEvent event); @Subscribe void onReceiveDeadEvent(DeadEvent deadEvent); @Override synchronized void stop();  }### Answer:
@Test public void testRemoveNode() throws Throwable { WaitableConnector connector = new WaitableConnector(); NodeImpl node = (NodeImpl) newNodeBuilder( NodeId.of("A"), new NodeEndpoint("A", "localhost", 2333), new NodeEndpoint("B", "localhost", 2334), new NodeEndpoint("C", "localhost", 2335)) .setConnector(connector) .setTaskExecutor(taskExecutor) .setGroupConfigChangeTaskExecutor(groupConfigChangeTaskExecutor) .build(); node.start(); node.electionTimeout(); node.processRequestVoteResult(new RequestVoteResult(1, true)).get(); GroupConfigChangeTaskReference reference = node.removeNode(NodeId.of("B")); connector.awaitAppendEntriesRpc(); node.onReceiveAppendEntriesResult(new AppendEntriesResultMessage( new AppendEntriesResult("", 1, true), NodeId.of("C"), createAppendEntriesRpc(2))); Assert.assertEquals(GroupConfigChangeTaskResult.OK, reference.getResult(1000L)); checkWithinTaskExecutor(node, () -> { Assert.assertEquals(2, node.getContext().group().getCountOfMajor()); Assert.assertNull(node.getContext().group().getMember(NodeId.of("B"))); }); }