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

#include "components/update_client/protocol_serializer_json.h"

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "base/version.h"
#include "build/branding_buildflags.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/protocol_serializer.h"
#include "components/update_client/test_activity_data_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/re2/src/re2/re2.h"

namespace update_client {

TEST(SerializeRequestJSON, Serialize) {
  // When no updater state is provided, then check that the elements and
  // attributes related to the updater state are not serialized.

  base::test::TaskEnvironment env;

  {
    auto pref = std::make_unique<TestingPrefServiceSimple>();
    RegisterPersistedDataPrefs(pref->registry());
    auto metadata = CreatePersistedData(
        base::BindRepeating([](PrefService* pref) { return pref; }, pref.get()),
        nullptr);
    std::vector<std::string> items = {"id1"};
    test::SetDateLastData(metadata.get(), items, 1234);

    std::vector<base::DictValue> events(2);
    events[0].Set("a", 1);
    events[0].Set("b", "2");
    events[1].Set("error", 0);

    std::vector<protocol_request::App> apps;
    apps.push_back(MakeProtocolApp(
        "id1", base::Version("1.0"), "ap1", "BRND", "ins_id", "lang", -1,
        "source1", "location1", {{"attr1", "1"}, {"attr2", "2"}}, "c1", "ch1",
        "cn1", "test", {0, 1}, /*cached_hashes=*/{},
        MakeProtocolUpdateCheck(true, "33.12", true, false),
        {{"install", "foobar_install_data_index", ""}},
        MakeProtocolPing("id1", metadata.get(), {}), std::move(events)));

    const auto request = std::make_unique<ProtocolSerializerJSON>()->Serialize(
        MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}",
                            "prod_id", "1.0", "channel", "OS", "cacheable",
                            std::nullopt, {{"extra", "params"}}, {},
                            std::move(apps)));
    static constexpr char regex[] =
        R"({"request":{"@os":"\w+","@updater":"prod_id",)"
        R"("acceptformat":"crx3,download,puff,run,xz,zucc",)"
        R"("apps":\[{"ap":"ap1","appid":"id1","attr1":"1","attr2":"2",)"
        R"("brand":"BRND","cohort":"c1","cohorthint":"ch1","cohortname":"cn1",)"
        R"("data":\[{"index":"foobar_install_data_index","name":"install"}],)"
        R"("disabled":\[{"reason":0},{"reason":1}],"enabled":false,)"
        R"("events":\[{"a":1,"b":"2"},{"error":0}],)"
        R"("iid":"ins_id",)"
        R"("installdate":-1,)"
        R"("installedby":"location1","installsource":"source1",)"
        R"("lang":"lang",)"
        R"("ping":{"ping_freshness":"{[-\w]{36}}","rd":1234},)"
        R"("release_channel":"test",)"
        R"("updatecheck":{"rollback_allowed":true,)"
        R"("targetversionprefix":"33.12",)"
        R"("updatedisabled":true},"version":"1.0"}],"arch":"\w+","dedup":"cr",)"
        R"("dlpref":"cacheable","extra":"params","hw":{"avx":(true|false),)"
        R"("physmemory":\d+,"sse":(true|false),"sse2":(true|false),)"
        R"("sse3":(true|false),"sse41":(true|false),"sse42":(true|false),)"
        R"("ssse3":(true|false)},)"
        R"("ismachine":false,)"
        R"("os":{"arch":"[_,-.\w]+","platform":"OS",)"
        R"(("sp":"[\s\w]+",)?"version":"[+-.\w]+"},"prodchannel":"channel",)"
        R"("prodversion":"1.0","protocol":"4.0","requestid":"{[-\w]{36}}",)"
        R"("sessionid":"{[-\w]{36}}","updaterchannel":"channel",)"
        R"("updaterversion":"1.0"(,"wow64":true)?}})";
    EXPECT_TRUE(RE2::FullMatch(request, regex)) << request << "\n VS \n"
                                                << regex;
  }
  {
    // Tests `sameversionupdate` presence with a minimal request for one app.
    std::vector<protocol_request::App> apps;
    apps.push_back(
        MakeProtocolApp("id1", base::Version("1.0"), "", "", "", "", -2, "", "",
                        {}, "", "", "", "", {}, /*cached_hashes=*/{},
                        MakeProtocolUpdateCheck(false, "", false, true), {},
                        std::nullopt, std::nullopt));

    const auto request = std::make_unique<ProtocolSerializerJSON>()->Serialize(
        MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "",
                            "", "", "", "", std::nullopt, {}, {},
                            std::move(apps)));

    static constexpr char regex[] =
        R"("apps":\[{"appid":"id1","enabled":true,)"
        R"("updatecheck":{"sameversionupdate":true},"version":"1.0"}])";
    EXPECT_TRUE(RE2::PartialMatch(request, regex)) << request << "\n VS \n"
                                                   << regex;
  }
}

TEST(SerializeRequestJSON, DownloadPreference) {
  base::test::TaskEnvironment env;
  // Verifies that an empty |download_preference| is not serialized.
  const auto serializer = std::make_unique<ProtocolSerializerJSON>();
  auto request = serializer->Serialize(
      MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "",
                          "", "", "", "", std::nullopt, {}, {}, {}));
  EXPECT_FALSE(RE2::PartialMatch(request, R"("dlpref":)")) << request;

  // Verifies that |download_preference| is serialized.
  request = serializer->Serialize(
      MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "",
                          "", "", "", "cacheable", std::nullopt, {}, {}, {}));
  EXPECT_TRUE(RE2::PartialMatch(request, R"("dlpref":"cacheable")")) << request;
}

// When present, updater state attributes are only serialized for Google builds,
// except the |domainjoined| attribute, which is serialized in all cases.
TEST(SerializeRequestJSON, UpdaterStateAttributes) {
  base::test::TaskEnvironment env;
  const auto serializer = std::make_unique<ProtocolSerializerJSON>();

  const auto request = serializer->Serialize(MakeProtocolRequest(
      true, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id", "1.0",
      "channel", "OS", "cacheable", true, {{"extra", "params"}},
      {{"ismachine", "1"},
       {"name", "Omaha"},
       {"version", "1.2.3.4"},
       {"laststarted", "1"},
       {"lastchecked", "2"},
       {"autoupdatecheckenabled", "0"},
       {"updatepolicy", "-1"}},
      {}));
  static constexpr char regex[] =
      R"({"request":{"@os":"\w+","@updater":"prod_id",)"
      R"("acceptformat":"crx3,download,puff,run,xz,zucc",)"
      R"("arch":"\w+","dedup":"cr",)"
      R"("dlpref":"cacheable","domainjoined":true,"extra":"params",)"
      R"("hw":{"avx":(true|false),)"
      R"("physmemory":\d+,"sse":(true|false),"sse2":(true|false),)"
      R"("sse3":(true|false),"sse41":(true|false),"sse42":(true|false),)"
      R"("ssse3":(true|false)},)"
      R"("ismachine":true,)"
      R"("os":{"arch":"[,-.\w]+","platform":"OS",("sp":"[\s\w]+",)?)"
      R"("version":"[+-.\w]+"},"prodchannel":"channel","prodversion":"1.0",)"
      R"("protocol":"4.0","requestid":"{[-\w]{36}}","sessionid":"{[-\w]{36}}",)"
      R"("updaterchannel":"channel",)"
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
      R"("updaters":{"autoupdatecheckenabled":false,"ismachine":true,)"
      R"("lastchecked":2,"laststarted":1,"name":"Omaha","updatepolicy":-1,)"
      R"("version":"1\.2\.3\.4"},)"
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
      R"("updaterversion":"1.0"(,"wow64":true)?}})";
  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request << "\n VS \n" << regex;
}

TEST(SerializeRequestJSON, DomainJoined) {
  base::test::TaskEnvironment env;

  const auto serializer = std::make_unique<ProtocolSerializerJSON>();
  std::string request = serializer->Serialize(
      MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "",
                          "", "", "", "", std::nullopt, {}, {}, {}));
  EXPECT_FALSE(RE2::PartialMatch(request, R"("domainjoined")")) << request;

  request = serializer->Serialize(
      MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "",
                          "", "", "", "", true, {}, {}, {}));
  EXPECT_TRUE(RE2::PartialMatch(request, R"("domainjoined":true)")) << request;

  request = serializer->Serialize(
      MakeProtocolRequest(false, "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "",
                          "", "", "", "", false, {}, {}, {}));
  EXPECT_TRUE(RE2::PartialMatch(request, R"("domainjoined":false)")) << request;
}

}  // namespace update_client
