/**
 * @file test_metadata.c
 * @author Radek Krejci <rkrejci@cesnet.cz>
 * @brief unit tests for Metadata extension (annotation) support
 *
 * Copyright (c) 2019 - 2022 CESNET, z.s.p.o.
 *
 * This source code is licensed under BSD 3-Clause License (the "License").
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://opensource.org/licenses/BSD-3-Clause
 */
#define _UTEST_MAIN_
#include "utests.h"

#include "libyang.h"
#include "plugins_exts.h"
#include "plugins_exts/metadata.h"

static void
test_yang(void **state)
{
    struct lys_module *mod;
    struct lysc_ext_instance *e;
    const char *units;

    const char *data = "module a {yang-version 1.1; namespace urn:tests:extensions:metadata:a; prefix a;"
            "import ietf-yang-metadata {prefix md;}"
            "feature f;"
            "md:annotation x {"
            "  description \"test\";"
            "  if-feature f;"
            "  reference \"test\";"
            "  status \"current\";"
            "  type uint8;"
            "  units meters;"
            "}}";
    const char *feats[] = {"f", NULL};

    UTEST_ADD_MODULE(data, LYS_IN_YANG, feats, &mod);
    assert_int_equal(1, LY_ARRAY_COUNT(mod->compiled->exts));
    e = &mod->compiled->exts[0];
    assert_non_null(e->compiled);
    assert_non_null(e->substmts);
    lyplg_ext_get_storage(e, LY_STMT_UNITS, sizeof units, (const void **)&units);
    assert_string_equal("meters", units);

    /* invalid */
    /* missing mandatory type substatement */
    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
            "import ietf-yang-metadata {prefix md;}"
            "md:annotation aa;}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
    CHECK_LOG_CTX("Ext plugin \"ly2 metadata\": Missing mandatory keyword \"type\" as a child of \"md:annotation aa\".",
            "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* not allowed substatement */
    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
            "import ietf-yang-metadata {prefix md;}"
            "md:annotation aa {default x;}}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
    CHECK_LOG_CTX("Invalid keyword \"default\" as a child of \"md:annotation aa\" extension instance.",
            "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* invalid cardinality of units substatement */
    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
            "import ietf-yang-metadata {prefix md;}"
            "md:annotation aa {type string; units x; units y;}}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
    CHECK_LOG_CTX("Duplicate keyword \"units\".", "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* invalid cardinality of status substatement */
    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
            "import ietf-yang-metadata {prefix md;}"
            "md:annotation aa {type string; status current; status obsolete;}}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
    CHECK_LOG_CTX("Duplicate keyword \"status\".", "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* invalid cardinality of status substatement */
    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
            "import ietf-yang-metadata {prefix md;}"
            "md:annotation aa {type string; type uint8;}}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
    CHECK_LOG_CTX("Duplicate keyword \"type\".", "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* duplication of the same annotation */
    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;"
            "import ietf-yang-metadata {prefix md;}"
            "md:annotation aa {type string;} md:annotation aa {type uint8;}}";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL));
    CHECK_LOG_CTX("Ext plugin \"ly2 metadata\": Extension md:annotation is instantiated multiple times.",
            "/aa:{ext-inst='md:annotation'}/aa", 0);
}

static void
test_yin(void **state)
{
    struct lys_module *mod;
    struct lysc_ext_instance *e;
    const char *data, *units;

    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"a\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:a\"/><prefix value=\"a\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<feature name=\"f\"/>\n"
            "<md:annotation name=\"x\">\n"
            "  <description><text>test</text></description>\n"
            "  <reference><text>test</text></reference>\n"
            "  <if-feature name=\"f\"/>\n"
            "  <status value=\"current\"/>\n"
            "  <type name=\"uint8\"/>\n"
            "  <units name=\"meters\"/>\n"
            "</md:annotation></module>";
    const char *feats[] = {"f", NULL};

    UTEST_ADD_MODULE(data, LYS_IN_YIN, feats, &mod);
    assert_int_equal(1, LY_ARRAY_COUNT(mod->compiled->exts));
    e = &mod->compiled->exts[0];
    assert_non_null(e->compiled);
    assert_non_null(e->substmts);
    lyplg_ext_get_storage(e, LY_STMT_UNITS, sizeof units, (const void **)&units);
    assert_string_equal("meters", units);

    /* invalid */
    /* missing mandatory type substatement */
    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:aa\"/><prefix value=\"aa\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<md:annotation name=\"aa\"/>\n"
            "</module>";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL));
    CHECK_LOG_CTX("Ext plugin \"ly2 metadata\": Missing mandatory keyword \"type\" as a child of \"md:annotation aa\".",
            "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* not allowed substatement */
    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:aa\"/><prefix value=\"aa\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<md:annotation name=\"aa\">\n"
            "  <default value=\"x\"/>\n"
            "</md:annotation></module>";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL));
    CHECK_LOG_CTX("Invalid keyword \"default\" as a child of \"md:annotation aa\" extension instance.",
            "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* invalid cardinality of units substatement */
    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:aa\"/><prefix value=\"aa\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<md:annotation name=\"aa\">\n"
            "  <type name=\"string\"/>\n"
            "  <units name=\"x\"/>\n"
            "  <units name=\"y\"/>\n"
            "</md:annotation></module>";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL));
    CHECK_LOG_CTX("Duplicate keyword \"units\".", "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* invalid cardinality of status substatement */
    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:aa\"/><prefix value=\"aa\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<md:annotation name=\"aa\">\n"
            "  <type name=\"string\"/>\n"
            "  <status value=\"current\"/>\n"
            "  <status value=\"obsolete\"/>\n"
            "</md:annotation></module>";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL));
    CHECK_LOG_CTX("Duplicate keyword \"status\".", "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* invalid cardinality of status substatement */
    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:aa\"/><prefix value=\"aa\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<md:annotation name=\"aa\">\n"
            "  <type name=\"string\"/>\n"
            "  <type name=\"uint8\"/>\n"
            "</md:annotation></module>";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL));
    CHECK_LOG_CTX("Duplicate keyword \"type\".", "/aa:{ext-inst='md:annotation'}/aa", 0);

    /* duplication of the same annotation */
    data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n"
            "<yang-version value=\"1.1\"/><namespace uri=\"urn:tests:extensions:metadata:aa\"/><prefix value=\"aa\"/>\n"
            "<import module=\"ietf-yang-metadata\"><prefix value=\"md\"/></import>\n"
            "<md:annotation name=\"aa\">\n"
            "  <type name=\"string\"/>\n"
            "</md:annotation><md:annotation name=\"aa\">\n"
            "  <type name=\"uint8\"/>\n"
            "</md:annotation></module>";
    assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL));
    CHECK_LOG_CTX("Ext plugin \"ly2 metadata\": Extension md:annotation is instantiated multiple times.",
            "/aa:{ext-inst='md:annotation'}/aa", 0);
}

int
main(void)
{
    const struct CMUnitTest tests[] = {
        UTEST(test_yang),
        UTEST(test_yin),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}
