### Question:
RaftLogShipper { public synchronized void onMismatch( long lastRemoteAppendIndex, LeaderContext leaderContext ) { switch ( mode ) { case MISMATCH: long logIndex = max( min( lastSentIndex - 1, lastRemoteAppendIndex ), MIN_INDEX ); sendEmpty( logIndex, leaderContext ); break; case PIPELINE: case CATCHUP: log.info( "%s: mismatch in mode %s from follower %s, moving to MISMATCH mode", statusAsString(), mode, follower ); mode = Mode.MISMATCH; sendEmpty( lastSentIndex, leaderContext ); break; default: throw new IllegalStateException( "Unknown mode: " + mode ); } lastLeaderContext = leaderContext; } RaftLogShipper( Outbound<MemberId, RaftMessages.RaftMessage> outbound, LogProvider logProvider,
                    ReadableRaftLog raftLog, Clock clock, TimerService timerService,
                    MemberId leader, MemberId follower, long leaderTerm, long leaderCommit, long retryTimeMillis,
                    int catchupBatchSize, int maxAllowedShippingLag, InFlightCache inFlightCache ); Object identity(); synchronized void start(); synchronized void stop(); synchronized void onMismatch( long lastRemoteAppendIndex, LeaderContext leaderContext ); synchronized void onMatch( long newMatchIndex, LeaderContext leaderContext ); synchronized void onNewEntries( long prevLogIndex, long prevLogTerm, RaftLogEntry[] newLogEntries,
                                           LeaderContext leaderContext ); synchronized void onCommitUpdate( LeaderContext leaderContext );  }### Answer:
@Test public void shouldSendPreviousEntryOnMismatch() throws Throwable { raftLog.append( entry0 ); raftLog.append( entry1 ); raftLog.append( entry2 ); startLogShipper(); outbound.clear(); logShipper.onMismatch( 0, new LeaderContext( 0, 0 ) ); RaftMessages.AppendEntries.Request expected = new RaftMessages.AppendEntries.Request( leader, leaderTerm, 0, 0, RaftLogEntry.empty, leaderCommit ); assertThat( outbound.sentTo( follower ), hasItem( expected ) ); }
@Test public void shouldKeepSendingFirstEntryAfterSeveralMismatches() throws Throwable { raftLog.append( entry0 ); raftLog.append( entry1 ); startLogShipper(); logShipper.onMismatch( 0, new LeaderContext( 0, 0 ) ); logShipper.onMismatch( 0, new LeaderContext( 0, 0 ) ); outbound.clear(); logShipper.onMismatch( 0, new LeaderContext( 0, 0 ) ); RaftMessages.AppendEntries.Request expected = new RaftMessages.AppendEntries.Request( leader, leaderTerm, 0, 0, RaftLogEntry.empty, leaderCommit ); assertThat( outbound.sentTo( follower ), hasItem( expected ) ); }
@Test public void shouldSendMostRecentlyAvailableEntryIfPruningHappened() throws IOException { raftLog.append( entry0 ); raftLog.append( entry1 ); raftLog.append( entry2 ); raftLog.append( entry3 ); startLogShipper(); raftLog.prune( 2 ); outbound.clear(); logShipper.onMismatch( 0, new LeaderContext( 0, 0 ) ); RaftMessages.AppendEntries.Request expected = new RaftMessages.AppendEntries.Request( leader, leaderTerm, 2, entry2.term(), RaftLogEntry.empty, leaderCommit ); assertThat( outbound.sentTo( follower ), hasItem( expected ) ); }