You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
8.2 KiB
258 lines
8.2 KiB
/*
|
|
** Zabbix
|
|
** Copyright (C) 2001-2023 Zabbix SIA
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** 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 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
**/
|
|
|
|
#include "zbxalgo.h"
|
|
#include "zbxjson.h"
|
|
#include "zbxregexp.h"
|
|
|
|
#include "zbxmocktest.h"
|
|
#include "zbxmockdata.h"
|
|
#include "zbxmockassert.h"
|
|
#include "zbxmockjson.h"
|
|
|
|
#define _FAIL(file, line, prefix, message, ...) \
|
|
\
|
|
do \
|
|
{ \
|
|
cm_print_error("%s%s" message "\n", (NULL != prefix_msg ? prefix_msg : ""), \
|
|
(NULL != prefix_msg && '\0' != *prefix_msg ? ": " : ""), \
|
|
__VA_ARGS__); \
|
|
_fail(file, line); \
|
|
} \
|
|
while(0)
|
|
|
|
void cm_print_error(const char * const format, ...);
|
|
|
|
static void json_flatten_contents(struct zbx_json_parse *jp, const char *prefix, zbx_vector_ptr_pair_t *props);
|
|
|
|
static void json_append_prop(zbx_vector_ptr_pair_t *props, const char *key, const char *value)
|
|
{
|
|
zbx_ptr_pair_t pair;
|
|
|
|
pair.first = zbx_strdup(NULL, key);
|
|
pair.second = zbx_strdup(NULL, value);
|
|
zbx_vector_ptr_pair_append_ptr(props, &pair);
|
|
}
|
|
|
|
static int json_compare_props(const void *d1, const void *d2)
|
|
{
|
|
const zbx_ptr_pair_t *p1 = (const zbx_ptr_pair_t *)d1;
|
|
const zbx_ptr_pair_t *p2 = (const zbx_ptr_pair_t *)d2;
|
|
|
|
return strcmp((const char *)p1->first, (const char *)p2->first);
|
|
}
|
|
|
|
static void json_flatten_value(const char *ptr, const char *path, zbx_vector_ptr_pair_t *props)
|
|
{
|
|
struct zbx_json_parse jp_value;
|
|
char *value = NULL;
|
|
size_t value_alloc = 0;
|
|
zbx_json_type_t type;
|
|
|
|
if (FAIL == zbx_json_brackets_open(ptr, &jp_value))
|
|
{
|
|
if (NULL == zbx_json_decodevalue_dyn(ptr, &value, &value_alloc, &type))
|
|
type = ZBX_JSON_TYPE_NULL;
|
|
|
|
json_append_prop(props, path, (ZBX_JSON_TYPE_NULL != type ? value : "null"));
|
|
}
|
|
else
|
|
json_flatten_contents(&jp_value, path, props);
|
|
|
|
zbx_free(value);
|
|
}
|
|
|
|
static int json_quote_key(char *key, size_t size)
|
|
{
|
|
char *ptr, *out;
|
|
int quotes = 0, dot = 1;
|
|
|
|
for (ptr = key; '\0' != *ptr; ptr++)
|
|
{
|
|
if ('\'' == *ptr || '\\' == *ptr)
|
|
quotes++;
|
|
|
|
if (0 == isalnum((unsigned char)*ptr) && '_' != *ptr && '-' != *ptr)
|
|
dot = 0;
|
|
}
|
|
|
|
if (1 == dot)
|
|
return FAIL;
|
|
|
|
if (0 == quotes)
|
|
return SUCCEED;
|
|
|
|
if (ptr - key + quotes + 1 > (int)size)
|
|
fail_msg("The hardcoded 2k limit exceeded by JSON key: %s", key);
|
|
|
|
for (out = ptr + quotes; ptr != key; ptr--)
|
|
{
|
|
*out-- = *ptr;
|
|
if ('\'' == *ptr || '\\' == *ptr)
|
|
*out-- = '\\';
|
|
}
|
|
|
|
return SUCCEED;
|
|
}
|
|
|
|
static void json_flatten_object(struct zbx_json_parse *jp, const char *prefix, zbx_vector_ptr_pair_t *props)
|
|
{
|
|
const char *pnext = NULL;
|
|
char *path = NULL, key[MAX_STRING_LEN];
|
|
size_t path_alloc = 0, path_offset;
|
|
|
|
while (NULL != (pnext = zbx_json_pair_next(jp, pnext, key, sizeof(key))))
|
|
{
|
|
path_offset = 0;
|
|
if (SUCCEED == json_quote_key(key, sizeof(key)))
|
|
zbx_snprintf_alloc(&path, &path_alloc, &path_offset, "%s['%s']", prefix, key);
|
|
else
|
|
zbx_snprintf_alloc(&path, &path_alloc, &path_offset, "%s.%s", prefix, key);
|
|
|
|
json_flatten_value(pnext, path, props);
|
|
}
|
|
zbx_free(path);
|
|
}
|
|
|
|
static void json_flatten_array(struct zbx_json_parse *jp, const char *parent, zbx_vector_ptr_pair_t *props)
|
|
{
|
|
const char *pnext;
|
|
char *path, *value = NULL;
|
|
int index = 0;
|
|
|
|
for (pnext = NULL; NULL != (pnext = zbx_json_next(jp, pnext));)
|
|
{
|
|
path = zbx_dsprintf(NULL, "%s[%d]", parent, index++);
|
|
json_flatten_value(pnext, path, props);
|
|
zbx_free(path);
|
|
}
|
|
|
|
zbx_free(value);
|
|
}
|
|
|
|
static void json_flatten_contents(struct zbx_json_parse *jp, const char *prefix, zbx_vector_ptr_pair_t *props)
|
|
{
|
|
if ('{' == *jp->start)
|
|
json_flatten_object(jp, prefix, props);
|
|
else if ('[' == *jp->start)
|
|
json_flatten_array(jp, prefix, props);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* *
|
|
* Purpose: flattens json into vector of key (json path), value pairs, sorted *
|
|
* by keys *
|
|
* *
|
|
******************************************************************************/
|
|
static void json_flatten(struct zbx_json_parse *jp, zbx_vector_ptr_pair_t *props)
|
|
{
|
|
json_flatten_contents(jp, "$", props);
|
|
zbx_vector_ptr_pair_sort(props, json_compare_props);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* *
|
|
* Purpose: compares returned json with expected *
|
|
* *
|
|
* Comments: The comparison is done by first flattening both jsons into *
|
|
* key(jsonpath)-value pairs, sorting by keys and then comparing *
|
|
* the resulting lists. *
|
|
* If expected value starts with / then regular expression match is *
|
|
* performed. *
|
|
* *
|
|
******************************************************************************/
|
|
void __zbx_mock_assert_json_eq(const char *file, int line, const char *prefix_msg, const char *expected_value,
|
|
const char *returned_value)
|
|
{
|
|
struct zbx_json_parse jp_expected, jp_returned;
|
|
zbx_vector_ptr_pair_t props_expected, props_returned;
|
|
int i, props_num;
|
|
|
|
if (FAIL == zbx_json_open(expected_value, &jp_expected))
|
|
_FAIL(file, line, prefix_msg, "Expected value is not a valid JSON object: %s", zbx_json_strerror());
|
|
|
|
if (FAIL == zbx_json_open(returned_value, &jp_returned))
|
|
_FAIL(file, line, prefix_msg, "Returned value is not a valid JSON object: %s", zbx_json_strerror());
|
|
|
|
zbx_vector_ptr_pair_create(&props_expected);
|
|
zbx_vector_ptr_pair_create(&props_returned);
|
|
|
|
json_flatten(&jp_expected, &props_expected);
|
|
json_flatten(&jp_returned, &props_returned);
|
|
|
|
props_num = MIN(props_expected.values_num, props_returned.values_num);
|
|
|
|
for (i = 0; i < props_num; i++)
|
|
{
|
|
zbx_ptr_pair_t *pair_expected = &props_expected.values[i];
|
|
zbx_ptr_pair_t *pair_returned = &props_returned.values[i];
|
|
|
|
if (0 != strcmp(pair_expected->first, pair_returned->first))
|
|
{
|
|
_FAIL(file, line, prefix_msg, "Expected key \"%s\" while got \"%s\"", pair_expected->first,
|
|
pair_returned->first);
|
|
}
|
|
|
|
if ('/' == *(char *)pair_expected->second)
|
|
{
|
|
char *pattern = (char *)pair_expected->second + 1;
|
|
if (NULL == zbx_regexp_match(pair_returned->second, pattern, NULL))
|
|
{
|
|
_FAIL(file, line, prefix_msg, "Key \"%s\" value \"%s\" does not match pattern \"%s\"",
|
|
pair_returned->first, pair_returned->second, pattern);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (0 != strcmp(pair_expected->second, pair_returned->second))
|
|
{
|
|
_FAIL(file, line, prefix_msg, "Expected key \"%s\" value \"%s\" while got \"%s\"",
|
|
pair_expected->first, pair_expected->second, pair_returned->second);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i < props_expected.values_num)
|
|
{
|
|
zbx_ptr_pair_t *pair_expected = &props_expected.values[i];
|
|
_FAIL(file, line, prefix_msg, "Expected key \"%s\" while got nothing", pair_expected->first);
|
|
}
|
|
|
|
if (i < props_returned.values_num)
|
|
{
|
|
zbx_ptr_pair_t *pair_returned = &props_returned.values[i];
|
|
_FAIL(file, line, prefix_msg, "Did not expect key \"%s\"", pair_returned->first);
|
|
}
|
|
|
|
for (i = 0; i < props_expected.values_num; i++)
|
|
{
|
|
zbx_free(props_expected.values[i].first);
|
|
zbx_free(props_expected.values[i].second);
|
|
}
|
|
zbx_vector_ptr_pair_destroy(&props_expected);
|
|
|
|
for (i = 0; i < props_returned.values_num; i++)
|
|
{
|
|
zbx_free(props_returned.values[i].first);
|
|
zbx_free(props_returned.values[i].second);
|
|
}
|
|
zbx_vector_ptr_pair_destroy(&props_returned);
|
|
}
|