blob: 5615d62a4fffb537ded984c0219d091ee360cc82 [file] [log] [blame]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fstream>
#include "unit/TestBase.h"
#include "unit/Catch.h"
#include "../agent/JsonSchema.h"
#include "nlohmann/json-schema.hpp"
#include "utils/RegexUtils.h"
#include "utils/StringUtils.h"
struct JsonError {
std::string path;
std::string error;
};
class ErrorHandler : public nlohmann::json_schema::error_handler {
public:
explicit ErrorHandler(std::function<void(const JsonError&)> handler)
: handler_(std::move(handler)) {}
void error(const nlohmann::json::json_pointer& ptr, const nlohmann::json& /*instance*/, const std::string& message) override {
handler_(JsonError{ptr.to_string(), message});
}
private:
std::function<void(const JsonError&)> handler_;
};
void extractExpectedErrors(nlohmann::json& node, const std::string& path, std::unordered_map<std::string, std::string>& errors) {
if (node.is_object() && node.contains("$err")) {
errors[path] = node["$err"].get<std::string>();
node = node["$value"];
}
if (node.is_object() || node.is_array()) {
for (auto& [key, val] : node.items()) {
extractExpectedErrors(val, utils::string::join_pack(path, "/", key), errors);
}
}
}
TEST_CASE("The generated JSON schema matches a valid json flow") {
const nlohmann::json config_schema = nlohmann::json::parse(minifi::docs::generateJsonSchema());
nlohmann::json_schema::json_validator validator;
validator.set_root_schema(config_schema);
auto config_json = R"(
{
"Flow Controller": {"name": "Test"},
"Processors": [
{
"id": "00000000-0000-0000-0000-000000000000",
"class": "GenerateFlowFile",
"name": "Proc1",
"scheduling strategy": "TIMER_DRIVEN",
"scheduling period": "1 min",
"Properties": {}
}
],
"Connections": [
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Conn1",
"source id": "00000000-0000-0000-0000-000000000000",
"source relationship names": ["success"],
"destination id": "00000000-0000-0000-0000-000000000000"
}
],
"Input Ports": [
{"id": "00000000-0000-0000-0000-000000000000", "name": "In1"}
],
"Output Ports": [
{"id": "00000000-0000-0000-0000-000000000000", "name": "Out1"}
],
"Funnels": [
{"id": "00000000-0000-0000-0000-000000000000", "name": "Fun1"}
],
"Process Groups": [
{
"name": "Group1",
"Processors": [],
"Connections": [],
"Process Groups": []
}
],
"Remote Process Groups": [
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "RPG1",
"Input Ports": [{
"id": "00000000-0000-0000-0000-000000000000",
"name": "RIn1",
"Properties": {
"Host Name": "localhost"
}
}]
}
],
"Controller Services": [
{
"id": "00000000-0000-0000-0000-000000000000",
"class": "SSLContextService",
"name": "Service1",
"Properties": {
"Client Certificate": "",
"Private Key": "",
"Passphrase": "",
"CA Certificate": ""
}
}
]
}
)"_json;
validator.validate(config_json);
}
TEST_CASE("The JSON schema detects invalid values in the json flow") {
const nlohmann::json config_schema = nlohmann::json::parse(minifi::docs::generateJsonSchema());
nlohmann::json_schema::json_validator validator;
validator.set_root_schema(config_schema);
// the objects of type {"$err": <error>, "$value": <value>} are special
// in the sense that they are preprocessed, replaced by <value> and we expect
// a validation error at the position of this object that matches the
// regex <error>
auto config_json = R"(
{
"Flow Controller": {"name": "Test"},
"Processors": [
{"$err": "property 'scheduling period' not found", "$value": {
"id": "00000000-0000-0000-0000-000000000000",
"class": "GenerateFlowFile",
"name": "Proc1",
"scheduling strategy": "TIMER_DRIVEN",
"Properties": {}
}},
{
"id": "00000000-0000-0000-0000-000000000000",
"class": "GenerateFlowFile",
"name": "Proc1",
"scheduling strategy": "TIMER_DRIVEN",
"scheduling period": "1 min",
"Properties": {
"Batch Size": {"$value": "not a number", "$err": "unexpected instance type"}
}
},
{
"id": "00000000-0000-0000-0000-000000000000",
"class": "GenerateFlowFile",
"name": "Proc1",
"scheduling strategy": "TIMER_DRIVEN",
"scheduling period": "1 min",
"Properties": {"$err": "", "$value": {
"No such property": 5
}}
}
],
"Connections": [
{"$err": "property 'name' not found", "$value": {
"id": {"$value": "00000000-0000-0000-0000-00000000000", "$err": ""},
"source id": "00000000-0000-0000-0000-000000000000",
"source relationship names": ["success"],
"destination id": "00000000-0000-0000-0000-000000000000"
}}
],
"Input Ports": [
{"id": "00000000-0000-0000-0000-000000000000", "name": {"$value": 5, "$err": "unexpected instance type"}}
],
"Output Ports": [
{"id": "00000000-0000-0000-0000-000000000000", "name": "Out1"}
],
"Funnels": [
{"id": {"$value": 5, "$err": "unexpected instance type"}}
],
"Process Groups": [
{"$err": "", "$value": {
"name": "Group1",
"no such property": []
}}
],
"Remote Process Groups": [
{"$err": "property 'Input Ports' not found", "$value": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "RPG1"
}}
],
"Controller Services": [
{
"id": {"$value": "00000000-0000-0000-0000-00000000000", "$err": ""},
"class": "SSLContextService",
"name": "Service1",
"Properties": {
"Client Certificate": 6,
"Private Key": "",
"Passphrase": "",
"CA Certificate": ""
}
},
{
"id": "00000000-0000-0000-0000-000000000001",
"class": "SSLContextService",
"name": "Service1",
"Properties": {
"Client Certificate": {"$value": 6, "$err": "unexpected instance type"},
"Private Key": "",
"Passphrase": "",
"CA Certificate": ""
}
},
{"$value": "kenyer", "$err": "unexpected instance type"}
]
}
)"_json;
std::unordered_map<std::string, std::string> errors;
extractExpectedErrors(config_json, "", errors);
ErrorHandler err_handler{[&] (const auto& err) {
auto it = errors.find(err.path);
if (it == errors.end()) {
throw std::logic_error("Unexpected error in json flow at " + err.path + ": " + err.error);
}
if (!it->second.empty()) {
minifi::utils::Regex re(it->second);
if (!minifi::utils::regexSearch(err.error, re)) {
throw std::logic_error("Error in json flow at " + err.path + " does not match expected pattern, expected: '" + it->second + "', actual: " + err.error);
}
}
errors.erase(it);
}};
validator.validate(config_json, err_handler);
// all expected errors should have been processed
if (!errors.empty()) {
for (const auto& [path, err] : errors) {
std::cerr << "Expected error at " << path << ": " << err << std::endl;
}
throw std::logic_error("There were some expected errors that did not occur");
}
}