Skip to content

Commit 039d197

Browse files
author
DrMimik
committed
rewrite idempotency logic
1 parent a1454eb commit 039d197

File tree

3 files changed

+41
-70
lines changed

3 files changed

+41
-70
lines changed

parse/src/main/java/com/parse/ParseRESTCommand.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.Collections;
2121
import java.util.Iterator;
2222
import java.util.Map;
23+
import java.util.UUID;
24+
2325
import org.json.JSONArray;
2426
import org.json.JSONException;
2527
import org.json.JSONObject;
@@ -37,8 +39,8 @@ class ParseRESTCommand extends ParseRequest<JSONObject> {
3739
/* package */ static final String HEADER_INSTALLATION_ID = "X-Parse-Installation-Id";
3840
/* package */ static final String HEADER_REQUEST_ID = "X-Parse-Request-Id";
3941
/* package */ static final String USER_AGENT = "User-Agent";
40-
/* package */ static final String HEADER_SESSION_TOKEN = "X-Parse-Session-Token";
41-
/* package */ static final String HEADER_MASTER_KEY = "X-Parse-Master-Key";
42+
private static final String HEADER_SESSION_TOKEN = "X-Parse-Session-Token";
43+
private static final String HEADER_MASTER_KEY = "X-Parse-Master-Key";
4244
private static final String PARAMETER_METHOD_OVERRIDE = "_method";
4345

4446
// Set via Parse.initialize(Configuration)
@@ -50,6 +52,7 @@ class ParseRESTCommand extends ParseRequest<JSONObject> {
5052
/* package */ String httpPath;
5153
private String installationId;
5254
private String operationSetUUID;
55+
private final String requestId = UUID.randomUUID().toString();
5356
private String localId;
5457

5558
public ParseRESTCommand(
@@ -216,20 +219,7 @@ protected void addAdditionalHeaders(ParseHttpRequest.Builder requestBuilder) {
216219
if (masterKey != null) {
217220
requestBuilder.addHeader(HEADER_MASTER_KEY, masterKey);
218221
}
219-
try {
220-
JSONObject jsonObject = jsonParameters != null ? new JSONObject(jsonParameters.toString()) : new JSONObject();
221-
// using header names so we don't override a parameter with the same key name,
222-
// using all headers to insure the requestId generated doesn't conflict with the rest of the users
223-
if (installationId != null)
224-
jsonObject.put(HEADER_INSTALLATION_ID, installationId);
225-
if (sessionToken != null)
226-
jsonObject.put(HEADER_SESSION_TOKEN, sessionToken);
227-
if (masterKey != null)
228-
jsonObject.put(HEADER_MASTER_KEY, masterKey);
229-
requestBuilder.addHeader(HEADER_REQUEST_ID, ParseDigestUtils.md5(toDeterministicString(jsonObject)));
230-
} catch (JSONException e) {
231-
throw new RuntimeException(e.getMessage());
232-
}
222+
requestBuilder.addHeader(HEADER_REQUEST_ID, requestId);
233223
}
234224

235225
@Override

parse/src/test/java/com/parse/ParseRESTUserCommandTest.java

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,13 @@
1919
import java.net.URL;
2020
import java.util.HashMap;
2121
import java.util.Map;
22-
import java.util.Random;
23-
24-
import org.json.JSONArray;
2522
import org.json.JSONObject;
2623
import org.junit.After;
2724
import org.junit.Before;
2825
import org.junit.Test;
2926
import org.skyscreamer.jsonassert.JSONCompareMode;
3027

3128
public class ParseRESTUserCommandTest {
32-
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";
3329

3430
@Before
3531
public void setUp() throws MalformedURLException {
@@ -59,7 +55,7 @@ public void testGetCurrentUserCommand() {
5955
@Test
6056
public void testLogInUserCommand() throws Exception {
6157
ParseRESTUserCommand command =
62-
ParseRESTUserCommand.logInUserCommand("userName", "password", true);
58+
ParseRESTUserCommand.logInUserCommand("userName", "password", true);
6359

6460
assertEquals("login", command.httpPath);
6561
assertEquals(ParseHttpRequest.Method.GET, command.method);
@@ -72,7 +68,7 @@ public void testLogInUserCommand() throws Exception {
7268
@Test
7369
public void testResetPasswordResetCommand() throws Exception {
7470
ParseRESTUserCommand command =
75-
ParseRESTUserCommand.resetPasswordResetCommand("[email protected]");
71+
ParseRESTUserCommand.resetPasswordResetCommand("[email protected]");
7672

7773
assertEquals("requestPasswordReset", command.httpPath);
7874
assertEquals(ParseHttpRequest.Method.POST, command.method);
@@ -86,7 +82,7 @@ public void testSignUpUserCommand() throws Exception {
8682
JSONObject parameters = new JSONObject();
8783
parameters.put("key", "value");
8884
ParseRESTUserCommand command =
89-
ParseRESTUserCommand.signUpUserCommand(parameters, "sessionToken", true);
85+
ParseRESTUserCommand.signUpUserCommand(parameters, "sessionToken", true);
9086

9187
assertEquals("users", command.httpPath);
9288
assertEquals(ParseHttpRequest.Method.POST, command.method);
@@ -100,7 +96,7 @@ public void testServiceLogInUserCommandWithParameters() throws Exception {
10096
JSONObject parameters = new JSONObject();
10197
parameters.put("key", "value");
10298
ParseRESTUserCommand command =
103-
ParseRESTUserCommand.serviceLogInUserCommand(parameters, "sessionToken", true);
99+
ParseRESTUserCommand.serviceLogInUserCommand(parameters, "sessionToken", true);
104100

105101
assertEquals("users", command.httpPath);
106102
assertEquals(ParseHttpRequest.Method.POST, command.method);
@@ -114,7 +110,7 @@ public void testServiceLogInUserCommandWithAuthType() throws Exception {
114110
Map<String, String> facebookAuthData = new HashMap<>();
115111
facebookAuthData.put("token", "test");
116112
ParseRESTUserCommand command =
117-
ParseRESTUserCommand.serviceLogInUserCommand("facebook", facebookAuthData, true);
113+
ParseRESTUserCommand.serviceLogInUserCommand("facebook", facebookAuthData, true);
118114

119115
assertEquals("users", command.httpPath);
120116
assertEquals(ParseHttpRequest.Method.POST, command.method);
@@ -136,7 +132,7 @@ public void testAddAdditionalHeaders() throws Exception {
136132
JSONObject parameters = new JSONObject();
137133
parameters.put("key", "value");
138134
ParseRESTUserCommand command =
139-
ParseRESTUserCommand.signUpUserCommand(parameters, "sessionToken", true);
135+
ParseRESTUserCommand.signUpUserCommand(parameters, "sessionToken", true);
140136

141137
ParseHttpRequest.Builder requestBuilder = new ParseHttpRequest.Builder();
142138
command.addAdditionalHeaders(requestBuilder);
@@ -157,54 +153,15 @@ public void testOnResponseAsync() {
157153
int statusCode = 200;
158154

159155
ParseHttpResponse response =
160-
new ParseHttpResponse.Builder()
161-
.setContent(new ByteArrayInputStream(content.getBytes()))
162-
.setContentType(contentType)
163-
.setStatusCode(statusCode)
164-
.build();
156+
new ParseHttpResponse.Builder()
157+
.setContent(new ByteArrayInputStream(content.getBytes()))
158+
.setContentType(contentType)
159+
.setStatusCode(statusCode)
160+
.build();
165161
command.onResponseAsync(response, null);
166162

167163
assertEquals(200, command.getStatusCode());
168164
}
169165

170-
@Test
171-
public void testRequestIdHeader() throws Exception {
172-
JSONArray nestedJSONArray = new JSONArray().put(true).put(1).put("test");
173-
JSONObject nestedJSON =
174-
new JSONObject().put("bool", false).put("int", 2).put("string", "test");
175-
String sessionToken = generateRandomString(32);
176-
String installationId = generateRandomString(32);
177-
String masterKey = generateRandomString(32);
178-
JSONObject json =
179-
new JSONObject()
180-
.put("json", nestedJSON)
181-
.put("jsonArray", nestedJSONArray)
182-
.put("bool", true)
183-
.put("int", 3)
184-
.put("string", "test");
185-
186-
String jsonString = ParseRESTCommand.toDeterministicString(json);
187-
188-
JSONObject jsonAgain = new JSONObject(jsonString);
189-
jsonAgain.put(ParseRESTCommand.HEADER_INSTALLATION_ID, installationId);
190-
jsonAgain.put(ParseRESTCommand.HEADER_SESSION_TOKEN, sessionToken);
191-
jsonAgain.put(ParseRESTCommand.HEADER_MASTER_KEY, masterKey);
192-
ParseRESTCommand restCommand = new ParseRESTCommand.Builder().jsonParameters(json)
193-
.installationId(installationId).sessionToken(sessionToken).masterKey(masterKey)
194-
.build();
195-
196-
ParseHttpRequest.Builder builder = new ParseHttpRequest.Builder();
197-
restCommand.addAdditionalHeaders(builder);
198-
assertEquals(ParseDigestUtils.md5(ParseRESTCommand.toDeterministicString(jsonAgain)), builder.build().getHeader(ParseRESTCommand.HEADER_REQUEST_ID));
199-
}
200-
201-
private static String generateRandomString(final int sizeOfRandomString) {
202-
final Random random = new Random();
203-
final StringBuilder sb = new StringBuilder(sizeOfRandomString);
204-
for (int i = 0; i < sizeOfRandomString; ++i)
205-
sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
206-
return sb.toString();
207-
}
208-
209166
// endregion
210167
}

parse/src/test/java/com/parse/ParseRequestTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
import static org.junit.Assert.assertEquals;
1212
import static org.junit.Assert.assertFalse;
13+
import static org.junit.Assert.assertNotNull;
1314
import static org.junit.Assert.assertTrue;
1415
import static org.mockito.ArgumentMatchers.any;
16+
import static org.mockito.ArgumentMatchers.argThat;
1517
import static org.mockito.Mockito.mock;
1618
import static org.mockito.Mockito.times;
1719
import static org.mockito.Mockito.verify;
@@ -24,8 +26,11 @@
2426
import java.io.ByteArrayInputStream;
2527
import java.io.File;
2628
import java.io.IOException;
29+
import java.net.URL;
2730
import java.util.LinkedList;
2831
import java.util.List;
32+
import java.util.concurrent.atomic.AtomicReference;
33+
2934
import org.junit.After;
3035
import org.junit.AfterClass;
3136
import org.junit.Before;
@@ -121,6 +126,25 @@ public void testDownloadProgress() throws Exception {
121126
assertProgressCompletedSuccessfully(downloadProgressCallback);
122127
}
123128

129+
@Test
130+
public void testIdempotencyLogic() throws Exception {
131+
ParseHttpClient mockHttpClient = mock(ParseHttpClient.class);
132+
AtomicReference<String> requestIdAtomicReference = new AtomicReference<>();
133+
when(mockHttpClient.execute(argThat(argument -> {
134+
assertNotNull(argument.getHeader(ParseRESTCommand.HEADER_REQUEST_ID));
135+
if (requestIdAtomicReference.get() == null) requestIdAtomicReference.set(argument.getHeader(ParseRESTCommand.HEADER_REQUEST_ID));
136+
assertEquals(argument.getHeader(ParseRESTCommand.HEADER_REQUEST_ID), requestIdAtomicReference.get());
137+
return true;
138+
}))).thenThrow(new IOException());
139+
140+
ParseRESTCommand.server = new URL("http://parse.com");
141+
ParseRESTCommand command = new ParseRESTCommand.Builder().build();
142+
Task<Void> task = command.executeAsync(mockHttpClient).makeVoid();
143+
task.waitForCompletion();
144+
145+
verify(mockHttpClient, times(5)).execute(any(ParseHttpRequest.class));
146+
}
147+
124148
private static class TestProgressCallback implements ProgressCallback {
125149
final List<Integer> history = new LinkedList<>();
126150

0 commit comments

Comments
 (0)