/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Places test code.
 *
 * The Initial Developer of the Original Code is Mozilla Corp.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Drew Willcoxon <adw@mozilla.com> (Original Author)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * Tests the bookmarks-restore-* nsIObserver notifications after restoring
 * bookmarks from JSON and HTML.  See bug 470314.
 */

// The topics and data passed to nsIObserver.observe() on bookmarks restore
const NSIOBSERVER_TOPIC_BEGIN    = "bookmarks-restore-begin";
const NSIOBSERVER_TOPIC_SUCCESS  = "bookmarks-restore-success";
const NSIOBSERVER_TOPIC_FAILED   = "bookmarks-restore-failed";
const NSIOBSERVER_DATA_JSON      = "json";
const NSIOBSERVER_DATA_HTML      = "html";
const NSIOBSERVER_DATA_HTML_INIT = "html-initial";

// Bookmarks are added for these URIs
var uris = [
  "http://example.com/1",
  "http://example.com/2",
  "http://example.com/3",
  "http://example.com/4",
  "http://example.com/5",
];

// Add tests here.  Each is an object with these properties:
//   desc:       description printed before test is run
//   currTopic:  the next expected topic that should be observed for the test;
//               set to NSIOBSERVER_TOPIC_BEGIN to begin
//   finalTopic: the last expected topic that should be observed for the test,
//               which then causes the next test to be run
//   data:       the data passed to nsIObserver.observe() corresponding to the
//               test
//   file:       the nsILocalFile that the test creates
//   folderId:   for HTML restore into a folder, the folder ID to restore into;
//               otherwise, set it to null
//   run:        a method that actually runs the test
var tests = [
  {
    desc:       "JSON restore: normal restore should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_JSON,
    folderId:   null,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.json");
      addBookmarks();
      PlacesUtils.backupBookmarksToFile(this.file);
      remove_all_bookmarks();
      try {
        PlacesUtils.restoreBookmarksFromJSONFile(this.file);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "JSON restore: empty file should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_JSON,
    folderId:   null,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.json");
      try {
        PlacesUtils.restoreBookmarksFromJSONFile(this.file);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "JSON restore: nonexisting file should fail",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_FAILED,
    data:       NSIOBSERVER_DATA_JSON,
    folderId:   null,
    run:        function () {
      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
      this.file.append("this file doesn't exist because nobody created it");
      try {
        PlacesUtils.restoreBookmarksFromJSONFile(this.file);
        do_throw("  Restore should have failed");
      }
      catch (e) {}
    }
  },

  {
    desc:       "HTML restore: normal restore should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_HTML,
    folderId:   null,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.html");
      addBookmarks();
      importer.exportHTMLToFile(this.file);
      remove_all_bookmarks();
      try {
        importer.importHTMLFromFile(this.file, false);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "HTML restore: empty file should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_HTML,
    folderId:   null,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.init.html");
      try {
        importer.importHTMLFromFile(this.file, false);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "HTML restore: nonexisting file should fail",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_FAILED,
    data:       NSIOBSERVER_DATA_HTML,
    folderId:   null,
    run:        function () {
      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
      this.file.append("this file doesn't exist because nobody created it");
      try {
        importer.importHTMLFromFile(this.file, false);
        do_throw("  Restore should have failed");
      }
      catch (e) {}
    }
  },

  {
    desc:       "HTML initial restore: normal restore should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_HTML_INIT,
    folderId:   null,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.init.html");
      addBookmarks();
      importer.exportHTMLToFile(this.file);
      remove_all_bookmarks();
      try {
        importer.importHTMLFromFile(this.file, true);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "HTML initial restore: empty file should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_HTML_INIT,
    folderId:   null,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.init.html");
      try {
        importer.importHTMLFromFile(this.file, true);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "HTML initial restore: nonexisting file should fail",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_FAILED,
    data:       NSIOBSERVER_DATA_HTML_INIT,
    folderId:   null,
    run:        function () {
      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
      this.file.append("this file doesn't exist because nobody created it");
      try {
        importer.importHTMLFromFile(this.file, true);
        do_throw("  Restore should have failed");
      }
      catch (e) {}
    }
  },

  {
    desc:       "HTML restore into folder: normal restore should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_HTML,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.html");
      addBookmarks();
      importer.exportHTMLToFile(this.file);
      remove_all_bookmarks();
      this.folderId = bmsvc.createFolder(bmsvc.unfiledBookmarksFolder,
                                         "test folder",
                                         bmsvc.DEFAULT_INDEX);
      print("  Sanity check: createFolder() should have succeeded");
      do_check_true(this.folderId > 0);
      try {
        importer.importHTMLFromFileToFolder(this.file, this.folderId, false);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "HTML restore into folder: empty file should succeed",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_SUCCESS,
    data:       NSIOBSERVER_DATA_HTML,
    run:        function () {
      this.file = createFile("bookmarks-test_restoreNotification.init.html");
      this.folderId = bmsvc.createFolder(bmsvc.unfiledBookmarksFolder,
                                         "test folder",
                                         bmsvc.DEFAULT_INDEX);
      print("  Sanity check: createFolder() should have succeeded");
      do_check_true(this.folderId > 0);
      try {
        importer.importHTMLFromFileToFolder(this.file, this.folderId, false);
      }
      catch (e) {
        do_throw("  Restore should not have failed");
      }
    }
  },

  {
    desc:       "HTML restore into folder: nonexisting file should fail",
    currTopic:  NSIOBSERVER_TOPIC_BEGIN,
    finalTopic: NSIOBSERVER_TOPIC_FAILED,
    data:       NSIOBSERVER_DATA_HTML,
    run:        function () {
      this.file = dirSvc.get("ProfD", Ci.nsILocalFile);
      this.file.append("this file doesn't exist because nobody created it");
      this.folderId = bmsvc.createFolder(bmsvc.unfiledBookmarksFolder,
                                         "test folder",
                                         bmsvc.DEFAULT_INDEX);
      print("  Sanity check: createFolder() should have succeeded");
      do_check_true(this.folderId > 0);
      try {
        importer.importHTMLFromFileToFolder(this.file, this.folderId, false);
        do_throw("  Restore should have failed");
      }
      catch (e) {}
    }
  }
];

// nsIObserver that observes bookmarks-restore-begin.
var beginObserver = {
  observe: function _beginObserver(aSubject, aTopic, aData) {
    var test = tests[currTestIndex];

    print("  Observed " + aTopic);
    print("  Topic for current test should be what is expected");
    do_check_eq(aTopic, test.currTopic);

    print("  Data for current test should be what is expected");
    do_check_eq(aData, test.data);

    // Update current expected topic to the next expected one.
    test.currTopic = test.finalTopic;
  }
};

// nsIObserver that observes bookmarks-restore-success/failed.  This starts
// the next test.
var successAndFailedObserver = {
  observe: function _successAndFailedObserver(aSubject, aTopic, aData) {
    var test = tests[currTestIndex];

    print("  Observed " + aTopic);
    print("  Topic for current test should be what is expected");
    do_check_eq(aTopic, test.currTopic);

    print("  Data for current test should be what is expected");
    do_check_eq(aData, test.data);

    // On restore failed, file may not exist, so wrap in try-catch.
    try {
      test.file.remove(false);
    }
    catch (exc) {}

    // Make sure folder ID is what is expected.  For importing HTML into a
    // folder, this will be an integer, otherwise null.
    if (aSubject) {
      do_check_eq(aSubject.QueryInterface(Ci.nsISupportsPRInt64).data,
                  test.folderId);
    }
    else
      do_check_eq(test.folderId, null);

    remove_all_bookmarks();
    doNextTest();
  }
};

// Index of the currently running test.  See doNextTest().
var currTestIndex = -1;

// Need JSON import/export in toolkit/components/places/src/utils.js
Components.utils.import("resource://gre/modules/utils.js");

var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
            getService(Ci.nsINavBookmarksService);

var obssvc = Cc["@mozilla.org/observer-service;1"].
             getService(Ci.nsIObserverService);

var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].
               getService(Ci.nsIPlacesImportExportService);

///////////////////////////////////////////////////////////////////////////////

/**
 * Adds some bookmarks for the URIs in |uris|.
 */
function addBookmarks() {
  uris.forEach(function (u) bmsvc.insertBookmark(bmsvc.bookmarksMenuFolder,
                                                 uri(u),
                                                 bmsvc.DEFAULT_INDEX,
                                                 u));
  checkBookmarksExist();
}

/**
 * Checks that all of the bookmarks created for |uris| exist.  It works by
 * creating one query per URI and then ORing all the queries.  The number of
 * results returned should be uris.length.
 */
function checkBookmarksExist() {
  var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
           getService(Ci.nsINavHistoryService);
  var queries = uris.map(function (u) {
    var q = hs.getNewQuery();
    q.uri = uri(u);
    return q;
  });
  var options = hs.getNewQueryOptions();
  options.queryType = options.QUERY_TYPE_BOOKMARKS;
  var root = hs.executeQueries(queries, uris.length, options).root;
  root.containerOpen = true;
  do_check_eq(root.childCount, uris.length);
  root.containerOpen = false;
}

/**
 * Creates an nsILocalFile in the profile directory.
 *
 * @param  aBasename
 *         e.g., "foo.txt" in the path /some/long/path/foo.txt
 * @return The nsILocalFile
 */
function createFile(aBasename) {
  var file = dirSvc.get("ProfD", Ci.nsILocalFile);
  file.append(aBasename);
  if (file.exists())
    file.remove(false);
  file.create(file.NORMAL_FILE_TYPE, 0666);
  if (!file.exists())
    do_throw("Couldn't create file: " + aBasename);
  return file;
}

/**
 * Runs the next test or if all tests have been run, finishes.
 */
function doNextTest() {
  currTestIndex++;
  if (currTestIndex >= tests.length) {
    obssvc.removeObserver(beginObserver, NSIOBSERVER_TOPIC_BEGIN);
    obssvc.removeObserver(successAndFailedObserver, NSIOBSERVER_TOPIC_SUCCESS);
    obssvc.removeObserver(successAndFailedObserver, NSIOBSERVER_TOPIC_FAILED);
    do_test_finished();
  }
  else {
    var test = tests[currTestIndex];
    print("Running test: " + test.desc);
    test.run();
  }
}

///////////////////////////////////////////////////////////////////////////////

function run_test() {
  do_test_pending();
  obssvc.addObserver(beginObserver, NSIOBSERVER_TOPIC_BEGIN, false);
  obssvc.addObserver(successAndFailedObserver, NSIOBSERVER_TOPIC_SUCCESS, false);
  obssvc.addObserver(successAndFailedObserver, NSIOBSERVER_TOPIC_FAILED, false);
  doNextTest();
}
