|
3 | 3 | import com.google.common.collect.ImmutableList; |
4 | 4 | import com.google.common.collect.ImmutableSet; |
5 | 5 | import com.launchdarkly.sdk.LDValue; |
| 6 | +import com.launchdarkly.sdk.ObjectBuilder; |
6 | 7 | import com.launchdarkly.sdk.UserAttribute; |
7 | 8 | import com.launchdarkly.sdk.server.DataModel.Clause; |
8 | 9 | import com.launchdarkly.sdk.server.DataModel.FeatureFlag; |
9 | 10 | import com.launchdarkly.sdk.server.DataModel.Operator; |
| 11 | +import com.launchdarkly.sdk.server.DataModel.Prerequisite; |
10 | 12 | import com.launchdarkly.sdk.server.DataModel.Rule; |
11 | 13 | import com.launchdarkly.sdk.server.DataModel.Segment; |
12 | 14 | import com.launchdarkly.sdk.server.DataModel.SegmentRule; |
13 | 15 | import com.launchdarkly.sdk.server.DataModel.Target; |
| 16 | +import com.launchdarkly.sdk.server.DataModel.WeightedVariation; |
14 | 17 | import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor; |
15 | 18 |
|
16 | 19 | import org.junit.Test; |
17 | 20 |
|
| 21 | +import java.util.Collections; |
| 22 | +import java.util.function.Consumer; |
| 23 | + |
18 | 24 | import static com.launchdarkly.sdk.server.DataModel.FEATURES; |
19 | 25 | import static com.launchdarkly.sdk.server.DataModel.SEGMENTS; |
20 | 26 | import static org.junit.Assert.assertEquals; |
|
27 | 33 | public class DataModelSerializationTest { |
28 | 34 | @Test |
29 | 35 | public void flagIsDeserializedWithAllProperties() { |
30 | | - String json0 = flagWithAllPropertiesJson().toJsonString(); |
31 | | - FeatureFlag flag0 = (FeatureFlag)FEATURES.deserialize(json0).getItem(); |
32 | | - assertFlagHasAllProperties(flag0); |
33 | | - |
34 | | - String json1 = FEATURES.serialize(new ItemDescriptor(flag0.getVersion(), flag0)); |
35 | | - FeatureFlag flag1 = (FeatureFlag)FEATURES.deserialize(json1).getItem(); |
36 | | - assertFlagHasAllProperties(flag1); |
| 36 | + assertFlagFromJson( |
| 37 | + flagWithAllPropertiesJson(), |
| 38 | + flag -> { |
| 39 | + assertFlagHasAllProperties(flag); |
| 40 | + |
| 41 | + String json1 = FEATURES.serialize(new ItemDescriptor(flag.getVersion(), flag)); |
| 42 | + assertFlagFromJson(LDValue.parse(json1), flag1 -> assertFlagHasAllProperties(flag1)); |
| 43 | + }); |
37 | 44 | } |
38 | 45 |
|
39 | 46 | @Test |
40 | 47 | public void flagIsDeserializedWithMinimalProperties() { |
41 | | - String json = LDValue.buildObject().put("key", "flag-key").put("version", 99).build().toJsonString(); |
42 | | - FeatureFlag flag = (FeatureFlag)FEATURES.deserialize(json).getItem(); |
43 | | - assertEquals("flag-key", flag.getKey()); |
44 | | - assertEquals(99, flag.getVersion()); |
45 | | - assertFalse(flag.isOn()); |
46 | | - assertNull(flag.getSalt()); |
47 | | - assertNotNull(flag.getTargets()); |
48 | | - assertEquals(0, flag.getTargets().size()); |
49 | | - assertNotNull(flag.getRules()); |
50 | | - assertEquals(0, flag.getRules().size()); |
51 | | - assertNull(flag.getFallthrough()); |
52 | | - assertNull(flag.getOffVariation()); |
53 | | - assertNotNull(flag.getVariations()); |
54 | | - assertEquals(0, flag.getVariations().size()); |
55 | | - assertFalse(flag.isClientSide()); |
56 | | - assertFalse(flag.isTrackEvents()); |
57 | | - assertFalse(flag.isTrackEventsFallthrough()); |
58 | | - assertNull(flag.getDebugEventsUntilDate()); |
| 48 | + assertFlagFromJson( |
| 49 | + LDValue.buildObject().put("key", "flag-key").put("version", 99).build(), |
| 50 | + flag -> { |
| 51 | + assertEquals("flag-key", flag.getKey()); |
| 52 | + assertEquals(99, flag.getVersion()); |
| 53 | + assertFalse(flag.isOn()); |
| 54 | + assertNull(flag.getSalt()); |
| 55 | + assertNotNull(flag.getTargets()); |
| 56 | + assertEquals(0, flag.getTargets().size()); |
| 57 | + assertNotNull(flag.getRules()); |
| 58 | + assertEquals(0, flag.getRules().size()); |
| 59 | + assertNull(flag.getFallthrough()); |
| 60 | + assertNull(flag.getOffVariation()); |
| 61 | + assertNotNull(flag.getVariations()); |
| 62 | + assertEquals(0, flag.getVariations().size()); |
| 63 | + assertFalse(flag.isClientSide()); |
| 64 | + assertFalse(flag.isTrackEvents()); |
| 65 | + assertFalse(flag.isTrackEventsFallthrough()); |
| 66 | + assertNull(flag.getDebugEventsUntilDate()); |
| 67 | + }); |
59 | 68 | } |
60 | 69 |
|
61 | 70 | @Test |
@@ -109,6 +118,147 @@ public void deletedSegmentIsConvertedToAndFromJsonPlaceholder() { |
109 | 118 | assertEquals(LDValue.parse(json0), LDValue.parse(json1)); |
110 | 119 | } |
111 | 120 |
|
| 121 | + @Test |
| 122 | + public void explicitNullsAreToleratedForNullableValues() { |
| 123 | + // Nulls are not *always* valid-- it is OK to raise a deserialization error if a null appears |
| 124 | + // where a non-nullable primitive type like boolean is expected, so for instance "version":null |
| 125 | + // is invalid. But for anything that is optional, an explicit null is equivalent to omitting |
| 126 | + // the property. Note: it would be nice to use Optional<T> for things like this, but we can't |
| 127 | + // do it because Gson does not play well with Optional. |
| 128 | + assertFlagFromJson( |
| 129 | + baseBuilder("flag-key").put("offVariation", LDValue.ofNull()).build(), |
| 130 | + flag -> assertNull(flag.getOffVariation()) |
| 131 | + ); |
| 132 | + assertFlagFromJson( |
| 133 | + baseBuilder("flag-key") |
| 134 | + .put("fallthrough", LDValue.buildObject().put("rollout", LDValue.ofNull()).build()) |
| 135 | + .build(), |
| 136 | + flag -> assertNull(flag.getFallthrough().getRollout()) |
| 137 | + ); |
| 138 | + assertFlagFromJson( |
| 139 | + baseBuilder("flag-key") |
| 140 | + .put("fallthrough", LDValue.buildObject().put("variation", LDValue.ofNull()).build()) |
| 141 | + .build(), |
| 142 | + flag -> assertNull(flag.getFallthrough().getVariation()) |
| 143 | + ); |
| 144 | + |
| 145 | + // Nulls for list values should always be considered equivalent to an empty list, because |
| 146 | + // that's how Go would serialize a nil slice |
| 147 | + assertFlagFromJson( |
| 148 | + baseBuilder("flag-key").put("prerequisites", LDValue.ofNull()).build(), |
| 149 | + flag -> assertEquals(Collections.<Prerequisite>emptyList(), flag.getPrerequisites()) |
| 150 | + ); |
| 151 | + assertFlagFromJson( |
| 152 | + baseBuilder("flag-key").put("rules", LDValue.ofNull()).build(), |
| 153 | + flag -> assertEquals(Collections.<Rule>emptyList(), flag.getRules()) |
| 154 | + ); |
| 155 | + assertFlagFromJson( |
| 156 | + baseBuilder("flag-key").put("targets", LDValue.ofNull()).build(), |
| 157 | + flag -> assertEquals(Collections.<Target>emptyList(), flag.getTargets()) |
| 158 | + ); |
| 159 | + assertFlagFromJson( |
| 160 | + baseBuilder("flag-key") |
| 161 | + .put("rules", LDValue.arrayOf( |
| 162 | + LDValue.buildObject().put("clauses", LDValue.ofNull()).build() |
| 163 | + )) |
| 164 | + .build(), |
| 165 | + flag -> assertEquals(Collections.<Clause>emptyList(), flag.getRules().get(0).getClauses()) |
| 166 | + ); |
| 167 | + assertFlagFromJson( |
| 168 | + baseBuilder("flag-key") |
| 169 | + .put("rules", LDValue.arrayOf( |
| 170 | + LDValue.buildObject().put("clauses", LDValue.arrayOf( |
| 171 | + LDValue.buildObject().put("values", LDValue.ofNull()).build() |
| 172 | + )).build() |
| 173 | + )) |
| 174 | + .build(), |
| 175 | + flag -> assertEquals(Collections.<LDValue>emptyList(), |
| 176 | + flag.getRules().get(0).getClauses().get(0).getValues()) |
| 177 | + ); |
| 178 | + assertFlagFromJson( |
| 179 | + baseBuilder("flag-key") |
| 180 | + .put("targets", LDValue.arrayOf( |
| 181 | + LDValue.buildObject().put("values", LDValue.ofNull()).build() |
| 182 | + )) |
| 183 | + .build(), |
| 184 | + flag -> assertEquals(Collections.<String>emptySet(), flag.getTargets().get(0).getValues()) |
| 185 | + ); |
| 186 | + assertFlagFromJson( |
| 187 | + baseBuilder("flag-key") |
| 188 | + .put("fallthrough", LDValue.buildObject().put("rollout", |
| 189 | + LDValue.buildObject().put("variations", LDValue.ofNull()).build() |
| 190 | + ).build()) |
| 191 | + .build(), |
| 192 | + flag -> assertEquals(Collections.<WeightedVariation>emptyList(), |
| 193 | + flag.getFallthrough().getRollout().getVariations()) |
| 194 | + ); |
| 195 | + assertSegmentFromJson( |
| 196 | + baseBuilder("segment-key").put("rules", LDValue.ofNull()).build(), |
| 197 | + segment -> assertEquals(Collections.<SegmentRule>emptyList(), segment.getRules()) |
| 198 | + ); |
| 199 | + assertSegmentFromJson( |
| 200 | + baseBuilder("segment-key") |
| 201 | + .put("rules", LDValue.arrayOf( |
| 202 | + LDValue.buildObject().put("clauses", LDValue.ofNull()).build() |
| 203 | + )) |
| 204 | + .build(), |
| 205 | + segment -> assertEquals(Collections.<Clause>emptyList(), segment.getRules().get(0).getClauses()) |
| 206 | + ); |
| 207 | + |
| 208 | + // Nulls in clause values are not useful since the clause can never match, but they're valid JSON; |
| 209 | + // we should normalize them to LDValue.ofNull() to avoid potential NPEs down the line |
| 210 | + assertFlagFromJson( |
| 211 | + baseBuilder("flag-key") |
| 212 | + .put("rules", LDValue.arrayOf( |
| 213 | + LDValue.buildObject() |
| 214 | + .put("clauses", LDValue.arrayOf( |
| 215 | + LDValue.buildObject() |
| 216 | + .put("values", LDValue.arrayOf(LDValue.ofNull())) |
| 217 | + .build() |
| 218 | + )) |
| 219 | + .build() |
| 220 | + )) |
| 221 | + .build(), |
| 222 | + flag -> assertEquals(LDValue.ofNull(), |
| 223 | + flag.getRules().get(0).getClauses().get(0).getValues().get(0)) |
| 224 | + ); |
| 225 | + assertSegmentFromJson( |
| 226 | + baseBuilder("segment-key") |
| 227 | + .put("rules", LDValue.arrayOf( |
| 228 | + LDValue.buildObject() |
| 229 | + .put("clauses", LDValue.arrayOf( |
| 230 | + LDValue.buildObject() |
| 231 | + .put("values", LDValue.arrayOf(LDValue.ofNull())) |
| 232 | + .build() |
| 233 | + )) |
| 234 | + .build() |
| 235 | + )) |
| 236 | + .build(), |
| 237 | + segment -> assertEquals(LDValue.ofNull(), |
| 238 | + segment.getRules().get(0).getClauses().get(0).getValues().get(0)) |
| 239 | + ); |
| 240 | + |
| 241 | + // Similarly, null for a flag variation isn't a useful value but it is valid JSON |
| 242 | + assertFlagFromJson( |
| 243 | + baseBuilder("flagKey").put("variations", LDValue.arrayOf(LDValue.ofNull())).build(), |
| 244 | + flag -> assertEquals(LDValue.ofNull(), flag.getVariations().get(0)) |
| 245 | + ); |
| 246 | + } |
| 247 | + |
| 248 | + private void assertFlagFromJson(LDValue flagJson, Consumer<FeatureFlag> action) { |
| 249 | + FeatureFlag flag = (FeatureFlag)FEATURES.deserialize(flagJson.toJsonString()).getItem(); |
| 250 | + action.accept(flag); |
| 251 | + } |
| 252 | + |
| 253 | + private void assertSegmentFromJson(LDValue segmentJson, Consumer<Segment> action) { |
| 254 | + Segment segment = (Segment)SEGMENTS.deserialize(segmentJson.toJsonString()).getItem(); |
| 255 | + action.accept(segment); |
| 256 | + } |
| 257 | + |
| 258 | + private ObjectBuilder baseBuilder(String key) { |
| 259 | + return LDValue.buildObject().put("key", key).put("version", 99); |
| 260 | + } |
| 261 | + |
112 | 262 | private LDValue flagWithAllPropertiesJson() { |
113 | 263 | return LDValue.buildObject() |
114 | 264 | .put("key", "flag-key") |
|
0 commit comments