blob: 1b50cf762072ac7c702eb424e5b0a37fa8f2ba84 [file] [log] [blame]
// 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_parser_json.h"
#include <memory>
#include "testing/gtest/include/gtest/gtest.h"
namespace update_client {
const char* kJSONValid = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"pipeline_id": "pipe1",
"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
const char* kJSONHash = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [
{"url": "http://example.com/extension_1_2_3_4.crx"}
],
"out": {"sha256": "1234"}},
{"type": "crx3",
"in": {"sha256": "1234"}}]}
]
}
}
]
}})";
const char* kJSONInvalidSizes = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines": [
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}],
"size": 1234},
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}],
"size": 9007199254740991},
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}],
"size": -1234},
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}],
"size": "-a"},
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}],
"size": -123467890123456789},
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}],
"size": 123467890123456789}
]}]
}
}
]
}})";
const char* kJSONInvalidMissingUrl = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"out": {"sha256": "1234"}}
{"type": "crx3",
"in": {"sha256": "1234"}}]}
]
}
}
]
}})";
const char* kJSONInvalidMissingUpdateCheck = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok"
}
]
}})";
// `updatecheck` is supposed to be a dictionary. It is a list here.
const char* kJSONInvalidUpdateCheck = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{
"appid":"12345",
"status":"ok",
"updatecheck": []
}
]
}})";
const char* kJSONMissingAppId = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
const char* kJSONInvalidCodebase = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "nonurl"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
const char* kJSONMissingVersion = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
const char* kJSONInvalidVersion = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.a",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
// Includes a <daystart> tag.
const char* kJSONWithDaystart = R"()]}'
{"response":{
"protocol":"4.0",
"daystart":{"elapsed_days":456},
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
// Indicates no updates available.
const char* kJSONNoUpdate = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"noupdate"
}
}
]
}})";
// Includes two app objects, one app with an error.
const char* kJSONTwoAppsOneError = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"aaaaaaaa",
"status":"error-unknownApplication"
},
{"appid":"bbbbbbbb",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
// Includes two <app> tags, both of which set the cohort.
const char* kJSONTwoAppsSetCohort = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"aaaaaaaa",
"cohort":"1:2q3/",
"status":"ok",
"updatecheck":{
"status":"noupdate"
}
},
{"appid":"bbbbbbbb",
"cohort":"1:33z@0.33",
"cohortname":"cname",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"}
]}
]
}
}
]
}})";
// Includes a run action for an update check with status='ok'.
const char* kJSONUpdateCheckStatusOkWithRunAction = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3"},
{"type": "run",
"path": "file.exe"}
]}
]
}
}
]
}})";
// Includes nine app objects with status different than 'ok'.
const char* kJSONAppsStatusError = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"aaaaaaaa", "status":"error-unknownApplication"},
{"appid":"bbbbbbbb", "status":"restricted"},
{"appid":"cccccccc", "status":"error-invalidAppId"},
{"appid":"dddddddd", "status":"error-osnotsupported"},
{"appid":"eeeeeeee", "status":"error-hwnotsupported"},
{"appid":"ffffffff", "status":"error-hash"},
{"appid":"gggggggg", "status":"error-unsupportedprotocol"},
{"appid":"hhhhhhhh", "status":"error-internal"},
{"appid":"iiiiiiii", "status":"foobar"}
]
}})";
// Includes a manifest |run| value for an update check with status='ok'. Also
// includes install data in the `data` element.
const char* kJSONManifestRun = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"data":[{
"status":"ok",
"name":"install",
"index":"foobar_install_data_index",
"#text":"sampledata"
}],
"updatecheck":{
"status":"ok",
"nextversion":"1.2.3.4",
"pipelines":[
{"operations":[
{"type": "download",
"urls": [{"url": "http://example.com/extension_1_2_3_4.crx"}]},
{"type": "crx3",
"path":"UpdaterSetup.exe",
"arguments":"--arg1 --arg2"
}
]}
]
}
}
]
}})";
// Includes two custom response attributes in the update_check.
const char* kJSONCustomAttributes = R"()]}'
{"response":{
"protocol":"4.0",
"apps":[
{"appid":"12345",
"status":"ok",
"updatecheck":{
"_example1":"example_value1",
"_example2":"example_value2",
"_example_bad": {"value": "bad-non-string-value"},
"_example_bad2": 15,
"status":"noupdate"
}
}
]
}})";
TEST(UpdateClientProtocolParserJSONTest, Parse) {
const auto parser = std::make_unique<ProtocolParserJSON>();
// Test parsing of a number of invalid JSON cases
EXPECT_FALSE(parser->Parse(std::string()));
EXPECT_FALSE(parser->errors().empty());
EXPECT_TRUE(parser->Parse(kJSONMissingAppId));
EXPECT_TRUE(parser->results().apps.empty());
EXPECT_FALSE(parser->errors().empty());
EXPECT_TRUE(parser->Parse(kJSONInvalidCodebase));
EXPECT_TRUE(parser->results().apps.empty());
EXPECT_FALSE(parser->errors().empty());
EXPECT_TRUE(parser->Parse(kJSONMissingVersion));
EXPECT_TRUE(parser->results().apps.empty());
EXPECT_FALSE(parser->errors().empty());
EXPECT_TRUE(parser->Parse(kJSONInvalidVersion));
EXPECT_TRUE(parser->results().apps.empty());
EXPECT_FALSE(parser->errors().empty());
EXPECT_TRUE(parser->Parse(kJSONInvalidMissingUpdateCheck));
EXPECT_TRUE(parser->results().apps.empty());
EXPECT_FALSE(parser->errors().empty());
EXPECT_TRUE(parser->Parse(kJSONInvalidUpdateCheck));
EXPECT_TRUE(parser->results().apps.empty());
EXPECT_FALSE(parser->errors().empty());
{
// Parse some valid XML, and check that all params came out as expected.
EXPECT_TRUE(parser->Parse(kJSONValid)) << parser->errors();
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
ASSERT_EQ(1u, parser->results().apps.size());
const auto* first_result = &parser->results().apps[0];
EXPECT_STREQ("ok", first_result->status.c_str());
ASSERT_EQ(1u, first_result->pipelines.size());
EXPECT_EQ(first_result->pipelines[0].pipeline_id, "pipe1");
ASSERT_EQ(2u, first_result->pipelines[0].operations.size());
ASSERT_EQ(1u, first_result->pipelines[0].operations[0].urls.size());
EXPECT_EQ(GURL("http://example.com/extension_1_2_3_4.crx"),
first_result->pipelines[0].operations[0].urls[0]);
EXPECT_EQ(base::Version("1.2.3.4"), first_result->nextversion);
}
{
// Parse xml with hash value.
EXPECT_TRUE(parser->Parse(kJSONHash));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
EXPECT_FALSE(parser->results().apps.empty());
const auto* first_result = &parser->results().apps[0];
EXPECT_EQ("1234", first_result->pipelines[0].operations[0].sha256_out);
}
{
// Parse xml with package size value.
EXPECT_TRUE(parser->Parse(kJSONInvalidSizes)) << parser->errors();
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
EXPECT_FALSE(parser->results().apps.empty());
const auto* first_result = &parser->results().apps[0];
EXPECT_EQ(first_result->pipelines[0].operations.size(), 7u);
EXPECT_EQ(1234, first_result->pipelines[0].operations[0].size);
EXPECT_EQ(9007199254740991, first_result->pipelines[0].operations[1].size);
EXPECT_EQ(0, first_result->pipelines[0].operations[2].size);
EXPECT_EQ(0, first_result->pipelines[0].operations[3].size);
EXPECT_EQ(0, first_result->pipelines[0].operations[4].size);
EXPECT_EQ(0, first_result->pipelines[0].operations[5].size);
EXPECT_EQ(0, first_result->pipelines[0].operations[6].size);
}
{
// Parse xml with a <daystart> element.
EXPECT_TRUE(parser->Parse(kJSONWithDaystart));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
EXPECT_FALSE(parser->results().apps.empty());
EXPECT_EQ(parser->results().daystart_elapsed_days, 456);
}
{
// Parse a no-update response.
EXPECT_TRUE(parser->Parse(kJSONNoUpdate)) << parser->errors();
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
EXPECT_FALSE(parser->results().apps.empty());
const auto* first_result = &parser->results().apps[0];
EXPECT_STREQ("noupdate", first_result->status.c_str());
EXPECT_EQ(first_result->app_id, "12345");
EXPECT_FALSE(first_result->nextversion.IsValid());
}
{
// Parse xml with one error and one success <app> tag.
EXPECT_TRUE(parser->Parse(kJSONTwoAppsOneError));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
ASSERT_EQ(2u, parser->results().apps.size());
const auto* first_result = &parser->results().apps[0];
EXPECT_EQ(first_result->app_id, "aaaaaaaa");
EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
EXPECT_FALSE(first_result->nextversion.IsValid());
const auto* second_result = &parser->results().apps[1];
EXPECT_EQ(second_result->app_id, "bbbbbbbb");
EXPECT_STREQ("ok", second_result->status.c_str());
EXPECT_EQ(base::Version("1.2.3.4"), second_result->nextversion);
}
{
// Parse xml with two apps setting the cohort info.
EXPECT_TRUE(parser->Parse(kJSONTwoAppsSetCohort));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
ASSERT_EQ(2u, parser->results().apps.size());
const auto* first_result = &parser->results().apps[0];
EXPECT_EQ(first_result->app_id, "aaaaaaaa");
EXPECT_EQ(first_result->cohort.value(), "1:2q3/");
EXPECT_FALSE(first_result->cohort_name.has_value());
EXPECT_FALSE(first_result->cohort_hint.has_value());
const auto* second_result = &parser->results().apps[1];
EXPECT_EQ(second_result->app_id, "bbbbbbbb");
EXPECT_EQ(second_result->cohort, "1:33z@0.33");
EXPECT_EQ(second_result->cohort_name, "cname");
EXPECT_FALSE(first_result->cohort_hint.has_value());
}
{
EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusOkWithRunAction));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
EXPECT_FALSE(parser->results().apps.empty());
const auto* first_result = &parser->results().apps[0];
EXPECT_STREQ("ok", first_result->status.c_str());
EXPECT_EQ(first_result->app_id, "12345");
EXPECT_EQ(first_result->pipelines[0].operations[2].path, "file.exe");
}
{
EXPECT_TRUE(parser->Parse(kJSONAppsStatusError));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
EXPECT_EQ(9u, parser->results().apps.size());
size_t index = 0;
for (const std::string expected_status : {
"error-unknownApplication",
"restricted",
"error-invalidAppId",
"error-osnotsupported",
"error-hwnotsupported",
"error-hash",
"error-unsupportedprotocol",
"error-internal",
"foobar",
}) {
const auto* result = &parser->results().apps[index];
EXPECT_EQ(result->app_id, std::string(8, 'a' + index++));
EXPECT_EQ(expected_status, result->status);
EXPECT_FALSE(result->nextversion.IsValid());
}
}
{
EXPECT_TRUE(parser->Parse(kJSONManifestRun));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
ASSERT_EQ(1u, parser->results().apps.size());
const auto& result = parser->results().apps[0];
EXPECT_STREQ("UpdaterSetup.exe",
result.pipelines[0].operations[1].path.c_str());
EXPECT_STREQ("--arg1 --arg2",
result.pipelines[0].operations[1].arguments.c_str());
ASSERT_EQ(1u, result.data.size());
EXPECT_STREQ("foobar_install_data_index",
result.data[0].install_data_index.c_str());
EXPECT_STREQ("sampledata", result.data[0].text.c_str());
}
}
TEST(UpdateClientProtocolParserJSONTest, ParseAttrs) {
const auto parser = std::make_unique<ProtocolParserJSON>();
{ // No custom attrs in kJSONManifestRun
EXPECT_TRUE(parser->Parse(kJSONManifestRun));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
ASSERT_EQ(1u, parser->results().apps.size());
const auto& result = parser->results().apps[0];
EXPECT_EQ(0u, result.custom_attributes.size());
}
{ // Two custom attrs in kJSONCustomAttributes
EXPECT_TRUE(parser->Parse(kJSONCustomAttributes));
EXPECT_TRUE(parser->errors().empty()) << parser->errors();
ASSERT_EQ(1u, parser->results().apps.size());
const auto& result = parser->results().apps[0];
ASSERT_EQ(2u, result.custom_attributes.size());
EXPECT_EQ("example_value1", result.custom_attributes.at("_example1"));
EXPECT_EQ("example_value2", result.custom_attributes.at("_example2"));
}
}
} // namespace update_client