// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert' show jsonEncode;
import 'dart:core' hide Duration;

import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart';
import 'package:protobuf/well_known_types/google/protobuf/any.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/duration.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/field_mask.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/struct.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart';
import 'package:protobuf/well_known_types/google/protobuf/wrappers.pb.dart';
import 'package:test/test.dart';

import 'gen/entity.pb.dart';
import 'gen/enum_name.pb.dart';
import 'gen/google/protobuf/unittest.pb.dart';
import 'gen/google/protobuf/unittest_well_known_types.pb.dart';
import 'gen/map_field.pb.dart';
import 'gen/nested_any.pb.dart';
import 'gen/oneof.pb.dart';
import 'oneof_test.dart';
import 'src/test_util.dart';

final testAllTypesJson = {
  'optionalInt32': 101,
  'optionalInt64': '102',
  'optionalUint32': 103,
  'optionalUint64': '104',
  'optionalSint32': 105,
  'optionalSint64': '106',
  'optionalFixed32': 107,
  'optionalFixed64': '108',
  'optionalSfixed32': 109,
  'optionalSfixed64': '110',
  'optionalFloat': 111.0,
  'optionalDouble': 112.0,
  'optionalBool': true,
  'optionalString': '115',
  'optionalBytes': 'MTE2',
  'optionalgroup': {'a': 117},
  'optionalNestedMessage': {'bb': 118},
  'optionalForeignMessage': {'c': 119},
  'optionalImportMessage': {'d': 120},
  'optionalNestedEnum': 'BAZ',
  'optionalForeignEnum': 'FOREIGN_BAZ',
  'optionalImportEnum': 'IMPORT_BAZ',
  'optionalStringPiece': '124',
  'optionalCord': '125',
  'repeatedInt32': [201, 301],
  'repeatedInt64': ['202', '302'],
  'repeatedUint32': [203, 303],
  'repeatedUint64': ['204', '304'],
  'repeatedSint32': [205, 305],
  'repeatedSint64': ['206', '306'],
  'repeatedFixed32': [207, 307],
  'repeatedFixed64': ['208', '308'],
  'repeatedSfixed32': [209, 309],
  'repeatedSfixed64': ['210', '310'],
  'repeatedFloat': [211.0, 311.0],
  'repeatedDouble': [212.0, 312.0],
  'repeatedBool': [true, false],
  'repeatedString': ['215', '315'],
  'repeatedBytes': ['MjE2', 'MzE2'],
  'repeatedgroup': [
    {'a': 217},
    {'a': 317},
  ],
  'repeatedNestedMessage': [
    {'bb': 218},
    {'bb': 318},
  ],
  'repeatedForeignMessage': [
    {'c': 219},
    {'c': 319},
  ],
  'repeatedImportMessage': [
    {'d': 220},
    {'d': 320},
  ],
  'repeatedNestedEnum': ['BAR', 'BAZ'],
  'repeatedForeignEnum': ['FOREIGN_BAR', 'FOREIGN_BAZ'],
  'repeatedImportEnum': ['IMPORT_BAR', 'IMPORT_BAZ'],
  'repeatedStringPiece': ['224', '324'],
  'repeatedCord': ['225', '325'],
  'defaultInt32': 401,
  'defaultInt64': '402',
  'defaultUint32': 403,
  'defaultUint64': '404',
  'defaultSint32': 405,
  'defaultSint64': '406',
  'defaultFixed32': 407,
  'defaultFixed64': '408',
  'defaultSfixed32': 409,
  'defaultSfixed64': '410',
  'defaultFloat': 411.0,
  'defaultDouble': 412.0,
  'defaultBool': false,
  'defaultString': '415',
  'defaultBytes': 'NDE2',
  'defaultNestedEnum': 'FOO',
  'defaultForeignEnum': 'FOREIGN_FOO',
  'defaultImportEnum': 'IMPORT_FOO',
  'defaultStringPiece': '424',
  'defaultCord': '425',
};

void main() {
  test('toProto3Json works', () {
    final proto =
        TopEntity()
          ..id = Int64(1)
          ..stringValue = 'foo'
          ..strings.addAll(['test', 'repeated', 'field'])
          ..anyValue = Any.pack(
            SubEntity()
              ..id = Int64(4)
              ..anyValue = Any.pack(SubSubEntity()..id = Int64(5)),
          )
          ..sub =
              (SubEntity()
                ..id = Int64(2)
                ..subSub = (SubSubEntity()..id = Int64(3)));
    final typeRegistry = TypeRegistry([SubEntity(), SubSubEntity()]);
    final json = proto.toProto3Json(typeRegistry: typeRegistry);
    expect(
      proto ==
          (TopEntity()..mergeFromProto3Json(json, typeRegistry: typeRegistry)),
      true,
    );
  });

  group('encode', () {
    test('testOutput', () {
      expect(getAllSet().toProto3Json(), testAllTypesJson);
    });

    test('testFrozenOutput', () {
      expect(getAllSet().freeze().toProto3Json(), testAllTypesJson);
    });

    test('testUnsignedOutput', () {
      final message = TestAllTypes();
      // These values are selected because they are large enough to set the sign bit.
      message.optionalUint64 = Int64.parseHex('f0000000ffff0000');
      message.optionalFixed64 = Int64.parseHex('f0000000ffff0001');

      expect(message.toProto3Json(), {
        'optionalUint64': '17293822573397606400',
        'optionalFixed64': '-1152921500311945215',
      });
    });

    test('doubles', () {
      void testValue(double value, Object expected) {
        final message =
            TestAllTypes()
              ..defaultFloat = value
              ..defaultDouble = value;
        expect(
          (message.toProto3Json() as Map)['defaultDouble'],
          equals(expected),
        );
      }

      testValue(-0.0, -0.0);
      testValue(0.0, 0);
      testValue(1.0, 1);
      testValue(-1.0, -1);
      testValue(double.nan, 'NaN');
      testValue(double.infinity, 'Infinity');
      testValue(double.negativeInfinity, '-Infinity');
    });

    test('map value', () {
      final message =
          TestMap()
            ..int32ToInt32Field[32] = 32
            ..int32ToStringField[0] = 'foo'
            ..int32ToStringField[1] = 'bar'
            ..int32ToBytesField[-1] = [1, 2, 3]
            ..int32ToEnumField[1] = TestMap_EnumValue.BAZ
            ..int32ToMessageField[21] =
                (TestMap_MessageValue()
                  ..value = 2
                  ..secondValue = 3)
            ..stringToInt32Field['key'] = -1
            ..uint32ToInt32Field[0] = 0
            ..int64ToInt32Field[Int64.ZERO] = 0
            ..int64ToInt32Field[Int64.ONE] = 1
            ..int64ToInt32Field[-Int64.ONE] = -1
            ..int64ToInt32Field[Int64.MIN_VALUE] = -2
            ..int64ToInt32Field[Int64.MAX_VALUE] = 2
            ..uint64ToInt32Field[Int64.MIN_VALUE] = -2;
      expect(message.toProto3Json(), {
        'int32ToInt32Field': {'32': 32},
        'int32ToStringField': {'0': 'foo', '1': 'bar'},
        'int32ToBytesField': {'-1': 'AQID'},
        'int32ToEnumField': {'1': 'BAZ'},
        'int32ToMessageField': {
          '21': {'value': 2, 'secondValue': 3},
        },
        'stringToInt32Field': {'key': -1},
        'uint32ToInt32Field': {'0': 0},
        'int64ToInt32Field': {
          '0': 0,
          '1': 1,
          '-1': -1,
          '-9223372036854775808': -2,
          '9223372036854775807': 2,
        },
        'uint64ToInt32Field': {'9223372036854775808': -2},
      });
    });

    test('Timestamp', () {
      expect(
        Timestamp.fromDateTime(
          DateTime.utc(1969, 7, 20, 20, 17, 40),
        ).toProto3Json(),
        '1969-07-20T20:17:40Z',
      );
      expect(
        Timestamp.fromDateTime(DateTime.utc(9)).toProto3Json(),
        '0009-01-01T00:00:00Z',
      );
      expect(
        Timestamp.fromDateTime(DateTime.utc(420)).toProto3Json(),
        '0420-01-01T00:00:00Z',
      );
      expect(
        () => Timestamp.fromDateTime(DateTime.utc(42001)).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
      expect(
        () => Timestamp.fromDateTime(DateTime.utc(-1)).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
      expect(
        Timestamp.fromDateTime(
          DateTime.utc(9999, 12, 31, 23, 59, 59),
        ).toProto3Json(),
        '9999-12-31T23:59:59Z',
      );

      expect(
        (Timestamp()..nanos = 1).toProto3Json(),
        '1970-01-01T00:00:00.000000001Z',
      );
      expect(
        (Timestamp()..nanos = 8200000).toProto3Json(),
        '1970-01-01T00:00:00.008200Z',
      );
      expect(
        () => (Timestamp()..nanos = -8200000).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
      expect(
        () => (Timestamp()..nanos = -8200000).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
      expect(
        () => (Timestamp()..nanos = 1000000000).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
    });

    test('Duration', () {
      expect(
        (Duration()
              ..seconds = Int64(0)
              ..nanos = 0)
            .toProto3Json(),
        '0s',
      );
      expect(
        (Duration()
              ..seconds = Int64(10)
              ..nanos = 0)
            .toProto3Json(),
        '10s',
      );
      expect(
        (Duration()
              ..seconds = Int64(10)
              ..nanos = 1)
            .toProto3Json(),
        '10.000000001s',
      );
      expect(
        (Duration()
              ..seconds = Int64(10)
              ..nanos = 10)
            .toProto3Json(),
        '10.00000001s',
      );
      expect(
        (Duration()
              ..seconds = -Int64(1)
              ..nanos = -99000)
            .toProto3Json(),
        '-1.000099s',
      );
    });

    test('Any', () {
      expect(
        Any.pack(
          TestAllTypes()..optionalFixed64 = Int64(100),
        ).toProto3Json(typeRegistry: TypeRegistry([TestAllTypes()])),
        {
          '@type': 'type.googleapis.com/proto2_unittest.TestAllTypes',
          'optionalFixed64': '100',
        },
      );
      expect(
        Any.pack(
          Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17)),
        ).toProto3Json(typeRegistry: TypeRegistry([Timestamp()])),
        {
          '@type': 'type.googleapis.com/google.protobuf.Timestamp',
          'value': '1969-07-20T20:17:00Z',
        },
      );
      expect(
        () =>
            Any.pack(
              Timestamp.fromDateTime(DateTime(1969, 7, 20, 20, 17)),
            ).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
    });

    test('Nested Any', () {
      final packedOne = Any.pack(AnyMessage1()..value = '1');
      final packedTwo = Any.pack(
        AnyMessage2()
          ..value = '2'
          ..anyField2 = packedOne,
      );
      expect(
        packedTwo.toProto3Json(
          typeRegistry: TypeRegistry([AnyMessage1(), AnyMessage2()]),
        ),
        {
          'anyField2': {
            'value': '1',
            '@type': 'type.googleapis.com/nested_any.AnyMessage1',
          },
          'value': '2',
          '@type': 'type.googleapis.com/nested_any.AnyMessage2',
        },
      );
    });

    test('struct', () {
      final s =
          Struct()
            ..fields['null'] = (Value()..nullValue = NullValue.NULL_VALUE)
            ..fields['number'] = (Value()..numberValue = 22.3)
            ..fields['string'] = (Value()..stringValue = 'foo')
            ..fields['bool'] = (Value()..boolValue = false)
            ..fields['struct'] =
                (Value()
                  ..structValue =
                      (Struct()..fields['a'] = (Value()..numberValue = 0.0)))
            ..fields['list'] =
                (Value()
                  ..listValue =
                      (ListValue()
                        ..values.addAll([
                          Value()..structValue = Struct(),
                          Value()..listValue = ListValue(),
                          Value()..stringValue = 'why',
                        ])));
      expect(s.toProto3Json(), {
        'null': null,
        'number': 22.3,
        'string': 'foo',
        'bool': false,
        'struct': {'a': 0},
        'list': [{}, [], 'why'],
      });
      expect(
        () => Value().toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
    });

    test('empty', () {
      expect(Empty().toProto3Json(), {});
    });

    test('wrapper types', () {
      final t =
          TestWellKnownTypes()
            ..doubleValueField = (DoubleValue()..value = 10.01)
            ..floatValueField = (FloatValue()..value = 3.0)
            ..int64ValueField = (Int64Value()..value = Int64.MIN_VALUE)
            ..uint64ValueField = (UInt64Value()..value = Int64.MIN_VALUE)
            ..int32ValueField = (Int32Value()..value = 101)
            ..uint32ValueField = (UInt32Value()..value = 102)
            ..boolValueField = (BoolValue()..value = false)
            ..stringValueField = (StringValue()..value = 'Pop')
            ..bytesValueField = (BytesValue()..value = [8, 9, 10]);
      expect(t.toProto3Json(), {
        'doubleValueField': 10.01,
        'floatValueField': 3,
        'int64ValueField': '-9223372036854775808',
        'uint64ValueField': '9223372036854775808',
        'int32ValueField': 101,
        'uint32ValueField': 102,
        'boolValueField': false,
        'stringValueField': 'Pop',
        'bytesValueField': 'CAkK',
      });
    });

    test('field mask', () {
      expect(FieldMask().toProto3Json(), '');
      expect(
        (FieldMask()..paths.addAll(['foo_bar_baz'])).toProto3Json(),
        'fooBarBaz',
      );
      expect(
        (FieldMask()..paths.addAll(['foo_bar', 'zop'])).toProto3Json(),
        'fooBar,zop',
      );
      expect(
        () => (FieldMask()..paths.add('foo_3_bar')).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
      expect(
        () => (FieldMask()..paths.add('foo__bar')).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
      expect(
        () => (FieldMask()..paths.add('fooBar')).toProto3Json(),
        throwsA(TypeMatcher<ArgumentError>()),
      );
    });
  });

  group('decode', () {
    Matcher parseFailure(List<String> expectedPath) => throwsA(
      predicate((e) {
        if (e is FormatException) {
          final pathExpression =
              RegExp(r'root(\["[^"]*"]*\])*').firstMatch(e.message)![0]!;
          final actualPath =
              RegExp(
                r'\["([^"]*)"\]',
              ).allMatches(pathExpression).map((match) => match[1]).toList();
          if (actualPath.length != expectedPath.length) return false;
          for (var i = 0; i < actualPath.length; i++) {
            if (actualPath[i] != expectedPath[i]) return false;
          }
          return true;
        }
        return false;
      }),
    );

    test('Nulls', () {
      final decoded =
          TestAllTypes()..mergeFromProto3Json({'defaultString': null});
      expect(decoded, TestAllTypes());
    });
    test('decode TestAllTypes', () {
      final decoded = TestAllTypes()..mergeFromProto3Json(testAllTypesJson);
      expect(decoded, getAllSet());
    });
    test('Type expectations', () {
      expect(
        () => TestAllTypes()..mergeFromProto3Json({1: 1}),
        parseFailure([]),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalBool': 1}),
        parseFailure(['optionalBool']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalBytes': 1}),
        parseFailure(['optionalBytes']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalBytes': '()'}),
        parseFailure(['optionalBytes']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalInt32': '()'}),
        parseFailure(['optionalInt32']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalInt32': 20.4}),
        parseFailure(['optionalInt32']),
      );
      expect(
        TestAllTypes()..mergeFromProto3Json({'optionalInt32': '28'}),
        TestAllTypes()..optionalInt32 = 28,
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalInt64': '()'}),
        parseFailure(['optionalInt64']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalInt64': 20.4}),
        parseFailure(['optionalInt64']),
      );
      expect(
        TestAllTypes()..mergeFromProto3Json({'optionalInt64': '28'}),
        TestAllTypes()..optionalInt64 = Int64(28),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalDouble': 'a'}),
        parseFailure(['optionalDouble']),
      );
      expect(
        TestAllTypes()..mergeFromProto3Json({'optionalDouble': 28}),
        TestAllTypes()..optionalDouble = 28.0,
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalDouble': 'a'}),
        parseFailure(['optionalDouble']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalString': 11}),
        parseFailure(['optionalString']),
      );
      expect(
        () =>
            TestAllTypes()..mergeFromProto3Json({'optionalEnum': 'wrongValue'}),
        parseFailure(['optionalEnum']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalEnum': []}),
        parseFailure(['optionalEnum']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'optionalNestedEnum': 100}),
        parseFailure(['optionalNestedEnum']),
      );
      expect(
        TestAllTypes()..mergeFromProto3Json({'optionalNestedEnum': 1}),
        TestAllTypes()
          ..optionalNestedEnum = TestAllTypes_NestedEnum.valueOf(1)!,
      );
      expect(
        TestAllTypes()..mergeFromProto3Json({'repeatedBool': null}),
        TestAllTypes(),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json({'repeatedBool': 100}),
        parseFailure(['repeatedBool']),
      );
      expect(
        () =>
            TestAllTypes()..mergeFromProto3Json({
              'repeatedBool': [true, false, 1],
            }),
        parseFailure(['repeatedBool', '2']),
      );
      expect(
        () => TestAllTypes()..mergeFromProto3Json(Object()),
        parseFailure([]),
      );
    });

    test('merging behavior', () {
      final t =
          TestAllTypes()
            ..optionalForeignMessage = ForeignMessage()
            ..repeatedForeignMessage.add(ForeignMessage()..c = 1);
      final f = t.optionalForeignMessage;
      expect(
        t..mergeFromProto3Json({
          'repeatedForeignMessage': [
            {'c': 2},
          ],
          'optionalForeignMessage': {'c': 2},
        }),
        TestAllTypes()
          ..optionalForeignMessage = (ForeignMessage()..c = 2)
          ..repeatedForeignMessage.addAll([
            ForeignMessage()..c = 1,
            ForeignMessage()..c = 2,
          ]),
      );
      expect(f, ForeignMessage()..c = 2);
    });

    test('names_with_underscores', () {
      expect(
        TestAllTypes()..mergeFromProto3Json({
          'optional_foreign_message': {'c': 1},
        }),
        TestAllTypes()..optionalForeignMessage = (ForeignMessage()..c = 1),
      );
      expect(
        () =>
            TestAllTypes()..mergeFromProto3Json({
              'optional_foreign_message': {'c': 1},
            }, supportNamesWithUnderscores: false),
        parseFailure(['optional_foreign_message']),
      );
    });
    test('permissive enums', () {
      final sparseB = SparseEnumMessage()..sparseEnum = TestSparseEnum.SPARSE_B;
      expect(
        SparseEnumMessage()..mergeFromProto3Json({'sparseEnum': 'SPARSE_B'}),
        sparseB,
      );
      expect(
        () =>
            SparseEnumMessage()
              ..mergeFromProto3Json({'sparseEnum': 'sparse_b'}),
        parseFailure(['sparseEnum']),
      );
      expect(
        () =>
            SparseEnumMessage()
              ..mergeFromProto3Json({'sparseEnum': 'SPARSE-B'}),
        parseFailure(['sparseEnum']),
      );
      expect(
        () =>
            SparseEnumMessage()
              ..mergeFromProto3Json({'sparseEnum': 'sPaRsE_b'}),
        parseFailure(['sparseEnum']),
      );
      expect(
        () =>
            SparseEnumMessage()..mergeFromProto3Json({'sparseEnum': 'sparseB'}),
        parseFailure(['sparseEnum']),
      );
      expect(
        () =>
            SparseEnumMessage()..mergeFromProto3Json({'sparseEnum': 'spaRSEB'}),
        parseFailure(['sparseEnum']),
      );

      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'x'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'X'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'x_'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'X_'}),
        parseFailure(['a']),
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_x'}),
        AMessage()..a = A.x_,
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': '_X'}),
        parseFailure(['a']),
      );

      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'y'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'Y'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'y_'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'Y_'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': '_y'}),
        parseFailure(['a']),
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_Y'}),
        AMessage()..a = A.Y_,
      );

      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'z'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'Z'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'z_'}),
        parseFailure(['a']),
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'Z_'}),
        AMessage()..a = A.Z_,
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': '_z'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': '_Z'}),
        parseFailure(['a']),
      );

      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'a_a'}),
        parseFailure(['a']),
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'A_A'}),
        AMessage()..a = A.A_A,
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'aA'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'AA'}),
        parseFailure(['a']),
      );

      expect(
        AMessage()..mergeFromProto3Json({'a': 'b_b'}),
        AMessage()..a = A.b_b,
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'B_B'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'bB'}),
        parseFailure(['a']),
      );
      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'BB'}),
        parseFailure(['a']),
      );

      expect(
        () => AMessage()..mergeFromProto3Json({'a': 'CAMEL_CASE'}),
        parseFailure(['a']),
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'camelCase'}),
        AMessage()..a = A.camelCase,
      );

      expect(
        AMessage()..mergeFromProto3Json({'a': 'x'}, permissiveEnums: true),
        AMessage()..a = A.x_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'X'}, permissiveEnums: true),
        AMessage()..a = A.x_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'x_'}, permissiveEnums: true),
        AMessage()..a = A.x_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'X_'}, permissiveEnums: true),
        AMessage()..a = A.x_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_x'}, permissiveEnums: true),
        AMessage()..a = A.x_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_X'}, permissiveEnums: true),
        AMessage()..a = A.x_,
      );

      expect(
        AMessage()..mergeFromProto3Json({'a': 'y'}, permissiveEnums: true),
        AMessage()..a = A.Y_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'Y'}, permissiveEnums: true),
        AMessage()..a = A.Y_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'y_'}, permissiveEnums: true),
        AMessage()..a = A.Y_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'Y_'}, permissiveEnums: true),
        AMessage()..a = A.Y_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_y'}, permissiveEnums: true),
        AMessage()..a = A.Y_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_Y'}, permissiveEnums: true),
        AMessage()..a = A.Y_,
      );

      expect(
        AMessage()..mergeFromProto3Json({'a': 'z'}, permissiveEnums: true),
        AMessage()..a = A.Z_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'Z'}, permissiveEnums: true),
        AMessage()..a = A.Z_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'z_'}, permissiveEnums: true),
        AMessage()..a = A.Z_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'Z_'}, permissiveEnums: true),
        AMessage()..a = A.Z_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_z'}, permissiveEnums: true),
        AMessage()..a = A.Z_,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': '_Z'}, permissiveEnums: true),
        AMessage()..a = A.Z_,
      );

      expect(
        AMessage()..mergeFromProto3Json({'a': 'a_a'}, permissiveEnums: true),
        AMessage()..a = A.A_A,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'A_A'}, permissiveEnums: true),
        AMessage()..a = A.A_A,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'aA'}, permissiveEnums: true),
        AMessage()..a = A.A_A,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'AA'}, permissiveEnums: true),
        AMessage()..a = A.A_A,
      );

      expect(
        AMessage()..mergeFromProto3Json({'a': 'b_b'}, permissiveEnums: true),
        AMessage()..a = A.b_b,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'B_B'}, permissiveEnums: true),
        AMessage()..a = A.b_b,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'bB'}, permissiveEnums: true),
        AMessage()..a = A.b_b,
      );
      expect(
        AMessage()..mergeFromProto3Json({'a': 'BB'}, permissiveEnums: true),
        AMessage()..a = A.b_b,
      );

      expect(
        AMessage()
          ..mergeFromProto3Json({'a': 'CAMEL_CASE'}, permissiveEnums: true),
        AMessage()..a = A.camelCase,
      );
      expect(
        AMessage()
          ..mergeFromProto3Json({'a': 'camelCase'}, permissiveEnums: true),
        AMessage()..a = A.camelCase,
      );

      expect(
        SparseEnumMessage()..mergeFromProto3Json({
          'sparseEnum': 'sparse_b',
        }, permissiveEnums: true),
        sparseB,
      );
      expect(
        SparseEnumMessage()..mergeFromProto3Json({
          'sparseEnum': 'SPARSE-B',
        }, permissiveEnums: true),
        sparseB,
      );
      expect(
        SparseEnumMessage()..mergeFromProto3Json({
          'sparseEnum': 'S-P-A-R-S-E-B',
        }, permissiveEnums: true),
        sparseB,
      );
      expect(
        SparseEnumMessage()..mergeFromProto3Json({
          'sparseEnum': 'sPaRsE_b',
        }, permissiveEnums: true),
        sparseB,
      );
      expect(
        SparseEnumMessage()..mergeFromProto3Json({
          'sparseEnum': 'sparseB',
        }, permissiveEnums: true),
        sparseB,
      );
      expect(
        SparseEnumMessage()..mergeFromProto3Json({
          'sparseEnum': 'spaRSEB',
        }, permissiveEnums: true),
        sparseB,
      );
      expect(
        () =>
            Any()..mergeFromProto3Json({
              '@type': 'type.googleapis.com/proto2_unittest.SparseEnumMessage',
              'sparseEnum': 'SPARSEB',
            }, typeRegistry: TypeRegistry([SparseEnumMessage()])),
        parseFailure(['sparseEnum']),
      );
      expect(
        Any()..mergeFromProto3Json(
          {
            '@type': 'type.googleapis.com/proto2_unittest.SparseEnumMessage',
            'sparseEnum': 'SPARSEB',
          },
          typeRegistry: TypeRegistry([SparseEnumMessage()]),
          permissiveEnums: true,
        ),
        Any.pack(sparseB),
        reason: 'Parsing options are passed through Any messages',
      );
    });

    test('map value', () {
      final expected =
          TestMap()
            ..int32ToInt32Field[32] = 32
            ..int32ToStringField[0] = 'foo'
            ..int32ToStringField[1] = 'bar'
            ..int32ToBytesField[-1] = [1, 2, 3]
            ..int32ToEnumField[1] = TestMap_EnumValue.BAZ
            ..int32ToMessageField[21] =
                (TestMap_MessageValue()
                  ..value = 2
                  ..secondValue = 3)
            ..stringToInt32Field['key'] = -1
            ..uint32ToInt32Field[0] = 0
            ..int64ToInt32Field[Int64.ZERO] = 0
            ..int64ToInt32Field[Int64.ONE] = 1
            ..int64ToInt32Field[-Int64.ONE] = -1
            ..int64ToInt32Field[Int64.MIN_VALUE] = -2
            ..int64ToInt32Field[Int64.MAX_VALUE] = 2
            ..uint64ToInt32Field[Int64.MIN_VALUE] = -2;
      expect(
        TestMap()..mergeFromProto3Json({
          'int32ToInt32Field': {'32': 32},
          'int32ToStringField': {'0': 'foo', '1': 'bar'},
          'int32ToBytesField': {'-1': 'AQID'},
          'int32ToEnumField': {'1': 'BAZ'},
          'int32ToMessageField': {
            '21': {'value': 2, 'secondValue': 3},
          },
          'stringToInt32Field': {'key': -1},
          'uint32ToInt32Field': {'0': 0},
          'int64ToInt32Field': {
            '0': 0,
            '1': 1,
            '-1': -1,
            '-9223372036854775808': -2,
            '9223372036854775807': 2,
          },
          'uint64ToInt32Field': {'9223372036854775808': -2},
        }),
        expected,
      );
      expect(() => TestMap()..mergeFromProto3Json([]), parseFailure([]));
      expect(
        () =>
            TestMap()..mergeFromProto3Json({
              'int32ToInt32Field': {'32': 'a'},
            }),
        parseFailure(['int32ToInt32Field', '32']),
      );
      expect(
        () =>
            TestMap()..mergeFromProto3Json({
              'int32ToInt32Field': {'2147483648': 1},
            }),
        parseFailure(['int32ToInt32Field', '2147483648']),
      );
      expect(
        () =>
            TestMap()..mergeFromProto3Json({
              'uint32ToInt32Field': {'-32': 21},
            }),
        parseFailure(['uint32ToInt32Field', '-32']),
      );
      expect(
        () =>
            TestMap()..mergeFromProto3Json({
              'uint32ToInt32Field': {'4294967296': 21},
            }),
        parseFailure(['uint32ToInt32Field', '4294967296']),
      );
      expect(
        TestMap()..mergeFromProto3Json({
          'int32ToInt32Field': <dynamic, dynamic>{'2': 21},
        }),
        TestMap()..int32ToInt32Field[2] = 21,
      );
    });
    test('ints', () {
      expect(
        (TestAllTypes()
              ..mergeFromProto3Json({'optionalUint64': '17293822573397606400'}))
            .optionalUint64,
        Int64.parseHex('f0000000ffff0000'),
      );

      // TODO(sigurdm): This should throw.
      expect(
        TestAllTypes()..mergeFromProto3Json({'optionalUint64': '-1'}),
        TestAllTypes()
          ..optionalUint64 = Int64.fromBytes([
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
          ]),
      );

      void expectRoundTrip(String typeName, int value) {
        final t = TestAllTypes()..mergeFromProto3Json({typeName: value});
        expect(t.getField(t.getTagNumber(typeName)!), value);
        final t2 =
            TestAllTypes()..mergeFromProto3Json({typeName: value.toString()});
        expect(t2.getField(t2.getTagNumber(typeName)!), value);
      }

      void expectFailure(String typeName, int value) {
        expect(
          () => TestAllTypes()..mergeFromProto3Json({typeName: -2147483649}),
          parseFailure([typeName]),
        );
      }

      void expectSigned32(String typeName) {
        expectRoundTrip(typeName, 1);
        expectRoundTrip(typeName, 0);
        expectRoundTrip(typeName, 2147483647);
        expectRoundTrip(typeName, -2147483648);
        expectFailure(typeName, 2147483648);
        expectFailure(typeName, -2147483649);
      }

      void expectUnsigned32(String typeName) {
        expectRoundTrip(typeName, 1);
        expectRoundTrip(typeName, 0);
        expectRoundTrip(typeName, 0xFFFFFFFF);
        expectFailure(typeName, 0xFFFFFFFF + 1);
        expectFailure(typeName, -1);
      }

      expectUnsigned32('optionalFixed32');
      expectUnsigned32('optionalUint32');
      expectSigned32('optionalInt32');
      expectSigned32('optionalSfixed32');
      expectSigned32('optionalSint32');
    });

    test('unknown fields', () {
      expect(
        () =>
            TestAllTypes()
              ..mergeFromProto3Json({'myOwnInventedField': 'blahblahblah'}),
        throwsA(const TypeMatcher<FormatException>()),
      );
      expect(
        () =>
            TestAllTypes()..mergeFromProto3Json({
              'myOwnInventedField': 'blahblahblah',
            }, ignoreUnknownFields: false),
        throwsA(const TypeMatcher<FormatException>()),
      );
      final t =
          TestAllTypes()..mergeFromProto3Json({
            'myOwnInventedField': 'blahblahblah',
          }, ignoreUnknownFields: true);
      expect(t, TestAllTypes());
      expect(t.unknownFields.isEmpty, isTrue);
    });

    test('Any', () {
      final m1 =
          Any()..mergeFromProto3Json({
            '@type': 'type.googleapis.com/proto2_unittest.TestAllTypes',
            'optionalFixed64': '100',
          }, typeRegistry: TypeRegistry([TestAllTypes()]));

      expect(m1.unpackInto(TestAllTypes()).optionalFixed64, Int64(100));

      final m2 =
          Any()..mergeFromProto3Json({
            '@type': 'type.googleapis.com/google.protobuf.Timestamp',
            'value': '1969-07-20T19:17:00Z',
          }, typeRegistry: TypeRegistry([Timestamp()]));

      expect(
        m2.unpackInto(Timestamp()).toDateTime().millisecondsSinceEpoch,
        DateTime.utc(1969, 7, 20, 19, 17).millisecondsSinceEpoch,
      );

      expect(
        () =>
            Any()..mergeFromProto3Json({
              '@type': 'type.googleapis.com/google.protobuf.Timestamp',
              'value': '1969-07-20T19:17:00Z',
            }),
        parseFailure([]),
      );

      expect(
        () =>
            Any()..mergeFromProto3Json({
              '@type': 11,
              'value': '1969-07-20T19:17:00Z',
            }),
        parseFailure([]),
      );

      final m3 =
          Any()..mergeFromProto3Json({
            '@type': 'type.googleapis.com/google.protobuf.Any',
            'value': {
              '@type': 'type.googleapis.com/google.protobuf.Timestamp',
              'value': '1969-07-20T19:17:00Z',
            },
          }, typeRegistry: TypeRegistry([Timestamp(), Any()]));

      expect(
        m3
            .unpackInto(Any())
            .unpackInto(Timestamp())
            .toDateTime()
            .millisecondsSinceEpoch,
        DateTime.utc(1969, 7, 20, 19, 17).millisecondsSinceEpoch,
      );

      // TODO(sigurdm): We would ideally like the error path to be
      // ['value', 'value'].
      expect(
        () =>
            Any()..mergeFromProto3Json({
              '@type': 'type.googleapis.com/google.protobuf.Any',
              'value': {
                '@type': 'type.googleapis.com/google.protobuf.Timestamp',
                'value': '1969',
              },
            }, typeRegistry: TypeRegistry([Timestamp(), Any()])),
        parseFailure([]),
      );

      expect(() => Any()..mergeFromProto3Json('@type'), parseFailure([]));

      expect(() => Any()..mergeFromProto3Json(11), parseFailure([]));

      expect(() => Any()..mergeFromProto3Json(['@type']), parseFailure([]));

      expect(
        () =>
            Any()..mergeFromProto3Json({
              '@type': 'type.googleapis.com/google.protobuf.Timestamp',
              'value': '1969-07-20T19:17:00Z',
            }),
        parseFailure([]),
      );
    });

    test('Nested Any', () {
      final m1 =
          Any()..mergeFromProto3Json({
            'anyField2': {
              'value': '1',
              '@type': 'type.googleapis.com/nested_any.AnyMessage1',
            },
            'value': '2',
            '@type': 'type.googleapis.com/nested_any.AnyMessage2',
          }, typeRegistry: TypeRegistry([AnyMessage1(), AnyMessage2()]));

      expect(
        m1.unpackInto(AnyMessage2()).anyField2.unpackInto(AnyMessage1()).value,
        '1',
      );
    });

    test('Duration', () {
      expect(
        Duration()..mergeFromProto3Json('0s'),
        Duration()
          ..seconds = Int64(0)
          ..nanos = 0,
      );
      expect(
        Duration()..mergeFromProto3Json('10s'),
        Duration()
          ..seconds = Int64(10)
          ..nanos = 0,
      );
      expect(
        Duration()..mergeFromProto3Json('10.000000001s'),
        Duration()
          ..seconds = Int64(10)
          ..nanos = 1,
      );
      expect(
        Duration()..mergeFromProto3Json('10.00000001s'),
        Duration()
          ..seconds = Int64(10)
          ..nanos = 10,
      );
      expect(
        Duration()..mergeFromProto3Json('-1.000099s'),
        Duration()
          ..seconds = -Int64(1)
          ..nanos = -99000,
      );

      expect(
        Duration()..mergeFromProto3Json('0.s'),
        Duration()
          ..seconds = Int64(0)
          ..nanos = 0,
      );
      expect(
        Duration()..mergeFromProto3Json('.0s'),
        Duration()
          ..seconds = Int64(0)
          ..nanos = 0,
      );
      expect(
        Duration()..mergeFromProto3Json('.5s'),
        Duration()
          ..seconds = Int64(0)
          ..nanos = 500000000,
      );
      expect(
        Duration()..mergeFromProto3Json('5.s'),
        Duration()
          ..seconds = Int64(5)
          ..nanos = 0,
      );
      expect(
        Duration()..mergeFromProto3Json('.s'),
        Duration()
          ..seconds = Int64(0)
          ..nanos = 0,
      );
      expect(() => Duration()..mergeFromProto3Json('0.5'), parseFailure([]));
      expect(() => Duration()..mergeFromProto3Json(100), parseFailure([]));
    });

    test('Timestamp', () {
      expect(
        Timestamp()..mergeFromProto3Json('1969-07-20T20:17:00Z'),
        Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17)),
      );
      expect(
        Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.000000001Z'),
        Timestamp()
          ..seconds = Int64(0)
          ..nanos = 1,
      );
      expect(
        Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.008200Z'),
        Timestamp.fromDateTime(DateTime.utc(1970))..nanos = 8200000,
      );
      expect(
        Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00-04:00'),
        Timestamp()
          ..seconds = Int64(82200)
          ..nanos = 0,
      );
      expect(
        Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00+04:00'),
        Timestamp()
          ..seconds = Int64(53400)
          ..nanos = 0,
      );
      expect(
        Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00.0001+04:00'),
        Timestamp()
          ..seconds = Int64(53400)
          ..nanos = 100000,
      );
      expect(
        () =>
            Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.0000000001Z'),
        parseFailure([]),
      );
      expect(() => Timestamp()..mergeFromProto3Json(1970), parseFailure([]));
      expect(
        () => Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00.'),
        parseFailure([]),
      );
    });

    test('wrapper types', () {
      expect(
        TestWellKnownTypes()..mergeFromProto3Json({
          'doubleValueField': 10.01,
          'floatValueField': 3,
          'int64ValueField': '-9223372036854775808',
          'uint64ValueField': '9223372036854775808',
          'int32ValueField': 101,
          'uint32ValueField': 102,
          'boolValueField': false,
          'stringValueField': 'Pop',
          'bytesValueField': 'CAkK',
        }),
        TestWellKnownTypes()
          ..doubleValueField = (DoubleValue()..value = 10.01)
          ..floatValueField = (FloatValue()..value = 3.0)
          ..int64ValueField = (Int64Value()..value = Int64.MIN_VALUE)
          ..uint64ValueField = (UInt64Value()..value = Int64.MIN_VALUE)
          ..int32ValueField = (Int32Value()..value = 101)
          ..uint32ValueField = (UInt32Value()..value = 102)
          ..boolValueField = (BoolValue()..value = false)
          ..stringValueField = (StringValue()..value = 'Pop')
          ..bytesValueField = (BytesValue()..value = [8, 9, 10]),
      );

      expect(
        TestWellKnownTypes()..mergeFromProto3Json({
          'doubleValueField': '10.01',
          'floatValueField': '3',
          'int64ValueField': -854775808,
          'uint64ValueField': 854775808,
          'int32ValueField': '101',
          'uint32ValueField': '102',
          'boolValueField': false,
        }),
        TestWellKnownTypes()
          ..doubleValueField = (DoubleValue()..value = 10.01)
          ..floatValueField = (FloatValue()..value = 3.0)
          ..int64ValueField = (Int64Value()..value = Int64(-854775808))
          ..uint64ValueField = (UInt64Value()..value = Int64(854775808))
          ..int32ValueField = (Int32Value()..value = 101)
          ..uint32ValueField = (UInt32Value()..value = 102)
          ..boolValueField = (BoolValue()..value = false),
        reason: 'alternative representations should be accepted',
      );

      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'doubleValueField': 'a'}),
        parseFailure(['doubleValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'doubleValueField': {}}),
        parseFailure(['doubleValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'floatValueField': 'a'}),
        parseFailure(['floatValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'floatValueField': {}}),
        parseFailure(['floatValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'int64ValueField': 'a'}),
        parseFailure(['int64ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'int64ValueField': {}}),
        parseFailure(['int64ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'int64ValueField': 10.4}),
        parseFailure(['int64ValueField']),
      );

      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'uint64ValueField': 'a'}),
        parseFailure(['uint64ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'uint64ValueField': 10.4}),
        parseFailure(['uint64ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'int32ValueField': 'a'}),
        parseFailure(['int32ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'int32ValueField': 10.4}),
        parseFailure(['int32ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'uint32ValueField': 'a'}),
        parseFailure(['uint32ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'uint32ValueField': 10.4}),
        parseFailure(['uint32ValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'boolValueField': 'false'}),
        parseFailure(['boolValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'stringValueField': 22}),
        parseFailure(['stringValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()..mergeFromProto3Json({'bytesValueField': 22}),
        parseFailure(['bytesValueField']),
      );
      expect(
        () =>
            TestWellKnownTypes()
              ..mergeFromProto3Json({'bytesValueField': '()'}),
        parseFailure(['bytesValueField']),
      );

      expect(
        TestWellKnownTypes()..mergeFromProto3Json({
          'doubleValueField': null,
          'floatValueField': null,
          'int64ValueField': null,
          'uint64ValueField': null,
          'int32ValueField': null,
          'uint32ValueField': null,
          'boolValueField': null,
          'stringValueField': null,
          'bytesValueField': null,
        }),
        TestWellKnownTypes(),
        reason:
            'having fields of wrapper types set to null will return an '
            'empty wrapper (with unset .value field)',
      );
    });

    test('struct', () {
      final f = {
        'null': null,
        'number': 22.3,
        'string': 'foo',
        'bool': false,
        'struct': {'a': 0},
        'list': [{}, [], 'why'],
      };

      final s =
          Struct()
            ..fields['null'] = (Value()..nullValue = NullValue.NULL_VALUE)
            ..fields['number'] = (Value()..numberValue = 22.3)
            ..fields['string'] = (Value()..stringValue = 'foo')
            ..fields['bool'] = (Value()..boolValue = false)
            ..fields['struct'] =
                (Value()
                  ..structValue =
                      (Struct()..fields['a'] = (Value()..numberValue = 0.0)))
            ..fields['list'] =
                (Value()
                  ..listValue =
                      (ListValue()
                        ..values.addAll([
                          Value()..structValue = Struct(),
                          Value()..listValue = ListValue(),
                          Value()..stringValue = 'why',
                        ])));
      expect(Struct()..mergeFromProto3Json(f), s);

      expect(
        Struct()..mergeFromProto3Json(<dynamic, dynamic>{'a': 12}),
        (Struct()..fields['a'] = (Value()..numberValue = 12.0)),
        reason: 'Allow key type to be `dynamic`',
      );

      expect(
        () => Struct()..mergeFromProto3Json({1: 2}),
        parseFailure([]),
        reason: 'Non-string key in JSON object literal',
      );

      expect(
        () => Struct()..mergeFromProto3Json([]),
        parseFailure([]),
        reason: 'Non object literal',
      );

      expect(
        () => Struct()..mergeFromProto3Json([]),
        parseFailure([]),
        reason: 'Non-string key in JSON object literal',
      );

      expect(
        () => Value()..mergeFromProto3Json(Object()),
        parseFailure([]),
        reason: 'Non JSON value',
      );

      expect(
        () => ListValue()..mergeFromProto3Json({}),
        parseFailure([]),
        reason: 'Non-list',
      );
    });

    test('field mask', () {
      expect(
        TestWellKnownTypes()
          ..mergeFromProto3Json({'fieldMaskField': 'foo,barBaz'}),
        TestWellKnownTypes()
          ..fieldMaskField = (FieldMask()..paths.addAll(['foo', 'bar_baz'])),
      );
      expect(
        () => FieldMask()..mergeFromProto3Json('foo,bar_bar'),
        parseFailure([]),
      );

      expect(FieldMask()..mergeFromProto3Json(''), FieldMask());
      expect(() => FieldMask()..mergeFromProto3Json(12), parseFailure([]));
    });

    test('one-of', () {
      expectFirstSet(Foo()..mergeFromProto3Json({'first': 'oneof'}));
      expectSecondSet(Foo()..mergeFromProto3Json({'second': 1}));
      expectOneofNotSet(Foo()..mergeFromProto3Json({}));
    });
  });

  group('Convert Double', () {
    test('With Decimal', () {
      final json = {'optionalDouble': 1.2};
      final proto = TestAllTypes()..optionalDouble = 1.2;
      expect(TestAllTypes()..mergeFromProto3Json(json), proto);
      expect(proto.toProto3Json(), json);
    });

    test('Whole Number', () {
      final json = {'optionalDouble': 5};
      final proto = TestAllTypes()..optionalDouble = 5.0;
      expect(TestAllTypes()..mergeFromProto3Json(json), proto);
      expect(proto.toProto3Json(), json);
      expect(jsonEncode(proto.toProto3Json()), '{"optionalDouble":5}');
    });

    test('Infinity', () {
      final json = {'optionalDouble': 'Infinity'};
      final proto = TestAllTypes()..optionalDouble = double.infinity;
      expect(TestAllTypes()..mergeFromProto3Json(json), proto);
      expect(proto.toProto3Json(), json);
    });

    test('Negative Infinity', () {
      final json = {'optionalDouble': '-Infinity'};
      final proto = TestAllTypes()..optionalDouble = double.negativeInfinity;
      expect(TestAllTypes()..mergeFromProto3Json(json), proto);
      expect(proto.toProto3Json(), json);
    });
  });
}
