/* * 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.i18n.LocalizableMessageBuilder; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ModificationType; import org.opends.server.TestCaseUtils; 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 final String ATTRIBUTE_NAME = "description"; private static final boolean CONFLICT = true; private static final boolean SUCCESS = false; 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); entry = TestCaseUtils.makeEntry( "dn: uid=test.user", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: test.user", "givenName: Test", "sn: User", "cn: Test User", "userPassword: password"); } @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.valueOfUtf8("string"); ByteString att2 = ByteString.valueOfUtf8("value"); ByteString att3 = ByteString.valueOfUtf8("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.getAttributeTypeOrNull(ATTRIBUTE_NAME); 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, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(csn, entry, mod, SUCCESS); assertNoAttributeValue(entry); } @Test public void replay_addThenAddThenOlderDelete() throws Exception { CSN[] t = newCSNs(3); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(ADD, "Y"); replayOperation(t[2], entry, mod, SUCCESS); assertAttributeValues(entry, "X", "Y"); mod = newModification(DELETE, "Y"); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertAttributeValues(entry, "X", "Y"); } @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, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_addThenDeleteWithValueThenOlderAdd() throws Exception { CSN[] t = newCSNs(3); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(t[2], entry, mod, SUCCESS); assertNoAttributeValue(entry); mod = newModification(ADD, "X"); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_addThenAdd() throws Exception { CSN[] t = newCSNs(2); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(ADD, "X"); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertAttributeValues(entry, "X"); } @Test public void replay_addThenDeleteThenAdd() throws Exception { CSN[] t = newCSNs(3); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertNoAttributeValue(entry); mod = newModification(ADD, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); } @Test public void replay_deleteNoPreviousHistory() throws Exception { mod = newModification(DELETE, "Y"); replayOperationSuppressMod(csn, entry, mod, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_addThenDelete() throws Exception { CSN[] t = newCSNs(2); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertNoAttributeValue(entry); } @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, CONFLICT); assertNoAttributeValue(entry); } @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, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(DELETE); replayOperation(tDel, entry, mod, SUCCESS); assertNoAttributeValue(entry); } @Test public void replay_replace() throws Exception { mod = newModification(REPLACE, "X"); replayOperation(csn, entry, mod, SUCCESS); assertAttributeValues(entry, "X"); } @Test public void replay_addThenOlderReplace() throws Exception { CSN[] t = newCSNs(2); mod = newModification(ADD, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertAttributeValues(entry, "X"); mod = newModification(REPLACE, "Y"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValues(entry, "X", "Y"); } @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, CONFLICT); assertNoAttributeValue(entry); } 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(ATTRIBUTE_NAME, attrValue)); } private Modification newModification(ModificationType modType) { return new Modification(modType, Attributes.empty(ATTRIBUTE_NAME)); } private void replayOperationSuppressMod(CSN csn, Entry entry, Modification mod, boolean shouldConflict) throws Exception { Iterator<Modification> itMod = mock(Iterator.class); replayOperation(itMod, csn, entry, mod, shouldConflict); verifyModSuppressed(itMod); } private void replayOperation(CSN csn, Entry entry, Modification mod, boolean shouldConflict) throws Exception { Iterator<Modification> itMod = mock(Iterator.class); replayOperation(itMod, csn, entry, mod, shouldConflict); verifyZeroInteractions(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); assertAttributeValues(entry, mod); conformsToSchema(entry); } } 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) { return getValues(entry.getAttribute(mod.getAttribute().getAttributeType())); } private List<ByteString> getValues(List<Attribute> attributes) { 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 conformsToSchema(Entry entry) { LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder(); final boolean isValid = entry.conformsToSchema(null, false, false, false, invalidReason); assertThat(isValid).as(invalidReason.toString()).isTrue(); } private void assertNoAttributeValue(Entry entry) { assertAttributeValues(entry); } private void assertAttributeValues(Entry entry, String... expectedValues) { List<ByteString> actualValues = getValues(entry.getAttribute(ATTRIBUTE_NAME)); assertThat(actualValues).containsOnly(toByteStrings(expectedValues)); } private ByteString[] toByteStrings(String... strings) { ByteString[] results = new ByteString[strings.length]; for (int i = 0; i < results.length; i++) { results[i] = ByteString.valueOfUtf8(strings[i]); } return results; } private void verifyModSuppressed(Iterator<Modification> it) { verify(it, times(1)).remove(); verify(it, only()).remove(); } }