diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index ababfa557..2e067f2bc 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -21,6 +21,7 @@ zcash_gtest_SOURCES += \ gtest/test_tautology.cpp \ gtest/test_deprecation.cpp \ gtest/test_equihash.cpp \ + gtest/test_httprpc.cpp \ gtest/test_joinsplit.cpp \ gtest/test_keystore.cpp \ gtest/test_noteencryption.cpp \ diff --git a/src/gtest/test_httprpc.cpp b/src/gtest/test_httprpc.cpp new file mode 100644 index 000000000..c630973fb --- /dev/null +++ b/src/gtest/test_httprpc.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include "httprpc.cpp" +#include "httpserver.h" + +using ::testing::Return; + +class MockHTTPRequest : public HTTPRequest { +public: + MOCK_METHOD0(GetPeer, CService()); + MOCK_METHOD0(GetRequestMethod, HTTPRequest::RequestMethod()); + MOCK_METHOD1(GetHeader, std::pair(const std::string& hdr)); + MOCK_METHOD2(WriteHeader, void(const std::string& hdr, const std::string& value)); + MOCK_METHOD2(WriteReply, void(int nStatus, const std::string& strReply)); + + MockHTTPRequest() : HTTPRequest(nullptr) {} + void CleanUp() { + // So the parent destructor doesn't try to send a reply + replySent = true; + } +}; + +TEST(HTTPRPC, FailsOnGET) { + MockHTTPRequest req; + EXPECT_CALL(req, GetRequestMethod()) + .WillRepeatedly(Return(HTTPRequest::GET)); + EXPECT_CALL(req, WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests")) + .Times(1); + EXPECT_FALSE(HTTPReq_JSONRPC(&req, "")); + req.CleanUp(); +} + +TEST(HTTPRPC, FailsWithoutAuthHeader) { + MockHTTPRequest req; + EXPECT_CALL(req, GetRequestMethod()) + .WillRepeatedly(Return(HTTPRequest::POST)); + EXPECT_CALL(req, GetHeader("authorization")) + .WillRepeatedly(Return(std::make_pair(false, ""))); + EXPECT_CALL(req, WriteHeader("WWW-Authenticate", "Basic realm=\"jsonrpc\"")) + .Times(1); + EXPECT_CALL(req, WriteReply(HTTP_UNAUTHORIZED, "")) + .Times(1); + EXPECT_FALSE(HTTPReq_JSONRPC(&req, "")); + req.CleanUp(); +} + +TEST(HTTPRPC, FailsWithBadAuth) { + MockHTTPRequest req; + EXPECT_CALL(req, GetRequestMethod()) + .WillRepeatedly(Return(HTTPRequest::POST)); + EXPECT_CALL(req, GetHeader("authorization")) + .WillRepeatedly(Return(std::make_pair(true, "Basic spam:eggs"))); + EXPECT_CALL(req, GetPeer()) + .WillRepeatedly(Return(CService("127.0.0.1:1337"))); + EXPECT_CALL(req, WriteHeader("WWW-Authenticate", "Basic realm=\"jsonrpc\"")) + .Times(1); + EXPECT_CALL(req, WriteReply(HTTP_UNAUTHORIZED, "")) + .Times(1); + EXPECT_FALSE(HTTPReq_JSONRPC(&req, "")); + req.CleanUp(); +} diff --git a/src/httpserver.h b/src/httpserver.h index b377dc19f..347e7c3ab 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -56,11 +56,14 @@ class HTTPRequest { private: struct evhttp_request* req; + + // For test access +protected: bool replySent; public: HTTPRequest(struct evhttp_request* req); - ~HTTPRequest(); + virtual ~HTTPRequest(); enum RequestMethod { UNKNOWN, @@ -76,17 +79,17 @@ public: /** Get CService (address:ip) for the origin of the http request. */ - CService GetPeer(); + virtual CService GetPeer(); /** Get request method. */ - RequestMethod GetRequestMethod(); + virtual RequestMethod GetRequestMethod(); /** * Get the request header specified by hdr, or an empty string. * Return an pair (isPresent,string). */ - std::pair GetHeader(const std::string& hdr); + virtual std::pair GetHeader(const std::string& hdr); /** * Read request body. @@ -101,7 +104,7 @@ public: * * @note call this before calling WriteErrorReply or Reply. */ - void WriteHeader(const std::string& hdr, const std::string& value); + virtual void WriteHeader(const std::string& hdr, const std::string& value); /** * Write HTTP reply. @@ -111,7 +114,7 @@ public: * @note Can be called only once. As this will give the request back to the * main thread, do not call any other HTTPRequest methods after calling this. */ - void WriteReply(int nStatus, const std::string& strReply = ""); + virtual void WriteReply(int nStatus, const std::string& strReply = ""); }; /** Event handler closure.