From 77d37889d8e3b08055a7639db5b3c01465ab2750 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Wed, 19 Aug 2015 14:10:08 +0000
Subject: [PATCH] OPENDJ-1192 Modify request replay failures

---
 opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalMultipleTest.java |  436 +++++++++++++++++++++++++++++
 /dev/null                                                                                               |  121 --------
 opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalSingleTest.java   |  327 +++++++++++++++++++++
 3 files changed, 763 insertions(+), 121 deletions(-)

diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalMultipleTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalMultipleTest.java
new file mode 100644
index 0000000..01916ce
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalMultipleTest.java
@@ -0,0 +1,436 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2015 ForgeRock AS.
+ */
+package org.opends.server.replication.plugin;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.forgerock.opendj.ldap.ModificationType.*;
+import static org.mockito.Mockito.*;
+import static org.testng.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ModificationType;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.CSN;
+import org.opends.server.replication.common.CSNGenerator;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.opends.server.util.TimeThread;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/** Test AttrHistoricalMultiple. */
+@SuppressWarnings("javadoc")
+public class AttrHistoricalMultipleTest extends ReplicationTestCase
+{
+  private static enum E
+  {
+    CONFLICT(true), CONFLICT_BUT_SHOULD_NOT_BE(true), NO_CONFLICT(false);
+
+    private final boolean expectedConflictStatus;
+
+    private E(boolean expectedResultForReplay)
+    {
+      this.expectedConflictStatus = expectedResultForReplay;
+    }
+
+    private boolean getExpectedResult()
+    {
+      return this.expectedConflictStatus;
+    }
+  }
+
+  private CSNGenerator csnGen = new CSNGenerator(1025, System.currentTimeMillis());
+  private AttrHistoricalMultiple attrHist;
+  private CSN csn;
+  private Entry entry;
+  /** Avoids declaring the variable in the tests. */
+  private Modification mod;
+
+  @BeforeMethod
+  public void localSetUp() throws Exception
+  {
+    attrHist = new AttrHistoricalMultiple();
+    csn = csnGen.newCSN();
+    entry = new Entry(null, null, null, null);
+  }
+
+  @AfterMethod
+  public void localTearDown() throws Exception
+  {
+    attrHist = null;
+    csn = null;
+  }
+
+  /** Build some data for the AttrInfo test below. */
+  @DataProvider(name = "attrInfo")
+  public Object[][] createData()
+  {
+    ByteString att1 = ByteString.valueOf("string");
+    ByteString att2 = ByteString.valueOf("value");
+    ByteString att3 = ByteString.valueOf("again");
+
+    CSN del1 = new CSN(1,  0,  1);
+    CSN del2 = new CSN(1,  1,  1);
+    CSN del3 = new CSN(1,  0,  2);
+
+    CSN upd1 = new CSN(TimeThread.getTime(), 123, 45);
+    CSN upd2 = new CSN(TimeThread.getTime() + 1000, 123,  45);
+    CSN upd3 = new CSN(TimeThread.getTime(), 321, 54);
+
+    return new Object[][]
+    {
+    { att1, del1, upd1 },
+    { att2, del2, upd2 },
+    { att3, del3, upd3 },
+    { att3, upd3, upd3 } };
+  }
+
+  /** Create a AttrInfo and check the methods. */
+  @Test(dataProvider = "attrInfo")
+  public void attrInfo(ByteString att, CSN deleteTime, CSN updateTime) throws Exception
+  {
+    // Create an empty AttrInfo
+    AttrHistoricalMultiple attrInfo1 = new AttrHistoricalMultiple();
+
+    // Check
+    attrInfo1.add(att, updateTime);
+    Set<AttrValueHistorical> values1 = attrInfo1.getValuesHistorical();
+    assertEquals(values1.size(), 1);
+    AttrValueHistorical valueInfo1 = new AttrValueHistorical(att, updateTime, null);
+    assertTrue(values1.contains(valueInfo1));
+
+    // Check constructor with parameter
+    AttrValueHistorical valueInfo2 = new AttrValueHistorical(att, updateTime, deleteTime);
+    AttrHistoricalMultiple attrInfo2 = new AttrHistoricalMultiple(
+        deleteTime, updateTime, Collections.singletonMap(valueInfo2, valueInfo2));
+
+    // Check equality
+    //assertTrue(attrInfo1.getDeleteTime().compareTo(attrInfo2.getDeleteTime())==0);
+
+    //  Check constructor with time parameter and not Value
+    AttrHistoricalMultiple attrInfo3 = new AttrHistoricalMultiple(deleteTime, updateTime, null);
+    attrInfo3.add(att, updateTime);
+    Set<AttrValueHistorical> values3 = attrInfo3.getValuesHistorical();
+    assertEquals(values3.size(), 1);
+    valueInfo1 = new AttrValueHistorical(att, updateTime, null);
+    assertTrue(values3.contains(valueInfo1));
+
+    // Check duplicate
+    AttrHistoricalMultiple attrInfo4 = attrInfo3.duplicate();
+    Set<AttrValueHistorical> values4 = attrInfo4.getValuesHistorical();
+    assertEquals(attrInfo4.getDeleteTime().compareTo(attrInfo3.getDeleteTime()), 0);
+    assertEquals(values4.size(), values3.size());
+
+    // Check
+    attrInfo4.delete(att, updateTime);
+    assertEquals(attrInfo4.getValuesHistorical().size(), 1);
+
+    // Check
+    AttributeType type = DirectoryServer.getAttributeType("description");
+    attrInfo3.delete(Attributes.create(type, att), updateTime) ;
+    assertEquals(attrInfo3.getValuesHistorical().size(), 1);
+
+    // Check
+    attrInfo2.delete(updateTime);
+    assertEquals(attrInfo2.getValuesHistorical().size(), 0);
+  }
+
+  @Test
+  public void replay_addDeleteSameTime() throws Exception
+  {
+    mod = newModification(ADD, "X");
+    replayOperation(csn, entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(DELETE, "X");
+    replayOperation(csn, entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addThenAddThenOlderDelete() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[0], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(ADD, "Y");
+    replayOperation(t[2], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(DELETE, "Y");
+    replayOperationSuppressMod(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addThenDeleteNoValueThenOlderAdd() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    replay_addDeleteNoValue(t[0], t[2]);
+
+    mod = newModification(ADD, "Y");
+    replayOperationSuppressMod(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addThenDeleteWithValueThenOlderAdd() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[0], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(DELETE, "X");
+    replayOperation(t[2], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuppressMod(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addThenAdd() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[0], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuppressMod(t[1], entry, mod, E.CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenAdd() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[0], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(DELETE, "X");
+    replayOperation(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[1], entry, mod, E.CONFLICT);
+  }
+
+  @Test
+  public void replay_deleteNoPreviousHistory() throws Exception
+  {
+    mod = newModification(DELETE, "Y");
+    replayOperationSuppressMod(csn, entry, mod, E.CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenDelete() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[0], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(DELETE, "X");
+    replayOperation(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenOlderDelete() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    replay_addDeleteNoValue(t[0], t[2]);
+
+    mod = newModification(DELETE, "X");
+    replayOperationSuppressMod(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addDeleteNoValueSameTimeNotConflict() throws Exception
+  {
+    replay_addDeleteNoValue(csn, csn);
+  }
+
+  @Test
+  public void replay_addThenDeleteNoValue() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+    replay_addDeleteNoValue(t[0], t[1]);
+  }
+
+  private void replay_addDeleteNoValue(CSN tAdd, CSN tDel) throws Exception
+  {
+    mod = newModification(ADD, "X");
+    replayOperation(tAdd, entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(DELETE);
+    replayOperation(tDel, entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_replace() throws Exception
+  {
+    mod = newModification(REPLACE, "X");
+    replayOperation(csn, entry, mod, E.NO_CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenOlderReplace() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+
+    mod = newModification(ADD, "X");
+    replayOperation(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+
+    mod = newModification(REPLACE, "Y");
+    replayOperation(t[0], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenOlderReplace() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    replay_addDeleteNoValue(t[0], t[2]);
+
+    mod = newModification(REPLACE, "Y");
+    replayOperationSuppressMod(t[1], entry, mod, E.CONFLICT_BUT_SHOULD_NOT_BE);
+  }
+
+  private CSN[] newCSNs(int nb)
+  {
+    CSN[] results = new CSN[nb];
+    for (int i = 0; i < nb; i++)
+    {
+      results[i] = csnGen.newCSN();
+    }
+    return results;
+  }
+
+  private Modification newModification(ModificationType modType, String attrValue)
+  {
+    return new Modification(modType, Attributes.create("description", attrValue));
+  }
+
+  private Modification newModification(ModificationType modType)
+  {
+    return new Modification(modType, Attributes.empty("display"));
+  }
+
+  private void replayOperationSuppressMod(CSN csn, Entry entry, Modification mod, E conflictStatus)
+      throws Exception
+  {
+    Iterator<Modification> itMod = mock(Iterator.class);
+    replayOperation(itMod, csn, entry, mod, conflictStatus);
+    verifyModNotReplayed(itMod);
+  }
+
+  private void replayOperation(CSN csn, Entry entry, Modification mod, E conflictStatus) throws Exception
+  {
+    replayOperation(null, csn, entry, mod, conflictStatus);
+  }
+
+  private void replayOperation(Iterator<Modification> modsIterator, CSN csn, Entry entry, Modification mod,
+      E conflictStatus) throws Exception
+  {
+    boolean result = attrHist.replayOperation(modsIterator, csn, entry, mod);
+    assertEquals(result, conflictStatus.getExpectedResult(),
+        "Expected " + (conflictStatus == E.CONFLICT ? "a" : "no") + " conflict when applying " + mod + " to " + entry);
+    if (entry != null && conflictStatus != E.CONFLICT)
+    {
+      entry.applyModification(mod);
+      assertAttributeValues(entry, mod);
+    }
+  }
+
+  private void assertAttributeValues(Entry entry, Modification mod)
+  {
+    List<ByteString> actualValues = getValues(entry, mod);
+    List<ByteString> expectedValues = getValues(mod.getAttribute());
+    switch (mod.getModificationType().asEnum())
+    {
+    case ADD:
+      assertThat(actualValues).containsAll(expectedValues);
+      return;
+
+    case REPLACE:
+      assertThat(actualValues).isEqualTo(expectedValues);
+      return;
+
+    case DELETE:
+      if (expectedValues.isEmpty())
+      {
+        assertThat(actualValues).isEmpty();
+      }
+      else
+      {
+        assertThat(actualValues).doesNotContainAnyElementsOf(expectedValues);
+      }
+      return;
+
+    case INCREMENT:
+      return;
+    }
+  }
+
+  private List<ByteString> getValues(Entry entry, Modification mod)
+  {
+    List<Attribute> attributes = entry.getAttribute(mod.getAttribute().getAttributeType());
+    if (attributes != null)
+    {
+      assertThat(attributes).hasSize(1);
+      return getValues(attributes.get(0));
+    }
+    return Collections.emptyList();
+  }
+
+  private List<ByteString> getValues(Attribute attribute)
+  {
+    List<ByteString> results = new ArrayList<>();
+    for (ByteString value : attribute)
+    {
+      results.add(value);
+    }
+    return results;
+  }
+
+  private void verifyModNotReplayed(Iterator<Modification> it)
+  {
+    verify(it, times(1)).remove();
+    verify(it, only()).remove();
+  }
+}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalSingleTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalSingleTest.java
new file mode 100644
index 0000000..113c588
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrHistoricalSingleTest.java
@@ -0,0 +1,327 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2015 ForgeRock AS
+ */
+package org.opends.server.replication.plugin;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.forgerock.opendj.ldap.ModificationType.*;
+import static org.mockito.Mockito.*;
+import static org.testng.Assert.*;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ModificationType;
+import org.opends.server.replication.ReplicationTestCase;
+import org.opends.server.replication.common.CSN;
+import org.opends.server.replication.common.CSNGenerator;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.Attributes;
+import org.opends.server.types.Entry;
+import org.opends.server.types.Modification;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("javadoc")
+public class AttrHistoricalSingleTest extends ReplicationTestCase
+{
+  private static final boolean CONFLICT = true;
+  private static final boolean NO_CONFLICT = false;
+  private CSNGenerator csnGen = new CSNGenerator(1025, System.currentTimeMillis());
+  private AttrHistoricalSingle attrHist;
+  private CSN csn;
+  private Entry entry;
+  /** Avoids declaring the variable in the tests */
+  private Modification mod;
+
+  @BeforeMethod
+  public void localSetUp() throws Exception
+  {
+    attrHist = new AttrHistoricalSingle();
+    csn = csnGen.newCSN();
+    entry = new Entry(null, null, null, null);
+  }
+
+  @AfterMethod
+  public void localTearDown() throws Exception
+  {
+    attrHist = null;
+    csn = null;
+  }
+
+  @Test
+  public void replay_addDeleteSameTime() throws Exception
+  {
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(csn, entry, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE, "X");
+    replayOperationSuccess(csn, entry, mod, NO_CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenOlderAdd() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[0], entry, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE, "X");
+    replayOperationSuccess(t[2], entry, mod, NO_CONFLICT);
+
+    mod = newModification(ADD, "X");
+    replayOperationFailure(t[1], entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenAddThenOlderAdd() throws Exception
+  {
+    CSN[] t = newCSNs(4);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[0], entry, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE, "X");
+    replayOperationSuccess(t[1], entry, mod, NO_CONFLICT);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[3], entry, mod, NO_CONFLICT);
+
+    mod = newModification(ADD, "Y");
+    replayOperationSuccess(t[2], entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenDelete() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[0], entry, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE, "X");
+    replayOperationSuccess(t[1], entry, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE, "X");
+    replayOperationFailure(t[2], entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenOlderDelete() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[1], null, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE, "X");
+    replayOperationFailure(t[0], entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_deleteMissingAttribute() throws Exception
+  {
+    mod = newModification(DELETE, "X");
+    replayOperationFailure(csn, entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_deleteMissingAttributeValue() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[0], entry, mod, NO_CONFLICT);
+
+    Iterator<Modification> it = mock(Iterator.class);
+    mod = newModification(DELETE, "Y");
+    replayOperationFailure(t[1], entry, mod, CONFLICT);
+
+    verifyModNotReplayed(it);
+  }
+
+  /**
+   * TODO JNR this looks dubious, is it ever possible in the server?
+   * <p>
+   * Could multi-threading make this scenario possible?
+   * <p>
+   * Or is it due to {@link AttrHistoricalSingle#assign(HistAttrModificationKey, ByteString, CSN)} ?
+   */
+  @Test
+  public void replay_deleteValueThatDoesNotExistOnEntry() throws Exception
+  {
+    CSN[] t = newCSNs(2);
+
+    mod = newModification(ADD, "X");
+    entry.applyModification(mod);
+
+    mod = newModification(DELETE, "Y");
+    replayOperationFailure(t[1], entry, mod, CONFLICT);
+  }
+
+  /**
+   * TODO JNR this looks dubious, is it ever possible in the server?
+   * <p>
+   * Could multi-threading make this scenario possible?
+   * <p>
+   * Or is it due to {@link AttrHistoricalSingle#assign(HistAttrModificationKey, ByteString, CSN)} ?
+   */
+  @Test
+  public void replay_deleteDubious() throws Exception
+  {
+    HistoricalAttributeValue histAttrVal = new HistoricalAttributeValue("display:" + csn + ":add:X");
+    attrHist.assign(histAttrVal.getHistKey(), histAttrVal.getAttributeValue(), csn);
+    mod = newModification(ADD, "X");
+    entry.applyModification(mod);
+
+    mod = newModification(DELETE, "Y");
+    replayOperationFailure(csn, entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_replaceWithValue() throws Exception
+  {
+    mod = newModification(REPLACE, "X");
+    replayOperationSuccess(csn, null, mod, NO_CONFLICT);
+  }
+
+  @Test
+  public void replay_replaceNoValue() throws Exception
+  {
+    mod = newModification(REPLACE);
+    replayOperationSuccess(csn, null, mod, NO_CONFLICT);
+  }
+
+  @Test
+  public void replay_addThenDeleteThenOlderReplace() throws Exception
+  {
+    CSN[] t = newCSNs(3);
+
+    mod = newModification(ADD, "X");
+    replayOperationSuccess(t[0], entry, mod, NO_CONFLICT);
+
+    mod = newModification(DELETE);
+    replayOperationSuccess(t[2], entry, mod, NO_CONFLICT);
+
+    mod = newModification(REPLACE);
+    replayOperationFailure(t[1], entry, mod, CONFLICT);
+  }
+
+  @Test
+  public void replay_increment() throws Exception
+  {
+    mod = newModification(INCREMENT, "X");
+    replayOperationSuccess(csn, null, mod, NO_CONFLICT);
+  }
+
+
+  private CSN[] newCSNs(int nb)
+  {
+    CSN[] results = new CSN[nb];
+    for (int i = 0; i < nb; i++)
+    {
+      results[i] = csnGen.newCSN();
+    }
+    return results;
+  }
+
+  private Modification newModification(ModificationType modType, String attrValue)
+  {
+    return new Modification(modType, Attributes.create("display", attrValue));
+  }
+
+  private Modification newModification(ModificationType modType)
+  {
+    return new Modification(modType, Attributes.empty("display"));
+  }
+
+  private void replayOperationSuccess(CSN csn, Entry entry, Modification mod, boolean shouldConflict) throws Exception
+  {
+    replayOperation(null, csn, entry, mod, shouldConflict);
+  }
+
+  private void replayOperationFailure(CSN csn, Entry entry, Modification mod, boolean shouldConflict) throws Exception
+  {
+    Iterator<Modification> itMod = mock(Iterator.class);
+    replayOperation(itMod, csn, entry, mod, shouldConflict);
+    verifyModNotReplayed(itMod);
+  }
+
+  private void replayOperation(Iterator<Modification> modsIterator, CSN csn, Entry entry, Modification mod,
+      boolean shouldConflict) throws Exception
+  {
+    boolean result = attrHist.replayOperation(modsIterator, csn, entry, mod);
+    assertEquals(result, shouldConflict,
+        "Expected " + (shouldConflict ? "a" : "no") + " conflict when applying " + mod + " to " + entry);
+    if (entry != null && !shouldConflict)
+    {
+      entry.applyModification(mod);
+      assertAttributeValue(entry, mod);
+    }
+  }
+
+  private void assertAttributeValue(Entry entry, Modification mod)
+  {
+    ByteString actualValue = getActualValue(entry, mod);
+    switch (mod.getModificationType().asEnum())
+    {
+    case ADD:
+    case REPLACE:
+      ByteString expectedValue = uniqueValue(mod.getAttribute());
+      assertEquals(actualValue, expectedValue);
+      return;
+
+    case DELETE:
+      assertNull(actualValue);
+      return;
+    }
+  }
+
+  private ByteString getActualValue(Entry entry, Modification mod)
+  {
+    List<Attribute> attributes = entry.getAttribute(mod.getAttribute().getAttributeType());
+    if (attributes != null)
+    {
+      assertThat(attributes).hasSize(1);
+      return uniqueValue(attributes.get(0));
+    }
+    return null;
+  }
+
+  private ByteString uniqueValue(Attribute attribute)
+  {
+    assertThat(attribute).hasSize(1);
+    ByteString attrValue = attribute.iterator().next();
+    return attrValue;
+  }
+
+  private void verifyModNotReplayed(Iterator<Modification> it)
+  {
+    verify(it, times(1)).remove();
+    verify(it, only()).remove();
+  }
+}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrInfoTest.java b/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrInfoTest.java
deleted file mode 100644
index 98aae65..0000000
--- a/opendj-server-legacy/src/test/java/org/opends/server/replication/plugin/AttrInfoTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Copyright 2006-2010 Sun Microsystems, Inc.
- *      Portions Copyright 2013-2015 ForgeRock AS.
- */
-package org.opends.server.replication.plugin;
-
-import java.util.Collections;
-import java.util.Set;
-
-import org.forgerock.opendj.ldap.ByteString;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.replication.ReplicationTestCase;
-import org.opends.server.replication.common.CSN;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.Attributes;
-import org.opends.server.util.TimeThread;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import static org.testng.Assert.*;
-
-/** Test AttrInfo and AttrInfoWithOptions. */
-@SuppressWarnings("javadoc")
-public class AttrInfoTest extends ReplicationTestCase
-{
-  /** Build some data for the AttrInfo test below. */
-  @DataProvider(name = "attrInfo")
-  public Object[][] createData()
-  {
-    ByteString att1 = ByteString.valueOf("string");
-    ByteString att2 = ByteString.valueOf("value");
-    ByteString att3 = ByteString.valueOf("again");
-
-    CSN del1 = new CSN(1,  0,  1);
-    CSN del2 = new CSN(1,  1,  1);
-    CSN del3 = new CSN(1,  0,  2);
-
-    CSN upd1 = new CSN(TimeThread.getTime(), 123, 45);
-    CSN upd2 = new CSN(TimeThread.getTime() + 1000, 123,  45);
-    CSN upd3 = new CSN(TimeThread.getTime(), 321, 54);
-
-    return new Object[][]
-    {
-    { att1, del1, upd1 },
-    { att2, del2, upd2 },
-    { att3, del3, upd3 },
-    { att3, upd3, upd3 } };
-  }
-
-  /** Create a AttrInfo and check the methods. */
-  @Test(dataProvider = "attrInfo")
-  public void attrInfo(ByteString att, CSN deleteTime, CSN updateTime) throws Exception
-  {
-    // Create an empty AttrInfo
-    AttrHistoricalMultiple attrInfo1 = new AttrHistoricalMultiple();
-
-    // Check
-    attrInfo1.add(att, updateTime);
-    Set<AttrValueHistorical> values1 = attrInfo1.getValuesHistorical();
-    assertEquals(values1.size(), 1);
-    AttrValueHistorical valueInfo1 = new AttrValueHistorical(att, updateTime, null);
-    assertTrue(values1.contains(valueInfo1));
-
-    // Check constructor with parameter
-    AttrValueHistorical valueInfo2 = new AttrValueHistorical(att, updateTime, deleteTime);
-    AttrHistoricalMultiple attrInfo2 = new AttrHistoricalMultiple(
-        deleteTime, updateTime, Collections.singletonMap(valueInfo2, valueInfo2));
-
-    // Check equality
-    //assertTrue(attrInfo1.getDeleteTime().compareTo(attrInfo2.getDeleteTime())==0);
-
-    //  Check constructor with time parameter and not Value
-    AttrHistoricalMultiple attrInfo3 = new AttrHistoricalMultiple(deleteTime, updateTime, null);
-    attrInfo3.add(att, updateTime);
-    Set<AttrValueHistorical> values3 = attrInfo3.getValuesHistorical();
-    assertEquals(values3.size(), 1);
-    valueInfo1 = new AttrValueHistorical(att, updateTime, null);
-    assertTrue(values3.contains(valueInfo1));
-
-    // Check duplicate
-    AttrHistoricalMultiple attrInfo4 = attrInfo3.duplicate();
-    Set<AttrValueHistorical> values4 = attrInfo4.getValuesHistorical();
-    assertEquals(attrInfo4.getDeleteTime().compareTo(attrInfo3.getDeleteTime()), 0);
-    assertEquals(values4.size(), values3.size());
-
-    // Check
-    attrInfo4.delete(att, updateTime);
-    assertEquals(attrInfo4.getValuesHistorical().size(), 1);
-
-    // Check
-    AttributeType type = DirectoryServer.getAttributeType("description");
-    attrInfo3.delete(Attributes.create(type, att), updateTime) ;
-    assertEquals(attrInfo3.getValuesHistorical().size(), 1);
-
-    // Check
-    attrInfo2.delete(updateTime);
-    assertEquals(attrInfo2.getValuesHistorical().size(), 0);
-  }
-}

--
Gitblit v1.10.0