/* * 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.opends.server.util.StaticUtils.*; import static org.testng.Assert.*; import java.util.Iterator; import java.util.List; 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.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 String ATTRIBUTE_NAME = "displayName"; private static final boolean CONFLICT = true; private static final boolean SUCCESS = 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 = 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; } @Test public void replay_addDeleteSameTime() throws Exception { mod = newModification(ADD, "X"); replayOperation(csn, entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(csn, entry, mod, SUCCESS); assertNoAttributeValue(entry); } @Test public void replay_addThenDeleteThenOlderAdd() throws Exception { CSN[] t = newCSNs(3); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE); replayOperation(t[2], entry, mod, SUCCESS); assertNoAttributeValue(entry); mod = newModification(ADD, "Z"); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_addThenDeleteThenAddThenOlderAdd() throws Exception { CSN[] t = newCSNs(4); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertNoAttributeValue(entry); mod = newModification(ADD, "X"); replayOperation(t[3], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(ADD, "Y"); replayOperation(t[2], entry, mod, CONFLICT); assertAttributeValue(entry, "X"); } @Test public void replay_addThenDeleteThenDelete() throws Exception { CSN[] t = newCSNs(3); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertNoAttributeValue(entry); mod = newModification(DELETE, "X"); replayOperationSuppressMod(t[2], entry, mod, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_addThenOlderDelete() throws Exception { CSN[] t = newCSNs(2); mod = newModification(ADD, "X"); replayOperation(t[1], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "X"); replayOperationSuppressMod(t[0], entry, mod, CONFLICT); assertAttributeValue(entry, "X"); } @Test public void replay_deleteMissingAttribute() throws Exception { mod = newModification(DELETE, "X"); replayOperationSuppressMod(csn, entry, mod, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_deleteMissingAttributeValue() throws Exception { CSN[] t = newCSNs(2); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "Y"); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertAttributeValue(entry, "X"); } /** * 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); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "Y"); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertAttributeValue(entry, "X"); } /** * 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); assertAttributeValue(entry, "X"); mod = newModification(DELETE, "Y"); replayOperationSuppressMod(csn, entry, mod, CONFLICT); assertAttributeValue(entry, "X"); } @Test public void replay_replaceWithValue() throws Exception { mod = newModification(REPLACE, "X"); replayOperation(csn, entry, mod, SUCCESS); assertAttributeValue(entry, "X"); } @Test public void replay_replaceNoValue() throws Exception { mod = newModification(REPLACE); replayOperation(csn, entry, mod, SUCCESS); assertNoAttributeValue(entry); } @Test public void replay_addThenDeleteThenOlderReplace() throws Exception { CSN[] t = newCSNs(3); mod = newModification(ADD, "X"); replayOperation(t[0], entry, mod, SUCCESS); assertAttributeValue(entry, "X"); mod = newModification(DELETE); replayOperation(t[2], entry, mod, SUCCESS); assertNoAttributeValue(entry); mod = newModification(REPLACE); replayOperationSuppressMod(t[1], entry, mod, CONFLICT); assertNoAttributeValue(entry); } @Test public void replay_increment() throws Exception { mod = newModification(INCREMENT, "X"); replayOperation(csn, null, mod, SUCCESS); } 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 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 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(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); conformsToSchema(entry); } } private void assertAttributeValue(Entry entry, Modification mod) { ByteString actualValue = getActualValue(entry, mod); switch (mod.getModificationType().asEnum()) { case ADD: { Attribute attribute = mod.getAttribute(); assertThat(attribute).hasSize(1); ByteString expectedValue = attribute.iterator().next(); assertEquals(actualValue, expectedValue); return; } case REPLACE: { Attribute attribute = mod.getAttribute(); if (!attribute.isEmpty()) { ByteString expectedValue = attribute.iterator().next(); assertEquals(actualValue, expectedValue); } else { assertNull(actualValue); } return; } case DELETE: assertNull(actualValue); return; } } private ByteString getActualValue(Entry entry, Modification mod) { return getActualValue(entry.getAttribute(mod.getAttribute().getAttributeType())); } private ByteString getActualValue(List<Attribute> attributes) { if (attributes != null) { assertThat(attributes).hasSize(1); Attribute attribute = attributes.get(0); assertThat(attribute).hasSize(1); return attribute.iterator().next(); } return null; } 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) { assertAttributeValue(entry, (String) null); } private void assertAttributeValue(Entry entry, String expectedValue) { ByteString actualValue = getActualValue(entry.getAttribute(toLowerCase(ATTRIBUTE_NAME))); assertEquals(actualValue, expectedValue != null ? ByteString.valueOfUtf8(expectedValue) : null); } private void verifyModSuppressed(Iterator<Modification> it) { verify(it, times(1)).remove(); verify(it, only()).remove(); } }