blob: b1c7b46cb2b5584bdfb86904937e082fd99c497c [file] [log] [blame]
Jon Skeetfb248822015-09-04 12:41:14 +01001#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2008 Google Inc. All rights reserved.
4// https://developers.google.com/protocol-buffers/
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10// * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12// * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16// * Neither the name of Google Inc. nor the names of its
17// contributors may be used to endorse or promote products derived from
18// this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#endregion
32
33using Google.Protobuf.TestProtos;
34using Google.Protobuf.WellKnownTypes;
35using NUnit.Framework;
36using System;
37
38namespace Google.Protobuf
39{
40 /// <summary>
41 /// Unit tests for JSON parsing. Some tests are ignored at the moment as the desired behaviour
42 /// isn't fully known, either in terms of which exceptions should be thrown or whether they should
43 /// count as valid values.
44 /// </summary>
45 public class JsonParserTest
46 {
47 // Sanity smoke test
48 [Test]
49 public void AllTypesRoundtrip()
50 {
51 AssertRoundtrip(SampleMessages.CreateFullTestAllTypes());
52 }
53
54 [Test]
55 public void Maps()
56 {
57 AssertRoundtrip(new TestMap { MapStringString = { { "with spaces", "bar" }, { "a", "b" } } });
58 AssertRoundtrip(new TestMap { MapInt32Int32 = { { 0, 1 }, { 2, 3 } } });
59 AssertRoundtrip(new TestMap { MapBoolBool = { { false, true }, { true, false } } });
60 }
61
62 [Test]
63 [TestCase(" 1 ")]
64 [TestCase("+1")]
65 [TestCase("1,000")]
66 [TestCase("1.5")]
67 public void IntegerMapKeysAreStrict(string keyText)
68 {
69 // Test that integer parsing is strict. We assume that if this is correct for int32,
70 // it's correct for other numeric key types.
71 var json = "{ \"mapInt32Int32\": { \"" + keyText + "\" : \"1\" } }";
72 Assert.Throws<InvalidProtocolBufferException>(() => JsonParser.Default.Parse<TestMap>(json));
73 }
74
75 [Test]
76 public void SourceContextRoundtrip()
77 {
78 AssertRoundtrip(new SourceContext { FileName = "foo.proto" });
79 }
80
81 [Test]
82 public void SingularWrappers_DefaultNonNullValues()
83 {
84 var message = new TestWellKnownTypes
85 {
86 StringField = "",
87 BytesField = ByteString.Empty,
88 BoolField = false,
89 FloatField = 0f,
90 DoubleField = 0d,
91 Int32Field = 0,
92 Int64Field = 0,
93 Uint32Field = 0,
94 Uint64Field = 0
95 };
96 AssertRoundtrip(message);
97 }
98
99 [Test]
100 public void SingularWrappers_NonDefaultValues()
101 {
102 var message = new TestWellKnownTypes
103 {
104 StringField = "x",
105 BytesField = ByteString.CopyFrom(1, 2, 3),
106 BoolField = true,
107 FloatField = 12.5f,
108 DoubleField = 12.25d,
109 Int32Field = 1,
110 Int64Field = 2,
111 Uint32Field = 3,
112 Uint64Field = 4
113 };
114 AssertRoundtrip(message);
115 }
116
117 [Test]
118 public void SingularWrappers_ExplicitNulls()
119 {
120 var message = new TestWellKnownTypes();
121 var json = new JsonFormatter(new JsonFormatter.Settings(true)).Format(message);
122 var parsed = JsonParser.Default.Parse<TestWellKnownTypes>(json);
123 Assert.AreEqual(message, parsed);
124 }
125
126 [Test]
127 [TestCase(typeof(Int32Value), "32", 32)]
128 [TestCase(typeof(Int64Value), "32", 32L)]
129 [TestCase(typeof(UInt32Value), "32", 32U)]
130 [TestCase(typeof(UInt64Value), "32", 32UL)]
131 [TestCase(typeof(StringValue), "\"foo\"", "foo")]
132 [TestCase(typeof(FloatValue), "1.5", 1.5f)]
133 [TestCase(typeof(DoubleValue), "1.5", 1.5d)]
134 public void Wrappers_Standalone(System.Type wrapperType, string json, object expectedValue)
135 {
136 IMessage parsed = (IMessage) Activator.CreateInstance(wrapperType);
137 IMessage expected = (IMessage) Activator.CreateInstance(wrapperType);
138 JsonParser.Default.Merge(parsed, "null");
139 Assert.AreEqual(expected, parsed);
140
141 JsonParser.Default.Merge(parsed, json);
142 expected.Descriptor.Fields[Wrappers.WrapperValueFieldNumber].Accessor.SetValue(expected, expectedValue);
143 Assert.AreEqual(expected, parsed);
144 }
145
146 [Test]
147 public void BytesWrapper_Standalone()
148 {
149 ByteString data = ByteString.CopyFrom(1, 2, 3);
150 // Can't do this with attributes...
151 var parsed = JsonParser.Default.Parse<BytesValue>("\"" + data.ToBase64() + "\"");
152 var expected = new BytesValue { Value = data };
153 Assert.AreEqual(expected, parsed);
154 }
155
156 [Test]
157 public void RepeatedWrappers()
158 {
159 var message = new RepeatedWellKnownTypes
160 {
161 BoolField = { true, false },
162 BytesField = { ByteString.CopyFrom(1, 2, 3), ByteString.CopyFrom(4, 5, 6), ByteString.Empty },
163 DoubleField = { 12.5, -1.5, 0d },
164 FloatField = { 123.25f, -20f, 0f },
165 Int32Field = { int.MaxValue, int.MinValue, 0 },
166 Int64Field = { long.MaxValue, long.MinValue, 0L },
167 StringField = { "First", "Second", "" },
168 Uint32Field = { uint.MaxValue, uint.MinValue, 0U },
169 Uint64Field = { ulong.MaxValue, ulong.MinValue, 0UL },
170 };
171 AssertRoundtrip(message);
172 }
173
174 [Test]
175 public void IndividualWrapperTypes()
176 {
177 Assert.AreEqual(new StringValue { Value = "foo" }, StringValue.Parser.ParseJson("\"foo\""));
178 Assert.AreEqual(new Int32Value { Value = 1 }, Int32Value.Parser.ParseJson("1"));
179 // Can parse strings directly too
180 Assert.AreEqual(new Int32Value { Value = 1 }, Int32Value.Parser.ParseJson("\"1\""));
181 }
182
183 private static void AssertRoundtrip<T>(T message) where T : IMessage<T>, new()
184 {
185 var clone = message.Clone();
186 var json = message.ToString();
187 var parsed = JsonParser.Default.Parse<T>(json);
188 Assert.AreEqual(clone, parsed);
189 }
190
191 [Test]
192 [TestCase("0", 0)]
193 [TestCase("-0", 0)] // Not entirely clear whether we intend to allow this...
194 [TestCase("1", 1)]
195 [TestCase("-1", -1)]
196 [TestCase("2147483647", 2147483647)]
197 [TestCase("-2147483648", -2147483648)]
198 public void StringToInt32_Valid(string jsonValue, int expectedParsedValue)
199 {
200 string json = "{ \"singleInt32\": \"" + jsonValue + "\"}";
201 var parsed = TestAllTypes.Parser.ParseJson(json);
202 Assert.AreEqual(expectedParsedValue, parsed.SingleInt32);
203 }
204
205 [Test]
206 [TestCase("+0")]
207 [TestCase("00")]
208 [TestCase("-00")]
209 [TestCase("--1")]
210 [TestCase("+1")]
211 [TestCase("1.5")]
212 [TestCase("1e10")]
213 [TestCase("2147483648")]
214 [TestCase("-2147483649")]
215 public void StringToInt32_Invalid(string jsonValue)
216 {
217 string json = "{ \"singleInt32\": \"" + jsonValue + "\"}";
218 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
219 }
220
221 [Test]
222 [TestCase("0", 0U)]
223 [TestCase("1", 1U)]
224 [TestCase("4294967295", 4294967295U)]
225 public void StringToUInt32_Valid(string jsonValue, uint expectedParsedValue)
226 {
227 string json = "{ \"singleUint32\": \"" + jsonValue + "\"}";
228 var parsed = TestAllTypes.Parser.ParseJson(json);
229 Assert.AreEqual(expectedParsedValue, parsed.SingleUint32);
230 }
231
232 // Assume that anything non-bounds-related is covered in the Int32 case
233 [Test]
234 [TestCase("-1")]
235 [TestCase("4294967296")]
236 public void StringToUInt32_Invalid(string jsonValue)
237 {
238 string json = "{ \"singleUint32\": \"" + jsonValue + "\"}";
239 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
240 }
241
242 [Test]
243 [TestCase("0", 0L)]
244 [TestCase("1", 1L)]
245 [TestCase("-1", -1L)]
246 [TestCase("9223372036854775807", 9223372036854775807)]
247 [TestCase("-9223372036854775808", -9223372036854775808)]
248 public void StringToInt64_Valid(string jsonValue, long expectedParsedValue)
249 {
250 string json = "{ \"singleInt64\": \"" + jsonValue + "\"}";
251 var parsed = TestAllTypes.Parser.ParseJson(json);
252 Assert.AreEqual(expectedParsedValue, parsed.SingleInt64);
253 }
254
255 // Assume that anything non-bounds-related is covered in the Int32 case
256 [Test]
257 [TestCase("-9223372036854775809")]
258 [TestCase("9223372036854775808")]
259 public void StringToInt64_Invalid(string jsonValue)
260 {
261 string json = "{ \"singleInt64\": \"" + jsonValue + "\"}";
262 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
263 }
264
265 [Test]
266 [TestCase("0", 0UL)]
267 [TestCase("1", 1UL)]
268 [TestCase("18446744073709551615", 18446744073709551615)]
269 public void StringToUInt64_Valid(string jsonValue, ulong expectedParsedValue)
270 {
271 string json = "{ \"singleUint64\": \"" + jsonValue + "\"}";
272 var parsed = TestAllTypes.Parser.ParseJson(json);
273 Assert.AreEqual(expectedParsedValue, parsed.SingleUint64);
274 }
275
276 // Assume that anything non-bounds-related is covered in the Int32 case
277 [Test]
278 [TestCase("-1")]
279 [TestCase("18446744073709551616")]
280 public void StringToUInt64_Invalid(string jsonValue)
281 {
282 string json = "{ \"singleUint64\": \"" + jsonValue + "\"}";
283 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
284 }
285
286 [Test]
287 [TestCase("0", 0d)]
288 [TestCase("1", 1d)]
289 [TestCase("1.000000", 1d)]
290 [TestCase("1.0000000000000000000000001", 1d)] // We don't notice that we haven't preserved the exact value
291 [TestCase("-1", -1d)]
292 [TestCase("1e1", 10d)]
293 [TestCase("1e01", 10d)] // Leading decimals are allowed in exponents
294 [TestCase("1E1", 10d)] // Either case is fine
295 [TestCase("-1e1", -10d)]
296 [TestCase("1.5e1", 15d)]
297 [TestCase("-1.5e1", -15d)]
298 [TestCase("15e-1", 1.5d)]
299 [TestCase("-15e-1", -1.5d)]
300 [TestCase("1.79769e308", 1.79769e308)]
301 [TestCase("-1.79769e308", -1.79769e308)]
302 [TestCase("Infinity", double.PositiveInfinity)]
303 [TestCase("-Infinity", double.NegativeInfinity)]
304 [TestCase("NaN", double.NaN)]
305 public void StringToDouble_Valid(string jsonValue, double expectedParsedValue)
306 {
307 string json = "{ \"singleDouble\": \"" + jsonValue + "\"}";
308 var parsed = TestAllTypes.Parser.ParseJson(json);
309 Assert.AreEqual(expectedParsedValue, parsed.SingleDouble);
310 }
311
312 [Test]
313 [TestCase("1.7977e308")]
314 [TestCase("-1.7977e308")]
315 [TestCase("1e309")]
316 [TestCase("1,0")]
317 [TestCase("1.0.0")]
318 [TestCase("+1")]
319 [TestCase("00")]
320 [TestCase("--1")]
321 [TestCase("\u00BD")] // 1/2 as a single Unicode character. Just sanity checking...
322 public void StringToDouble_Invalid(string jsonValue)
323 {
324 string json = "{ \"singleDouble\": \"" + jsonValue + "\"}";
325 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
326 }
327
328 [Test]
329 [TestCase("0", 0f)]
330 [TestCase("1", 1f)]
331 [TestCase("1.000000", 1f)]
332 [TestCase("-1", -1f)]
333 [TestCase("3.402823e38", 3.402823e38f)]
334 [TestCase("-3.402823e38", -3.402823e38f)]
335 [TestCase("1.5e1", 15f)]
336 [TestCase("15e-1", 1.5f)]
337 public void StringToFloat_Valid(string jsonValue, float expectedParsedValue)
338 {
339 string json = "{ \"singleFloat\": \"" + jsonValue + "\"}";
340 var parsed = TestAllTypes.Parser.ParseJson(json);
341 Assert.AreEqual(expectedParsedValue, parsed.SingleFloat);
342 }
343
344 [Test]
345 [TestCase("3.402824e38")]
346 [TestCase("-3.402824e38")]
347 [TestCase("1,0")]
348 [TestCase("1.0.0")]
349 [TestCase("+1")]
350 [TestCase("00")]
351 [TestCase("--1")]
352 public void StringToFloat_Invalid(string jsonValue)
353 {
354 string json = "{ \"singleFloat\": \"" + jsonValue + "\"}";
355 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
356 }
357
358 [Test]
359 [TestCase("0", 0)]
360 [TestCase("-0", 0)] // Not entirely clear whether we intend to allow this...
361 [TestCase("1", 1)]
362 [TestCase("-1", -1)]
363 [TestCase("2147483647", 2147483647)]
364 [TestCase("-2147483648", -2147483648)]
365 public void NumberToInt32_Valid(string jsonValue, int expectedParsedValue)
366 {
367 string json = "{ \"singleInt32\": " + jsonValue + "}";
368 var parsed = TestAllTypes.Parser.ParseJson(json);
369 Assert.AreEqual(expectedParsedValue, parsed.SingleInt32);
370 }
371
372 [Test]
373 [TestCase("+0")]
374 [TestCase("00")]
375 [TestCase("-00")]
376 [TestCase("--1")]
377 [TestCase("+1")]
378 [TestCase("1.5", Ignore = true, Reason = "Desired behaviour unclear")]
379 [TestCase("1e10")]
380 [TestCase("2147483648")]
381 [TestCase("-2147483649")]
382 public void NumberToInt32_Invalid(string jsonValue)
383 {
384 string json = "{ \"singleInt32\": " + jsonValue + "}";
385 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
386 }
387
388 [Test]
389 [TestCase("0", 0U)]
390 [TestCase("1", 1U)]
391 [TestCase("4294967295", 4294967295U)]
392 public void NumberToUInt32_Valid(string jsonValue, uint expectedParsedValue)
393 {
394 string json = "{ \"singleUint32\": " + jsonValue + "}";
395 var parsed = TestAllTypes.Parser.ParseJson(json);
396 Assert.AreEqual(expectedParsedValue, parsed.SingleUint32);
397 }
398
399 // Assume that anything non-bounds-related is covered in the Int32 case
400 [Test]
401 [TestCase("-1")]
402 [TestCase("4294967296")]
403 public void NumberToUInt32_Invalid(string jsonValue)
404 {
405 string json = "{ \"singleUint32\": " + jsonValue + "}";
406 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
407 }
408
409 [Test]
410 [TestCase("0", 0L)]
411 [TestCase("1", 1L)]
412 [TestCase("-1", -1L)]
413 [TestCase("9223372036854775807", 9223372036854775807, Ignore = true, Reason = "Desired behaviour unclear")]
414 [TestCase("-9223372036854775808", -9223372036854775808, Ignore = true, Reason = "Desired behaviour unclear")]
415 public void NumberToInt64_Valid(string jsonValue, long expectedParsedValue)
416 {
417 string json = "{ \"singleInt64\": " + jsonValue + "}";
418 var parsed = TestAllTypes.Parser.ParseJson(json);
419 Assert.AreEqual(expectedParsedValue, parsed.SingleInt64);
420 }
421
422 // Assume that anything non-bounds-related is covered in the Int32 case
423 [Test]
424 [TestCase("-9223372036854775809", Ignore = true, Reason = "Desired behaviour unclear")]
425 [TestCase("9223372036854775808", Ignore = true, Reason = "Desired behaviour unclear")]
426 public void NumberToInt64_Invalid(string jsonValue)
427 {
428 string json = "{ \"singleInt64\": " + jsonValue + "}";
429 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
430 }
431
432 [Test]
433 [TestCase("0", 0UL)]
434 [TestCase("1", 1UL)]
435 [TestCase("18446744073709551615", 18446744073709551615, Ignore = true, Reason = "Desired behaviour unclear")]
436 public void NumberToUInt64_Valid(string jsonValue, ulong expectedParsedValue)
437 {
438 string json = "{ \"singleUint64\": " + jsonValue + "}";
439 var parsed = TestAllTypes.Parser.ParseJson(json);
440 Assert.AreEqual(expectedParsedValue, parsed.SingleUint64);
441 }
442
443 // Assume that anything non-bounds-related is covered in the Int32 case
444 [Test]
445 [TestCase("-1")]
446 [TestCase("18446744073709551616")]
447 public void NumberToUInt64_Invalid(string jsonValue)
448 {
449 string json = "{ \"singleUint64\": " + jsonValue + "}";
450 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
451 }
452
453 [Test]
454 [TestCase("0", 0d)]
455 [TestCase("1", 1d)]
456 [TestCase("1.000000", 1d)]
457 [TestCase("1.0000000000000000000000001", 1d)] // We don't notice that we haven't preserved the exact value
458 [TestCase("-1", -1d)]
459 [TestCase("1e1", 10d)]
460 [TestCase("1e01", 10d)] // Leading decimals are allowed in exponents
461 [TestCase("1E1", 10d)] // Either case is fine
462 [TestCase("-1e1", -10d)]
463 [TestCase("1.5e1", 15d)]
464 [TestCase("-1.5e1", -15d)]
465 [TestCase("15e-1", 1.5d)]
466 [TestCase("-15e-1", -1.5d)]
467 [TestCase("1.79769e308", 1.79769e308)]
468 [TestCase("-1.79769e308", -1.79769e308)]
469 public void NumberToDouble_Valid(string jsonValue, double expectedParsedValue)
470 {
471 string json = "{ \"singleDouble\": " + jsonValue + "}";
472 var parsed = TestAllTypes.Parser.ParseJson(json);
473 Assert.AreEqual(expectedParsedValue, parsed.SingleDouble);
474 }
475
476 [Test]
477 [TestCase("1.7977e308", Ignore = true, Reason = "Desired behaviour unclear")]
478 [TestCase("-1.7977e308", Ignore = true, Reason = "Desired behaviour unclear")]
479 [TestCase("1e309", Ignore = true, Reason = "Desired behaviour unclear")]
480 [TestCase("1,0")]
481 [TestCase("1.0.0")]
482 [TestCase("+1")]
483 [TestCase("00")]
484 [TestCase("--1")]
485 [TestCase("\u00BD")] // 1/2 as a single Unicode character. Just sanity checking...
486 public void NumberToDouble_Invalid(string jsonValue)
487 {
488 string json = "{ \"singleDouble\": " + jsonValue + "}";
489 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
490 }
491
492 [Test]
493 [TestCase("0", 0f)]
494 [TestCase("1", 1f)]
495 [TestCase("1.000000", 1f)]
496 [TestCase("-1", -1f)]
497 [TestCase("3.402823e38", 3.402823e38f)]
498 [TestCase("-3.402823e38", -3.402823e38f)]
499 [TestCase("1.5e1", 15f)]
500 [TestCase("15e-1", 1.5f)]
501 public void NumberToFloat_Valid(string jsonValue, float expectedParsedValue)
502 {
503 string json = "{ \"singleFloat\": " + jsonValue + "}";
504 var parsed = TestAllTypes.Parser.ParseJson(json);
505 Assert.AreEqual(expectedParsedValue, parsed.SingleFloat);
506 }
507
508 [Test]
509 [TestCase("3.402824e38")]
510 [TestCase("-3.402824e38")]
511 [TestCase("1,0")]
512 [TestCase("1.0.0")]
513 [TestCase("+1")]
514 [TestCase("00")]
515 [TestCase("--1")]
516 public void NumberToFloat_Invalid(string jsonValue)
517 {
518 string json = "{ \"singleFloat\": " + jsonValue + "}";
519 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
520 }
521
522 // The simplest way of testing that the value has parsed correctly is to reformat it,
523 // as we trust the formatting. In many cases that will give the same result as the input,
524 // so in those cases we accept an expectedFormatted value of null. Sometimes the results
525 // will be different though, due to a different number of digits being provided.
526 [Test]
527 // Z offset
528 [TestCase("2015-10-09T14:46:23.123456789Z", null)]
529 [TestCase("2015-10-09T14:46:23.123456Z", null)]
530 [TestCase("2015-10-09T14:46:23.123Z", null)]
531 [TestCase("2015-10-09T14:46:23Z", null)]
532 [TestCase("2015-10-09T14:46:23.123456000Z", "2015-10-09T14:46:23.123456Z")]
533 [TestCase("2015-10-09T14:46:23.1234560Z", "2015-10-09T14:46:23.123456Z")]
534 [TestCase("2015-10-09T14:46:23.123000000Z", "2015-10-09T14:46:23.123Z")]
535 [TestCase("2015-10-09T14:46:23.1230Z", "2015-10-09T14:46:23.123Z")]
536 [TestCase("2015-10-09T14:46:23.00Z", "2015-10-09T14:46:23Z")]
537
538 // +00:00 offset
539 [TestCase("2015-10-09T14:46:23.123456789+00:00", "2015-10-09T14:46:23.123456789Z")]
540 [TestCase("2015-10-09T14:46:23.123456+00:00", "2015-10-09T14:46:23.123456Z")]
541 [TestCase("2015-10-09T14:46:23.123+00:00", "2015-10-09T14:46:23.123Z")]
542 [TestCase("2015-10-09T14:46:23+00:00", "2015-10-09T14:46:23Z")]
543 [TestCase("2015-10-09T14:46:23.123456000+00:00", "2015-10-09T14:46:23.123456Z")]
544 [TestCase("2015-10-09T14:46:23.1234560+00:00", "2015-10-09T14:46:23.123456Z")]
545 [TestCase("2015-10-09T14:46:23.123000000+00:00", "2015-10-09T14:46:23.123Z")]
546 [TestCase("2015-10-09T14:46:23.1230+00:00", "2015-10-09T14:46:23.123Z")]
547 [TestCase("2015-10-09T14:46:23.00+00:00", "2015-10-09T14:46:23Z")]
548
549 // Other offsets (assume by now that the subsecond handling is okay)
550 [TestCase("2015-10-09T15:46:23.123456789+01:00", "2015-10-09T14:46:23.123456789Z")]
551 [TestCase("2015-10-09T13:46:23.123456789-01:00", "2015-10-09T14:46:23.123456789Z")]
552 [TestCase("2015-10-09T15:16:23.123456789+00:30", "2015-10-09T14:46:23.123456789Z")]
553 [TestCase("2015-10-09T14:16:23.123456789-00:30", "2015-10-09T14:46:23.123456789Z")]
554 [TestCase("2015-10-09T16:31:23.123456789+01:45", "2015-10-09T14:46:23.123456789Z")]
555 [TestCase("2015-10-09T13:01:23.123456789-01:45", "2015-10-09T14:46:23.123456789Z")]
556 [TestCase("2015-10-10T08:46:23.123456789+18:00", "2015-10-09T14:46:23.123456789Z")]
557 [TestCase("2015-10-08T20:46:23.123456789-18:00", "2015-10-09T14:46:23.123456789Z")]
558
559 // Leap years and min/max
560 [TestCase("2016-02-29T14:46:23.123456789Z", null)]
561 [TestCase("2000-02-29T14:46:23.123456789Z", null)]
562 [TestCase("0001-01-01T00:00:00Z", null)]
563 [TestCase("9999-12-31T23:59:59.999999999Z", null)]
564 public void Timestamp_Valid(string jsonValue, string expectedFormatted)
565 {
566 expectedFormatted = expectedFormatted ?? jsonValue;
567 string json = "\"" + jsonValue + "\"";
568 var parsed = Timestamp.Parser.ParseJson(json);
569 Assert.AreEqual(expectedFormatted, parsed.ToString());
570 }
571
572 [Test]
573 [TestCase("2015-10-09 14:46:23.123456789Z", Description = "No T between date and time")]
574 [TestCase("2015/10/09T14:46:23.123456789Z", Description = "Wrong date separators")]
575 [TestCase("2015-10-09T14.46.23.123456789Z", Description = "Wrong time separators")]
576 [TestCase("2015-10-09T14:46:23,123456789Z", Description = "Wrong fractional second separators (valid ISO-8601 though)")]
577 [TestCase(" 2015-10-09T14:46:23.123456789Z", Description = "Whitespace at start")]
578 [TestCase("2015-10-09T14:46:23.123456789Z ", Description = "Whitespace at end")]
579 [TestCase("2015-10-09T14:46:23.1234567890", Description = "Too many digits")]
580 [TestCase("2015-10-09T14:46:23.123456789", Description = "No offset")]
581 [TestCase("2015-13-09T14:46:23.123456789Z", Description = "Invalid month")]
582 [TestCase("2015-10-32T14:46:23.123456789Z", Description = "Invalid day")]
583 [TestCase("2015-10-09T24:00:00.000000000Z", Description = "Invalid hour (valid ISO-8601 though)")]
584 [TestCase("2015-10-09T14:60:23.123456789Z", Description = "Invalid minutes")]
585 [TestCase("2015-10-09T14:46:60.123456789Z", Description = "Invalid seconds")]
586 [TestCase("2015-10-09T14:46:23.123456789+18:01", Description = "Offset too large (positive)")]
587 [TestCase("2015-10-09T14:46:23.123456789-18:01", Description = "Offset too large (negative)")]
588 [TestCase("2015-10-09T14:46:23.123456789-00:00", Description = "Local offset (-00:00) makes no sense here")]
589 [TestCase("0001-01-01T00:00:00+00:01", Description = "Value before earliest when offset applied")]
590 [TestCase("9999-12-31T23:59:59.999999999-00:01", Description = "Value after latest when offset applied")]
591 [TestCase("2100-02-29T14:46:23.123456789Z", Description = "Feb 29th on a non-leap-year")]
592 public void Timestamp_Invalid(string jsonValue)
593 {
594 string json = "\"" + jsonValue + "\"";
595 Assert.Throws<InvalidProtocolBufferException>(() => Timestamp.Parser.ParseJson(json));
596 }
597
598 [Test]
599 public void StructValue_Null()
600 {
601 Assert.AreEqual(new Value { NullValue = 0 }, Value.Parser.ParseJson("null"));
602 }
603
604 [Test]
605 public void StructValue_String()
606 {
607 Assert.AreEqual(new Value { StringValue = "hi" }, Value.Parser.ParseJson("\"hi\""));
608 }
609
610 [Test]
611 public void StructValue_Bool()
612 {
613 Assert.AreEqual(new Value { BoolValue = true }, Value.Parser.ParseJson("true"));
614 Assert.AreEqual(new Value { BoolValue = false }, Value.Parser.ParseJson("false"));
615 }
616
617 [Test]
618 public void StructValue_List()
619 {
620 Assert.AreEqual(Value.ForList(Value.ForNumber(1), Value.ForString("x")), Value.Parser.ParseJson("[1, \"x\"]"));
621 }
622
623 [Test]
624 public void ParseListValue()
625 {
626 Assert.AreEqual(new ListValue { Values = { Value.ForNumber(1), Value.ForString("x") } }, ListValue.Parser.ParseJson("[1, \"x\"]"));
627 }
628
629 [Test]
630 public void StructValue_Struct()
631 {
632 Assert.AreEqual(
633 Value.ForStruct(new Struct { Fields = { { "x", Value.ForNumber(1) }, { "y", Value.ForString("z") } } }),
634 Value.Parser.ParseJson("{ \"x\": 1, \"y\": \"z\" }"));
635 }
636
637 [Test]
638 public void ParseStruct()
639 {
640 Assert.AreEqual(new Struct { Fields = { { "x", Value.ForNumber(1) }, { "y", Value.ForString("z") } } },
641 Struct.Parser.ParseJson("{ \"x\": 1, \"y\": \"z\" }"));
642 }
643
644 // TODO for duration parsing: upper and lower bounds.
645 // +/- 315576000000 seconds
646
647 [Test]
648 [TestCase("1.123456789s", null)]
649 [TestCase("1.123456s", null)]
650 [TestCase("1.123s", null)]
651 [TestCase("1.12300s", "1.123s")]
652 [TestCase("1.12345s", "1.123450s")]
653 [TestCase("1s", null)]
654 [TestCase("-1.123456789s", null)]
655 [TestCase("-1.123456s", null)]
656 [TestCase("-1.123s", null)]
657 [TestCase("-1s", null)]
658 [TestCase("0.123s", null)]
659 [TestCase("-0.123s", null)]
660 [TestCase("123456.123s", null)]
661 [TestCase("-123456.123s", null)]
662 // Upper and lower bounds
663 [TestCase("315576000000s", null)]
664 [TestCase("-315576000000s", null)]
665 public void Duration_Valid(string jsonValue, string expectedFormatted)
666 {
667 expectedFormatted = expectedFormatted ?? jsonValue;
668 string json = "\"" + jsonValue + "\"";
669 var parsed = Duration.Parser.ParseJson(json);
670 Assert.AreEqual(expectedFormatted, parsed.ToString());
671 }
672
673 // The simplest way of testing that the value has parsed correctly is to reformat it,
674 // as we trust the formatting. In many cases that will give the same result as the input,
675 // so in those cases we accept an expectedFormatted value of null. Sometimes the results
676 // will be different though, due to a different number of digits being provided.
677 [Test]
678 [TestCase("1.1234567890s", Description = "Too many digits")]
679 [TestCase("1.123456789", Description = "No suffix")]
680 [TestCase("1.123456789ss", Description = "Too much suffix")]
681 [TestCase("1.123456789S", Description = "Upper case suffix")]
682 [TestCase("+1.123456789s", Description = "Leading +")]
683 [TestCase(".123456789s", Description = "No integer before the fraction")]
684 [TestCase("1,123456789s", Description = "Comma as decimal separator")]
685 [TestCase("1x1.123456789s", Description = "Non-digit in integer part")]
686 [TestCase("1.1x3456789s", Description = "Non-digit in fractional part")]
687 [TestCase(" 1.123456789s", Description = "Whitespace before fraction")]
688 [TestCase("1.123456789s ", Description = "Whitespace after value")]
689 [TestCase("01.123456789s", Description = "Leading zero (positive)")]
690 [TestCase("-01.123456789s", Description = "Leading zero (negative)")]
691 [TestCase("--0.123456789s", Description = "Double minus sign")]
692 // Violate upper/lower bounds in various ways
693 [TestCase("315576000001s", Description = "Integer part too large")]
694 [TestCase("315576000000.000000001s", Description = "Integer part is upper bound; non-zero fraction")]
695 [TestCase("3155760000000s", Description = "Integer part too long (positive)")]
696 [TestCase("-3155760000000s", Description = "Integer part too long (negative)")]
697 public void Duration_Invalid(string jsonValue)
698 {
699 string json = "\"" + jsonValue + "\"";
700 Assert.Throws<InvalidProtocolBufferException>(() => Duration.Parser.ParseJson(json));
701 }
702
703 // Not as many tests for field masks as I'd like; more to be added when we have more
704 // detailed specifications.
705
706 [Test]
707 [TestCase("")]
708 [TestCase("foo", "foo")]
709 [TestCase("foo,bar", "foo", "bar")]
710 [TestCase("foo.bar", "foo.bar")]
711 [TestCase("fooBar", "foo_bar")]
712 [TestCase("fooBar.bazQux", "foo_bar.baz_qux")]
713 public void FieldMask_Valid(string jsonValue, params string[] expectedPaths)
714 {
715 string json = "\"" + jsonValue + "\"";
716 var parsed = FieldMask.Parser.ParseJson(json);
717 CollectionAssert.AreEqual(expectedPaths, parsed.Paths);
718 }
719
720 [Test]
721 public void DataAfterObject()
722 {
723 string json = "{} 10";
724 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseJson(json));
725 }
726 }
727}