@Test
  public void markAsUndeleteLifeVersion() throws Exception {
    assumeTrue(isLogSegmented && addUndeletes);
    short lifeVersion = 2;
    IndexEntry entry = state.addPutEntries(1, PUT_RECORD_SIZE, 0, lifeVersion).get(0);
    MockId id = (MockId) entry.getKey();
    // ID already expired, undelete it with higher life version, should succeed
    state.appendToLog(UNDELETE_RECORD_SIZE);
    FileSpan fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    long operationTime = state.time.milliseconds();
    state.index.markAsUndeleted(id, fileSpan, null, operationTime, (short) (lifeVersion + 1));
    IndexValue value = state.index.findKey(id);
    checkUndelete(value, false, (short) (lifeVersion + 1), id.getAccountId(), id.getContainerId(), UNDELETE_RECORD_SIZE,
        fileSpan.getStartOffset(), entry.getValue().getExpiresAtMs(), operationTime);

    entry = state.addPutEntries(1, PUT_RECORD_SIZE, 0, lifeVersion).get(0);
    id = (MockId) entry.getKey();
    state.appendToLog(UNDELETE_RECORD_SIZE);
    fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    // Undelete with -1 as lifeVersion, since its's not deleted, should fail
    try {
      state.index.markAsUndeleted(id, fileSpan, null, state.time.milliseconds(),
          MessageInfo.LIFE_VERSION_FROM_FRONTEND);
      fail("should not succeed");
    } catch (StoreException e) {
      assertEquals(StoreErrorCodes.IDNotDeleted, e.getErrorCode());
    }

    // Undelete with smaller lifeVersion, should fail
    fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    try {
      state.index.markAsUndeleted(id, fileSpan, null, state.time.milliseconds(), (short) (lifeVersion - 1));
      fail("should not succeed");
    } catch (StoreException e) {
      assertEquals(StoreErrorCodes.LifeVersionConflict, e.getErrorCode());
    }

    // Undelete with same lifeVersion, should fail
    fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    try {
      state.index.markAsUndeleted(id, fileSpan, null, state.time.milliseconds(), lifeVersion);
      fail("should not succeed");
    } catch (StoreException e) {
      assertEquals(StoreErrorCodes.LifeVersionConflict, e.getErrorCode());
    }

    // Undelete with larger lifeVersion, should succeed
    fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    operationTime = state.time.milliseconds();
    state.index.markAsUndeleted(id, fileSpan, null, operationTime, (short) (lifeVersion + 1));
    value = state.index.findKey(id);
    checkUndelete(value, false, (short) (lifeVersion + 1), id.getAccountId(), id.getContainerId(), UNDELETE_RECORD_SIZE,
        fileSpan.getStartOffset(), entry.getValue().getExpiresAtMs(), operationTime);

    state.appendToLog(UNDELETE_RECORD_SIZE);
    // Undelete with same lifeVersion, should fail.
    fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    try {
      state.index.markAsUndeleted(id, fileSpan, null, operationTime, (short) (lifeVersion + 1));
      fail("should not succeed");
    } catch (StoreException e) {
      assertEquals(StoreErrorCodes.IDUndeleted, e.getErrorCode());
    }

    // Undelete with even larger lifeVersion, should succeed.
    fileSpan = state.log.getFileSpanForMessage(state.index.getCurrentEndOffset(), UNDELETE_RECORD_SIZE);
    operationTime = state.time.milliseconds();
    state.index.markAsUndeleted(id, fileSpan, null, operationTime, (short) (lifeVersion + 4));
    value = state.index.findKey(id);
    checkUndelete(value, false, (short) (lifeVersion + 4), id.getAccountId(), id.getContainerId(), UNDELETE_RECORD_SIZE,
        fileSpan.getStartOffset(), entry.getValue().getExpiresAtMs(), operationTime);
  }
Test {@link PersistentIndex#markAsUndeleted(StoreKey, FileSpan, MessageInfo, long, short)} when the lifeVersion passed
into the method is not {@link MessageInfo#LIFE_VERSION_FROM_FRONTEND}.
@throws Exception
ambry-store/src/test/java/com/github/ambry/store/IndexTest.java