/** * @file lliohttpnode_tut.cpp * @date May 2006 * @brief HTTP server unit tests * * $LicenseInfo:firstyear=2006&license=viewergpl$ * * Copyright (c) 2006-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/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["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) { } 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()); } }