// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Test fixture for sync internals WebUI testing.
 * @constructor
 * @extends {testing.Test}
 */
function SyncInternalsWebUITest() {}

SyncInternalsWebUITest.prototype = {
  __proto__: testing.Test.prototype,

  /**
   * Browse to the sync internals page.
   * @override
   */
  browsePreload: 'chrome://sync-internals',

  /**
   * Disable accessibility testing for this page.
   * @override
   */
  runAccessibilityChecks: false,

  /** @override */
  preLoad: function() {
    this.makeAndRegisterMockHandler([
        'getAllNodes',
    ]);
  },

  /**
   * Checks aboutInfo's details section for the specified field.
   * @param {boolean} isValid Whether the field is valid.
   * @param {string} key The name of the key to search for in details.
   * @param {string} value The expected value if |key| is found.
   * @return {boolean} whether the field was found in the details.
   * @protected
   */
  hasInDetails: function(isValid, key, value) {
    var details = chrome.sync.aboutInfo.details;
    if (!details)
      return false;
    for (var i = 0; i < details.length; ++i) {
      if (!details[i].data)
        continue;
      for (var j = 0; j < details[i].data.length; ++j) {
        var obj = details[i].data[j];
        if (obj.stat_name == key)
          return obj.is_valid == isValid && obj.stat_value == value;
      }
    }
    return false;
  }
};

/**
 * Constant hard-coded value to return from mock getAllNodes.
 * @const
 */
var HARD_CODED_ALL_NODES = [{
  'nodes': [{
    'ATTACHMENT_METADATA': '',
    'BASE_SERVER_SPECIFICS': {},
    'BASE_VERSION': '1396470970810000',
    'CTIME': 'Wednesday, December 31, 1969 4:00:00 PM',
    'ID': 'sZ:ADqtAZwzF4GOIyvkI2enSI62AU5p/7MNmvuSSyf7yXJ1SkJwpp1YL' +
      '6bbMkF8inzqW+EO6n2aPJ/uXccW9GHxorBlnKoZAWHVzg==',
    'IS_DEL': false,
    'IS_DIR': true,
    'IS_UNAPPLIED_UPDATE': false,
    'IS_UNSYNCED': false,
    'LOCAL_EXTERNAL_ID': '0',
    'META_HANDLE': '387',
    'MTIME': 'Wednesday, December 31, 1969 4:00:00 PM',
    'NON_UNIQUE_NAME': 'Autofill',
    'PARENT_ID': 'r',
    'SERVER_CTIME': 'Wednesday, December 31, 1969 4:00:00 PM',
    'SERVER_IS_DEL': false,
    'SERVER_IS_DIR': true,
    'SERVER_MTIME': 'Wednesday, December 31, 1969 4:00:00 PM',
    'SERVER_NON_UNIQUE_NAME': 'Autofill',
    'SERVER_PARENT_ID': 'r',
    'SERVER_SPECIFICS': {
      'autofill': {
        'usage_timestamp': []
      }
    },
    'SERVER_UNIQUE_POSITION': 'INVALID[]',
    'SERVER_VERSION': '1396470970810000',
    'SPECIFICS': {
      'autofill': {
        'usage_timestamp': []
      }
    },
    'SYNCING': false,
    'TRANSACTION_VERSION': '1',
    'UNIQUE_BOOKMARK_TAG': '',
    'UNIQUE_CLIENT_TAG': '',
    'UNIQUE_POSITION': 'INVALID[]',
    'UNIQUE_SERVER_TAG': 'google_chrome_autofill',
    'isDirty': false,
    'serverModelType': 'Autofill'
  }, {
    'ATTACHMENT_METADATA': '',
    'BASE_SERVER_SPECIFICS': {},
    'BASE_VERSION': '1394241139528639',
    'CTIME': 'Friday, March 7, 2014 5:12:19 PM',
    'ID': 'sZ:ADqtAZwzc/ol1iaz+yNLjjWak9PBE0o/hATzpqJsyq/HX2xzV2f88' +
      'FaOrT7HDE4tyn7zx2LWgkAFvZfCA5mOy4p0XFgiY0L+mw==',
    'IS_DEL': false,
    'IS_DIR': false,
    'IS_UNAPPLIED_UPDATE': false,
    'IS_UNSYNCED': false,
    'LOCAL_EXTERNAL_ID': '0',
    'META_HANDLE': '2989',
    'MTIME': 'Friday, March 7, 2014 5:12:19 PM',
    'NON_UNIQUE_NAME': 'autofill_entry|Email|rlsynctet2',
    'PARENT_ID': 'sZ:ADqtAZwzF4GOIyvkI2enSI62AU5p/7MNmvuSSyf7yXJ1Sk' +
      'Jwpp1YL6bbMkF8inzqW+EO6n2aPJ/uXccW9GHxorBlnKoZAWHVzg==',
    'SERVER_CTIME': 'Friday, March 7, 2014 5:12:19 PM',
    'SERVER_IS_DEL': false,
    'SERVER_IS_DIR': false,
    'SERVER_MTIME': 'Friday, March 7, 2014 5:12:19 PM',
    'SERVER_NON_UNIQUE_NAME': 'autofill_entry|Email|rlsynctet2',
    'SERVER_PARENT_ID': 'sZ:ADqtAZwzF4GOIyvkI2enSI62AU5p/7MNmvuSSyf' +
      '7yXJ1SkJwpp1YL6bbMkF8inzqW+EO6n2aPJ/uXccW9GHxorBlnKoZAWHVzg==',
    'SERVER_SPECIFICS': {
      'autofill': {
        'name': 'Email',
        'usage_timestamp': ['13038713887000000', '13038713890000000'],
        'value': 'rlsynctet2'
      }
    },
    'SERVER_UNIQUE_POSITION': 'INVALID[]',
    'SERVER_VERSION': '1394241139528639',
    'SPECIFICS': {
      'autofill': {
        'name': 'Email',
        'usage_timestamp': ['13038713887000000', '13038713890000000'],
        'value': 'rlsynctet2'
      }
    },
    'SYNCING': false,
    'TRANSACTION_VERSION': '1',
    'UNIQUE_BOOKMARK_TAG': '',
    'UNIQUE_CLIENT_TAG': 'EvliorKUf1rLjT+BGkNZp586Tsk=',
    'UNIQUE_POSITION': 'INVALID[]',
    'UNIQUE_SERVER_TAG': '',
    'isDirty': false,
    'serverModelType': 'Autofill'
  }],
  'type': 'Autofill'
}];

/**
 * A value to return in mock onReceivedUpdatedAboutInfo event.
 * @const
 */
HARD_CODED_ABOUT_INFO = {
  'actionable_error': [
    {
      'is_valid': false,
      'stat_name': 'Error Type',
      'stat_value': 'Uninitialized'
    },
    {
      'is_valid': false,
      'stat_name': 'Action',
      'stat_value': 'Uninitialized'
    },
    {
      'is_valid': false,
      'stat_name': 'URL',
      'stat_value': 'Uninitialized'
    },
    {
      'is_valid': false,
      'stat_name': 'Error Description',
      'stat_value': 'Uninitialized'
    }
  ],
  'actionable_error_detected': false,
  'details': [
    {
      'data': [
        {
          'is_valid': true,
          'stat_name': 'Summary',
          'stat_value': 'Sync service initialized'
        }
      ],
      'is_sensitive': false,
      'title': 'Summary'
    },
  ],
  'type_status': [
    {
      'name': 'Model Type',
      'num_entries': 'Total Entries',
      'num_live': 'Live Entries',
      'status': 'header',
      'value': 'Group Type'
    },
    {
      'name': 'Bookmarks',
      'num_entries': 2793,
      'num_live': 2793,
      'status': 'ok',
      'value': 'Active: GROUP_UI'
    },
  ],
  'unrecoverable_error_detected': false
};

NETWORK_EVENT_DETAILS_1 = {
  'details': 'Notified types: Bookmarks, Autofill',
  'proto': {},
  'time': 1395874542192.407,
  'type': 'Normal GetUpdate request',
};

NETWORK_EVENT_DETAILS_2 = {
  'details': 'Received error: SYNC_AUTH_ERROR',
  'proto': {},
  'time': 1395874542192.837,
  'type': 'GetUpdates Response',
};

TEST_F('SyncInternalsWebUITest', 'Uninitialized', function() {
  assertNotEquals(null, chrome.sync.aboutInfo);
  expectTrue(this.hasInDetails(true, 'Username', ''));
  expectTrue(this.hasInDetails(false, 'Summary', 'Uninitialized'));
});

TEST_F('SyncInternalsWebUITest', 'LoadPastedAboutInfo', function() {
  // Expose the text field.
  $('import-status').click();

  // Fill it with fake data.
  $('status-text').value = JSON.stringify(HARD_CODED_ABOUT_INFO);

  // Trigger the import.
  $('import-status').click();

  expectTrue(this.hasInDetails(true, 'Summary', 'Sync service initialized'));
});

TEST_F('SyncInternalsWebUITest', 'NetworkEventsTest', function() {
  networkEvent1 = new Event('onProtocolEvent');
  networkEvent1.details = NETWORK_EVENT_DETAILS_1;
  networkEvent2 = new Event('onProtocolEvent');
  networkEvent2.details = NETWORK_EVENT_DETAILS_2;

  chrome.sync.events.dispatchEvent(networkEvent1);
  chrome.sync.events.dispatchEvent(networkEvent2);

  expectEquals(2, $('traffic-event-container').children.length);

  // Test that repeated events are not re-displayed.
  chrome.sync.events.dispatchEvent(networkEvent1);
  expectEquals(2, $('traffic-event-container').children.length);
});

TEST_F('SyncInternalsWebUITest', 'SearchTabDoesntChangeOnItemSelect',
       function() {
  // Select the search tab.
  $('sync-search-tab').selected = true;
  expectTrue($('sync-search-tab').selected);

  // Build the data model and attach to result list.
  cr.ui.List.decorate($('sync-results-list'));
  $('sync-results-list').dataModel = new cr.ui.ArrayDataModel([
    {
      value: 'value 0',
      toString: function() { return 'node 0'; },
    },
    {
      value: 'value 1',
      toString: function() { return 'node 1'; },
    }
  ]);

  // Select the first list item and verify the search tab remains selected.
  $('sync-results-list').getListItemByIndex(0).selected = true;
  expectTrue($('sync-search-tab').selected);
});

TEST_F('SyncInternalsWebUITest', 'NodeBrowserTest', function() {
  var getAllNodesSavedArgs = new SaveMockArguments();
  this.mockHandler.expects(once()).
      getAllNodes(getAllNodesSavedArgs.match(ANYTHING)).
      will(callFunctionWithSavedArgs(getAllNodesSavedArgs,
                                     chrome.sync.getAllNodesCallback,
                                     HARD_CODED_ALL_NODES));

  // Hit the refresh button.
  $('node-browser-refresh-button').click();

  // Check that the refresh time was updated.
  expectNotEquals($('node-browser-refresh-time').textContent, 'Never');

  // Verify some hard-coded assumptions.  These depend on the vaue of the
  // hard-coded nodes, specified elsewhere in this file.

  // Start with the tree itself.
  var tree = $('sync-node-tree');
  assertEquals(1, tree.items.length);

  // Check the type root and expand it.
  var typeRoot = tree.items[0];
  expectFalse(typeRoot.expanded);
  typeRoot.expanded = true;
  assertEquals(1, typeRoot.items.length);

  // An actual sync node.  The child of the type root.
  var leaf = typeRoot.items[0];

  // Verify that selecting it affects the details view.
  expectTrue($('node-details').hasAttribute('hidden'));
  leaf.selected = true;
  expectFalse($('node-details').hasAttribute('hidden'));
});

TEST_F('SyncInternalsWebUITest', 'NodeBrowserRefreshOnTabSelect', function() {
  var getAllNodesSavedArgs = new SaveMockArguments();
  this.mockHandler.expects(once()).
      getAllNodes(getAllNodesSavedArgs.match(ANYTHING)).
      will(callFunctionWithSavedArgs(getAllNodesSavedArgs,
                                     chrome.sync.getAllNodesCallback,
                                     HARD_CODED_ALL_NODES));

  // Should start with non-refreshed node browser.
  expectEquals($('node-browser-refresh-time').textContent, 'Never');

  // Selecting the tab will refresh it.
  $('sync-browser-tab').selected = true;
  expectNotEquals($('node-browser-refresh-time').textContent, 'Never');

  // Re-selecting the tab shouldn't re-refresh.
  $('node-browser-refresh-time').textContent = 'TestCanary';
  $('sync-browser-tab').selected = false;
  $('sync-browser-tab').selected = true;
  expectEquals($('node-browser-refresh-time').textContent, 'TestCanary');
});

// Tests that the events log page correctly receives and displays an event.
TEST_F('SyncInternalsWebUITest', 'EventLogTest', function() {
  // Dispatch an event.
  var connectionEvent = new Event('onConnectionStatusChange');
  connectionEvent.details = {'status': 'CONNECTION_OK'};
  chrome.sync.events.dispatchEvent(connectionEvent);

  // Verify that it is displayed in the events log.
  var syncEventsTable = $('sync-events');
  var firstRow = syncEventsTable.children[0];

  // Makes some assumptions about column ordering.  We'll need re-think this if
  // it turns out to be a maintenance burden.
  assertEquals(4, firstRow.children.length);
  var submoduleName = firstRow.children[1].textContent;
  var eventName = firstRow.children[2].textContent;
  var detailsText = firstRow.children[3].textContent;

  expectGE(submoduleName.indexOf('manager'), 0,
      'submoduleName=' + submoduleName);
  expectGE(eventName.indexOf('onConnectionStatusChange'), 0,
      'eventName=' + eventName);
  expectGE(detailsText.indexOf('CONNECTION_OK'), 0,
      'detailsText=' + detailsText);
});

TEST_F('SyncInternalsWebUITest', 'DumpSyncEventsToText', function() {
  // Dispatch an event.
  var connectionEvent = new Event('onConnectionStatusChange');
  connectionEvent.details = {'status': 'CONNECTION_OK'};
  chrome.sync.events.dispatchEvent(connectionEvent);

  // Click the dump-to-text button.
  $('dump-to-text').click();

  // Verify our event is among the results.
  var eventDumpText = $('data-dump').textContent;

  expectGE(eventDumpText.indexOf('onConnectionStatusChange'), 0);
  expectGE(eventDumpText.indexOf('CONNECTION_OK'), 0);
});