// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.proto2.MessageTest');
goog.setTestOnly('goog.proto2.MessageTest');

goog.require('goog.testing.TestCase');
goog.require('goog.testing.jsunit');
goog.require('proto2.TestAllTypes');
goog.require('proto2.TestAllTypes.NestedEnum');
goog.require('proto2.TestAllTypes.NestedMessage');
goog.require('proto2.TestAllTypes.OptionalGroup');
goog.require('proto2.TestAllTypes.RepeatedGroup');

function setUp() {
  // TODO(b/25875505): Fix unreported assertions (go/failonunreportedasserts).
  goog.testing.TestCase.getActiveTestCase().failOnUnreportedAsserts = false;
}

function testEqualsWithEmptyMessages() {
  var message1 = new proto2.TestAllTypes();
  assertTrue('same message object', message1.equals(message1));
  assertFalse('comparison with null', message1.equals(null));
  assertFalse('comparison with undefined', message1.equals(undefined));

  var message2 = new proto2.TestAllTypes();
  assertTrue('two empty message objects', message1.equals(message2));

  var message3 = new proto2.TestAllTypes.NestedMessage();
  assertFalse('different message types', message3.equals(message1));
}

function testEqualsWithSingleInt32Field() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();

  message1.setOptionalInt32(1);
  assertFalse('message1 has an extra int32 field', message1.equals(message2));

  message2.setOptionalInt32(1);
  assertTrue('same int32 field in both messages', message1.equals(message2));

  message2.setOptionalInt32(2);
  assertFalse('different int32 field', message1.equals(message2));

  message1.clearOptionalInt32();
  assertFalse('message2 has an extra int32 field', message1.equals(message2));
}

function testEqualsWithRepeatedInt32Fields() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();

  message1.addRepeatedInt32(0);
  message2.addRepeatedInt32(0);
  assertTrue('equal repeated int32 field', message1.equals(message2));

  message1.addRepeatedInt32(1);
  assertFalse('message1 has more items', message1.equals(message2));

  message2.addRepeatedInt32(1);
  message2.addRepeatedInt32(1);
  assertFalse('message2 has more items', message1.equals(message2));

  message1.addRepeatedInt32(2);
  assertFalse('different int32 items', message1.equals(message2));
}

function testEqualsWithDefaultValue() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();
  message1.setOptionalInt64('1');

  assertEquals(
      'message1.getOptionalInt64OrDefault should return 1', '1',
      message1.getOptionalInt64OrDefault());
  assertEquals(
      'message2.getOptionalInt64OrDefault should return 1 too', '1',
      message2.getOptionalInt64OrDefault());
  assertTrue(
      'message1.hasOptionalInt64() should be true',
      message1.hasOptionalInt64());
  assertFalse(
      'message2.hasOptionalInt64() should be false',
      message2.hasOptionalInt64());
  assertFalse('as a result they are not equal', message1.equals(message2));
}

function testEqualsWithOptionalGroup() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();
  var group1 = new proto2.TestAllTypes.OptionalGroup();
  var group2 = new proto2.TestAllTypes.OptionalGroup();

  message1.setOptionalgroup(group1);
  assertFalse(
      'only message1 has OptionalGroup field', message1.equals(message2));

  message2.setOptionalgroup(group2);
  assertTrue(
      'both messages have OptionalGroup field', message1.equals(message2));

  group1.setA(0);
  group2.setA(1);
  assertFalse(
      'different value in the optional group', message1.equals(message2));

  message1.clearOptionalgroup();
  assertFalse(
      'only message2 has OptionalGroup field', message1.equals(message2));
}

function testEqualsWithRepeatedGroup() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();
  var group1 = new proto2.TestAllTypes.RepeatedGroup();
  var group2 = new proto2.TestAllTypes.RepeatedGroup();

  message1.addRepeatedgroup(group1);
  assertFalse('message1 has more RepeatedGroups', message1.equals(message2));

  message2.addRepeatedgroup(group2);
  assertTrue('both messages have one RepeatedGroup', message1.equals(message2));

  group1.addA(1);
  assertFalse(
      'message1 has more int32s in RepeatedGroup', message1.equals(message2));

  group2.addA(1);
  assertTrue(
      'both messages have one int32 in RepeatedGroup',
      message1.equals(message2));

  group1.addA(1);
  group2.addA(2);
  assertFalse(
      'the messages have different int32s in RepeatedGroup',
      message1.equals(message2));
}

function testEqualsWithNestedMessage() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();
  var nested1 = new proto2.TestAllTypes.NestedMessage();
  var nested2 = new proto2.TestAllTypes.NestedMessage();

  message1.setOptionalNestedMessage(nested1);
  assertFalse('only message1 has nested message', message1.equals(message2));

  message2.setOptionalNestedMessage(nested2);
  assertTrue('both messages have nested message', message1.equals(message2));

  nested1.setB(1);
  assertFalse(
      'different int32 in the nested messages', message1.equals(message2));

  message1.clearOptionalNestedMessage();
  assertFalse('only message2 has nested message', message1.equals(message2));
}

function testEqualsWithNestedEnum() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();

  message1.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.FOO);
  assertFalse('only message1 has nested enum', message1.equals(message2));

  message2.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.FOO);
  assertTrue('both messages have nested enum', message1.equals(message2));

  message2.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.BAR);
  assertFalse('different enum value', message1.equals(message2));

  message1.clearOptionalNestedEnum();
  assertFalse('only message2 has nested enum', message1.equals(message2));
}

function testEqualsWithUnknownFields() {
  var message1 = new proto2.TestAllTypes();
  var message2 = new proto2.TestAllTypes();
  message1.setUnknown(999, 'foo');
  message1.setUnknown(999, 'bar');
  assertTrue('unknown fields are ignored', message1.equals(message2));
}

function testCloneEmptyMessage() {
  var message = new proto2.TestAllTypes();
  var clone = message.clone();
  assertObjectEquals('cloned empty message', message, clone);
}

function testCloneMessageWithSeveralFields() {
  var message = new proto2.TestAllTypes();
  message.setOptionalInt32(1);
  message.addRepeatedInt32(2);
  var optionalGroup = new proto2.TestAllTypes.OptionalGroup();
  optionalGroup.setA(3);
  message.setOptionalgroup(optionalGroup);
  var repeatedGroup = new proto2.TestAllTypes.RepeatedGroup();
  repeatedGroup.addA(4);
  message.addRepeatedgroup(repeatedGroup);
  var nestedMessage = new proto2.TestAllTypes.NestedMessage();
  nestedMessage.setB(5);
  message.setOptionalNestedMessage(nestedMessage);
  message.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.FOO);
  message.setUnknown(999, 'foo');

  var clone = message.clone();
  assertNotEquals(
      'different OptionalGroup instance', message.getOptionalgroup(),
      clone.getOptionalgroup());
  assertNotEquals(
      'different RepeatedGroup array instance', message.repeatedgroupArray(),
      clone.repeatedgroupArray());
  assertNotEquals(
      'different RepeatedGroup array item instance',
      message.getRepeatedgroup(0), clone.getRepeatedgroup(0));
  assertNotEquals(
      'different NestedMessage instance', message.getOptionalNestedMessage(),
      clone.getOptionalNestedMessage());
}

function testCloneWithUnknownFields() {
  var message = new proto2.TestAllTypes();
  message.setUnknown(999, 'foo');

  var clone = message.clone();
  assertTrue('clone.equals(message) returns true', clone.equals(message));
  clone.forEachUnknown(function(tag, value) {
    fail('the unknown fields should not have been cloned');
  });

  clone.setUnknown(999, 'foo');
  assertObjectEquals(
      'the original and the cloned message are equal except ' +
          'for the unknown fields',
      message, clone);
}

function testCopyFromSameMessage() {
  var source = new proto2.TestAllTypes();
  source.setOptionalInt32(32);
  source.copyFrom(source);
  assertEquals(32, source.getOptionalInt32());
}

function testCopyFromFlatMessage() {
  // Recursive copying is implicitly tested in the testClone... methods.

  var source = new proto2.TestAllTypes();
  source.setOptionalInt32(32);
  source.setOptionalInt64('64');
  source.addRepeatedInt32(32);

  var target = new proto2.TestAllTypes();
  target.setOptionalInt32(33);
  target.setOptionalUint32(33);
  target.addRepeatedInt32(33);

  target.copyFrom(source);
  assertObjectEquals(
      'source and target are equal after copyFrom', source, target);

  target.copyFrom(source);
  assertObjectEquals('second copyFrom call has no effect', source, target);

  source.setUnknown(999, 'foo');
  target.setUnknown(999, 'bar');
  target.copyFrom(source);
  assertThrows(
      'unknown fields are not copied',
      goog.partial(assertObjectEquals, source, target));
}

function testMergeFromEmptyMessage() {
  var source = new proto2.TestAllTypes();
  source.setOptionalInt32(32);
  source.setOptionalInt64('64');
  var nested = new proto2.TestAllTypes.NestedMessage();
  nested.setB(66);
  source.setOptionalNestedMessage(nested);

  var target = new proto2.TestAllTypes();
  target.mergeFrom(source);
  assertObjectEquals(
      'source and target are equal after mergeFrom', source, target);
}

function testMergeFromFlatMessage() {
  var source = new proto2.TestAllTypes();
  source.setOptionalInt32(32);
  source.setOptionalString('foo');
  source.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.FOO);

  var target = new proto2.TestAllTypes();
  target.setOptionalInt64('64');
  target.setOptionalString('bar');
  target.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.BAR);

  var expected = new proto2.TestAllTypes();
  expected.setOptionalInt32(32);
  expected.setOptionalInt64('64');
  expected.setOptionalString('foo');
  expected.setOptionalNestedEnum(proto2.TestAllTypes.NestedEnum.FOO);

  target.mergeFrom(source);
  assertObjectEquals(
      'expected and target are equal after mergeFrom', expected, target);
}

function testMergeFromNestedMessage() {
  var source = new proto2.TestAllTypes();
  var nested = new proto2.TestAllTypes.NestedMessage();
  nested.setB(66);
  source.setOptionalNestedMessage(nested);

  var target = new proto2.TestAllTypes();
  nested = new proto2.TestAllTypes.NestedMessage();
  nested.setC(77);
  target.setOptionalNestedMessage(nested);

  var expected = new proto2.TestAllTypes();
  nested = new proto2.TestAllTypes.NestedMessage();
  nested.setB(66);
  nested.setC(77);
  expected.setOptionalNestedMessage(nested);

  target.mergeFrom(source);
  assertObjectEquals(
      'expected and target are equal after mergeFrom', expected, target);
}

function testMergeFromRepeatedMessage() {
  var source = new proto2.TestAllTypes();
  source.addRepeatedInt32(2);
  source.addRepeatedInt32(3);

  var target = new proto2.TestAllTypes();
  target.addRepeatedInt32(1);

  target.mergeFrom(source);
  assertArrayEquals(
      'repeated_int32 array has elements from both messages', [1, 2, 3],
      target.repeatedInt32Array());
}

function testInitDefaultsWithEmptyMessage() {
  var message = new proto2.TestAllTypes();
  message.initDefaults(false);

  assertFalse('int32 field is not set', message.hasOptionalInt32());
  assertFalse('int64 [default=1] field is not set', message.hasOptionalInt64());
  assertTrue('optional group field is set', message.hasOptionalgroup());
  assertFalse(
      'int32 inside the group is not set', message.getOptionalgroup().hasA());
  assertObjectEquals(
      'value of the optional group', new proto2.TestAllTypes.OptionalGroup(),
      message.getOptionalgroup());
  assertTrue('nested message is set', message.hasOptionalNestedMessage());
  assertObjectEquals(
      'value of the nested message', new proto2.TestAllTypes.NestedMessage(),
      message.getOptionalNestedMessage());
  assertFalse('nested enum is not set', message.hasOptionalNestedEnum());
  assertFalse('repeated int32 is not set', message.hasRepeatedInt32());
  assertFalse(
      'repeated nested message is not set', message.hasRepeatedNestedMessage());

  message = new proto2.TestAllTypes();
  message.initDefaults(true);

  assertTrue('int32 field is set', message.hasOptionalInt32());
  assertEquals('value of the int32 field', 0, message.getOptionalInt32());
  assertTrue('int64 [default=1] field is set', message.hasOptionalInt64());
  assertEquals('value of the int64 field', '1', message.getOptionalInt64());
  assertTrue(
      'int32 inside nested message is set',
      message.getOptionalNestedMessage().hasB());
  assertEquals(
      'value of the int32 field inside the nested message', 0,
      message.getOptionalNestedMessage().getB());
}

function testInitDefaultsWithNonEmptyMessage() {
  var message = new proto2.TestAllTypes();
  message.setOptionalInt32(32);
  message.setOptionalInt64('64');
  message.setOptionalgroup(new proto2.TestAllTypes.OptionalGroup());
  var nested1 = new proto2.TestAllTypes.NestedMessage();
  nested1.setB(66);
  message.setOptionalNestedMessage(nested1);
  var nested2 = new proto2.TestAllTypes.NestedMessage();
  message.addRepeatedNestedMessage(nested2);
  var nested3 = new proto2.TestAllTypes.NestedMessage();
  nested3.setB(66);
  message.addRepeatedNestedMessage(nested3);

  message.initDefaults(true);
  assertEquals('int32 field is unchanged', 32, message.getOptionalInt32());
  assertEquals(
      'int64 [default=1] field is unchanged', '64', message.getOptionalInt64());
  assertTrue('bool field is initialized', message.hasOptionalBool());
  assertFalse('value of the bool field', message.getOptionalBool());
  assertTrue(
      'int32 inside the optional group is initialized',
      message.getOptionalgroup().hasA());
  assertEquals(
      'value of the int32 inside the optional group', 0,
      message.getOptionalgroup().getA());
  assertEquals(
      'int32 inside nested message is unchanged', 66,
      message.getOptionalNestedMessage().getB());
  assertTrue(
      'int32 at index 0 of the repeated nested message is initialized',
      message.getRepeatedNestedMessage(0).hasB());
  assertEquals(
      'value of int32 at index 0 of the repeated nested message', 0,
      message.getRepeatedNestedMessage(0).getB());
  assertEquals(
      'int32 at index 1 of the repeated nested message is unchanged', 66,
      message.getRepeatedNestedMessage(1).getB());
}

function testInitDefaultsTwice() {
  var message = new proto2.TestAllTypes();
  message.initDefaults(false);
  var clone = message.clone();
  clone.initDefaults(false);
  assertObjectEquals(
      'second call of initDefaults(false) has no effect', message, clone);

  message = new proto2.TestAllTypes();
  message.initDefaults(true);
  clone = message.clone();
  clone.initDefaults(true);
  assertObjectEquals(
      'second call of initDefaults(true) has no effect', message, clone);
}

function testInitDefaultsThenClone() {
  var message = new proto2.TestAllTypes();
  message.initDefaults(true);
  assertObjectEquals('message is cloned properly', message, message.clone());
}

function testClassGetDescriptorEqualToInstanceGetDescriptor() {
  var classDescriptor = proto2.TestAllTypes.getDescriptor();
  var instanceDescriptor = new proto2.TestAllTypes().getDescriptor();
  assertEquals(classDescriptor, instanceDescriptor);
}

function testGetAfterSetWithLazyDeserializer() {
  // Test makes sure that the lazy deserializer for a field is not
  // erroneously called when get$Value is called after set$Value.
  var message = new proto2.TestAllTypes();

  var fakeDeserializer = {};  // stub with no methods defined; fails hard
  message.initializeForLazyDeserializer(fakeDeserializer, {} /* data */);
  message.setOptionalBool(true);
  assertEquals(true, message.getOptionalBool());
}

function testHasOnLazyDeserializer() {
  // Test that null values for fields are treated as absent by the lazy
  // deserializer.
  var message = new proto2.TestAllTypes();

  var fakeDeserializer = {};  // stub with no methods defined; fails hard
  message.initializeForLazyDeserializer(
      fakeDeserializer, {13: false} /* data */);
  assertEquals(true, message.hasOptionalBool());
}

function testHasOnLazyDeserializerWithNulls() {
  // Test that null values for fields are treated as absent by the lazy
  // deserializer.
  var message = new proto2.TestAllTypes();

  var fakeDeserializer = {};  // stub with no methods defined; fails hard
  message.initializeForLazyDeserializer(
      fakeDeserializer, {13: null} /* data */);
  assertEquals(false, message.hasOptionalBool());
}
