434 lines
12 KiB
C++
434 lines
12 KiB
C++
/**
|
|
* @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());
|
|
}
|
|
}
|