// Copyright (c) 2011 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.

#include "extensions/browser/updater/safe_manifest_parser.h"

#include "base/run_loop.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/updater/safe_manifest_parser.h"
#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

class ExtensionUpdateManifestTest : public testing::Test {
 public:
  void TestParseUpdateManifest(const std::string& xml) {
    base::RunLoop run_loop;
    ParseUpdateManifest(
        test_data_decoder_service_.connector(), xml,
        base::BindOnce(&ExtensionUpdateManifestTest::OnUpdateManifestParsed,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

 protected:
  UpdateManifestResults* results() const { return results_.get(); }
  const base::Optional<std::string>& error() const { return error_; }

  void ExpectNoError() {
    EXPECT_FALSE(error_) << "Unexpected error: '" << *error_;
  }

 private:
  void OnUpdateManifestParsed(base::Closure quit_loop,
                              std::unique_ptr<UpdateManifestResults> results,
                              const base::Optional<std::string>& error) {
    results_ = std::move(results);
    error_ = error;
    std::move(quit_loop).Run();
  }

  content::TestBrowserThreadBundle browser_thread_bundle_;
  std::unique_ptr<UpdateManifestResults> results_;
  base::Optional<std::string> error_;
  data_decoder::TestDataDecoderService test_data_decoder_service_;
};

}  // namespace

TEST_F(ExtensionUpdateManifestTest, InvalidXml) {
  TestParseUpdateManifest(std::string());
  EXPECT_FALSE(results());
  EXPECT_TRUE(error());
}

TEST_F(ExtensionUpdateManifestTest, MissingAppId) {
  TestParseUpdateManifest(
      "<?xml version='1.0'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' />"
      " </app>"
      "</gupdate>");
  EXPECT_FALSE(results());
  EXPECT_TRUE(error());
}

TEST_F(ExtensionUpdateManifestTest, InvalidCodebase) {
  TestParseUpdateManifest(
      "<?xml version='1.0'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='12345' status='ok'>"
      "  <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' />"
      " </app>"
      "</gupdate>");
  EXPECT_FALSE(results());
  EXPECT_TRUE(error());
}

TEST_F(ExtensionUpdateManifestTest, MissingVersion) {
  TestParseUpdateManifest(
      "<?xml version='1.0'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='12345' status='ok'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
      " </app>"
      "</gupdate>");
  EXPECT_FALSE(results());
  EXPECT_TRUE(error());
}

TEST_F(ExtensionUpdateManifestTest, InvalidVersion) {
  TestParseUpdateManifest(
      "<?xml version='1.0'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='12345' status='ok'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
      "               version='1.2.3.a'/>"
      " </app>"
      "</gupdate>");
  EXPECT_FALSE(results());
  EXPECT_TRUE(error());
}

TEST_F(ExtensionUpdateManifestTest, ValidXml) {
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='12345'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' prodversionmin='2.0.143.0' />"
      " </app>"
      "</gupdate>");
  ExpectNoError();
  ASSERT_TRUE(results());
  EXPECT_EQ(1U, results()->list.size());
  const UpdateManifestResult& first_result = results()->list.at(0);
  EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
            first_result.crx_url);
  EXPECT_EQ("1.2.3.4", first_result.version);
  EXPECT_EQ("2.0.143.0", first_result.browser_min_version);
}

TEST_F(ExtensionUpdateManifestTest, ValidXmlWithNamespacePrefix) {
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<g:gupdate xmlns:g='http://www.google.com/update2/response'"
      "           protocol='2.0'>"
      " <g:app appid='12345'>"
      "  <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' prodversionmin='2.0.143.0' />"
      " </g:app>"
      "</g:gupdate>");
  ExpectNoError();
  ASSERT_TRUE(results());
  EXPECT_EQ(1U, results()->list.size());
  const UpdateManifestResult& first_result = results()->list.at(0);
  EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
            first_result.crx_url);
  EXPECT_EQ("1.2.3.4", first_result.version);
  EXPECT_EQ("2.0.143.0", first_result.browser_min_version);
}

TEST_F(ExtensionUpdateManifestTest, SimilarTagnames) {
  // Includes unrelated <app> tags from other xml namespaces.
  // This should not cause problems, the unrelated app tags should be ignored.
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<gupdate xmlns='http://www.google.com/update2/response'"
      "         xmlns:a='http://a' protocol='2.0'>"
      " <a:app/>"
      " <b:app xmlns:b='http://b' />"
      " <app appid='12345'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' prodversionmin='2.0.143.0' />"
      " </app>"
      " <a:app appid='12345'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' prodversionmin='2.0.143.0' />"
      " </a:app>"
      "</gupdate>");
  ExpectNoError();
  ASSERT_TRUE(results());
  // We should still have parsed the gupdate app tag.
  EXPECT_EQ(1U, results()->list.size());
  EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
            results()->list.at(0).crx_url);
}

TEST_F(ExtensionUpdateManifestTest, XmlWithHash) {
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='12345'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' prodversionmin='2.0.143.0' "
      "               hash_sha256='1234'/>"
      " </app>"
      "</gupdate>");
  ExpectNoError();
  ASSERT_TRUE(results());
  EXPECT_EQ(1U, results()->list.size());
  const UpdateManifestResult& first_result = results()->list.at(0);
  EXPECT_EQ("1234", first_result.package_hash);
}

TEST_F(ExtensionUpdateManifestTest, XmlWithDaystart) {
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <daystart elapsed_seconds='456' />"
      " <app appid='12345'>"
      "  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
      "               version='1.2.3.4' prodversionmin='2.0.143.0' />"
      " </app>"
      "</gupdate>");
  ExpectNoError();
  ASSERT_TRUE(results());
  EXPECT_EQ(results()->daystart_elapsed_seconds, 456);
}

TEST_F(ExtensionUpdateManifestTest, NoUpdateResponse) {
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='12345'>"
      "  <updatecheck status='noupdate' />"
      " </app>"
      "</gupdate>");
  ExpectNoError();
  ASSERT_TRUE(results());
  ASSERT_FALSE(results()->list.empty());
  const UpdateManifestResult& first_result = results()->list.at(0);
  EXPECT_EQ(first_result.extension_id, "12345");
  EXPECT_TRUE(first_result.version.empty());
}

TEST_F(ExtensionUpdateManifestTest, TwoAppsOneError) {
  TestParseUpdateManifest(
      "<?xml version='1.0' encoding='UTF-8'?>"
      "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
      " <app appid='aaaaaaaa' status='error-unknownApplication'>"
      "  <updatecheck status='error-unknownapplication'/>"
      " </app>"
      " <app appid='bbbbbbbb'>"
      "  <updatecheck codebase='http://example.com/b_3.1.crx' version='3.1'/>"
      " </app>"
      "</gupdate>");
  EXPECT_TRUE(error());
  ASSERT_TRUE(results());
  EXPECT_EQ(1U, results()->list.size());
  const UpdateManifestResult& first_result = results()->list.at(0);
  EXPECT_EQ(first_result.extension_id, "bbbbbbbb");
}

}  // namespace extensions
