unittest/mysql_secret_store_t.cc (1,257 lines of code) (raw):
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <rapidjson/document.h>
#include <algorithm>
#include <iterator>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "mysql-secret-store/include/mysql-secret-store/api.h"
#include "mysqlshdk/libs/db/generic_uri.h"
#include "mysqlshdk/libs/db/uri_parser.h"
#include "mysqlshdk/libs/utils/process_launcher.h"
#include "mysqlshdk/libs/utils/utils_file.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_path.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#include "mysqlshdk/libs/utils/version.h"
#include "unittest/gtest_clean.h"
#include "unittest/test_utils/mocks/gmock_clean.h"
#include "unittest/test_utils/shell_test_wrapper.h"
using mysql::secret_store::api::get_available_helpers;
using mysql::secret_store::api::get_helper;
using mysql::secret_store::api::Helper_interface;
using mysql::secret_store::api::Helper_name;
using mysql::secret_store::api::Secret_spec;
using mysql::secret_store::api::Secret_type;
namespace tests {
namespace {
const auto g_format_parameter = [](const auto &info) {
return shcore::str_replace(info.param, "-", "_");
};
template <typename T>
::testing::AssertionResult is_empty(const T &container) {
if (container.empty()) {
return ::testing::AssertionSuccess();
} else {
return ::testing::AssertionFailure()
<< "contains: " << shcore::str_join(container, ", ");
}
}
constexpr auto k_secret_type_password = "password";
std::string to_string(Secret_type type) {
switch (type) {
case Secret_type::PASSWORD:
return k_secret_type_password;
}
throw std::runtime_error{"Unknown secret type"};
}
#define SCOPED_TRACE_TYPE(msg) \
SCOPED_TRACE(msg + std::string{", type: "} + to_string(type))
class Helper_tester {
public:
virtual ~Helper_tester() = default;
virtual void select_helper(const std::string &name) = 0;
virtual void clear_store() = 0;
virtual bool has_get() = 0;
virtual bool store(Secret_type type, const std::string &url,
const std::string &secret) = 0;
virtual bool get(Secret_type type, const std::string &url,
std::string *secret) = 0;
virtual bool erase(Secret_type type, const std::string &url) = 0;
virtual bool list(std::vector<Secret_spec> *specs) = 0;
virtual void expect_error(const std::string &msg) = 0;
virtual void expect_no_error() = 0;
virtual void expect_contains(Secret_type type, const std::string &url,
const std::string &secret) = 0;
virtual void expect_not_contain(Secret_type type, const std::string &url) = 0;
};
class Mysql_secret_store_api_tester : public Helper_tester {
public:
static std::vector<std::string> list_helpers() {
return call_get_available_helpers();
}
static std::vector<std::string> call_get_available_helpers(
const std::string &path = "") {
std::vector<std::string> names;
auto helpers = get_available_helpers(path);
std::transform(helpers.begin(), helpers.end(), std::back_inserter(names),
[](const Helper_name &name) { return name.get(); });
return names;
}
void select_helper(const std::string &name) override {
SCOPED_TRACE("Mysql_secret_store_api_tester::select_helper()");
auto helpers = get_available_helpers();
auto helper =
std::find_if(helpers.begin(), helpers.end(),
[&name](const Helper_name &n) { return n.get() == name; });
ASSERT_NE(helpers.end(), helper);
m_helper = get_helper(*helper);
ASSERT_NE(nullptr, m_helper);
}
void clear_store() override {
SCOPED_TRACE("Mysql_secret_store_api_tester::clear_store()");
std::vector<Secret_spec> specs;
ASSERT_NE(nullptr, m_helper);
EXPECT_TRUE(m_helper->list(&specs));
expect_no_error();
for (const auto &spec : specs) {
EXPECT_TRUE(m_helper->erase(spec));
expect_no_error();
}
}
bool has_get() override { return true; }
bool store(Secret_type type, const std::string &url,
const std::string &secret) override {
SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::store()");
return m_helper->store({type, url}, secret);
}
bool get(Secret_type type, const std::string &url,
std::string *secret) override {
SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::get()");
return m_helper->get({type, url}, secret);
}
bool erase(Secret_type type, const std::string &url) override {
SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::erase()");
return m_helper->erase({type, url});
}
bool list(std::vector<Secret_spec> *specs) override {
SCOPED_TRACE("Mysql_secret_store_api_tester::list()");
return m_helper->list(specs);
}
void expect_error(const std::string &msg) override {
SCOPED_TRACE("Mysql_secret_store_api_tester::expect_error()");
EXPECT_THAT(m_helper->get_last_error(), ::testing::HasSubstr(msg));
}
void expect_no_error() override {
SCOPED_TRACE("Mysql_secret_store_api_tester::expect_no_error()");
EXPECT_EQ("", m_helper->get_last_error());
}
void expect_contains(Secret_type type, const std::string &url,
const std::string &secret) override {
SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::expect_contains()");
std::string stored_secret;
EXPECT_TRUE(get(type, url, &stored_secret));
expect_no_error();
EXPECT_EQ(secret, stored_secret);
}
void expect_not_contain(Secret_type type, const std::string &url) override {
SCOPED_TRACE_TYPE("Mysql_secret_store_api_tester::expect_not_contain()");
std::string stored_secret;
EXPECT_FALSE(get(type, url, &stored_secret));
}
std::string name() const { return m_helper->name().get(); }
private:
std::unique_ptr<Helper_interface> m_helper;
};
class Shell_api_tester : public Helper_tester {
public:
Shell_api_tester() {}
static std::vector<std::string> list_helpers() {
// this is invoked too early to call execute(), use library function
return Mysql_secret_store_api_tester::call_get_available_helpers();
}
std::vector<std::string> call_list_helpers() {
execute("print(shell.listCredentialHelpers());");
return parse_output();
}
void select_helper(const std::string &name) override {
SCOPED_TRACE("Shell_api_tester::select_helper()");
m_wrapper.reset(new Shell_test_wrapper{false});
execute("shell.options[\"credentialStore.helper\"] = " +
shcore::quote_string(name, '\"') + ";");
ASSERT_EQ("", m_wrapper->get_output_handler().std_err);
}
void clear_store() override {
SCOPED_TRACE("Shell_api_tester::clear_store()");
expect_delete_all_credentials();
}
bool has_get() override { return false; }
bool store(Secret_type type, const std::string &url,
const std::string &secret) override {
SCOPED_TRACE_TYPE("Shell_api_tester::store()");
EXPECT_EQ(Secret_type::PASSWORD, type);
#ifdef HAVE_JS
execute("shell.storeCredential(" + shcore::quote_string(url, '\"') + ", " +
shcore::quote_string(secret, '\"') + ");");
#else
execute("shell.store_credential(" + shcore::quote_string(url, '\"') + ", " +
shcore::quote_string(secret, '\"') + ");");
#endif
return m_wrapper->get_output_handler().std_err.empty();
}
bool get(Secret_type, const std::string &, std::string *) override {
ADD_FAILURE() << "This should not be called";
return false;
}
bool erase(Secret_type type, const std::string &url) override {
SCOPED_TRACE_TYPE("Shell_api_tester::erase()");
EXPECT_EQ(Secret_type::PASSWORD, type);
#ifdef HAVE_JS
execute("shell.deleteCredential(" + shcore::quote_string(url, '\"') + ");");
#else
execute("shell.delete_credential(" + shcore::quote_string(url, '\"') +
");");
#endif
return m_wrapper->get_output_handler().std_err.empty();
}
bool list(std::vector<Secret_spec> *specs) override {
SCOPED_TRACE("Shell_api_tester::list()");
#ifdef HAVE_JS
execute("print(shell.listCredentials());");
#else
execute("print(shell.list_credentials());");
#endif
for (const auto &url : parse_output()) {
specs->emplace_back(Secret_spec{Secret_type::PASSWORD, url});
}
return m_wrapper->get_output_handler().std_err.empty();
}
void expect_error(const std::string &msg) override {
SCOPED_TRACE("Shell_api_tester::expect_error()");
m_wrapper->get_output_handler().validate_stderr_content(msg, true);
}
void expect_no_error() override {
SCOPED_TRACE("Shell_api_tester::expect_no_error()");
EXPECT_EQ("", m_wrapper->get_output_handler().std_err);
}
void expect_contains(Secret_type type, const std::string &url,
const std::string &) override {
SCOPED_TRACE_TYPE("Shell_api_tester::expect_contains()");
EXPECT_EQ(Secret_type::PASSWORD, type);
EXPECT_TRUE(has_url(url));
}
void expect_not_contain(Secret_type type, const std::string &url) override {
SCOPED_TRACE_TYPE("Shell_api_tester::expect_not_contain()");
EXPECT_EQ(Secret_type::PASSWORD, type);
EXPECT_FALSE(has_url(url));
}
void expect_delete_all_credentials() {
SCOPED_TRACE("Shell_api_tester::expect_delete_all_credentials()");
#ifdef HAVE_JS
execute("shell.deleteAllCredentials();");
#else
execute("shell.delete_all_credentials();");
#endif
EXPECT_EQ("", m_wrapper->get_output_handler().std_err);
}
private:
void execute(const std::string &code) {
m_wrapper->get_output_handler().wipe_all();
m_wrapper->execute(code);
}
std::vector<std::string> parse_output() {
std::vector<std::string> output;
rapidjson::Document doc;
doc.Parse(m_wrapper->get_output_handler().std_out.c_str());
if (!doc.HasParseError() && doc.IsArray()) {
for (const auto &s : doc.GetArray()) {
output.emplace_back(s.GetString());
}
}
return output;
}
bool has_url(const std::string &url) {
SCOPED_TRACE("Shell_api_tester::has_url()");
std::vector<Secret_spec> specs;
EXPECT_TRUE(list(&specs));
expect_no_error();
mysqlshdk::db::uri::Generic_uri copts;
mysqlshdk::db::uri::Uri_parser parser(mysqlshdk::db::uri::Type::Generic);
parser.parse(url, &copts);
auto tokens = mysqlshdk::db::uri::formats::user_transport();
if (copts.has_value(mysqlshdk::db::kScheme) &&
(copts.get(mysqlshdk::db::kScheme) == "ssh" ||
copts.get(mysqlshdk::db::kScheme) == "file"))
tokens.set(mysqlshdk::db::uri::Tokens::Scheme);
auto normalized = copts.as_uri(tokens);
return specs.end() !=
std::find(specs.begin(), specs.end(),
Secret_spec{Secret_type::PASSWORD, normalized});
}
std::unique_ptr<Shell_test_wrapper> m_wrapper;
};
class Helper_invoker {
public:
explicit Helper_invoker(const std::string &path) : m_path{path} {}
bool store(const std::string &input, std::string *output) const {
return invoke("store", input, output);
}
bool get(const std::string &input, std::string *output) const {
return invoke("get", input, output);
}
bool erase(const std::string &input, std::string *output) const {
return invoke("erase", input, output);
}
bool list(std::string *output) const { return invoke("list", {}, output); }
bool version(std::string *output) const {
return invoke("version", {}, output);
}
bool invoke(const char *command, const std::string &input,
std::string *output) const {
try {
const char *const args[] = {m_path.c_str(), command, nullptr};
shcore::Process_launcher app{args};
app.start();
if (!input.empty()) {
app.write(input.c_str(), input.length());
app.finish_writing();
}
*output = shcore::str_strip(app.read_all());
return app.wait() == 0;
} catch (const std::exception &ex) {
*output = std::string{"Exception caught while running helper command '"} +
command + "': " + ex.what();
return false;
}
}
const std::string m_path;
};
class Helper_executable_tester : public Helper_tester {
public:
static std::vector<std::string> list_helpers() {
std::vector<std::string> output;
auto paths = list_helper_paths();
std::transform(paths.begin(), paths.end(), std::back_inserter(output),
get_helper_name);
return output;
}
void select_helper(const std::string &name) override {
SCOPED_TRACE("Helper_executable_tester::select_helper()");
auto paths = list_helper_paths();
auto helper = std::find_if(
paths.begin(), paths.end(),
[&name](const std::string &p) { return get_helper_name(p) == name; });
ASSERT_NE(paths.end(), helper);
m_invoker = std::make_unique<Helper_invoker>(*helper);
ASSERT_NE(nullptr, m_invoker);
}
void clear_store() override {
SCOPED_TRACE("Helper_executable_tester::clear_store()");
ASSERT_NE(nullptr, m_invoker);
std::string output;
if (!m_invoker->list(&output)) {
::testing::AssertionFailure() << output;
return;
}
rapidjson::Document doc;
doc.Parse(output.c_str());
if (doc.HasParseError()) {
::testing::AssertionFailure() << "Failed to parse JSON";
return;
}
if (!doc.IsArray()) {
::testing::AssertionFailure() << "Expected array";
return;
}
for (const auto &s : doc.GetArray()) {
output.clear();
if (!m_invoker->erase(to_json(s[k_secret_type].GetString(),
s[k_server_url].GetString()),
&output)) {
::testing::AssertionFailure() << output << "\n";
}
}
}
bool has_get() override { return true; }
bool store(Secret_type type, const std::string &url,
const std::string &secret) override {
SCOPED_TRACE_TYPE("Helper_executable_tester::store()");
std::string output;
bool ret = m_invoker->store(to_json(type, url, secret), &output);
if (ret) {
clear_last_error();
} else {
set_last_error(output);
}
return ret;
}
bool get(Secret_type type, const std::string &url,
std::string *secret) override {
SCOPED_TRACE_TYPE("Helper_executable_tester::get()");
std::string output;
bool ret = m_invoker->get(to_json(type, url), &output);
if (ret) {
clear_last_error();
Secret_spec spec;
ret = to_secret(output, &spec, secret);
if (spec.type != type || spec.url != url) {
ret = false;
set_last_error("Helper returned mismatched secret");
}
} else {
set_last_error(output);
}
return ret;
}
bool erase(Secret_type type, const std::string &url) override {
SCOPED_TRACE_TYPE("Helper_executable_tester::erase()");
std::string output;
bool ret = m_invoker->erase(to_json(type, url), &output);
if (ret) {
clear_last_error();
} else {
set_last_error(output);
}
return ret;
}
bool list(std::vector<Secret_spec> *specs) override {
SCOPED_TRACE("Helper_executable_tester::list()");
std::string output;
bool ret = m_invoker->list(&output);
if (ret) {
clear_last_error();
ret = to_specs(output, specs);
} else {
set_last_error(output);
}
return ret;
}
void expect_error(const std::string &msg) override {
SCOPED_TRACE("Helper_executable_tester::expect_error()");
EXPECT_THAT(m_last_error, ::testing::HasSubstr(msg));
}
void expect_no_error() override {
SCOPED_TRACE("Helper_executable_tester::expect_no_error()");
EXPECT_EQ("", m_last_error);
}
void expect_contains(Secret_type type, const std::string &url,
const std::string &secret) override {
SCOPED_TRACE_TYPE("Helper_executable_tester::expect_contains()");
std::string stored_secret;
EXPECT_TRUE(get(type, url, &stored_secret));
expect_no_error();
EXPECT_EQ(secret, stored_secret);
}
void expect_not_contain(Secret_type type, const std::string &url) override {
SCOPED_TRACE_TYPE("Helper_executable_tester::expect_not_contain()");
std::string stored_secret;
EXPECT_FALSE(get(type, url, &stored_secret));
}
Helper_invoker &get_invoker() { return *m_invoker; }
private:
static std::string get_helper_name(const std::string &path) {
std::string name =
shcore::path::basename(path).substr(strlen(k_exe_prefix));
name = name.substr(0, name.find_last_of("."));
return name;
}
static std::vector<std::string> list_helper_paths() {
std::vector<std::string> helpers;
const auto folder = shcore::get_binary_folder();
if (!shcore::is_folder(folder)) {
return helpers;
}
for (const auto &entry : shcore::listdir(folder)) {
const std::string path = shcore::path::join_path(folder, entry);
std::string version;
if (entry.compare(0, strlen(k_exe_prefix), k_exe_prefix) == 0 &&
!shcore::is_folder(path) && Helper_invoker{path}.version(&version)) {
helpers.emplace_back(path);
}
}
return helpers;
}
static std::string to_json(Secret_type type, const std::string &url,
const std::string &secret) {
auto json = to_json(type, url);
json.pop_back();
return json + ",\"" + k_secret +
"\":" + shcore::quote_string(secret, '\"') + "}";
}
static std::string to_json(Secret_type type, const std::string &url) {
return to_json(to_string(type), url);
}
static std::string to_json(const std::string &type, const std::string &url) {
return std::string{"{\""} + k_server_url +
"\":" + shcore::quote_string(url, '\"') + ",\"" + k_secret_type +
"\":" + shcore::quote_string(type, '\"') + "}";
}
bool validate_object(const rapidjson::Value &object,
const std::set<std::string> &members) {
if (!object.IsObject()) {
set_last_error("Expected object");
return false;
}
for (const auto &m : members) {
if (!object.HasMember(m.c_str())) {
set_last_error("Missing member: \"" + m + "\"");
}
if (!object[m.c_str()].IsString()) {
set_last_error("Member \"" + m + "\" is not a string");
}
}
return true;
}
bool to_secret_type(const std::string &type, Secret_type *output_type) {
if (type == k_secret_type_password) {
*output_type = Secret_type::PASSWORD;
} else {
set_last_error("Unknown secret type: \"" + type + "\"");
return false;
}
return true;
}
bool to_spec(const rapidjson::Value &object, Secret_spec *spec) {
if (!validate_object(object, {k_secret_type, k_server_url})) {
return false;
}
if (!to_secret_type(object[k_secret_type].GetString(), &spec->type)) {
return false;
}
spec->url = object[k_server_url].GetString();
return true;
}
bool to_secret(const std::string &json, Secret_spec *spec,
std::string *secret) {
rapidjson::Document doc;
doc.Parse(json.c_str());
if (doc.HasParseError()) {
set_last_error("Failed to parse JSON");
return false;
}
if (!to_spec(doc, spec)) {
return false;
}
if (!validate_object(doc, {k_secret})) {
return false;
}
*secret = doc[k_secret].GetString();
return true;
}
bool to_specs(const std::string &json, std::vector<Secret_spec> *specs) {
rapidjson::Document doc;
doc.Parse(json.c_str());
if (doc.HasParseError()) {
set_last_error("Failed to parse JSON");
return false;
}
if (!doc.IsArray()) {
set_last_error("Expected array");
return false;
}
for (const auto &s : doc.GetArray()) {
Secret_spec spec;
if (!to_spec(s, &spec)) {
return false;
} else {
specs->emplace_back(spec);
}
}
return true;
}
void set_last_error(const std::string &str) const { m_last_error = str; }
void clear_last_error() const { m_last_error.clear(); }
static const char k_exe_prefix[];
static const char k_secret[];
static const char k_secret_type[];
static const char k_server_url[];
std::unique_ptr<Helper_invoker> m_invoker;
mutable std::string m_last_error;
};
const char Helper_executable_tester::k_exe_prefix[] = "mysql-secret-store-";
const char Helper_executable_tester::k_secret[] = "Secret";
const char Helper_executable_tester::k_secret_type[] = "SecretType";
const char Helper_executable_tester::k_server_url[] = "ServerURL";
template <typename T>
class Parametrized_helper_test : public ::testing::TestWithParam<std::string> {
public:
static std::set<std::string> list_helpers() {
SCOPED_TRACE("Parametrized_helper_test::list_helpers()");
auto helpers = T::list_helpers();
std::set<std::string> ret{helpers.begin(), helpers.end()};
EXPECT_TRUE(helpers.size() == ret.size())
<< "List of helpers contains duplicate values";
return ret;
}
protected:
void SetUp() override {
tester.select_helper(GetParam());
clear_store();
}
void TearDown() override { clear_store(); }
bool store(const std::string &url, const std::string &secret) {
return tester.store(Secret_type::PASSWORD, url, secret);
}
bool get(const std::string &url, std::string *secret) {
return tester.get(Secret_type::PASSWORD, url, secret);
}
bool erase(const std::string &url) {
return tester.erase(Secret_type::PASSWORD, url);
}
void expect_list(const std::set<std::string> &urls) {
SCOPED_TRACE("Parametrized_helper_test::expect_list()");
std::vector<Secret_spec> specs;
EXPECT_TRUE(tester.list(&specs));
expect_no_error();
std::set<std::string> list_urls;
for (const auto &s : specs) {
SCOPED_TRACE(s.url + " -> " + to_string(s.type));
EXPECT_EQ(Secret_type::PASSWORD, s.type);
list_urls.emplace(s.url);
}
EXPECT_EQ(urls, list_urls);
}
void expect_contains(const std::string &url, const std::string &secret) {
tester.expect_contains(Secret_type::PASSWORD, url, secret);
}
void expect_not_contain(const std::string &url) {
tester.expect_not_contain(Secret_type::PASSWORD, url);
}
void expect_error(const std::string &msg) { tester.expect_error(msg); }
void expect_no_error() { tester.expect_no_error(); }
void clear_store() { tester.clear_store(); }
T tester;
};
class Config_editor_invoker {
public:
void store(const std::string &name, const std::string &url,
const std::string &secret) {
std::vector<std::string> args = {"set", "--skip-warn", "--password"};
args.emplace_back("--login-path=" + name);
mysqlshdk::db::uri::Generic_uri options;
mysqlshdk::db::uri::Uri_parser parser(mysqlshdk::db::uri::Type::Generic);
parser.parse(url, &options);
if (options.has_value(mysqlshdk::db::kUser)) {
args.emplace_back("--user=" + options.get(mysqlshdk::db::kUser));
}
if (options.has_value(mysqlshdk::db::kHost)) {
args.emplace_back("--host=" + options.get(mysqlshdk::db::kHost));
}
if (options.has_value(mysqlshdk::db::kPort)) {
args.emplace_back("--port=" + std::to_string(options.get_numeric(
mysqlshdk::db::kPort)));
}
if (options.has_value(mysqlshdk::db::kSocket)) {
args.emplace_back("--socket=" + options.get(mysqlshdk::db::kSocket));
}
invoke(args, true, secret);
}
void clear() { invoke({"reset"}); }
mysqlshdk::utils::Version version() const {
return mysqlshdk::utils::Version{
shcore::str_split(invoke({"--version"}), " ", -1, true)[2]};
}
std::string invoke(const std::vector<std::string> &args,
bool uses_terminal = false,
const std::string &password = "") const {
std::vector<const char *> process_args;
process_args.emplace_back("mysql_config_editor");
for (const auto &arg : args) {
process_args.emplace_back(arg.c_str());
}
process_args.emplace_back(nullptr);
shcore::Process_launcher app{&process_args[0]};
if (uses_terminal) {
app.enable_child_terminal();
}
app.start();
if (uses_terminal) {
// wait for password prompt
app.read_from_terminal();
app.write_to_terminal(password.c_str(), password.length());
app.write_to_terminal("\n", 1);
}
std::string output = shcore::str_strip(app.read_all());
if (0 != app.wait()) {
throw std::runtime_error{output};
}
return output;
}
};
void verify_available_helpers(const std::set<std::string> &helpers) {
SCOPED_TRACE("verify_available_helpers()");
std::set<std::string> expected = {"plaintext"};
#ifdef _WIN32
expected.emplace("windows-credential");
#else // ! _WIN32
#ifdef __APPLE__
expected.emplace("keychain");
#else // ! __APPLE__
expected.emplace("login-path");
#endif // ! __APPLE__
#endif // ! _WIN32
std::set<std::string> missing;
std::set_difference(expected.begin(), expected.end(), helpers.begin(),
helpers.end(), std::inserter(missing, missing.begin()));
EXPECT_TRUE(is_empty(missing));
}
void verify_available_helpers(const std::vector<std::string> &helpers) {
verify_available_helpers(
std::set<std::string>{helpers.begin(), helpers.end()});
}
template <typename T>
void test_available_helpers() {
SCOPED_TRACE("test_available_helpers()");
verify_available_helpers(T::list_helpers());
}
#define IGNORE_TEST(test_case_name, test_name) \
class test_case_name##_##test_name : public test_case_name { \
private: \
void test_body(); \
}; \
void test_case_name##_##test_name::test_body()
#define MY_EXPECT_ERROR(expression, error) \
do { \
EXPECT_FALSE(expression); \
expect_error(error); \
} while (false)
#define MY_EXPECT_NO_ERROR(expression) \
do { \
EXPECT_TRUE(expression); \
expect_no_error(); \
} while (false)
#define GET_EXPECT_ERROR(url, secret, error) \
if (tester.has_get()) { \
std::string s; \
MY_EXPECT_ERROR(get(url, &s), error); \
}
#define INVALID_URL_ERROR "Invalid URL"
#define STORE_INVALID_URL(url, secret) \
do { \
SCOPED_TRACE(url + std::string{" -> "} + secret); \
MY_EXPECT_ERROR(store(url, secret), INVALID_URL_ERROR); \
} while (false)
#define GET_INVALID_URL(url, secret) \
do { \
SCOPED_TRACE(url + std::string{" -> "} + secret); \
GET_EXPECT_ERROR(url, secret, INVALID_URL_ERROR) \
} while (false)
#define ERASE_INVALID_URL(url, secret) \
do { \
SCOPED_TRACE(url + std::string{" -> "} + secret); \
MY_EXPECT_ERROR(erase(url), INVALID_URL_ERROR); \
} while (false)
#define OP_INVALID_URL_TESTS(op) \
do { \
op("host", "password"); \
op("user@", "password"); \
op("user@@", "password"); \
op("user:secret@host", "password"); \
op("http://user@host", "password"); \
op("http://user@host:3306", "password"); \
op("mysql://user@host", "password"); \
op("mysql://user@host:3306", "password"); \
op("mysqlx://user@host", "password"); \
op("mysqlx://user@host:33060", "password"); \
op("user@host/schema", "password"); \
op("user@host:3306/schema", "password"); \
op("user@host?tls-version=xyz", "password"); \
op("user@host:3306?tls-version=xyz", "password"); \
op("user@host/schema?tls-version=xyz", "password"); \
op("user@host:3306/schema?tls-version=xyz", "password"); \
op("http://user@host/schema?tls-version=xyz", "password"); \
op("http://user@host:3306/schema?tls-version=xyz", "password"); \
} while (false)
#define STORE_AND_CHECK(url, secret) \
do { \
SCOPED_TRACE(url + std::string{" -> "} + secret); \
MY_EXPECT_NO_ERROR(store(url, secret)); \
expect_contains(url, secret); \
} while (false)
#define STORE_AND_ERASE(url, secret) \
do { \
SCOPED_TRACE(url + std::string{" -> "} + secret); \
MY_EXPECT_NO_ERROR(store(url, secret)); \
expect_contains(url, secret); \
MY_EXPECT_NO_ERROR(erase(url)); \
expect_not_contain(url); \
} while (false)
#ifdef _WIN32
#define URL_WITH_SOCKET "user@\\\\.\\named.pipe"
#define URL_WITH_SOCKET_AND_PARENTHESES "user@\\\\.\\(named.pipe)"
#else // !_WIN32
#define URL_WITH_SOCKET "user@/socket"
#define URL_WITH_SOCKET_AND_PARENTHESES "user@(/socket)"
#endif // !_WIN32
#define STORE_AND_OP_TESTS(op) \
do { \
op("empty@host", ""); \
op("user@host", "one"); \
op("user@host:3306", "two"); \
op(URL_WITH_SOCKET, "three"); \
op("user@host:33060", "five"); \
op("user@host:33060", "six"); \
op("ssh://user@host.com:22", "test"); \
op("file:/user/host/com", "test"); \
if (GetParam() == "login-path") { \
op("user@host:55555", \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmn"); \
} else { \
op("user@host:55555", \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); \
} \
op("user@example.com", "zażółć gęślą jaźń"); \
op("user@host", "\"pass\"'word'"); \
op("user@host1", "\\"); \
op("user@host2", "\\\\"); \
op("user@host3", "\\\"\\'"); \
op("user@host4", "\"password\""); \
op("user@host5", "'password'"); \
op("user@[::1]", "seven"); \
op("user@[::1]:7777", "eight"); \
op("user@[fe80::850a:5a7c:6ab7:aec4%25enp0s3]", "nine"); \
op("user@[fe80::850a:5a7c:6ab7:aec4%25enp0s3]:8888", "ten"); \
op("test%21%23%24%26%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D" \
"@example.com:9999", \
"eleven"); \
} while (false)
#define ADD_TESTS(test_case_name) \
VALIDATION_TEST(test_case_name, store_invalid_url) { \
OP_INVALID_URL_TESTS(STORE_INVALID_URL); \
} \
\
VALIDATION_TEST(test_case_name, get_invalid_url) { \
if (!tester.has_get()) { \
return; \
} \
OP_INVALID_URL_TESTS(GET_INVALID_URL); \
} \
\
VALIDATION_TEST(test_case_name, erase_invalid_url) { \
OP_INVALID_URL_TESTS(ERASE_INVALID_URL); \
} \
\
TEST_P(test_case_name, store_and_check) { \
STORE_AND_OP_TESTS(STORE_AND_CHECK); \
} \
\
TEST_P(test_case_name, store_and_erase) { \
STORE_AND_OP_TESTS(STORE_AND_ERASE); \
} \
\
TEST_P(test_case_name, list) { \
std::set<std::string> urls{"user@host", "user@host:3306", \
"user@host:33060", URL_WITH_SOCKET}; \
for (const auto &url : urls) { \
STORE_AND_CHECK(url, url); \
} \
expect_list(urls); \
STORE_AND_CHECK(*urls.begin(), "new pass"); \
expect_list(urls); \
} \
\
TEST_P(test_case_name, get_nonexistent_url) { \
constexpr auto error = "Could not find the secret"; \
GET_EXPECT_ERROR("user@nonexistent.host", "secret", error); \
} \
\
TEST_P(test_case_name, erase_nonexistent_url) { \
constexpr auto error = "Could not find the secret"; \
MY_EXPECT_ERROR(erase("user@nonexistent.host"), error); \
} \
\
NORMALIZATION_TEST(test_case_name, url_normalization) { \
STORE_AND_CHECK(URL_WITH_SOCKET, "one"); \
STORE_AND_CHECK(URL_WITH_SOCKET_AND_PARENTHESES, "two"); \
expect_contains(URL_WITH_SOCKET, "two"); \
} \
\
TEST(Helpers, test_case_name##_list_helpers) { \
test_available_helpers<test_case_name>(); \
} \
\
TEST_P(test_case_name, login_path_external_entry) { \
if (GetParam() != "login-path") { \
return; \
} \
Config_editor_invoker invoker; \
invoker.clear(); \
std::set<std::string> urls{"user@host", "user@host:3306", \
"user@host:33060", URL_WITH_SOCKET}; \
for (const auto &url : urls) { \
invoker.store(url, url, url); \
expect_contains(url, url); \
} \
expect_list(urls); \
invoker.clear(); \
} \
\
TEST_P(test_case_name, login_path_external_and_helper_entries) { \
if (GetParam() != "login-path") { \
return; \
} \
Config_editor_invoker invoker; \
invoker.clear(); \
invoker.store("one", "user@host", "one"); \
STORE_AND_CHECK("user@host", "two"); \
expect_contains("user@host", "two"); \
expect_list({"user@host"}); \
erase("user@host"); \
expect_contains("user@host", "one"); \
expect_list({"user@host"}); \
erase("user@host"); \
expect_list({}); \
invoker.clear(); \
} \
\
TEST_P(test_case_name, login_path_external_entry_with_port_and_socket) { \
if (GetParam() != "login-path") { \
return; \
} \
Config_editor_invoker invoker; \
invoker.clear(); \
auto initialize = [&invoker]() { \
std::vector<std::string> args = {"set", "--skip-warn", \
"--password", "--login-path=one", \
"--user=user", "--host=localhost", \
"--port=3306", "--socket=/socket"}; \
invoker.invoke(args, true, "one"); \
}; \
initialize(); \
expect_list({"user@localhost:3306", URL_WITH_SOCKET}); \
STORE_AND_CHECK("user@localhost:3306", "two"); \
STORE_AND_CHECK(URL_WITH_SOCKET, "three"); \
expect_list({"user@localhost:3306", URL_WITH_SOCKET}); \
erase(URL_WITH_SOCKET); \
expect_contains(URL_WITH_SOCKET, "one"); \
expect_contains("user@localhost:3306", "two"); \
expect_list({"user@localhost:3306", URL_WITH_SOCKET}); \
erase("user@localhost:3306"); \
expect_contains(URL_WITH_SOCKET, "one"); \
expect_contains("user@localhost:3306", "one"); \
expect_list({"user@localhost:3306", URL_WITH_SOCKET}); \
erase(URL_WITH_SOCKET); \
expect_contains("user@localhost:3306", "one"); \
expect_list({"user@localhost:3306"}); \
erase("user@localhost:3306"); \
expect_list({}); \
invoker.clear(); \
} \
\
TEST_P(test_case_name, login_path_ipv6_stored_by_config_editor) { \
if (GetParam() != "login-path") { \
return; \
} \
Config_editor_invoker invoker; \
auto initialize = [&invoker](const std::string &host) { \
std::vector<std::string> args = { \
"set", "--skip-warn", "--password", "--login-path=path", \
"--user=user", "--host=" + host, "--port=3306"}; \
invoker.clear(); \
invoker.invoke(args, true, "pass"); \
}; \
initialize("::1"); \
expect_list({"user@[::1]:3306"}); \
initialize("fe80::850a:5a7c:6ab7:aec4"); \
expect_list({"user@[fe80::850a:5a7c:6ab7:aec4]:3306"}); \
initialize("fe80::850a:5a7c:6ab7:aec4%enp0s3"); \
expect_list({"user@[fe80::850a:5a7c:6ab7:aec4%25enp0s3]:3306"}); \
invoker.clear(); \
} \
\
TEST_P(test_case_name, login_path_ipv6_stored_by_helper) { \
if (GetParam() != "login-path") { \
return; \
} \
Config_editor_invoker invoker; \
invoker.clear(); \
const auto quoted = \
invoker.version() >= mysqlshdk::utils::Version(8, 0, 24); \
const auto host = ["ed](const std::string &h) { \
return "host = " + (quoted ? shcore::quote_string(h, '"') : h); \
}; \
STORE_AND_CHECK("user@[::1]", "one"); \
auto output = invoker.invoke({"print", "--all"}); \
EXPECT_THAT(output, ::testing::HasSubstr(host("::1"))); \
invoker.clear(); \
STORE_AND_CHECK("user@[fe80::850a:5a7c:6ab7:aec4]:4321", "two"); \
output = invoker.invoke({"print", "--all"}); \
EXPECT_THAT(output, \
::testing::HasSubstr(host("fe80::850a:5a7c:6ab7:aec4"))); \
EXPECT_THAT(output, ::testing::HasSubstr("port = 4321")); \
invoker.clear(); \
STORE_AND_CHECK("user@[fe80::850a:5a7c:6ab7:aec4%25enp0s3]", "three"); \
output = invoker.invoke({"print", "--all"}); \
EXPECT_THAT(output, ::testing::HasSubstr( \
host("fe80::850a:5a7c:6ab7:aec4%enp0s3"))); \
invoker.clear(); \
}
#define REGISTER_TESTS(test_case_name) \
namespace { \
const auto test_case_name##_list = test_case_name::list_helpers(); \
} \
INSTANTIATE_TEST_SUITE_P(Helpers, test_case_name, \
::testing::ValuesIn(test_case_name##_list), \
g_format_parameter)
} // namespace
#define VALIDATION_TEST TEST_P
#define NORMALIZATION_TEST TEST_P
class Mysql_secret_store_api_test
: public Parametrized_helper_test<Mysql_secret_store_api_tester> {};
ADD_TESTS(Mysql_secret_store_api_test)
#undef VALIDATION_TEST
#undef NORMALIZATION_TEST
TEST_P(Mysql_secret_store_api_test, helper_name) {
EXPECT_EQ(GetParam(), tester.name());
}
TEST_P(Mysql_secret_store_api_test, get_nullptr) {
EXPECT_FALSE(get("url", nullptr));
expect_error("Invalid pointer");
}
TEST_P(Mysql_secret_store_api_test, list_nullptr) {
EXPECT_FALSE(tester.list(nullptr));
expect_error("Invalid pointer");
}
TEST(Helpers, Mysql_secret_store_api_test_secret_spec_operators) {
Secret_spec one{Secret_type::PASSWORD, "first URL"};
Secret_spec two{Secret_type::PASSWORD, "second URL"};
EXPECT_FALSE(one == two);
EXPECT_TRUE(one != two);
one.url = two.url;
EXPECT_TRUE(one == two);
EXPECT_FALSE(one != two);
}
TEST(Helpers, Mysql_secret_store_api_test_get_available_helpers_custom_dir) {
verify_available_helpers(
Mysql_secret_store_api_tester::call_get_available_helpers(
shcore::get_binary_folder()));
}
TEST(Helpers, Mysql_secret_store_api_test_get_available_helpers_invalid_dir) {
auto home = shcore::get_home_dir();
do {
home = shcore::path::join_path(home, "random_name");
} while (shcore::is_folder(home));
EXPECT_TRUE(is_empty(
Mysql_secret_store_api_tester::call_get_available_helpers(home)));
}
REGISTER_TESTS(Mysql_secret_store_api_test);
#define VALIDATION_TEST TEST_P
#define NORMALIZATION_TEST TEST_P
class Shell_api_test : public Parametrized_helper_test<Shell_api_tester> {};
ADD_TESTS(Shell_api_test)
#undef VALIDATION_TEST
#undef NORMALIZATION_TEST
TEST_P(Shell_api_test, delete_all_credentials) {
std::set<std::string> urls{"user@host", "user@host:3306", "user@host:33060",
URL_WITH_SOCKET};
for (const auto &url : urls) {
STORE_AND_CHECK(url, url);
}
tester.expect_delete_all_credentials();
expect_list({});
}
REGISTER_TESTS(Shell_api_test);
#define VALIDATION_TEST IGNORE_TEST
#define NORMALIZATION_TEST IGNORE_TEST
class Helper_executable_test
: public Parametrized_helper_test<Helper_executable_tester> {};
ADD_TESTS(Helper_executable_test)
#undef VALIDATION_TEST
#undef NORMALIZATION_TEST
TEST_P(Helper_executable_test, uri_should_not_be_validated) {
std::string output;
std::string spec = R"("ServerURL":"some+string","SecretType":"password")";
auto &invoker = tester.get_invoker();
EXPECT_TRUE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_EQ("", output);
output.clear();
EXPECT_TRUE(invoker.get("{" + spec + "}", &output));
EXPECT_NE("", output);
output.clear();
EXPECT_TRUE(invoker.erase("{" + spec + "}", &output));
EXPECT_EQ("", output);
output.clear();
}
TEST_P(Helper_executable_test, secret_type_should_not_be_validated) {
if (GetParam() == "login-path") {
// login-path can only store passwords
return;
}
std::string output;
std::string spec = R"("ServerURL":"user@host","SecretType":"some_type")";
auto &invoker = tester.get_invoker();
EXPECT_TRUE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_EQ("", output);
output.clear();
EXPECT_TRUE(invoker.get("{" + spec + "}", &output));
EXPECT_NE("", output);
output.clear();
EXPECT_TRUE(invoker.erase("{" + spec + "}", &output));
EXPECT_EQ("", output);
output.clear();
}
TEST_P(Helper_executable_test, invalid_command) {
std::string output;
std::string spec = R"({"ServerURL":"user@host","SecretType":"password"})";
auto &invoker = tester.get_invoker();
EXPECT_FALSE(invoker.invoke("unknown", spec, &output));
EXPECT_THAT(output, ::testing::HasSubstr("Unknown command"));
}
TEST_P(Helper_executable_test, missing_command) {
std::string output;
std::string spec = R"({"ServerURL":"user@host","SecretType":"password"})";
auto &invoker = tester.get_invoker();
EXPECT_FALSE(invoker.invoke(nullptr, spec, &output));
EXPECT_THAT(output, ::testing::HasSubstr("Missing command"));
}
TEST_P(Helper_executable_test, invalid_json_input_misspelled_server_url) {
const std::string error_message = R"("ServerURL" is missing)";
auto &invoker = tester.get_invoker();
std::string output;
for (const auto &s : {"ServerURl", "SErverURL", "ServerURLL", "SrverURL"}) {
SCOPED_TRACE(s);
std::string spec =
R"(")" + std::string{s} + R"(":"user@host","SecretType":"password")";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
}
TEST_P(Helper_executable_test, invalid_json_input_misspelled_secret_type) {
const std::string error_message = R"("SecretType" is missing)";
auto &invoker = tester.get_invoker();
std::string output;
for (const auto &s :
{"Secrettype", "SEcretType", "SecretTypee", "ScretType"}) {
SCOPED_TRACE(s);
std::string spec =
R"("ServerURL":"user@host",")" + std::string{s} + R"(":"password")";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
}
TEST_P(Helper_executable_test, invalid_json_input_misspelled_secret) {
const std::string error_message = R"("Secret" is missing)";
auto &invoker = tester.get_invoker();
std::string output;
for (const auto &s : {"secret", "SEcret", "Secrett", "Scret"}) {
SCOPED_TRACE(s);
std::string spec = R"({"ServerURL":"user@host","SecretType":"password",")" +
std::string{s} + R"(":"pass"})";
EXPECT_FALSE(invoker.store(spec, &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
}
TEST_P(Helper_executable_test, invalid_json_input_unknown_member) {
const std::string error_message = "JSON object contains unknown member";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec =
R"("ServerURL":"user@host","SecretType":"password","InvalidMember":12345)";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, invalid_json_input_missing_server_url) {
const std::string error_message = R"("ServerURL" is missing)";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec = R"("SecretType":"password")";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, invalid_json_input_missing_secret_type) {
const std::string error_message = R"("SecretType" is missing)";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec = R"("ServerURL":"user@host")";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, invalid_json_input_missing_secret) {
const std::string error_message = R"("Secret" is missing)";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec = R"({"ServerURL":"user@host","SecretType":"password"})";
EXPECT_FALSE(invoker.store(spec, &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, invalid_json_input_wrong_type_server_url) {
const std::string error_message = R"("ServerURL" should be a string)";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec = R"("ServerURL":false,"SecretType":"password")";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, invalid_json_input_wrong_type_secret_type) {
const std::string error_message = R"("SecretType" should be a string)";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec = R"("ServerURL":"user@host","SecretType":false)";
EXPECT_FALSE(invoker.store("{" + spec + R"(,"Secret":"pass"})", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.get("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
EXPECT_FALSE(invoker.erase("{" + spec + "}", &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, invalid_json_input_wrong_type_secret) {
const std::string error_message = R"("Secret" should be a string)";
auto &invoker = tester.get_invoker();
std::string output;
std::string spec =
R"({"ServerURL":"user@host","SecretType":"password","Secret":false})";
EXPECT_FALSE(invoker.store(spec, &output));
EXPECT_THAT(output, ::testing::HasSubstr(error_message));
output.clear();
}
TEST_P(Helper_executable_test, version_command) {
auto &invoker = tester.get_invoker();
std::string output;
EXPECT_TRUE(invoker.version(&output));
EXPECT_THAT(output, ::testing::HasSubstr(shcore::get_long_version()));
output.clear();
}
REGISTER_TESTS(Helper_executable_test);
} // namespace tests