Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/locales/android_translatable_strings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ payload.file.not.set=Payload file path is not set in preferences
custom.restore.file.not.exist=Custom restore file does not exist
custom.restore.file.not.set=Custom restore file path is not set in preferences
custom.restore.error=Error loading custom sync
restore.failed.error=Restore file contains at least one report with more than 100K records. Please report this problem to CommCare Support

start.recording=Start Recording
start.recording.failed=Unable to start recording while another application is using the microphone!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
public class CommcareRequestEndpointsMock implements CommcareRequestEndpoints {
private final static List<Integer> caseFetchResponseCodeStack = new ArrayList<>();
private static String errorMessagePayload;
private static String errorMessageContentType;

/**
* Set the response code for the next N requests
Expand All @@ -41,8 +42,9 @@ public static void setCaseFetchResponseCodes(Integer[] responseCodes) {
/**
* Set the response body for the next 406 request
*/
public static void setErrorResponseBody(String body) {
public static void setErrorResponseBody(String body, String contentType) {
errorMessagePayload = body;
errorMessageContentType = contentType;
}

@Override
Expand All @@ -60,7 +62,8 @@ public Response<ResponseBody> makeCaseFetchRequest(String baseUri, boolean inclu
.build();
return OkHTTPResponseMockFactory.createResponse(202, headers);
} else if (responseCode == 406) {
ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json"), errorMessagePayload);

ResponseBody responseBody = ResponseBody.create(MediaType.parse(errorMessageContentType), errorMessagePayload);
return Response.error(responseCode, responseBody);
} else {
return OkHTTPResponseMockFactory.createResponse(responseCode);
Expand Down
81 changes: 75 additions & 6 deletions app/src/org/commcare/network/HttpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@
import org.commcare.utils.StringUtils;
import org.javarosa.core.model.User;
import org.javarosa.core.services.locale.Localization;
import org.javarosa.xml.ElementParser;
import org.javarosa.xml.util.InvalidStructureException;
import org.javarosa.xml.util.UnfullfilledRequirementsException;
import org.json.JSONException;
import org.json.JSONObject;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Credentials;
import okhttp3.ResponseBody;
Expand Down Expand Up @@ -88,13 +95,75 @@ public static String parseUserVisibleError(Response<ResponseBody> response) {
String responseStr = null;
try {
responseStr = response.errorBody().string();
JSONObject errorKeyAndDefault = new JSONObject(responseStr);

Map<String, String> errorBodyKeyValuePairs = null;
if (response.errorBody().contentType().toString().contains("application/json")) {
errorBodyKeyValuePairs = parseJsonErrorResponseBody(responseStr);
} else {
errorBodyKeyValuePairs = parseXmlErrorResponseBody(responseStr);
}
message = Localization.getWithDefault(
errorKeyAndDefault.getString("error"),
errorKeyAndDefault.getString("default_response"));
} catch (JSONException | IOException e) {
errorBodyKeyValuePairs.get("error"),
errorBodyKeyValuePairs.get("default_response"));
} catch (JSONException | IOException | InvalidStructureException | UnfullfilledRequirementsException
| XmlPullParserException e) {
message = responseStr != null ? responseStr : "Unknown issue";
}
return message;
}

/* *
* Parses the JSON-formatted error response body from HQ. The response body is expected to be in the format:
* {
* "error" : "error.message.key",
* "default_response" : "Default message in English"
* }
* Returns a map containing these key-value pairs.
*/
public static Map<String, String> parseJsonErrorResponseBody(String responseStr) throws JSONException {
Map<String, String> map = new HashMap<>();
JSONObject jsonObject = new JSONObject(responseStr);
if (jsonObject != null) {
map.put("error", jsonObject.getString("error"));
map.put("default_response", jsonObject.getString("default_response"));
}
return map;
}

/* *
* Parses XML-formatted error response body from HQ. The response body is expected to be in the format:
* <OpenRosaResponse xmlns="http://openrosa.org/http/response">
* <message nature="ota_restore_error">
* <error>
* error.message.string.key
* </error>
* <default_response>
* Default message in English
* </default_response>
* </message>
* </OpenRosaResponse>
* Returns a map containing the relevant key-value pairs.
*/
public static Map<String, String> parseXmlErrorResponseBody(String responseStr)
throws IOException, InvalidStructureException, UnfullfilledRequirementsException,
XmlPullParserException {

KXmlParser baseParser = ElementParser.instantiateParser(
new ByteArrayInputStream(responseStr.getBytes(StandardCharsets.UTF_8)));
ElementParser<Map<String, String>> responseParser = new ElementParser<>(baseParser) {
@Override
public Map<String, String> parse() throws InvalidStructureException, IOException,
XmlPullParserException {
Map<String, String> map = new HashMap<>();
checkNode("OpenRosaResponse");
nextTag("message");
nextTag("error");
map.put("error", parser.nextText());
nextTag("default_response");
map.put("default_response", parser.nextText());
return map;
}
};
return responseParser.parse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,11 @@ public void dataPullRecoverWithRetryTest() {
}

@Test
public void dataPullFailWithMessage() {
public void dataPullFailWithJsonMessage() {
installLoginAndUseLocalKeys();
CommcareRequestEndpointsMock.setErrorResponseBody("{\"error\": \"some.fake.locale.key\", \"default_response\": \"hello world\"}");
CommcareRequestEndpointsMock.setErrorResponseBody(
"{\"error\": \"some.fake.locale.key\", \"default_response\": \"hello world\"}",
"application/json");
runDataPull(406, GOOD_RESTORE);
Assert.assertEquals(DataPullTask.PullTaskResult.ACTIONABLE_FAILURE, dataPullResult.data);
Assert.assertEquals("hello world", dataPullResult.errorMessage);
Expand Down Expand Up @@ -156,6 +158,19 @@ public void asyncRestoreTest() {
Assert.assertTrue(pullTask.getAsyncRestoreHelper().serverProgressCompletedSoFar == 55);
}

@Test
public void dataPullFailWithXmlMessage() {
installLoginAndUseLocalKeys();
CommcareRequestEndpointsMock.setErrorResponseBody(
"<OpenRosaResponse xmlns=\"http://openrosa.org/http/response\"><message nature=" +
"\"ota_restore_error\"><error>some.fake.locale.key</error><default_response>hello world" +
"</default_response></message></OpenRosaResponse>",
"application/xml");
runDataPull(406, GOOD_RESTORE);
Assert.assertEquals(DataPullTask.PullTaskResult.ACTIONABLE_FAILURE, dataPullResult.data);
Assert.assertEquals("hello world", dataPullResult.errorMessage);
}

private static void runDataPullWithAsyncRestore() {
runDataPull(new Integer[]{202, 202, 202, 200},
new String[]{RETRY_RESPONSE, RETRY_RESPONSE, RETRY_RESPONSE, GOOD_RESTORE});
Expand Down