blob: 3ba955bae8abadcb8e5181e89b54b42342a9329c [file] [log] [blame]
/**
* 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;
}
}