/**
 * @file lliohttpnode_tut.cpp
 * @date   May 2006
 * @brief HTTP server unit tests
 *
 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "lltut.h"
#include "llhttpnode.h"
#include "llsdhttpserver.h"

namespace tut
{
    struct HTTPNodeTestData
    {
        LLHTTPNode mRoot;
        LLSD mContext;

        const LLSD& context() { return mContext; }

        std::string remainderPath()
        {
            std::ostringstream pathOutput;
            bool addSlash = false;

            LLSD& remainder = mContext[CONTEXT_REQUEST]["remainder"];
            for (LLSD::array_const_iterator i = remainder.beginArray();
                i != remainder.endArray();
                ++i)
            {
                if (addSlash) { pathOutput << '/'; }
                pathOutput << i->asString();
                addSlash = true;
            }

            return pathOutput.str();
        }

        void ensureRootTraversal(const std::string& path,
            const LLHTTPNode* expectedNode,
            const char* expectedRemainder)
        {
            mContext.clear();

            const LLHTTPNode* actualNode = mRoot.traverse(path, mContext);

            ensure_equals("traverse " + path + " node",
                actualNode, expectedNode);
            ensure_equals("traverse " + path + " remainder",
                remainderPath(), expectedRemainder);
        }

        class Response : public LLHTTPNode::Response
        {
        public:
            static LLPointer<Response> create() {return new Response();}

            LLSD mResult;

            void result(const LLSD& result) { mResult = result; }
            void status(S32 code, const std::string& message) { }
            void extendedResult(S32 code, const std::string& message, const LLSD& headers) { }
            void extendedResult(S32 code, const LLSD& result, const LLSD& headers) { }

        private:
            Response() {;} // Must be accessed through LLPointer.
        };

        typedef LLPointer<Response> ResponsePtr;

        LLSD get(const std::string& path)
        {
            mContext.clear();
            const LLHTTPNode* node = mRoot.traverse(path, mContext);
            ensure(path + " found", node != NULL);

            ResponsePtr response = Response::create();
            node->get(LLHTTPNode::ResponsePtr(response), mContext);
            return response->mResult;
        }

        LLSD post(const std::string& path, const LLSD& input)
        {
            mContext.clear();
            const LLHTTPNode* node = mRoot.traverse(path, mContext);
            ensure(path + " found", node != NULL);

            ResponsePtr response = Response::create();
            node->post(LLHTTPNode::ResponsePtr(response), mContext, input);
            return response->mResult;
        }

        void ensureMemberString(const std::string& name,
            const LLSD& actualMap, const std::string& member,
            const std::string& expectedValue)
        {
            ensure_equals(name + " " + member,
                actualMap[member].asString(), expectedValue);
        }


        void ensureInArray(const LLSD& actualArray,
            const std::string& expectedValue)
        {
            LLSD::array_const_iterator i = actualArray.beginArray();
            LLSD::array_const_iterator end = actualArray.endArray();

            for (; i != end; ++i)
            {
                std::string path = i->asString();

                if (path == expectedValue)
                {
                    return;
                }
            }

            fail("didn't find " + expectedValue);
        }

    };

    typedef test_group<HTTPNodeTestData>    HTTPNodeTestGroup;
    typedef HTTPNodeTestGroup::object       HTTPNodeTestObject;
    HTTPNodeTestGroup httpNodeTestGroup("http node");

    template<> template<>
    void HTTPNodeTestObject::test<1>()
    {
        // traversal of the lone node

        ensureRootTraversal("", &mRoot, "");
        ensureRootTraversal("/", &mRoot, "");
        ensureRootTraversal("foo", NULL, "foo");
        ensureRootTraversal("foo/bar", NULL, "foo/bar");

        ensure_equals("root of root", mRoot.rootNode(), &mRoot);
    }

    template<> template<>
    void HTTPNodeTestObject::test<2>()
    {
        // simple traversal of a single node

        LLHTTPNode* helloNode = new LLHTTPNode;
        mRoot.addNode("hello", helloNode);

        ensureRootTraversal("hello", helloNode, "");
        ensureRootTraversal("/hello", helloNode, "");
        ensureRootTraversal("hello/", helloNode, "");
        ensureRootTraversal("/hello/", helloNode, "");

        ensureRootTraversal("hello/there", NULL, "there");

        ensure_equals("root of hello", helloNode->rootNode(), &mRoot);
    }

    template<> template<>
    void HTTPNodeTestObject::test<3>()
    {
        // traversal of mutli-branched tree

        LLHTTPNode* greekNode = new LLHTTPNode;
        LLHTTPNode* alphaNode = new LLHTTPNode;
        LLHTTPNode* betaNode = new LLHTTPNode;
        LLHTTPNode* gammaNode = new LLHTTPNode;

        greekNode->addNode("alpha", alphaNode);
        greekNode->addNode("beta", betaNode);
        greekNode->addNode("gamma", gammaNode);
        mRoot.addNode("greek", greekNode);

        LLHTTPNode* hebrewNode = new LLHTTPNode;
        LLHTTPNode* alephNode = new LLHTTPNode;

        hebrewNode->addNode("aleph", alephNode);
        mRoot.addNode("hebrew", hebrewNode);

        ensureRootTraversal("greek/alpha", alphaNode, "");
        ensureRootTraversal("greek/beta", betaNode, "");
        ensureRootTraversal("greek/delta", NULL, "delta");
        ensureRootTraversal("greek/gamma", gammaNode, "");
        ensureRootTraversal("hebrew/aleph", alephNode, "");

        ensure_equals("root of greek", greekNode->rootNode(), &mRoot);
        ensure_equals("root of alpha", alphaNode->rootNode(), &mRoot);
        ensure_equals("root of beta", betaNode->rootNode(), &mRoot);
        ensure_equals("root of gamma", gammaNode->rootNode(), &mRoot);
        ensure_equals("root of hebrew", hebrewNode->rootNode(), &mRoot);
        ensure_equals("root of aleph", alephNode->rootNode(), &mRoot);
    }

    template<> template<>
    void HTTPNodeTestObject::test<4>()
    {
        // automatic creation of parent nodes and not overriding existing nodes

        LLHTTPNode* alphaNode = new LLHTTPNode;
        LLHTTPNode* betaNode = new LLHTTPNode;
        LLHTTPNode* gammaNode = new LLHTTPNode;
        LLHTTPNode* gamma2Node = new LLHTTPNode;

        mRoot.addNode("greek/alpha", alphaNode);
        mRoot.addNode("greek/beta", betaNode);

        mRoot.addNode("greek/gamma", gammaNode);
        mRoot.addNode("greek/gamma", gamma2Node);

        LLHTTPNode* alephNode = new LLHTTPNode;

        mRoot.addNode("hebrew/aleph", alephNode);

        ensureRootTraversal("greek/alpha", alphaNode, "");
        ensureRootTraversal("greek/beta", betaNode, "");
        ensureRootTraversal("greek/delta", NULL, "delta");
        ensureRootTraversal("greek/gamma", gammaNode, "");
        ensureRootTraversal("hebrew/aleph", alephNode, "");

        ensure_equals("root of alpha", alphaNode->rootNode(), &mRoot);
        ensure_equals("root of beta", betaNode->rootNode(), &mRoot);
        ensure_equals("root of gamma", gammaNode->rootNode(), &mRoot);
        ensure_equals("root of aleph", alephNode->rootNode(), &mRoot);
    }

    class IntegerNode : public LLHTTPNode
    {
    public:
        virtual void get(ResponsePtr response, const LLSD& context) const
        {
            int n = context["extra"]["value"];

            LLSD info;
            info["value"] = n;
            info["positive"] = n > 0;
            info["zero"] = n == 0;
            info["negative"] = n < 0;

            response->result(info);
        }

        virtual bool validate(const std::string& name, LLSD& context) const
        {
            int n;
            std::istringstream i_stream(name);
            i_stream >> n;

            if (i_stream.fail()  ||  i_stream.get() != EOF)
            {
                return false;
            }

            context["extra"]["value"] = n;
            return true;
        }
    };

    class SquareNode : public LLHTTPNode
    {
    public:
        virtual void get(ResponsePtr response, const LLSD& context) const
        {
            int n = context["extra"]["value"];
            response->result(n*n);
        }
    };

    template<> template<>
    void HTTPNodeTestObject::test<5>()
    {
        // wildcard nodes

        LLHTTPNode* miscNode = new LLHTTPNode;
        LLHTTPNode* iNode = new IntegerNode;
        LLHTTPNode* sqNode = new SquareNode;

        mRoot.addNode("test/misc", miscNode);
        mRoot.addNode("test/<int>", iNode);
        mRoot.addNode("test/<int>/square", sqNode);

        ensureRootTraversal("test/42", iNode, "");
        ensure_equals("stored integer",
            context()["extra"]["value"].asInteger(), 42);

        ensureRootTraversal("test/bob", NULL, "bob");
        ensure("nothing stored",
            context()["extra"]["value"].isUndefined());

        ensureRootTraversal("test/3/square", sqNode, "");
        ResponsePtr response = Response::create();
        sqNode->get(LLHTTPNode::ResponsePtr(response), context());
        ensure_equals("square result", response->mResult.asInteger(), 9);
    }

    class AlphaNode : public LLHTTPNode
    {
    public:
        virtual bool handles(const LLSD& remainder, LLSD& context) const
        {
            LLSD::array_const_iterator i = remainder.beginArray();
            LLSD::array_const_iterator end = remainder.endArray();

            for (; i != end; ++i)
            {
                std::string s = i->asString();
                if (s.empty() || s[0] != 'a')
                {
                    return false;
                }
            }

            return true;
        }
    };

    template<> template<>
    void HTTPNodeTestObject::test<6>()
    {
        // nodes that handle remainders

        LLHTTPNode* miscNode = new LLHTTPNode;
        LLHTTPNode* aNode = new AlphaNode;
        LLHTTPNode* zNode = new LLHTTPNode;

        mRoot.addNode("test/misc", miscNode);
        mRoot.addNode("test/alpha", aNode);
        mRoot.addNode("test/alpha/zebra", zNode);

        ensureRootTraversal("test/alpha", aNode, "");
        ensureRootTraversal("test/alpha/abe", aNode, "abe");
        ensureRootTraversal("test/alpha/abe/amy", aNode, "abe/amy");
        ensureRootTraversal("test/alpha/abe/bea", NULL, "abe/bea");
        ensureRootTraversal("test/alpha/bob", NULL, "bob");
        ensureRootTraversal("test/alpha/zebra", zNode, "");
    }

    template<> template<>
    void HTTPNodeTestObject::test<7>()
    {
        // test auto registration

        LLHTTPStandardServices::useServices();
        LLHTTPRegistrar::buildAllServices(mRoot);

        {
            LLSD result = get("web/hello");
            ensure_equals("hello result", result.asString(), "hello");
        }
        {
            LLSD stuff = 3.14159;
            LLSD result = post("web/echo", stuff);
            ensure_equals("echo result", result, stuff);
        }
    }

    template<> template<>
    void HTTPNodeTestObject::test<8>()
    {
        // test introspection

        LLHTTPRegistrar::buildAllServices(mRoot);

        mRoot.addNode("test/misc", new LLHTTPNode);
        mRoot.addNode("test/<int>", new IntegerNode);
        mRoot.addNode("test/<int>/square", new SquareNode);

        const LLSD result = get("web/server/api");

        ensure("result is array", result.isArray());
        ensure("result size", result.size() >= 2);

        ensureInArray(result, "web/echo");
        ensureInArray(result, "web/hello");
        ensureInArray(result, "test/misc");
        ensureInArray(result, "test/<int>");
        ensureInArray(result, "test/<int>/square");
    }

    template<> template<>
    void HTTPNodeTestObject::test<9>()
    {
        // test introspection details

        LLHTTPRegistrar::buildAllServices(mRoot);

        const LLSD helloDetails = get("web/server/api/web/hello");

        ensure_contains("hello description",
            helloDetails["description"].asString(), "hello");
        ensure_equals("method name", helloDetails["api"][0].asString(), std::string("GET"));
        ensureMemberString("hello", helloDetails, "output", "\"hello\"");
        ensure_contains("hello __file__",
            helloDetails["__file__"].asString(), "llsdhttpserver.cpp");
        ensure("hello line", helloDetails["__line__"].isInteger());


        const LLSD echoDetails = get("web/server/api/web/echo");

        ensure_contains("echo description",
            echoDetails["description"].asString(), "echo");
        ensure_equals("method name", echoDetails["api"][0].asString(), std::string("POST"));
        ensureMemberString("echo", echoDetails, "input", "<any>");
        ensureMemberString("echo", echoDetails, "output", "<the input>");
        ensure_contains("echo __file__",
            echoDetails["__file__"].asString(), "llsdhttpserver.cpp");
        ensure("echo", echoDetails["__line__"].isInteger());
    }
}