#include "json_decoder.cpp" 
 
#include <library/cpp/monlib/consumers/collecting_consumer.h> 
#include <library/cpp/testing/unittest/registar.h> 
 
#include <array> 
 
 
using namespace NMonitoring; 
 
enum EJsonPart : ui8 { 
    METRICS = 0, 
    COMMON_TS = 1, 
    COMMON_LABELS = 2, 
}; 
 
constexpr std::array<TStringBuf, 3> JSON_PARTS = { 
    TStringBuf(R"("metrics": [{
        "labels": { "key": "value" }, 
        "type": "GAUGE", 
        "value": 123 
    }])"), 
 
    TStringBuf(R"("ts": 1)"),
 
    TStringBuf(R"("commonLabels": {
        "key1": "value1", 
        "key2": "value2" 
    })"), 
}; 
 
TString BuildJson(std::initializer_list<EJsonPart> parts) { 
    TString data = "{"; 
 
    for (auto it = parts.begin(); it != parts.end(); ++it) { 
        data += JSON_PARTS[*it]; 
 
        if (it + 1 != parts.end()) { 
            data += ","; 
        } 
    } 
 
    data += "}"; 
    return data; 
} 
 
void ValidateCommonParts(TCommonParts&& commonParts, bool checkLabels, bool checkTs) { 
    if (checkTs) { 
        UNIT_ASSERT_VALUES_EQUAL(commonParts.CommonTime.MilliSeconds(), 1000); 
    } 
 
    if (checkLabels) { 
        auto& labels = commonParts.CommonLabels; 
        UNIT_ASSERT_VALUES_EQUAL(labels.Size(), 2); 
        UNIT_ASSERT(labels.Has(TStringBuf("key1")));
        UNIT_ASSERT(labels.Has(TStringBuf("key2")));
        UNIT_ASSERT_VALUES_EQUAL(labels.Get(TStringBuf("key1")).value()->Value(), "value1");
        UNIT_ASSERT_VALUES_EQUAL(labels.Get(TStringBuf("key2")).value()->Value(), "value2");
    } 
} 
 
void ValidateMetrics(const TVector<TMetricData>& metrics) { 
    UNIT_ASSERT_VALUES_EQUAL(metrics.size(), 1); 
 
    auto& m = metrics[0]; 
    UNIT_ASSERT_VALUES_EQUAL(m.Kind, EMetricType::GAUGE); 
    auto& l = m.Labels; 
    UNIT_ASSERT_VALUES_EQUAL(l.Size(), 1); 
    UNIT_ASSERT_VALUES_EQUAL(l.Get(0)->Name(), "key"); 
    UNIT_ASSERT_VALUES_EQUAL(l.Get(0)->Value(), "value"); 
 
    UNIT_ASSERT_VALUES_EQUAL(m.Values->Size(), 1); 
    UNIT_ASSERT_VALUES_EQUAL((*m.Values)[0].GetValue().AsDouble(), 123); 
} 
 
void CheckCommonPartsCollector(TString data, bool shouldBeStopped, bool checkLabels = true, bool checkTs = true, TStringBuf metricNameLabel = "name") {
    TCommonPartsCollector commonPartsCollector; 
    TMemoryInput memIn(data); 
    TDecoderJson decoder(data, &commonPartsCollector, metricNameLabel);
 
    bool isOk{false}; 
    UNIT_ASSERT_NO_EXCEPTION(isOk = NJson::ReadJson(&memIn, &decoder)); 
    UNIT_ASSERT_VALUES_EQUAL(isOk, !shouldBeStopped); 
 
    ValidateCommonParts(commonPartsCollector.CommonParts(), checkLabels, checkTs); 
} 
 
Y_UNIT_TEST_SUITE(TJsonDecoderTest) { 
    Y_UNIT_TEST(FullCommonParts) { 
        CheckCommonPartsCollector(BuildJson({COMMON_LABELS, COMMON_TS, METRICS}), true); 
        CheckCommonPartsCollector(BuildJson({COMMON_TS, COMMON_LABELS, METRICS}), true); 
 
        CheckCommonPartsCollector(BuildJson({METRICS, COMMON_TS, COMMON_LABELS}), true); 
        CheckCommonPartsCollector(BuildJson({METRICS, COMMON_LABELS, COMMON_TS}), true); 
 
        CheckCommonPartsCollector(BuildJson({COMMON_LABELS, METRICS, COMMON_TS}), true); 
        CheckCommonPartsCollector(BuildJson({COMMON_TS, METRICS, COMMON_LABELS}), true); 
    } 
 
    Y_UNIT_TEST(PartialCommonParts) { 
        CheckCommonPartsCollector(BuildJson({COMMON_TS, METRICS}), false, false, true); 
        CheckCommonPartsCollector(BuildJson({COMMON_LABELS, METRICS}), false, true, false); 
 
        CheckCommonPartsCollector(BuildJson({METRICS, COMMON_LABELS}), false, true, false); 
        CheckCommonPartsCollector(BuildJson({METRICS, COMMON_TS}), false, false, true); 
 
        CheckCommonPartsCollector(BuildJson({METRICS}), false, false, false); 
    } 
 
    Y_UNIT_TEST(CheckCommonPartsAndMetrics) { 
        auto data = BuildJson({COMMON_LABELS, COMMON_TS, METRICS}); 
        TCollectingConsumer collector; 
 
        DecodeJson(data, &collector); 
 
        TCommonParts commonParts; 
        commonParts.CommonTime = collector.CommonTime; 
        commonParts.CommonLabels = collector.CommonLabels; 
 
        ValidateCommonParts(std::move(commonParts), true, true); 
        ValidateMetrics(collector.Metrics); 
    } 

    Y_UNIT_TEST(CanParseHistogramsWithInf) {
        const char* metricsData = R"({
"metrics":
    [
        {
            "hist": {
                "bounds": [
                    10
                ],
                "buckets": [
                    11
                ],
                "inf": 12
            },
            "name":"s1",
            "type": "HIST_RATE"
        },
        {
            "hist": {
                "bounds": [
                    20
                ],
                "buckets": [
                    21
                ]
            },
            "name":"s2",
            "type":"HIST_RATE"
        }
    ]
})";
        TCollectingConsumer consumer(false);
        DecodeJson(metricsData, &consumer);

        UNIT_ASSERT_VALUES_EQUAL(consumer.Metrics.size(), 2);
        {
            const auto& m = consumer.Metrics[0];
            UNIT_ASSERT_VALUES_EQUAL(m.Kind, EMetricType::HIST_RATE);
            UNIT_ASSERT_VALUES_EQUAL(m.Values->Size(), 1);
            const auto* histogram = (*m.Values)[0].GetValue().AsHistogram();
            UNIT_ASSERT_VALUES_EQUAL(histogram->Count(), 2);
            UNIT_ASSERT_VALUES_EQUAL(histogram->UpperBound(1), Max<TBucketBound>());
            UNIT_ASSERT_VALUES_EQUAL(histogram->Value(0), 11);
            UNIT_ASSERT_VALUES_EQUAL(histogram->Value(1), 12);
        }
        {
            const auto& m = consumer.Metrics[1];
            UNIT_ASSERT_VALUES_EQUAL(m.Kind, EMetricType::HIST_RATE);
            UNIT_ASSERT_VALUES_EQUAL(m.Values->Size(), 1);
            const auto* histogram = (*m.Values)[0].GetValue().AsHistogram();
            UNIT_ASSERT_VALUES_EQUAL(histogram->Count(), 1);
            UNIT_ASSERT_VALUES_EQUAL(histogram->UpperBound(0), 20);
            UNIT_ASSERT_VALUES_EQUAL(histogram->Value(0), 21);
        }
    }
}