/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.box.server.robots.operations; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.wave.api.Element; import com.google.wave.api.ElementType; import com.google.wave.api.Gadget; import com.google.wave.api.InvalidRequestException; import com.google.wave.api.OperationRequest; import com.google.wave.api.OperationType; import com.google.wave.api.Range; import com.google.wave.api.JsonRpcConstant.ParamsProperty; import com.google.wave.api.OperationRequest.Parameter; import com.google.wave.api.data.ApiView; import com.google.wave.api.data.ApiView.ElementInfo; import com.google.wave.api.impl.DocumentModifyAction; import com.google.wave.api.impl.DocumentModifyAction.BundledAnnotation; import com.google.wave.api.impl.DocumentModifyAction.ModifyHow; import com.google.wave.api.impl.DocumentModifyQuery; import org.waveprotocol.box.server.robots.RobotsTestBase; import org.waveprotocol.box.server.robots.testing.OperationServiceHelper; import org.waveprotocol.wave.model.conversation.ObservableConversationBlip; import org.waveprotocol.wave.model.document.Document; import org.waveprotocol.wave.model.document.util.LineContainers; import org.waveprotocol.wave.model.document.util.XmlStringBuilder; import java.util.Collections; import java.util.List; import java.util.Map; /** * Unit tests for {@link DocumentModifyService}. * * @author ljvderijk@google.com (Lennard de Rijk) */ public class DocumentModifyServiceTest extends RobotsTestBase { private static final String NO_ANNOTATION_KEY = null; private static final List<String> NO_VALUES = Collections.<String> emptyList(); private static final List<BundledAnnotation> NO_BUNDLED_ANNOTATIONS = Collections.emptyList(); private static final List<Element> NO_ELEMENTS = Collections.emptyList(); private static final String ANNOTATION_VALUE = "annotationValue"; private static final String ANNOTATION_KEY = "annotationKey"; private static final String INITIAL_CONTENT = "Hello world!"; private static final int CONTENT_START_TEXT = 1; private static final int CONTENT_START_XML = 3; private DocumentModifyService service; private OperationServiceHelper helper; private String rootBlipId; @Override protected void setUp() throws Exception { service = DocumentModifyService.create(); helper = new OperationServiceHelper(WAVELET_NAME, ALEX); ObservableConversationBlip rootBlip = getRootBlip(); rootBlipId = rootBlip.getId(); LineContainers.appendToLastLine( rootBlip.getContent(), XmlStringBuilder.createText(INITIAL_CONTENT)); } public void testFailOnMultipleWhereParams() throws Exception { OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction()), Parameter.of(ParamsProperty.RANGE, new Range(0, 1)), Parameter.of(ParamsProperty.INDEX, 0)); try { service.execute(operation, helper.getContext(), ALEX); fail("Expected InvalidRequestException"); } catch (InvalidRequestException e) { // expected } } public void testAnnotate() throws Exception { OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.ANNOTATE, Collections.singletonList(ANNOTATION_VALUE), ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); String annotation = getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY); assertEquals("Expected the text to be annotated", ANNOTATION_VALUE, annotation); assertNull("Expected this text not to be annotated", getRootBlip().getContent().getAnnotation(CONTENT_START_XML + 1, ANNOTATION_KEY)); } public void testClearAnnotatation() throws Exception { Document doc = getRootBlip().getContent(); doc.setAnnotation(CONTENT_START_XML, CONTENT_START_XML + 1, ANNOTATION_KEY, ANNOTATION_VALUE); String annotation = getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY); assertEquals("Expected the text to be annotated", ANNOTATION_VALUE, annotation); OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.CLEAR_ANNOTATION, NO_VALUES, ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); assertNull("Expected this text not to be annotated", getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY)); } public void testInsertGadget() throws Exception { String gadgetUrl = "http://wave-api.appspot.com/public/gadgets/areyouin/gadget.xml"; List<Element> elementsIn = Lists.newArrayListWithCapacity(1); Map<String,String> properties = Maps.newHashMap(); properties.put("url", gadgetUrl); properties.put("author", ALEX.getAddress()); elementsIn.add(new Gadget(properties)); OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT, NO_VALUES, NO_ANNOTATION_KEY, elementsIn, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); Gadget gadget = null; List<ElementInfo> elementsOut = getApiView().getElements(); int size = 0; for (ElementInfo elementOut : elementsOut) { if (!elementOut.element.isGadget()) { continue; } else { size++; gadget = (Gadget)elementOut.element; } } assertEquals(1, size); assertEquals(gadgetUrl, gadget.getUrl()); assertEquals(ALEX.getAddress(), gadget.getAuthor()); } public void testUpdateGadget() throws Exception { String propertyName = "propertyName"; String propertyValue = "propertyValue"; String propertyNameToDelete = "propertyNameToDelete"; String propertyValueToDelete = "propertyValueToDelete"; String gadgetUrl = "http://wave-api.appspot.com/public/gadgets/areyouin/gadget.xml"; String gadgetXml = "<gadget author=\"" + ALEX.getAddress() + "\" prefs=\"\" state=\"\" title=\"\" " + "url=\"" + gadgetUrl + "\">" + "<state name=\"author\" value=\"" + ALEX.getAddress()+ "\"/>" + "<state name=\"url\" " + "value=\"" + gadgetUrl + "\"/>" + "<state name=\"" + propertyName + "\" value=\"" + propertyValue + "\"/>" + "<state name=\"" + propertyNameToDelete + "\" value=\"" + propertyValueToDelete + "\"/>" + "</gadget>"; ObservableConversationBlip rootBlip = getRootBlip(); rootBlipId = rootBlip.getId(); LineContainers.appendToLastLine(rootBlip.getContent(), XmlStringBuilder.createFromXmlString(gadgetXml)); List<Element> updatedElementsIn = Lists.newArrayListWithCapacity(1); Map<String, String> newProperties = Maps.newHashMap(); String updatedPropertyValue = "updatedPropertyValue"; newProperties.put(propertyName, updatedPropertyValue); String newPropertyName = "newPropertyName"; String newPropertyValue = "newPropertyValue"; newProperties.put(newPropertyName, newPropertyValue); newProperties.put(propertyNameToDelete, null); updatedElementsIn.add(new Gadget(newProperties)); OperationRequest updateOperation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.UPDATE_ELEMENT, NO_VALUES, NO_ANNOTATION_KEY, updatedElementsIn, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.MODIFY_QUERY, new DocumentModifyQuery(ElementType.GADGET, ImmutableMap.of("url", gadgetUrl), 1))); service.execute(updateOperation, helper.getContext(), ALEX); Gadget gadget = null; List<ElementInfo> elementsOut = getApiView().getElements(); for (ElementInfo elementOut : elementsOut) { if (elementOut.element.isGadget()) { gadget = (Gadget) elementOut.element; } } assertEquals(gadgetUrl, gadget.getUrl()); assertEquals(ALEX.getAddress(), gadget.getAuthor()); assertEquals(updatedPropertyValue, gadget.getProperty(propertyName)); assertNotNull(gadget.getProperty(newPropertyName)); assertEquals(newPropertyValue, gadget.getProperty(newPropertyName)); assertNull(gadget.getProperty(propertyNameToDelete)); } public void testDelete() throws Exception { OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.DELETE, NO_VALUES, NO_ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String after = getApiView().apiContents().substring(1); assertEquals("First character should be deleted", INITIAL_CONTENT.substring(1), after); } public void testInsert() throws Exception { String toInsert = "insertedText"; // Insert a new piece of annotated text before the current text. OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT, Collections.singletonList(toInsert), NO_ANNOTATION_KEY, NO_ELEMENTS, BundledAnnotation.listOf(ANNOTATION_KEY, ANNOTATION_VALUE), false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String result = getApiView().apiContents().substring(1); assertEquals( "The result should start with the inserted text", toInsert + INITIAL_CONTENT, result); String annotation = getRootBlip().getContent().getAnnotation(CONTENT_START_XML, ANNOTATION_KEY); assertEquals("Expected the text to be annotated", ANNOTATION_VALUE, annotation); } public void testInsertAfter() throws Exception { String toInsert = "insertedText"; OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.INSERT_AFTER, Collections.singletonList(toInsert), NO_ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.INDEX, CONTENT_START_TEXT)); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String after = getApiView().apiContents().substring(1); assertEquals("Content should be insterted after the first character", INITIAL_CONTENT.charAt(0) + toInsert + INITIAL_CONTENT.substring(1), after); } public void testReplace() throws Exception { String replacement = "replacedText"; OperationRequest operation = operationRequest(OperationType.DOCUMENT_MODIFY, rootBlipId, Parameter.of(ParamsProperty.MODIFY_ACTION, new DocumentModifyAction(ModifyHow.REPLACE, Collections.singletonList(replacement), NO_ANNOTATION_KEY, NO_ELEMENTS, NO_BUNDLED_ANNOTATIONS, false)), Parameter.of(ParamsProperty.RANGE, new Range(CONTENT_START_TEXT, CONTENT_START_TEXT + INITIAL_CONTENT.length()))); service.execute(operation, helper.getContext(), ALEX); // Cut off the /n String after = getApiView().apiContents().substring(1); assertEquals("The entire text should be replaced", replacement, after); } private ApiView getApiView() throws InvalidRequestException { ApiView view = new ApiView( getRootBlip().getContent(), helper.getContext().openWavelet(WAVE_ID, WAVELET_ID, ALEX)); return view; } private ObservableConversationBlip getRootBlip() throws InvalidRequestException { ObservableConversationBlip rootBlip = helper.getContext().openConversation(WAVE_ID, WAVELET_ID, ALEX).getRoot().getRootThread() .getFirstBlip(); return rootBlip; } }