diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index a4452a05e4..a060d9f4d8 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -4758,6 +4758,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 8fce5671c5..c274f87049 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -5463,6 +5463,9 @@
test\rpc
+
+ test\rpc
+
test\rpc
diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp
new file mode 100644
index 0000000000..29b05ebc38
--- /dev/null
+++ b/src/test/rpc/LedgerRPC_test.cpp
@@ -0,0 +1,301 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012-2016 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+
+class LedgerRPC_test : public beast::unit_test::suite
+{
+ static
+ std::unique_ptr
+ makeNonAdminConfig()
+ {
+ auto p = std::make_unique();
+ test::setupConfigForUnitTests(*p);
+ (*p)["port_rpc"].set("admin","");
+ (*p)["port_ws"].set("admin","");
+ return p;
+ }
+
+ void
+ checkErrorValue(
+ Json::Value const& jv,
+ std::string const& err,
+ std::string const& msg)
+ {
+ if (BEAST_EXPECT(jv.isMember(jss::status)))
+ BEAST_EXPECT(jv[jss::status] == "error");
+ if (BEAST_EXPECT(jv.isMember(jss::error)))
+ BEAST_EXPECT(jv[jss::error] == err);
+ if (msg.empty())
+ {
+ BEAST_EXPECT(
+ jv[jss::error_message] == Json::nullValue ||
+ jv[jss::error_message] == "");
+ }
+ else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
+ BEAST_EXPECT(jv[jss::error_message] == msg);
+ };
+
+ void testLedgerRequest()
+ {
+ testcase("Basic Request");
+ using namespace test::jtx;
+
+ Env env {*this};
+
+ env.close();
+ BEAST_EXPECT(env.current()->info().seq == 4);
+
+ {
+ // in this case, numeric string converted to number
+ auto const jrr = env.rpc("ledger", "1") [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
+ BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
+ BEAST_EXPECT(jrr[jss::ledger][jss::accepted] == true);
+ BEAST_EXPECT(jrr[jss::ledger][jss::totalCoins] == env.balance(env.master).value().getText());
+ }
+
+ {
+ // using current identifier
+ auto const jrr = env.rpc("ledger", "current") [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger][jss::closed] == false);
+ BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == to_string(env.current()->info().seq));
+ BEAST_EXPECT(jrr[jss::ledger_current_index] == env.current()->info().seq);
+ }
+ }
+
+ void testBadInput()
+ {
+ testcase("Bad Input");
+ using namespace test::jtx;
+ Env env { *this };
+ Account const gw { "gateway" };
+ auto const USD = gw["USD"];
+ Account const bob { "bob" };
+
+ env.fund(XRP(10000), gw, bob);
+ env.close();
+ env.trust(USD(1000), bob);
+ env.close();
+
+ {
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = "0"; // NOT an integer
+ auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result];
+ checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
+ }
+
+ {
+ // ask for a bad ledger index
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = 10u;
+ auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result];
+ checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
+ }
+
+ {
+ // unrecognized string arg -- error
+ auto const jrr = env.rpc("ledger", "arbitrary_text") [jss::result];
+ checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
+ }
+
+ }
+
+ void testLedgerCurrent()
+ {
+ testcase("ledger_current Request");
+ using namespace test::jtx;
+
+ Env env {*this};
+
+ env.close();
+ BEAST_EXPECT(env.current()->info().seq == 4);
+
+ {
+ auto const jrr = env.rpc("ledger_current") [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger_current_index] == env.current()->info().seq);
+ }
+ }
+
+ void testAccountRoot()
+ {
+ testcase("Basic Ledger Entry Request");
+ using namespace test::jtx;
+ Env env {*this};
+ Account const alice {"alice"};
+ env.fund(XRP(10000), alice);
+ env.close();
+
+ auto jrr = env.rpc("ledger_closed") [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger_hash] == to_string(env.closed()->info().hash));
+ BEAST_EXPECT(jrr[jss::ledger_index] == 3);
+
+ Json::Value jvParams;
+ jvParams[jss::account_root] = alice.human();
+ jvParams[jss::ledger_hash] = jrr[jss::ledger_hash];
+ jrr = env.rpc ( "json", "ledger_entry", to_string(jvParams) ) [jss::result];
+ BEAST_EXPECT(jrr.isMember(jss::node));
+ BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
+ BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName] == XRP(10000).value().getText());
+ }
+
+ void testLedgerFull()
+ {
+ testcase("Ledger Request, Full Option");
+ using namespace test::jtx;
+
+ Env env {*this};
+
+ env.close();
+
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = 3u;
+ jvParams[jss::full] = true;
+ auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
+ BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
+ BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 2u);
+ }
+
+ void testLedgerFullNonAdmin()
+ {
+ testcase("Ledger Request, Full Option Without Admin");
+ using namespace test::jtx;
+
+ Env env { *this, makeNonAdminConfig() };
+
+ env.close();
+
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = 3u;
+ jvParams[jss::full] = true;
+ auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result];
+ checkErrorValue(jrr, "noPermission", "You don't have permission for this command."); }
+
+ void testLedgerAccounts()
+ {
+ testcase("Ledger Request, Accounts Option");
+ using namespace test::jtx;
+
+ Env env {*this};
+
+ env.close();
+
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = 3u;
+ jvParams[jss::accounts] = true;
+ auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
+ BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
+ BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 2u);
+ }
+
+ void testMalformedAccountRoot()
+ {
+ testcase("Malformed Ledger Entry Request");
+ using namespace test::jtx;
+ Env env {*this};
+ Account const alice {"alice"};
+ env.fund(XRP(10000), alice);
+ env.close();
+
+ auto jrr = env.rpc("ledger_closed") [jss::result];
+
+ Json::Value jvParams;
+ jvParams[jss::account_root] = std::string(alice.human()).replace(0, 2, 2, 'x');
+ jvParams[jss::ledger_hash] = jrr[jss::ledger_hash];
+ jrr = env.rpc ( "json", "ledger_entry", to_string(jvParams) ) [jss::result];
+ checkErrorValue(jrr, "malformedAddress", "");
+ }
+
+ void testNotFoundAccountRoot()
+ {
+ testcase("Ledger Entry Not Found");
+ using namespace test::jtx;
+ Env env {*this};
+ Account const alice {"alice"};
+ env.fund(XRP(10000), alice);
+ env.close();
+
+ auto jrr = env.rpc("ledger_closed") [jss::result];
+
+ Json::Value jvParams;
+ jvParams[jss::account_root] = Account("bob").human();
+ jvParams[jss::ledger_hash] = jrr[jss::ledger_hash];
+ jrr = env.rpc ( "json", "ledger_entry", to_string(jvParams) ) [jss::result];
+ checkErrorValue(jrr, "entryNotFound", "");
+ }
+
+ void testAccountRootFromIndex()
+ {
+ testcase("Ledger Entry Request From Index");
+ using namespace test::jtx;
+ Env env {*this};
+ Account const alice {"alice"};
+ env.fund(XRP(10000), alice);
+ env.close();
+
+ auto jrr = env.rpc("ledger_closed") [jss::result];
+ BEAST_EXPECT(jrr[jss::ledger_hash] == to_string(env.closed()->info().hash));
+ BEAST_EXPECT(jrr[jss::ledger_index] == 3);
+
+ {
+ Json::Value jvParams;
+ jvParams[jss::account_root] = alice.human();
+ jvParams[jss::ledger_hash] = jrr[jss::ledger_hash];
+ jrr = env.rpc ( "json", "ledger_entry", to_string(jvParams) ) [jss::result];
+ }
+ {
+ Json::Value jvParams;
+ jvParams[jss::index] = jrr[jss::index];
+ jrr = env.rpc ( "json", "ledger_entry", to_string(jvParams) ) [jss::result];
+ BEAST_EXPECT(jrr.isMember(jss::node_binary));
+ BEAST_EXPECT(jrr[jss::node_binary] ==
+ "1100612200800000240000000225000000032D00000000554294BEBE5B569"
+ "A18C0A2702387C9B1E7146DC3A5850C1E87204951C6FDAA4C426240000002"
+ "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D");
+ }
+ }
+
+public:
+ void run ()
+ {
+ testLedgerRequest();
+ testBadInput();
+ testLedgerCurrent();
+ testAccountRoot();
+ testLedgerFull();
+ testLedgerFullNonAdmin();
+ testLedgerAccounts();
+ testMalformedAccountRoot();
+ testNotFoundAccountRoot();
+ testAccountRootFromIndex();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(LedgerRPC,app,ripple);
+
+} // ripple
+
diff --git a/src/unity/rpc_test_unity.cpp b/src/unity/rpc_test_unity.cpp
index d71dcc0dde..9b31bbbbfa 100644
--- a/src/unity/rpc_test_unity.cpp
+++ b/src/unity/rpc_test_unity.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include