diff --git a/src/main/java/com/google/firebase/remoteconfig/Condition.java b/src/main/java/com/google/firebase/remoteconfig/Condition.java index c10498c36..6ccde59d9 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Condition.java +++ b/src/main/java/com/google/firebase/remoteconfig/Condition.java @@ -69,9 +69,7 @@ public Condition(@NonNull String name, @NonNull String expression, @Nullable Tag checkNotNull(conditionResponse); this.name = conditionResponse.getName(); this.expression = conditionResponse.getExpression(); - if (conditionResponse.getTagColor() == null) { - this.tagColor = TagColor.UNSPECIFIED; - } else { + if (!Strings.isNullOrEmpty(conditionResponse.getTagColor())) { this.tagColor = TagColor.valueOf(conditionResponse.getTagColor()); } } @@ -168,4 +166,9 @@ public boolean equals(Object o) { return Objects.equals(name, condition.name) && Objects.equals(expression, condition.expression) && tagColor == condition.tagColor; } + + @Override + public int hashCode() { + return Objects.hash(name, expression, tagColor); + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index aff5439ac..7425673fb 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -121,7 +121,7 @@ public Template publishTemplate(@NonNull Template template, boolean validateOnly boolean forcePublish) throws FirebaseRemoteConfigException { checkArgument(template != null, "Template must not be null."); HttpRequestInfo request = HttpRequestInfo.buildRequest("PUT", remoteConfigUrl, - new JsonHttpContent(jsonFactory, template.toTemplateResponse())) + new JsonHttpContent(jsonFactory, template.toTemplateResponse(false))) .addAllHeaders(COMMON_HEADERS) .addHeader("If-Match", forcePublish ? "*" : template.getETag()); if (validateOnly) { diff --git a/src/main/java/com/google/firebase/remoteconfig/RemoteConfigUtil.java b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigUtil.java index 13a49076a..4011af8ff 100644 --- a/src/main/java/com/google/firebase/remoteconfig/RemoteConfigUtil.java +++ b/src/main/java/com/google/firebase/remoteconfig/RemoteConfigUtil.java @@ -20,20 +20,74 @@ import com.google.common.base.Strings; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; final class RemoteConfigUtil { + // SimpleDateFormat cannot handle fractional seconds in timestamps + // (example: "2014-10-02T15:01:23.045123456Z"). Therefore, we strip fractional seconds + // from the date string (example: "2014-10-02T15:01:23") when parsing Zulu timestamp strings. + // The backend API expects timestamps in Zulu format with fractional seconds. To generate correct + // timestamps in payloads we use ".SSS000000'Z'" suffix. + // Hence, two Zulu date patterns are used below. + private static final String ZULU_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS000000'Z'"; + private static final String ZULU_DATE_NO_FRAC_SECS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; + private static final String UTC_DATE_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z"; + private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC"); + static boolean isValidVersionNumber(String versionNumber) { return !Strings.isNullOrEmpty(versionNumber) && versionNumber.matches("^\\d+$"); } + static long convertToMilliseconds(String dateString) throws ParseException { + try { + return convertFromUtcZuluFormat(dateString); + } catch (ParseException e) { + return convertFromUtcDateFormat(dateString); + } + } + static String convertToUtcZuluFormat(long millis) { + // sample output date string: 2020-11-12T22:12:02.000000000Z checkArgument(millis >= 0, "Milliseconds duration must not be negative"); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS000000'Z'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + SimpleDateFormat dateFormat = new SimpleDateFormat(ZULU_DATE_PATTERN); + dateFormat.setTimeZone(UTC_TIME_ZONE); return dateFormat.format(new Date(millis)); } + + static String convertToUtcDateFormat(long millis) { + // sample output date string: Tue, 08 Dec 2020 15:49:51 GMT + checkArgument(millis >= 0, "Milliseconds duration must not be negative"); + SimpleDateFormat dateFormat = new SimpleDateFormat(UTC_DATE_PATTERN); + dateFormat.setTimeZone(UTC_TIME_ZONE); + return dateFormat.format(new Date(millis)); + } + + static long convertFromUtcZuluFormat(String dateString) throws ParseException { + checkArgument(!Strings.isNullOrEmpty(dateString), "Date string must not be null or empty"); + // Input timestamp is in RFC3339 UTC "Zulu" format, accurate to + // nanoseconds (up to 9 fractional seconds digits). + // SimpleDateFormat cannot handle fractional seconds, therefore we strip fractional seconds + // from the input date string before parsing. + // example: input -> "2014-10-02T15:01:23.045123456Z" + // formatted -> "2014-10-02T15:01:23" + int indexOfPeriod = dateString.indexOf("."); + if (indexOfPeriod != -1) { + dateString = dateString.substring(0, indexOfPeriod); + } + SimpleDateFormat dateFormat = new SimpleDateFormat(ZULU_DATE_NO_FRAC_SECS_PATTERN); + dateFormat.setTimeZone(UTC_TIME_ZONE); + return dateFormat.parse(dateString).getTime(); + } + + static long convertFromUtcDateFormat(String dateString) throws ParseException { + // sample input date string: Tue, 08 Dec 2020 15:49:51 GMT + checkArgument(!Strings.isNullOrEmpty(dateString), "Date string must not be null or empty"); + SimpleDateFormat dateFormat = new SimpleDateFormat(UTC_DATE_PATTERN); + dateFormat.setTimeZone(UTC_TIME_ZONE); + return dateFormat.parse(dateString).getTime(); + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/Template.java b/src/main/java/com/google/firebase/remoteconfig/Template.java index c6ffcb49a..24bd68a5b 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Template.java +++ b/src/main/java/com/google/firebase/remoteconfig/Template.java @@ -16,11 +16,17 @@ package com.google.firebase.remoteconfig; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.client.googleapis.util.Utils; +import com.google.api.client.json.JsonFactory; +import com.google.common.base.Strings; +import com.google.firebase.ErrorCode; import com.google.firebase.internal.NonNull; import com.google.firebase.remoteconfig.internal.TemplateResponse; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,11 +46,18 @@ public final class Template { /** * Creates a new {@link Template}. + * + * @param etag The ETag of this template. */ - public Template() { - parameters = new HashMap<>(); - conditions = new ArrayList<>(); - parameterGroups = new HashMap<>(); + public Template(String etag) { + this.parameters = new HashMap<>(); + this.conditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + this.etag = etag; + } + + Template() { + this((String) null); } Template(@NonNull TemplateResponse templateResponse) { @@ -73,6 +86,29 @@ public Template() { if (templateResponse.getVersion() != null) { this.version = new Version(templateResponse.getVersion()); } + this.etag = templateResponse.getEtag(); + } + + /** + * Creates and returns a new Remote Config template from a JSON string. + * Input JSON string must contain an {@code etag} property to create a valid template. + * + * @param json A non-null JSON string to populate a Remote Config template. + * @return A new {@link Template} instance. + * @throws FirebaseRemoteConfigException If the input JSON string is not parsable. + */ + public static Template fromJSON(@NonNull String json) throws FirebaseRemoteConfigException { + checkArgument(!Strings.isNullOrEmpty(json), "JSON String must not be null or empty."); + // using the default json factory as no rpc calls are made here + JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); + try { + TemplateResponse templateResponse = jsonFactory.createJsonParser(json) + .parseAndClose(TemplateResponse.class); + return new Template(templateResponse); + } catch (IOException e) { + throw new FirebaseRemoteConfigException(ErrorCode.INVALID_ARGUMENT, + "Unable to parse JSON string."); + } } /** @@ -177,12 +213,26 @@ public Template setVersion(Version version) { return this; } + /** + * Gets the JSON-serializable representation of this template. + * + * @return A JSON-serializable representation of this {@link Template} instance. + */ + public String toJSON() { + JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); + try { + return jsonFactory.toString(this.toTemplateResponse(true)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + Template setETag(String etag) { this.etag = etag; return this; } - TemplateResponse toTemplateResponse() { + TemplateResponse toTemplateResponse(boolean includeAll) { Map parameterResponses = new HashMap<>(); for (Map.Entry entry : this.parameters.entrySet()) { parameterResponses.put(entry.getKey(), entry.getValue().toParameterResponse()); @@ -196,12 +246,16 @@ TemplateResponse toTemplateResponse() { parameterGroupResponse.put(entry.getKey(), entry.getValue().toParameterGroupResponse()); } TemplateResponse.VersionResponse versionResponse = (this.version == null) ? null - : this.version.toVersionResponse(); - return new TemplateResponse() + : this.version.toVersionResponse(includeAll); + TemplateResponse templateResponse = new TemplateResponse() .setParameters(parameterResponses) .setConditions(conditionResponses) .setParameterGroups(parameterGroupResponse) .setVersion(versionResponse); + if (includeAll) { + return templateResponse.setEtag(this.etag); + } + return templateResponse; } @Override diff --git a/src/main/java/com/google/firebase/remoteconfig/User.java b/src/main/java/com/google/firebase/remoteconfig/User.java index ae21328b0..264fe8b63 100644 --- a/src/main/java/com/google/firebase/remoteconfig/User.java +++ b/src/main/java/com/google/firebase/remoteconfig/User.java @@ -88,4 +88,11 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(email, name, imageUrl); } + + UserResponse toUserResponse() { + return new UserResponse() + .setEmail(this.email) + .setImageUrl(this.imageUrl) + .setName(this.name); + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/Version.java b/src/main/java/com/google/firebase/remoteconfig/Version.java index e3c182457..5aa6c942d 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Version.java +++ b/src/main/java/com/google/firebase/remoteconfig/Version.java @@ -25,11 +25,7 @@ import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Objects; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Represents a Remote Config template version. @@ -64,18 +60,8 @@ private Version() { this.versionNumber = versionResponse.getVersionNumber(); if (!Strings.isNullOrEmpty(versionResponse.getUpdateTime())) { - // Update Time is a timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. - // example: "2014-10-02T15:01:23.045123456Z" - // SimpleDateFormat cannot handle nanoseconds, therefore we strip nanoseconds from the string. - String updateTime = versionResponse.getUpdateTime(); - int indexOfPeriod = updateTime.indexOf("."); - if (indexOfPeriod != -1) { - updateTime = updateTime.substring(0, indexOfPeriod); - } - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); try { - this.updateTime = dateFormat.parse(updateTime).getTime(); + this.updateTime = RemoteConfigUtil.convertToMilliseconds(versionResponse.getUpdateTime()); } catch (ParseException e) { throw new IllegalStateException("Unable to parse update time.", e); } @@ -196,9 +182,19 @@ public Version setDescription(String description) { return this; } - VersionResponse toVersionResponse() { - return new VersionResponse() - .setDescription(this.description); + VersionResponse toVersionResponse(boolean includeAll) { + VersionResponse versionResponse = new VersionResponse().setDescription(this.description); + if (includeAll) { + versionResponse.setUpdateTime(this.updateTime > 0L + ? RemoteConfigUtil.convertToUtcDateFormat(this.updateTime) : null) + .setLegacy(this.legacy) + .setRollbackSource(this.rollbackSource) + .setUpdateOrigin(this.updateOrigin) + .setUpdateType(this.updateType) + .setUpdateUser((this.updateUser == null) ? null : this.updateUser.toUserResponse()) + .setVersionNumber(this.versionNumber); + } + return versionResponse; } @Override diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java index b89abf1eb..09e71ef84 100644 --- a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java +++ b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java @@ -39,6 +39,11 @@ public final class TemplateResponse { @Key("version") private VersionResponse version; + // For local JSON serialization and deserialization purposes only. + // ETag in response type is never set by the HTTP response. + @Key("etag") + private String etag; + public Map getParameters() { return parameters; } @@ -55,6 +60,10 @@ public VersionResponse getVersion() { return version; } + public String getEtag() { + return etag; + } + public TemplateResponse setParameters( Map parameters) { this.parameters = parameters; @@ -78,6 +87,11 @@ public TemplateResponse setVersion(VersionResponse version) { return this; } + public TemplateResponse setEtag(String etag) { + this.etag = etag; + return this; + } + /** * The Data Transfer Object for parsing Remote Config parameter responses from the * Remote Config service. diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index 690abcb99..9be41852d 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -105,7 +105,6 @@ public class FirebaseRemoteConfigClientImplTest { .setTagColor(TagColor.INDIGO), new Condition("android_en", "device.os == 'android' && device.country in ['us', 'uk']") - .setTagColor(TagColor.UNSPECIFIED) ); private static final Version EXPECTED_VERSION = new Version(new TemplateResponse.VersionResponse() diff --git a/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java b/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java index 57f5a2116..ad4d48f87 100644 --- a/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java @@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.firebase.remoteconfig.internal.TemplateResponse; import java.util.List; import java.util.Map; @@ -91,9 +92,22 @@ public void testConstructor() { assertNull(template.getETag()); } + @Test + public void testConstructorWithETag() { + Template template = new Template("etag-01-324324"); + + assertNotNull(template.getParameters()); + assertNotNull(template.getConditions()); + assertNotNull(template.getParameterGroups()); + assertTrue(template.getParameters().isEmpty()); + assertTrue(template.getConditions().isEmpty()); + assertTrue(template.getParameterGroups().isEmpty()); + assertEquals("etag-01-324324", template.getETag()); + } + @Test(expected = NullPointerException.class) public void testConstructorWithNullTemplateResponse() { - new Template(null); + new Template((TemplateResponse) null); } @Test(expected = NullPointerException.class) @@ -133,8 +147,7 @@ public void testEquality() { assertEquals(TEMPLATE_WITH_CONDITIONS_PARAMETERS_GROUPS, templateThree); - final Template templateFour = new Template() - .setETag("etag-123456789097-20"); + final Template templateFour = new Template("etag-123456789097-20"); assertEquals(TEMPLATE_WITH_ETAG, templateFour); @@ -153,4 +166,181 @@ public void testEquality() { assertNotEquals(templateThree, templateFive); assertNotEquals(templateFour, templateFive); } + + @Test(expected = FirebaseRemoteConfigException.class) + public void testFromJSONWithInvalidString() throws FirebaseRemoteConfigException { + Template.fromJSON("abc"); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromJSONWithEmptyString() throws FirebaseRemoteConfigException { + Template.fromJSON(""); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromJSONWithNullString() throws FirebaseRemoteConfigException { + Template.fromJSON(null); + } + + @Test + public void testFromJSONWithEmptyTemplateString() throws FirebaseRemoteConfigException { + Template template = Template.fromJSON("{}"); + + assertNotNull(template.getParameters()); + assertNotNull(template.getConditions()); + assertNotNull(template.getParameterGroups()); + assertTrue(template.getParameters().isEmpty()); + assertTrue(template.getConditions().isEmpty()); + assertTrue(template.getParameterGroups().isEmpty()); + assertNull(template.getETag()); + } + + @Test + public void testFromJSONWithNonEmptyTemplateString() throws FirebaseRemoteConfigException { + Template template = Template.fromJSON("{" + + " \"etag\": \"etag-001234\"," + + " \"conditions\": [" + + " {" + + " \"name\": \"ios_en\"," + + " \"expression\": \"device.os == 'ios' && device.country in ['us', 'uk']\"," + + " \"tagColor\": \"INDIGO\"" + + " }," + + " {" + + " \"name\": \"android_en\"," + + " \"expression\": \"device.os == 'android' && device.country in ['us', 'uk']\"" + + " }" + + " ]" + + "}"); + + assertNotNull(template.getParameters()); + assertNotNull(template.getConditions()); + assertNotNull(template.getParameterGroups()); + assertTrue(template.getParameters().isEmpty()); + assertEquals(2, template.getConditions().size()); + assertEquals("ios_en", template.getConditions().get(0).getName()); + assertEquals("device.os == 'ios' && device.country in ['us', 'uk']", + template.getConditions().get(0).getExpression()); + assertEquals(TagColor.INDIGO, template.getConditions().get(0).getTagColor()); + assertEquals("android_en", template.getConditions().get(1).getName()); + assertEquals("device.os == 'android' && device.country in ['us', 'uk']", + template.getConditions().get(1).getExpression()); + assertNull(template.getConditions().get(1).getTagColor()); + assertTrue(template.getParameterGroups().isEmpty()); + assertEquals("etag-001234", template.getETag()); + } + + @Test + public void testFromJSONWithVersion() throws FirebaseRemoteConfigException { + final Version expectedVersion = new Version(new TemplateResponse.VersionResponse() + .setDescription("template version") + .setUpdateTime("2020-12-08T15:49:51.887878Z") + .setUpdateUser(new TemplateResponse.UserResponse().setEmail("user@user.com")) + .setLegacy(false) + .setUpdateType("INCREMENTAL_UPDATE") + .setRollbackSource("26") + .setVersionNumber("34") + .setUpdateOrigin("ADMIN_SDK_NODE") + ); + String jsonString = "{\"parameters\":{},\"conditions\":[],\"parameterGroups\":{}," + + "\"version\":{\"versionNumber\":\"34\"," + + "\"updateTime\":\"Tue, 08 Dec 2020 15:49:51 UTC\"," + + "\"updateOrigin\":\"ADMIN_SDK_NODE\",\"updateType\":\"INCREMENTAL_UPDATE\"," + + "\"updateUser\":{\"email\":\"user@user.com\"},\"rollbackSource\":\"26\"," + + "\"legacy\":false,\"description\":\"template version\"}}"; + Template template = Template.fromJSON(jsonString); + + assertNotNull(template.getParameters()); + assertNotNull(template.getConditions()); + assertNotNull(template.getParameterGroups()); + assertTrue(template.getParameters().isEmpty()); + assertTrue(template.getConditions().isEmpty()); + assertTrue(template.getParameterGroups().isEmpty()); + assertNull(template.getETag()); + // check version + assertEquals(expectedVersion, template.getVersion()); + // update time should be correctly converted to milliseconds + assertEquals(1607442591000L, template.getVersion().getUpdateTime()); + } + + @Test + public void testToJSONWithEmptyTemplate() { + String jsonString = new Template().toJSON(); + + assertEquals("{\"conditions\":[]," + + "\"parameterGroups\":{},\"parameters\":{}}", jsonString); + } + + @Test + public void testToJSONWithParameterValues() { + Template t = new Template(); + t.getParameters() + .put("with_value", new Parameter().setDefaultValue(ParameterValue.of("hello"))); + t.getParameters() + .put("with_inApp", new Parameter().setDefaultValue(ParameterValue.inAppDefault())); + String jsonString = t.toJSON(); + + assertEquals("{\"conditions\":[],\"parameterGroups\":{}," + + "\"parameters\":{\"with_value\":{\"conditionalValues\":{}," + + "\"defaultValue\":{\"value\":\"hello\"}},\"with_inApp\":{\"conditionalValues\":{}," + + "\"defaultValue\":{\"useInAppDefault\":true}}}}", jsonString); + } + + @Test + public void testToJSONWithEtag() { + String jsonString = new Template("etag-12345").toJSON(); + + assertEquals("{\"conditions\":[],\"etag\":\"etag-12345\",\"parameterGroups\":{}," + + "\"parameters\":{}}", jsonString); + } + + @Test + public void testToJSONWithEtagAndConditions() { + String jsonString = new Template("etag-0010201") + .setConditions(CONDITIONS).toJSON(); + + assertEquals("{\"conditions\":[{\"expression\":\"exp ios\",\"name\":\"ios_en\"," + + "\"tagColor\":\"INDIGO\"},{\"expression\":\"exp android\"," + + "\"name\":\"android_en\"}],\"etag\":\"etag-0010201\",\"parameterGroups\":{}," + + "\"parameters\":{}}", jsonString); + } + + @Test + public void testToJSONWithVersion() { + Version version = new Version(new TemplateResponse.VersionResponse() + .setDescription("template version") + .setUpdateTime("2020-12-08T15:49:51.887878Z") + .setUpdateUser(new TemplateResponse.UserResponse().setEmail("user@user.com")) + .setLegacy(false) + .setUpdateType("INCREMENTAL_UPDATE") + .setRollbackSource("26") + .setVersionNumber("34") + .setUpdateOrigin("ADMIN_SDK_NODE") + ); + String jsonString = new Template().setVersion(version).toJSON(); + + assertEquals("{\"conditions\":[],\"parameterGroups\":{},\"parameters\":{}," + + "\"version\":{\"description\":\"template version\",\"legacy\":false," + + "\"rollbackSource\":\"26\",\"updateOrigin\":\"ADMIN_SDK_NODE\"," + + "\"updateTime\":\"Tue, 08 Dec 2020 15:49:51 UTC\"," + + "\"updateType\":\"INCREMENTAL_UPDATE\",\"updateUser\":{" + + "\"email\":\"user@user.com\"},\"versionNumber\":\"34\"}}", jsonString); + } + + @Test + public void testToJSONAndFromJSON() throws FirebaseRemoteConfigException { + Template originalTemplate = new Template(); + Template otherTemplate = Template.fromJSON(originalTemplate.toJSON()); + + assertEquals(originalTemplate, otherTemplate); + + Version expectedVersion = Version.withDescription("promo version"); + Template expectedTemplate = new Template("etag-0010201") + .setParameters(PARAMETERS) + .setConditions(CONDITIONS) + .setParameterGroups(PARAMETER_GROUPS) + .setVersion(expectedVersion); + Template actualTemplate = Template.fromJSON(expectedTemplate.toJSON()); + + assertEquals(expectedTemplate, actualTemplate); + } } diff --git a/src/test/java/com/google/firebase/remoteconfig/VersionTest.java b/src/test/java/com/google/firebase/remoteconfig/VersionTest.java index c0a600445..515629660 100644 --- a/src/test/java/com/google/firebase/remoteconfig/VersionTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/VersionTest.java @@ -38,6 +38,20 @@ public void testConstructorWithInvalidUpdateTime() { .setUpdateTime("sunday,26th")); } + @Test + public void testConstructorWithValidZuluUpdateTime() { + Version version = new Version(new VersionResponse() + .setUpdateTime("2020-12-08T15:49:51.887878Z")); + assertEquals(1607442591000L, version.getUpdateTime()); + } + + @Test + public void testConstructorWithValidUTCUpdateTime() { + Version version = new Version(new VersionResponse() + .setUpdateTime("Tue, 08 Dec 2020 15:49:51 GMT")); + assertEquals(1607442591000L, version.getUpdateTime()); + } + @Test public void testWithDescription() { final Version version = Version.withDescription("version description text");