Repository: Wikidata/Wikidata-Toolkit (java)

Issue:
Editing API: return revision id on successful edit
The main method to make edits to existing entities looks like this:
```java
public void editEntityDocument(
	EntityUpdate update, boolean clear, String summary, List<String> tags)
        throws IOException, MediaWikiApiErrorException;
```
It would be convenient if it could instead return some information about the edit (if it was made successfully). For instance, the new revision id.

Pull request description:
Wikibase API: Return revision id after making an edit.
Closes #795.

Patch:
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/EditingResult.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/EditingResult.java
new file mode 100644
index 00000000..2bf07816
--- /dev/null
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/EditingResult.java
@@ -0,0 +1,71 @@
+package org.wikidata.wdtk.wikibaseapi;
+
+/*-
+ * #%L
+ * Wikidata Toolkit Wikibase API
+ * %%
+ * Copyright (C) 2014 - 2023 Wikidata Toolkit Developers
+ * %%
+ * 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.
+ * #L%
+ */
+
+import java.util.Objects;
+import java.util.OptionalLong;
+
+/**
+ * Holds information about a successful edit made via {@link WikibaseDataEditor}.
+ * The state of the entity after edit is not provided here because it is not possible
+ * for WDTK to determine it reliably from the response of the server. Indeed, it is
+ * possible that the data in the entity after the edit differs from the data in the
+ * entity before the edit plus the changes of the edit itself, because it can be that
+ * another edit touched independent parts of the entity. This can happen even if the base
+ * revision id is provided.
+ */
+public class EditingResult {
+    
+    private final long revisionId;
+
+    public EditingResult(long revisionId) {
+        super();
+        this.revisionId = revisionId;
+    }
+
+    /**
+     * The identifier of the revision of the last edit made by the editing action,
+     * if any edit was made.
+     */
+    public OptionalLong getLastRevisionId() {
+        return revisionId == 0 ? OptionalLong.empty() : OptionalLong.of(revisionId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(revisionId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        EditingResult other = (EditingResult) obj;
+        return revisionId == other.revisionId;
+    }
+
+    @Override
+    public String toString() {
+        return "EditingResult [revisionId=" + revisionId + "]";
+    }
+    
+}
diff --git a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java
index 60f5c3a2..1b0a9b69 100644
--- a/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java
+++ b/wdtk-wikibaseapi/src/main/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditor.java
@@ -56,6 +56,9 @@ import org.wikidata.wdtk.datamodel.interfaces.TermedStatementDocument;
 import org.wikidata.wdtk.datamodel.interfaces.TermedStatementDocumentUpdate;
 import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+
 /**
  * Class that provides high-level editing functionality for Wikibase data.
  *
@@ -530,13 +533,13 @@ public class WikibaseDataEditor {
 	 * @throws MediaWikiApiErrorException
 	 *             if MediaWiki API returned an error response
 	 */
-	public void editEntityDocument(
+	public EditingResult editEntityDocument(
 			EntityUpdate update, boolean clear, String summary, List<String> tags)
 			throws IOException, MediaWikiApiErrorException {
 		long revisionId = update.getBaseRevisionId();
 		if (!clear) {
 			if (update.isEmpty())
-				return;
+				return new EditingResult(0L);
 			if (update instanceof StatementDocumentUpdate) {
 				StatementDocumentUpdate typed = (StatementDocumentUpdate) update;
 				if (typed.getStatements().getAdded().size() == 1) {
@@ -547,9 +550,9 @@ public class WikibaseDataEditor {
 					if (builder.build().equals(update)) {
 						String statementId = guidGenerator.freshStatementId(typed.getEntityId().getId());
 						Statement prepared = statement.withStatementId(statementId);
-						wbEditingAction.wbSetClaim(JsonSerializer.getJsonString(prepared),
+						JsonNode response = wbEditingAction.wbSetClaim(JsonSerializer.getJsonString(prepared),
 								editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 				if (typed.getStatements().getReplaced().size() == 1) {
@@ -558,9 +561,9 @@ public class WikibaseDataEditor {
 					Statement statement = typed.getStatements().getReplaced().values().stream().findFirst().get();
 					builder.updateStatements(StatementUpdateBuilder.create().replace(statement).build());
 					if (builder.build().equals(update)) {
-						wbEditingAction.wbSetClaim(JsonSerializer.getJsonString(statement),
+						JsonNode response = wbEditingAction.wbSetClaim(JsonSerializer.getJsonString(statement),
 								editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 				if (!typed.getStatements().getRemoved().isEmpty()
@@ -574,8 +577,8 @@ public class WikibaseDataEditor {
 					}
 					builder.updateStatements(statementBuilder.build());
 					if (builder.build().equals(update)) {
-						wbEditingAction.wbRemoveClaims(statementIds, editAsBot, revisionId, summary, tags);
-						return;
+					    JsonNode response = wbEditingAction.wbRemoveClaims(statementIds, editAsBot, revisionId, summary, tags);
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 			}
@@ -587,9 +590,9 @@ public class WikibaseDataEditor {
 					MonolingualTextValue label = typed.getLabels().getModified().values().stream().findFirst().get();
 					builder.updateLabels(TermUpdateBuilder.create().put(label).build());
 					if (builder.build().equals(update)) {
-						wbEditingAction.wbSetLabel(update.getEntityId().getId(), null, null, null,
+						JsonNode response = wbEditingAction.wbSetLabel(update.getEntityId().getId(), null, null, null,
 								label.getLanguageCode(), label.getText(), editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 				if (typed.getLabels().getRemoved().size() == 1) {
@@ -598,9 +601,9 @@ public class WikibaseDataEditor {
 					String language = typed.getLabels().getRemoved().stream().findFirst().get();
 					builder.updateLabels(TermUpdateBuilder.create().remove(language).build());
 					if (builder.build().equals(update)) {
-						wbEditingAction.wbSetLabel(update.getEntityId().getId(), null, null, null,
+						JsonNode response = wbEditingAction.wbSetLabel(update.getEntityId().getId(), null, null, null,
 								language, null, editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 			}
@@ -613,10 +616,10 @@ public class WikibaseDataEditor {
 							.values().stream().findFirst().get();
 					builder.updateDescriptions(TermUpdateBuilder.create().put(description).build());
 					if (builder.build().equals(update)) {
-						wbEditingAction.wbSetDescription(update.getEntityId().getId(), null, null, null,
+						JsonNode response = wbEditingAction.wbSetDescription(update.getEntityId().getId(), null, null, null,
 								description.getLanguageCode(), description.getText(),
 								editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 				if (typed.getDescriptions().getRemoved().size() == 1) {
@@ -625,9 +628,9 @@ public class WikibaseDataEditor {
 					String language = typed.getDescriptions().getRemoved().stream().findFirst().get();
 					builder.updateDescriptions(TermUpdateBuilder.create().remove(language).build());
 					if (builder.build().equals(update)) {
-						wbEditingAction.wbSetDescription(update.getEntityId().getId(), null, null, null,
+						JsonNode response = wbEditingAction.wbSetDescription(update.getEntityId().getId(), null, null, null,
 								language, null, editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 				if (typed.getAliases().size() == 1) {
@@ -646,9 +649,9 @@ public class WikibaseDataEditor {
 						List<String> recreated = aliases.getRecreated()
 								.map(l -> l.stream().map(a -> a.getText()).collect(toList()))
 								.orElse(null);
-						wbEditingAction.wbSetAliases(update.getEntityId().getId(), null, null, null,
+						JsonNode response = wbEditingAction.wbSetAliases(update.getEntityId().getId(), null, null, null,
 								language, added, removed, recreated, editAsBot, revisionId, summary, tags);
-						return;
+						return new EditingResult(getRevisionIdFromResponse(response));
 					}
 				}
 			}
@@ -660,8 +663,7 @@ public class WikibaseDataEditor {
 					SenseUpdate sense = typed.getUpdatedSenses().values().stream().findFirst().get();
 					builder.updateSense(sense);
 					if (builder.build().equals(update)) {
-						editEntityDocument(sense, false, summary, tags);
-						return;
+						return editEntityDocument(sense, false, summary, tags);
 					}
 				}
 				if (typed.getUpdatedForms().size() == 1) {
@@ -670,15 +672,15 @@ public class WikibaseDataEditor {
 					FormUpdate form = typed.getUpdatedForms().values().stream().findFirst().get();
 					builder.updateForm(form);
 					if (builder.build().equals(update)) {
-						editEntityDocument(form, false, summary, tags);
-						return;
+						return editEntityDocument(form, false, summary, tags);
 					}
 				}
 			}
 		}
 		String data = JsonSerializer.getJsonString(update);
-		wbEditingAction.wbEditEntity(
+		EntityDocument document = wbEditingAction.wbEditEntity(
 				update.getEntityId().getId(), null, null, null, data, clear, editAsBot, revisionId, summary, tags);
+		return new EditingResult(document.getRevisionId());
 	}
 
 	/**
@@ -1131,4 +1133,29 @@ public class WikibaseDataEditor {
 				JsonSerializer.getJsonString(update), false, editAsBot, currentDocument.getRevisionId(), null, null);
 	}
 
+    /**
+     * Extracts the last revision id from the JSON response returned
+     * by the API after an edit
+     * 
+     * @param response
+     *      the response as returned by Mediawiki
+     * @return
+     *      the new revision id of the edited entity
+     * @throws JsonProcessingException 
+     */
+    protected long getRevisionIdFromResponse(JsonNode response) throws JsonProcessingException {
+        if(response == null) {
+            throw new MalformedResponseException("API response is null");
+        }
+        JsonNode entity = null;
+        if(response.has("entity")) {
+            entity = response.path("entity");
+        } else if(response.has("pageinfo")) {
+            entity = response.path("pageinfo");
+        } 
+        if(entity != null && entity.has("lastrevid")) {
+            return entity.path("lastrevid").asLong();
+        }
+        throw new MalformedResponseException("The last revision id could not be found in API response");
+    }
 }


Test patch:
diff --git a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/EditingResultTest.java b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/EditingResultTest.java
new file mode 100644
index 00000000..6caa3b69
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/EditingResultTest.java
@@ -0,0 +1,44 @@
+package org.wikidata.wdtk.wikibaseapi;
+
+/*-
+ * #%L
+ * Wikidata Toolkit Wikibase API
+ * %%
+ * Copyright (C) 2014 - 2023 Wikidata Toolkit Developers
+ * %%
+ * 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.
+ * #L%
+ */
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.OptionalLong;
+
+import org.junit.Test;
+
+public class EditingResultTest {
+
+    @Test
+    public void testGetRevisionId() {
+        EditingResult SUT = new EditingResult(1234L);
+        
+        assertEquals(SUT.getLastRevisionId(), OptionalLong.of(1234L));
+    }
+    
+    @Test
+    public void testNoRevisionId() {
+        EditingResult SUT = new EditingResult(0L);
+        
+        assertEquals(SUT.getLastRevisionId(), OptionalLong.empty());
+    }
+}
diff --git a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java
index 190382db..b7a79fb0 100644
--- a/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java
+++ b/wdtk-wikibaseapi/src/test/java/org/wikidata/wdtk/wikibaseapi/WikibaseDataEditorTest.java
@@ -22,9 +22,11 @@ package org.wikidata.wdtk.wikibaseapi;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeStringValue;
+import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeQuantityValue;
 import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeWikidataFormIdValue;
 import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeWikidataItemIdValue;
 import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeWikidataLexemeIdValue;
@@ -32,6 +34,7 @@ import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeWikidataProperty
 import static org.wikidata.wdtk.datamodel.helpers.Datamodel.makeWikidataSenseIdValue;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -55,23 +58,30 @@ import org.wikidata.wdtk.datamodel.helpers.TermUpdateBuilder;
 import org.wikidata.wdtk.datamodel.interfaces.DatatypeIdValue;
 import org.wikidata.wdtk.datamodel.interfaces.EntityDocument;
 import org.wikidata.wdtk.datamodel.interfaces.EntityUpdate;
+import org.wikidata.wdtk.datamodel.interfaces.FormDocument;
 import org.wikidata.wdtk.datamodel.interfaces.FormUpdate;
 import org.wikidata.wdtk.datamodel.interfaces.ItemDocument;
 import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
 import org.wikidata.wdtk.datamodel.interfaces.ItemUpdate;
+import org.wikidata.wdtk.datamodel.interfaces.LexemeDocument;
 import org.wikidata.wdtk.datamodel.interfaces.LexemeUpdate;
 import org.wikidata.wdtk.datamodel.interfaces.MediaInfoDocument;
 import org.wikidata.wdtk.datamodel.interfaces.MediaInfoIdValue;
 import org.wikidata.wdtk.datamodel.interfaces.MonolingualTextValue;
 import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
 import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.SenseDocument;
 import org.wikidata.wdtk.datamodel.interfaces.SenseUpdate;
 import org.wikidata.wdtk.datamodel.interfaces.Statement;
+import org.wikidata.wdtk.testing.MockStringContentFactory;
 import org.wikidata.wdtk.util.CompressionType;
 import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
 import org.wikidata.wdtk.wikibaseapi.apierrors.TagsApplyNotAllowedException;
 import org.wikidata.wdtk.wikibaseapi.apierrors.TokenErrorException;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import static org.junit.Assert.*;
 
 public class WikibaseDataEditorTest {
@@ -81,6 +91,7 @@ public class WikibaseDataEditorTest {
 	PropertyIdValue P31 = Datamodel.makeWikidataPropertyIdValue("P31");
 	static final String TEST_GUID = "427C0317-BA8C-95B0-16C8-1A1B5FAC1081";
 	MockGuidGenerator guids = new MockGuidGenerator(TEST_GUID);
+	ObjectMapper mapper = new ObjectMapper();
 
 	@Before
 	public void setUp() throws IOException {
@@ -878,26 +889,41 @@ public class WikibaseDataEditorTest {
 				Collections.<Statement>emptyList(), "testing tags", Collections.singletonList("tag_which_does_not_exist"));
 	}
 
-	private WbEditingAction mockEntityUpdate(EntityUpdate update) throws MediaWikiApiErrorException, IOException {
-		WbEditingAction action = mock(WbEditingAction.class);
+	private EditingResult mockEntityUpdate(WbEditingAction action, EntityUpdate update) throws MediaWikiApiErrorException, IOException {
 		WikibaseDataFetcher fetcher = new WikibaseDataFetcher(con, Datamodel.SITE_WIKIDATA);
 		WikibaseDataEditor wde = new WikibaseDataEditor(action, fetcher, Datamodel.SITE_WIKIDATA, guids);
-		wde.editEntityDocument(update, false, "test summary", Arrays.asList("tag1"));
-		return action;
+		return wde.editEntityDocument(update, false, "test summary", Arrays.asList("tag1"));
+	}
+	
+	private JsonNode json(String resourceFileName) {
+	    try {
+    	    String contents = MockStringContentFactory
+                    .getStringFromUrl(WikibaseDataEditor.class.getResource(resourceFileName));
+    	    return mapper.readTree(contents);
+	    } catch (Exception e) {
+	        throw new IllegalArgumentException("Unable to read test JSON resource "+resourceFileName);
+	    }
 	}
 
 	@Test
 	public void testReductionToSetNewClaim() throws MediaWikiApiErrorException, IOException {
 		ItemIdValue subject = makeWikidataItemIdValue("Q1");
 		Statement statement = StatementBuilder.forSubjectAndProperty(subject, makeWikidataPropertyIdValue("P1"))
-				.withValue(makeStringValue("some value"))
+				.withValue(makeQuantityValue(new BigDecimal("456")))
 				.build();
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder.forBaseRevisionId(subject, 123)
+	    Statement statementWithId = statement.withStatementId(guids.freshStatementId(subject.getId()));
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetClaim(
+                JsonSerializer.getJsonString(statementWithId), false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbsetclaim.json"));
+		
+		EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder.forBaseRevisionId(subject, 123)
 				.updateStatements(StatementUpdateBuilder.create()
 						.add(statement)
 						.build())
 				.build());
-		Statement statementWithId = statement.withStatementId(guids.freshStatementId(subject.getId()));
+		
+		assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetClaim(
 				JsonSerializer.getJsonString(statementWithId), false, 123, "test summary", Arrays.asList("tag1"));
 	}
@@ -906,12 +932,19 @@ public class WikibaseDataEditorTest {
 	public void testReductionToSetExistingClaim() throws MediaWikiApiErrorException, IOException {
 		ItemIdValue subject = makeWikidataItemIdValue("Q1");
 		Statement statement = StatementBuilder.forSubjectAndProperty(subject, makeWikidataPropertyIdValue("P1"))
-				.withValue(makeStringValue("some value"))
+				.withValue(makeQuantityValue(new BigDecimal("456")))
 				.withId(guids.freshStatementId(subject.getId()))
 				.build();
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder.forBaseRevisionId(subject, 123)
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetClaim(
+                JsonSerializer.getJsonString(statement), false, 123, "test summary", Arrays.asList("tag1")))
+            .thenReturn(json("/wbsetclaim.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder.forBaseRevisionId(subject, 123)
 				.updateStatements(StatementUpdateBuilder.create().replace(statement).build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetClaim(
 				JsonSerializer.getJsonString(statement), false, 123, "test summary", Arrays.asList("tag1"));
 	}
@@ -920,65 +953,106 @@ public class WikibaseDataEditorTest {
 	public void testReductionToRemoveClaims() throws MediaWikiApiErrorException, IOException {
 		ItemIdValue subject = makeWikidataItemIdValue("Q1");
 		String id = guids.freshStatementId(subject.getId());
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder.forBaseRevisionId(subject, 123)
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbRemoveClaims(Arrays.asList(id), false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbremoveclaims.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder.forBaseRevisionId(subject, 123)
 				.updateStatements(StatementUpdateBuilder.create().remove(id).build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbRemoveClaims(Arrays.asList(id), false, 123, "test summary", Arrays.asList("tag1"));
 	}
 
 	@Test
 	public void testReductionToSetLabel() throws MediaWikiApiErrorException, IOException {
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetLabel(
+                "Q1", null, null, null, "en", "hello", false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbsetlabel.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder
 				.forBaseRevisionId(makeWikidataItemIdValue("Q1"), 123)
 				.updateLabels(TermUpdateBuilder.create()
 						.put(Datamodel.makeMonolingualTextValue("hello", "en"))
 						.build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetLabel(
 				"Q1", null, null, null, "en", "hello", false, 123, "test summary", Arrays.asList("tag1"));
 	}
 
 	@Test
 	public void testReductionToSetNullLabel() throws MediaWikiApiErrorException, IOException {
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetLabel(
+                "Q1", null, null, null, "en", null, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbsetlabel-null.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder
 				.forBaseRevisionId(makeWikidataItemIdValue("Q1"), 123)
 				.updateLabels(TermUpdateBuilder.create().remove("en").build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetLabel(
 				"Q1", null, null, null, "en", null, false, 123, "test summary", Arrays.asList("tag1"));
 	}
 
 	@Test
 	public void testReductionToSetDescription() throws MediaWikiApiErrorException, IOException {
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetDescription(
+                "Q1", null, null, null, "en", "hello", false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbsetdescription.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder
 				.forBaseRevisionId(makeWikidataItemIdValue("Q1"), 123)
 				.updateDescriptions(TermUpdateBuilder.create()
 						.put(Datamodel.makeMonolingualTextValue("hello", "en"))
 						.build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetDescription(
 				"Q1", null, null, null, "en", "hello", false, 123, "test summary", Arrays.asList("tag1"));
 	}
 
 	@Test
 	public void testReductionToSetNullDescription() throws MediaWikiApiErrorException, IOException {
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetDescription(
+                "Q1", null, null, null, "en", null, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbsetdescription-null.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder
 				.forBaseRevisionId(makeWikidataItemIdValue("Q1"), 123)
 				.updateDescriptions(TermUpdateBuilder.create().remove("en").build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetDescription(
 				"Q1", null, null, null, "en", null, false, 123, "test summary", Arrays.asList("tag1"));
 	}
 
 	@Test
 	public void testReductionToSetAliases() throws MediaWikiApiErrorException, IOException {
-		WbEditingAction action = mockEntityUpdate(ItemUpdateBuilder
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbSetAliases("Q1", null, null, null, "en",
+                Arrays.asList("hello"), Arrays.asList("bye"), null, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(json("/wbsetaliases-add-remove.json"));
+		
+        EditingResult result = mockEntityUpdate(action, ItemUpdateBuilder
 				.forBaseRevisionId(makeWikidataItemIdValue("Q1"), 123)
 				.updateAliases("en", AliasUpdateBuilder.create()
 						.add(Datamodel.makeMonolingualTextValue("hello", "en"))
 						.remove(Datamodel.makeMonolingualTextValue("bye", "en"))
 						.build())
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbSetAliases("Q1", null, null, null, "en",
 				Arrays.asList("hello"), Arrays.asList("bye"), null, false, 123, "test summary", Arrays.asList("tag1"));
 	}
@@ -989,10 +1063,19 @@ public class WikibaseDataEditorTest {
 				.forEntityId(makeWikidataSenseIdValue("L1-S1"))
 				.updateGlosses(TermUpdateBuilder.create().remove("en").build())
 				.build();
-		WbEditingAction action = mockEntityUpdate(LexemeUpdateBuilder
+		SenseDocument senseDocument = mock(SenseDocument.class);
+		when(senseDocument.getRevisionId()).thenReturn(1234L);
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbEditEntity("L1-S1", null, null, null, JsonSerializer.getJsonString(update),
+                false, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(senseDocument);
+		
+        EditingResult result = mockEntityUpdate(action, LexemeUpdateBuilder
 				.forBaseRevisionId(makeWikidataLexemeIdValue("L1"), 123)
 				.updateSense(update)
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbEditEntity("L1-S1", null, null, null, JsonSerializer.getJsonString(update),
 				false, false, 123, "test summary", Arrays.asList("tag1"));
 	}
@@ -1003,10 +1086,19 @@ public class WikibaseDataEditorTest {
 				.forEntityId(makeWikidataFormIdValue("L1-F1"))
 				.updateRepresentations(TermUpdateBuilder.create().remove("en").build())
 				.build();
-		WbEditingAction action = mockEntityUpdate(LexemeUpdateBuilder
+		FormDocument formDocument = mock(FormDocument.class);
+        when(formDocument.getRevisionId()).thenReturn(1234L);
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbEditEntity("L1-F1", null, null, null, JsonSerializer.getJsonString(update),
+                false, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(formDocument);
+		
+        EditingResult result = mockEntityUpdate(action, LexemeUpdateBuilder
 				.forBaseRevisionId(makeWikidataLexemeIdValue("L1"), 123)
 				.updateForm(update)
 				.build());
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbEditEntity("L1-F1", null, null, null, JsonSerializer.getJsonString(update),
 				false, false, 123, "test summary", Arrays.asList("tag1"));
 	}
@@ -1017,7 +1109,16 @@ public class WikibaseDataEditorTest {
 				.forBaseRevisionId(makeWikidataLexemeIdValue("L1"), 123)
 				.setLanguage(Datamodel.makeWikidataItemIdValue("Q1"))
 				.build();
-		WbEditingAction action = mockEntityUpdate(update);
+		LexemeDocument lexemeDocument = mock(LexemeDocument.class);
+		when(lexemeDocument.getRevisionId()).thenReturn(1234L);
+		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbEditEntity("L1", null, null, null, JsonSerializer.getJsonString(update),
+                false, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(lexemeDocument);
+		
+        EditingResult result = mockEntityUpdate(action, update);
+        
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbEditEntity("L1", null, null, null, JsonSerializer.getJsonString(update),
 				false, false, 123, "test summary", Arrays.asList("tag1"));
 	}
@@ -1028,19 +1129,31 @@ public class WikibaseDataEditorTest {
 				.forBaseRevisionId(makeWikidataItemIdValue("Q1"), 123)
 				.updateLabels(TermUpdateBuilder.create().remove("en").build())
 				.build();
+		ItemDocument itemDocument = mock(ItemDocument.class);
+		when(itemDocument.getRevisionId()).thenReturn(1234L);
 		WbEditingAction action = mock(WbEditingAction.class);
+		when(action.wbEditEntity("Q1", null, null, null, JsonSerializer.getJsonString(update),
+                true, false, 123, "test summary", Arrays.asList("tag1")))
+		    .thenReturn(itemDocument);
 		WikibaseDataFetcher fetcher = new WikibaseDataFetcher(con, Datamodel.SITE_WIKIDATA);
 		WikibaseDataEditor wde = new WikibaseDataEditor(action, fetcher, Datamodel.SITE_WIKIDATA, guids);
-		wde.editEntityDocument(update, true, "test summary", Arrays.asList("tag1"));
+		
+		EditingResult result = wde.editEntityDocument(update, true, "test summary", Arrays.asList("tag1"));
+		
+        assertEquals(result, new EditingResult(1234L));
 		verify(action, only()).wbEditEntity("Q1", null, null, null, JsonSerializer.getJsonString(update),
 				true, false, 123, "test summary", Arrays.asList("tag1"));
 	}
 
 	@Test
 	public void testReductionToNoEdit() throws MediaWikiApiErrorException, IOException {
-		verifyNoInteractions(mockEntityUpdate(LexemeUpdateBuilder
-				.forBaseRevisionId(makeWikidataLexemeIdValue("L1"), 123)
-				.build()));
+	    WbEditingAction action = mock(WbEditingAction.class);
+	    EditingResult result = mockEntityUpdate(action, LexemeUpdateBuilder
+                .forBaseRevisionId(makeWikidataLexemeIdValue("L1"), 123)
+                .build());
+	    
+	    assertEquals(result, new EditingResult(0L));
+		verifyNoInteractions(action);
 	}
 
 }
diff --git a/wdtk-wikibaseapi/src/test/resources/wbremoveclaims.json b/wdtk-wikibaseapi/src/test/resources/wbremoveclaims.json
new file mode 100644
index 00000000..e58ff8ea
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbremoveclaims.json
@@ -0,0 +1,7 @@
+{
+  "pageinfo" : {
+    "lastrevid" : 1234
+  },
+  "success" : 1,
+  "claims" : [ "Q1$427C0317-BA8C-95B0-16C8-1A1B5FAC1081" ]
+}
diff --git a/wdtk-wikibaseapi/src/test/resources/wbsetaliases-add-remove.json b/wdtk-wikibaseapi/src/test/resources/wbsetaliases-add-remove.json
new file mode 100644
index 00000000..1e0944c5
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbsetaliases-add-remove.json
@@ -0,0 +1,17 @@
+{
+  "entity" : {
+    "aliases" : {
+      "en" : [ {
+        "language" : "en",
+        "value" : "old alias"
+      }, {
+        "language" : "en",
+        "value" : "hello"
+      } ]
+    },
+    "id" : "Q1",
+    "type" : "item",
+    "lastrevid" : 1234
+  },
+  "success" : 1
+}
\ No newline at end of file
diff --git a/wdtk-wikibaseapi/src/test/resources/wbsetclaim.json b/wdtk-wikibaseapi/src/test/resources/wbsetclaim.json
new file mode 100644
index 00000000..01abb069
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbsetclaim.json
@@ -0,0 +1,24 @@
+{
+  "pageinfo" : {
+    "lastrevid" : 1234
+  },
+  "success" : 1,
+  "claim" : {
+    "mainsnak" : {
+      "snaktype" : "value",
+      "property" : "P1",
+      "hash" : "a3438cfe0cbb10e069ce2dbae1d7baf9e81ac111",
+      "datavalue" : {
+        "value" : {
+          "amount" : "+456",
+          "unit" : "1"
+        },
+        "type" : "quantity"
+      },
+      "datatype" : "quantity"
+    },
+    "type" : "statement",
+    "id" : "Q1$427C0317-BA8C-95B0-16C8-1A1B5FAC1081",
+    "rank" : "normal"
+  }
+}
\ No newline at end of file
diff --git a/wdtk-wikibaseapi/src/test/resources/wbsetdescription-null.json b/wdtk-wikibaseapi/src/test/resources/wbsetdescription-null.json
new file mode 100644
index 00000000..5df1dec0
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbsetdescription-null.json
@@ -0,0 +1,14 @@
+{
+  "entity" : {
+    "descriptions" : {
+      "en" : {
+        "language" : "en",
+        "value" : "hello"
+      }
+    },
+    "id" : "Q1",
+    "type" : "item",
+    "lastrevid" : 1234
+  },
+  "success" : 1
+}
\ No newline at end of file
diff --git a/wdtk-wikibaseapi/src/test/resources/wbsetdescription.json b/wdtk-wikibaseapi/src/test/resources/wbsetdescription.json
new file mode 100644
index 00000000..5df1dec0
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbsetdescription.json
@@ -0,0 +1,14 @@
+{
+  "entity" : {
+    "descriptions" : {
+      "en" : {
+        "language" : "en",
+        "value" : "hello"
+      }
+    },
+    "id" : "Q1",
+    "type" : "item",
+    "lastrevid" : 1234
+  },
+  "success" : 1
+}
\ No newline at end of file
diff --git a/wdtk-wikibaseapi/src/test/resources/wbsetlabel-null.json b/wdtk-wikibaseapi/src/test/resources/wbsetlabel-null.json
new file mode 100644
index 00000000..a97d8e2e
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbsetlabel-null.json
@@ -0,0 +1,11 @@
+{
+  "entity" : {
+    "labels" : {
+      "en" : null
+    },
+    "id" : "Q1",
+    "type" : "item",
+    "lastrevid" : 1234
+  },
+  "success" : 1
+}
\ No newline at end of file
diff --git a/wdtk-wikibaseapi/src/test/resources/wbsetlabel.json b/wdtk-wikibaseapi/src/test/resources/wbsetlabel.json
new file mode 100644
index 00000000..7af27889
--- /dev/null
+++ b/wdtk-wikibaseapi/src/test/resources/wbsetlabel.json
@@ -0,0 +1,14 @@
+{
+  "entity" : {
+    "labels" : {
+      "en" : {
+        "language" : "en",
+        "value" : "hello"
+      }
+    },
+    "id" : "Q1",
+    "type" : "item",
+    "lastrevid" : 1234
+  },
+  "success" : 1
+}
\ No newline at end of file