// Copyright (C) 2009-2023 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include using namespace isc::data; using namespace isc::config; using namespace std; namespace { /// @brief Convenience method for creating elements from JSON string /// /// @param str string to be converted /// @return Element structure ElementPtr el(const std::string& str) { return (Element::fromJSON(str)); } // This test verifies that that createAnswer method is able to generate // various answers. TEST(CommandInterpreterTest, createAnswer) { ConstElementPtr answer; // By default the answer is a successful one. answer = createAnswer(); EXPECT_EQ("{ \"result\": 0 }", answer->str()); // Let's check if we can generate an error. answer = createAnswer(CONTROL_RESULT_ERROR, "error"); EXPECT_EQ("{ \"result\": 1, \"text\": \"error\" }", answer->str()); // This is expected to throw. When status code is non-zero (indicating error), // textual explanation is mandatory. EXPECT_THROW(createAnswer(CONTROL_RESULT_ERROR, ElementPtr()), CtrlChannelError); EXPECT_THROW(createAnswer(CONTROL_RESULT_ERROR, Element::create(1)), CtrlChannelError); // Let's check if answer can be generate with some data in it. ConstElementPtr arg = el("[ \"just\", \"some\", \"data\" ]"); answer = createAnswer(CONTROL_RESULT_SUCCESS, arg); EXPECT_EQ("{ \"arguments\": [ \"just\", \"some\", \"data\" ], \"result\": 0 }", answer->str()); } // This test checks whether parseAnswer is able to handle good and malformed // answers. TEST(CommandInterpreterTest, parseAnswer) { ConstElementPtr answer; ConstElementPtr arg; int rcode; EXPECT_THROW(parseAnswer(rcode, ElementPtr()), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("1")), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("[]")), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("{ }")), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("{ \"something\": 1 }")), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 0 ] }")), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1 ] }")), CtrlChannelError); EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1, 1 ] }")), CtrlChannelError); answer = el("{ \"result\": 0 }"); arg = parseAnswer(rcode, answer); EXPECT_EQ(0, rcode); EXPECT_TRUE(isNull(arg)); answer = el("{ \"result\": 3, \"text\": \"error\", \"arguments\": [ \"some\", \"data\" ] }"); arg = parseAnswer(rcode, answer); ASSERT_TRUE(arg); EXPECT_EQ(3, rcode); EXPECT_EQ("[ \"some\", \"data\" ]", arg->str()); } // Checks if parseAnswerText can return the text TEST(CommandInterpreterTest, parseAnswerText) { ConstElementPtr answer; ConstElementPtr arg; int rcode; answer = el("{ \"result\": 5, \"text\": \"error\", \"arguments\": [ \"some\", \"data\" ] }"); arg = parseAnswerText(rcode, answer); ASSERT_TRUE(arg); EXPECT_EQ(5, rcode); EXPECT_EQ("error", arg->stringValue()); } // This checks whether we can convert an answer to easily printable form. TEST(CommandInterpreterTest, answerToText) { ConstElementPtr answer; // Doing jolly good here. answer = el("{ \"result\": 0 }"); EXPECT_EQ("success(0)", answerToText(answer)); // Sometimes things don't go according to plan. answer = el("{ \"result\": 1, \"text\": \"ho lee fuk sum ting wong\" }"); EXPECT_EQ("failure(1), text=ho lee fuk sum ting wong", answerToText(answer)); } // This test checks whether createCommand function is able to create commands // with and without parameters. TEST(CommandInterpreterTest, createCommand) { ConstElementPtr command; ConstElementPtr arg; string service; command = createCommand("my_command"); ASSERT_EQ("{ \"command\": \"my_command\" }", command->str()); arg = el("1"); command = createCommand("my_command", arg); ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\" }", command->str()); arg = el("[ \"a\", \"b\" ]"); command = createCommand("my_cmd", arg); ASSERT_EQ("{ \"arguments\": [ \"a\", \"b\" ], \"command\": \"my_cmd\" }", command->str()); arg = el("{ \"a\": \"map\" }"); command = createCommand("foo", arg); ASSERT_EQ("{ \"arguments\": { \"a\": \"map\" }, \"command\": \"foo\" }", command->str()); command = createCommand("my_command", "my_service"); ASSERT_EQ("{ \"command\": \"my_command\", " "\"service\": [ \"my_service\" ] }", command->str()); arg = el("1"); command = createCommand("my_command", arg, "my_service"); ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\", " "\"service\": [ \"my_service\" ] }", command->str()); arg = el("[ \"a\", \"b\" ]"); command = createCommand("my_cmd", arg, "my_server"); ASSERT_EQ("{ \"arguments\": [ \"a\", \"b\" ], " "\"command\": \"my_cmd\", " "\"service\": [ \"my_server\" ] }", command->str()); arg = el("{ \"a\": \"map\" }"); command = createCommand("foo", arg, "bar"); ASSERT_EQ("{ \"arguments\": { \"a\": \"map\" }, " "\"command\": \"foo\", " "\"service\": [ \"bar\" ] }", command->str()); } // This test checks whether parseCommand function is able to parse various valid // and malformed commands. TEST(CommandInterpreterTest, parseCommand) { ConstElementPtr arg; std::string cmd; // should throw EXPECT_THROW(parseCommand(arg, ElementPtr()), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("1")), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("{ }")), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("{ \"not a command\": 1 }")), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("{ \"command\": 1 }")), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("{ \"command\": [] }")), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("{ \"command\": [ 1 ] }")), CtrlChannelError); EXPECT_THROW(parseCommand(arg, el("{ \"command\": \"my_command\", " "\"unknown\": \"xyz\" }")), CtrlChannelError); cmd = parseCommand(arg, el("{ \"command\": \"my_command\" }")); EXPECT_EQ("my_command", cmd); EXPECT_FALSE(arg); // Include "service" to verify that it is not rejected. cmd = parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": 1, " " \"service\": [ \"dhcp4\" ] }")); ASSERT_TRUE(arg); EXPECT_EQ("my_command", cmd); EXPECT_EQ("1", arg->str()); parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": " "[ \"some\", \"argument\", \"list\" ] }")); EXPECT_EQ("my_command", cmd); ASSERT_TRUE(arg); EXPECT_EQ("[ \"some\", \"argument\", \"list\" ]", arg->str()); } // This test checks whether parseCommandWithArgs function is able to parse // various valid and malformed commands. TEST(CommandInterpreterTest, parseCommandWithArgs) { ConstElementPtr arg; std::string cmd; // Arguments are required. EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\" }")), CtrlChannelError); // Arguments must be a map. EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " "\"arguments\": [ 1, 2, 3 ] }")), CtrlChannelError); // Arguments must not be empty. EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " "\"arguments\": { } }")), CtrlChannelError); // Command with unsupported parameter is rejected. EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " " \"arguments\": { \"arg1\": \"value1\" }, " " \"unsupported\": 1 }")), CtrlChannelError); // Specifying arguments in non empty map should be successful. EXPECT_NO_THROW( cmd = parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " " \"arguments\": { \"arg1\": \"value1\" } }")) ); ASSERT_TRUE(arg); ASSERT_EQ(Element::map, arg->getType()); auto arg1 = arg->get("arg1"); ASSERT_TRUE(arg1); ASSERT_EQ(Element::string, arg1->getType()); EXPECT_EQ("value1", arg1->stringValue()); EXPECT_EQ("my_command", cmd); // The "service" parameter should be allowed. EXPECT_NO_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " " \"service\": [ \"dhcp4\" ], " " \"arguments\": { \"arg1\": \"value1\" } }")) ); } }