/** * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.model.wave.opbased; import junit.framework.TestCase; import org.waveprotocol.wave.model.document.MutableDocument; import org.waveprotocol.wave.model.operation.SilentOperationSink; import org.waveprotocol.wave.model.operation.wave.AddParticipant; import org.waveprotocol.wave.model.operation.wave.BlipContentOperation; import org.waveprotocol.wave.model.operation.wave.BlipOperation; import org.waveprotocol.wave.model.operation.wave.NoOp; import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.testing.BasicFactories; import org.waveprotocol.wave.model.testing.FakeSilentOperationSink; import org.waveprotocol.wave.model.testing.MockParticipationHelper; import org.waveprotocol.wave.model.testing.OpBasedWaveletFactory; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; import java.util.Collections; import java.util.List; /** * White-box test for {@link OpBasedWavelet}. * * @author zdwang@google.com (David Wang) */ public abstract class OpBasedWaveletTestBase extends TestCase { private OpBasedWaveletFactory factory; /** Stub sink for catching operations. */ private final FakeSilentOperationSink<WaveletOperation> sink = new FakeSilentOperationSink<WaveletOperation>(); private OpBasedWavelet target; /** * Creates a wavelet for testing. */ protected abstract ObservableWaveletData.Factory<?> createWaveletDataFactory(); @Override protected void setUp() throws Exception { super.setUp(); factory = buildFactory(createWaveletDataFactory(), sink); target = factory.create(); } /** * Builds the wavelet factory, based on an underlying holder factory and an * output operation sink. */ private static OpBasedWaveletFactory buildFactory( ObservableWaveletData.Factory<?> holderFactory, SilentOperationSink<WaveletOperation> sink) { return BasicFactories.opBasedWaveletFactoryBuilder().with(sink).with(holderFactory).build(); } /** * Asserts that some operation is adding a certain participant. * * @param creator of the operation adding the participant. * @param participant that should be added. * @param op to check. */ private static void assertAddParticipant(ParticipantId creator, ParticipantId participant, WaveletOperation op) { assertTrue("Expected AddParticipant but was " + op.getClass(), op instanceof AddParticipant); AddParticipant add = (AddParticipant) op; assertEquals(creator, add.getContext().getCreator()); assertEquals(participant, add.getParticipantId()); } public void testAddingCreatorAsParticipantProducesOperation() { ParticipantId creator = target.getCreatorId(); target.addParticipant(creator); assertTrue(sink.getConsumedOp() instanceof AddParticipant); assertEquals(creator, ((AddParticipant) sink.getConsumedOp()).getParticipantId()); } public void testAddingManyParticipantProducesManyOperations() { // Creator must be added as first op. target.addParticipant(target.getCreatorId()); for (int i = 0; i < 20; i++) { ParticipantId participant = new ParticipantId("foo" + i + "@bar.com"); target.addParticipant(participant); assertTrue(sink.getConsumedOp() instanceof AddParticipant); assertEquals(participant, ((AddParticipant) sink.getConsumedOp()).getParticipantId()); sink.clear(); } } /** * Tests that creating a blip doesn't produce an operation. */ public void testCreatingRootBlipProducesNoOperation() { target.createBlip("b+fake"); assertTrue(sink.getOps().isEmpty()); } /** * Test that touching a wavelet produces an empty operation. */ public void testTouchingProducesOperation() { target.touch(); assertTrue(sink.getConsumedOp() instanceof NoOp); } /** * Tests that mutating a data document produces (only) a document mutation. * * This test is suppressed because the test setup * can't approporiately hook up the op-based wavelet as a document * listener. Enable when either the op-based wavelet installs itself * as a listener or the model test setup is refactored to allow this. */ public void suppressedTestMutatingDataDocumentProducesDocumentOp() { MutableDocument<?, ?, ?> doc = target.getDocument("foo"); // No op for data document creation. assertNull(sink.getConsumedOp()); doc.with(new MutableDocument.Action() { @Override public <N, E extends N, T extends N> void exec(MutableDocument<N, E, T> doc) { doc.createChildElement( doc.getDocumentElement(), "x", Collections.<String, String>emptyMap()); } }); assertTrue("expected WaveletBlipOperation but got " + sink.getConsumedOp(), // \u2620 sink.getConsumedOp() instanceof WaveletBlipOperation); BlipOperation blipOp = ((WaveletBlipOperation) sink.getConsumedOp()).getBlipOp(); assertTrue(blipOp instanceof BlipContentOperation); } /** * Tests the situation where someone not on a wavelet's participant list * attempts to perform some operation. Ensures that the model adds them as a * participant before the attempted operations go through. */ public void testNonParticipantAutoAddedByAuthoriser() { final ParticipantId creator = target.getCreatorId(); final ParticipantId bob = new ParticipantId("bob@example.com"); target.addParticipant(target.getCreatorId()); sink.clear(); factory.getLastContextFactory().setParticipantId(bob); factory.getLastAuthoriser().program(new MockParticipationHelper.Frame(creator, bob, creator)); target.addParticipant(ParticipantId.ofUnsafe("myfriend@example.com")); List<WaveletOperation> ops = sink.getOps(); assertEquals(2, ops.size()); assertAddParticipant(creator, bob, ops.get(0)); assertTrue(ops.get(1) instanceof AddParticipant); assertEquals(bob, ops.get(1).getContext().getCreator()); } /** * Like {@link #testNonParticipantAutoAddedByAuthoriser()}, but make sure * that when the attempted operation is adding themselves that only the * single add-participant operation is generated. This should be in the form * of addParticipant suppressing the given operation and only performing the * auto-injected one. */ public void testNonParticipantAutoAddedByAuthoriserSuppressesDuplicateAddParticipantOp() { final ParticipantId creator = target.getCreatorId(); final ParticipantId fred = new ParticipantId("fred@google.com"); target.addParticipant(target.getCreatorId()); OpBasedBlip root = target.createBlip("b+fake"); sink.clear(); factory.getLastContextFactory().setParticipantId(fred); factory.getLastAuthoriser().program(new MockParticipationHelper.Frame(creator, fred, creator)); target.addParticipant(fred); List<WaveletOperation> ops = sink.getOps(); assertEquals(1, ops.size()); assertAddParticipant(creator, fred, ops.get(0)); } /** * Like {@link #testNonParticipantAutoAddedByAuthoriser()}, but tests that, * even though a user adding themselves suppresses duplicate operations, it * doesn't suppress all add-participant ops. */ public void testNonParticipantAutoAddedByAuthoriserDoesntSuppressAddParticipantOp() { final ParticipantId creator = target.getCreatorId(); final ParticipantId fred = new ParticipantId("fred@google.com"); final ParticipantId gary = new ParticipantId("gary@google.com"); target.addParticipant(target.getCreatorId()); OpBasedBlip root = target.createBlip("b+fake"); sink.clear(); factory.getLastContextFactory().setParticipantId(fred); factory.getLastAuthoriser().program(new MockParticipationHelper.Frame(creator, fred, creator)); target.addParticipant(gary); List<WaveletOperation> ops = sink.getOps(); assertEquals(2, ops.size()); assertAddParticipant(creator, fred, ops.get(0)); assertAddParticipant(fred, gary, ops.get(1)); } }