#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%require-types
%enable-all-warnings
%new-style
%strict-args
%allow-injection

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/QUnit.qm
%requires ../../../../qlib/Logger.qm
%requires ../../../../qlib/DataProvider
%requires ../../../../qlib/RestClient.qm
%requires ../../../../qlib/RestClientDataProvider
%requires ../../../../qlib/HttpServerUtil.qm
%requires ../../../../qlib/HttpServer.qm

%try-module yaml >= 0.5
%define NoYaml
%endtry

%try-module xml >= 1.3
%define NoXml
%endtry

%try-module json >= 1.5
%define NoJson
%endtry

%exec-class RestClientDataProviderTest

const VALUE_TEXT = {
    "input": "Lorem Ipsum",
    "output": "Lorem Ipsum",
};
const VALUE_XML = {
    "input": "<foo><bar>1</bar></foo>",
    "output": {"foo": {"bar": "1"}},
};
const VALUE_XMLRPC = {
    "input": "<struct><member><name>foo</name><value><i4>1</i4></value></member></struct>",
    "output": {"foo": 1},
};
const VALUE_YAML = {
    "input": "foo:\n bar: 1\n other: '2017-01-01 00:00:00Z'",
    "output": {"foo": {"bar": 1, "other": 2017-01-01Z}},
};
const VALUE_JSON = {
    "input" : "{\"foo\":{\"bar\":1}}",
    "output" : {"foo" : {"bar" : 1}},
};

class SimpleStringHandler inherits AbstractHttpRequestHandler {
    private {
        *string m_data;
        string m_mime;
    }
    constructor (string mime, *string str) {
        m_mime = mime;
        m_data = str;
    }
    hash<auto> handleRequest(hash<auto> cx, hash<auto> hdr, *data body) {
        if (hdr."do-error") {
            return makeResponse(404, "test error", {"Content-Type": MimeTypeText});
        }
        if (hdr.method == "GET") {
            return makeResponse(200, m_data ?? binary(body), {"Content-Type": m_mime});
        } else if (hdr.method == "PUT") {
            return makeResponse(200, m_data ?? binary(body), {"Content-Type": m_mime});
        } else if (hdr.method == "PATCH") {
            return makeResponse(200, m_data ?? binary(body), {"Content-Type": m_mime});
        } else if (hdr.method == "POST") {
            return makeResponse(200, m_data ?? binary(body), {"Content-Type": m_mime});
        } else if (hdr.method == "DELETE") {
            return makeResponse(404, m_data ?? binary(body), {"Content-Type": m_mime});
        } else if (hdr.method == "OPTIONS") {
            return makeResponse(200, m_data ?? binary(body), {"Content-Type": m_mime});
        }
    }
}

public class RestClientDataProviderTest inherits QUnit::Test {
    private {
        HttpServer m_http;
        int port;
    }

    constructor() : Test("RestClientDataProvider Test", "1.0") {
        addTestCase("test", \httpClientDataProviderTest());

        Logger logger("test", LoggerLevel::getLevelInfo());
        if (m_options.verbose > 2) {
            logger.addAppender(new StdoutAppender());
        }
        hash<HttpServerOptionInfo> http_opts = <HttpServerOptionInfo>{
            "logger": logger,
            "debug": True,
        };
        m_http = new HttpServer(http_opts);
        m_http.setHandler("/api_text", "/api_text", MimeTypeText, new SimpleStringHandler(MimeTypeText, VALUE_TEXT.input));
        m_http.setHandler("/api_xml", "/api_xml", MimeTypeXmlApp, new SimpleStringHandler(MimeTypeXmlApp, VALUE_XML.input));
        m_http.setHandler("/api_rpc", "/api_rpc", MimeTypeXml, new SimpleStringHandler(MimeTypeXml, VALUE_XMLRPC.input));
        m_http.setHandler("/api_json", "/api_json", MimeTypeJson, new SimpleStringHandler(MimeTypeJson, VALUE_JSON.input));
        m_http.setHandler("/api_yaml", "/api_yaml", MimeTypeYaml, new SimpleStringHandler(MimeTypeYaml, VALUE_YAML.input));
        m_http.setHandler("/api_bin", "/api_bin", MimeTypeOctetStream, new SimpleStringHandler(MimeTypeOctetStream));
        port = m_http.addListener(<HttpListenerOptionInfo>{"service": 0}).port;

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    tearDown() {
        delete m_http;
    }

    httpClientDataProviderTest() {
        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            hash<DataProviderSummaryInfo> sinfo = parent.getSummaryInfo();
            assertEq("rest", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            AbstractDataProvider prov = parent.getChildProviderEx("get");
            sinfo = prov.getSummaryInfo();
            assertEq("get", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("get", prov.getName());

            assertThrows("REST-RESPONSE-ERROR", \prov.doRequest(), {"path": "/api_text", "hdr": {"Do-Error": True}});
        }
        {
            RestClientDataProvider parent({"url": "http://localhost:" + port, "error_passthru": True});
            AbstractDataProvider prov = parent.getChildProviderEx("get");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("get", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("get", prov.getName());

            hash<auto> h = prov.doRequest({"path": "/api_text", "hdr": {"Do-Error": True}});
            on_error printf("h: %y\n", h);
            assertEq(404, h."status-code");
            assertEq("test error", h."response-body");
        }

        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            AbstractDataProvider prov = parent.getChildProviderEx("options");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("options", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("options", prov.getName());

            hash<auto> h = prov.doRequest({"path": "/api_bin"});
            assertEq("0", h."response-headers"."content-length");
        }

        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            AbstractDataProvider prov = parent.getChildProviderEx("delete");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("delete", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("delete", prov.getName());
        }

        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            AbstractDataProvider prov = parent.getChildProviderEx("head");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("head", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("head", prov.getName());
        }

        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            AbstractDataProvider prov = parent.getChildProviderEx("patch");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("patch", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("patch", prov.getName());
        }

        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            AbstractDataProvider prov = parent.getChildProviderEx("post");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("post", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("post", prov.getName());
        }

        {
            RestClientDataProvider parent({"url": "http://localhost:" + port});
            AbstractDataProvider prov = parent.getChildProviderEx("put");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("put", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("put", prov.getName());
        }

        {
            RestClientDataProvider parent({
                "url": "http://localhost:" + port,
                "pre_encoded_urls": True,
            });
            AbstractDataProvider prov = parent.getChildProviderEx("put");
            hash<DataProviderSummaryInfo> sinfo = prov.getSummaryInfo();
            assertEq("put", sinfo.name);
            assertEq(Type::String, sinfo.desc.type());
            assertEq("put", prov.getName());
        }
    }
}
