RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
core/banker/testing/banker_account_test.cc
00001 /* banker_account_test.cc
00002    Jeremy Barnes, 16 November 2012
00003    Copyright (c) 2012 Datacratic.  All rights reserved.
00004 
00005    Test for Banker accounts.
00006 */
00007 
00008 #define BOOST_TEST_MAIN
00009 #define BOOST_TEST_DYN_LINK
00010 
00011 #include <boost/test/unit_test.hpp>
00012 #include "jml/arch/format.h"
00013 #include "jml/arch/exception_handler.h"
00014 #include "jml/utils/guard.h"
00015 #include "rtbkit/common/account_key.h"
00016 #include "rtbkit/core/banker/account.h"
00017 #include "jml/utils/environment.h"
00018 #include <boost/thread/thread.hpp>
00019 #include "jml/arch/atomic_ops.h"
00020 #include "jml/arch/timers.h"
00021 #include "jml/utils/ring_buffer.h"
00022 
00023 
00024 using namespace std;
00025 using namespace ML;
00026 using namespace Datacratic;
00027 using namespace RTBKIT;
00028 
00029 BOOST_AUTO_TEST_CASE( test_account_set_budget )
00030 {
00031     Account account;
00032 
00033     /* set initial budget */
00034     account.setBudget(USD(8));
00035     BOOST_CHECK_EQUAL(account.balance, USD(8));
00036     BOOST_CHECK_EQUAL(account.budgetIncreases, USD(8));
00037     BOOST_CHECK_EQUAL(account.budgetDecreases, USD(0));
00038 
00039     /* adjust budget down:
00040        1 usd added to adjustmentsOut (deduced from budget) */
00041     account.setBudget(USD(7));
00042     BOOST_CHECK_EQUAL(account.balance, USD(7));
00043     BOOST_CHECK_EQUAL(account.budgetIncreases, USD(8));
00044     BOOST_CHECK_EQUAL(account.budgetDecreases, USD(1));
00045 
00046     /* adjust budget up:
00047        1 usd added to adjustmentsIn to balance with adj.Out */
00048     account.setBudget(USD(8));
00049     BOOST_CHECK_EQUAL(account.balance, USD(8));
00050     BOOST_CHECK_EQUAL(account.budgetIncreases, USD(9));
00051     BOOST_CHECK_EQUAL(account.budgetDecreases, USD(1));
00052 
00053     /* adjust budget up:
00054        3 usd put in budget */
00055     account.setBudget(USD(13));
00056     BOOST_CHECK_EQUAL(account.balance, USD(13));
00057     BOOST_CHECK_EQUAL(account.budgetIncreases, USD(14));
00058     BOOST_CHECK_EQUAL(account.budgetDecreases, USD(1));
00059 
00060     /* negative adjustments must be limited by "available":
00061        of the previous 13 usd budget, 10 already have been spent, which means
00062        we cannot go below 10 USD, even though 3 USD are still available
00063      */
00064     account.allocatedOut = USD(10);
00065     account.balance = USD(3);
00066     account.checkInvariants();
00067     {
00068         auto notrace = Set_Trace_Exceptions(false);
00069         BOOST_CHECK_THROW(account.setBudget(USD(9)),
00070                           std::exception);
00071     }
00072 
00073     /* we adjust the budget down the the least possible value and ensure that
00074        "available" is adjusted by taking the "allocatedOut" into account */
00075     account.setBudget(USD(10));
00076     BOOST_CHECK_EQUAL(account.balance, USD(0));
00077 }
00078 
00079 BOOST_AUTO_TEST_CASE( test_account_tojson )
00080 {
00081     Account account;
00082 
00083     Json::Value testState = Json::parse(
00084         "{ 'md':  { 'objectType': 'Account',"
00085         "           'version': 1 },"
00086         "  'type': 'none',"
00087         "  'budgetIncreases': {},"
00088         "  'budgetDecreases': {},"
00089         "  'spent': {},"
00090         "  'recycledIn': {},"
00091         "  'recycledOut': {},"
00092         "  'allocatedIn': {},"
00093         "  'allocatedOut': {},"
00094         "  'commitmentsMade': {},"
00095         "  'commitmentsRetired': {},"
00096         "  'adjustmentsIn': {},"
00097         "  'adjustmentsOut': {},"
00098         "  'lineItems': {},"
00099         "  'adjustmentLineItems': {}}");
00100 
00101     /* fresh and clean account */
00102     BOOST_CHECK_EQUAL(account.toJson(), testState);
00103 
00104     /* account with a 10 USD budget */
00105     account.setBudget(USD(10));
00106     testState["budgetIncreases"]["USD/1M"] = 10000000;
00107     BOOST_CHECK_EQUAL(account.toJson(), testState);
00108 }
00109 
00110 BOOST_AUTO_TEST_CASE( test_account_hierarchy )
00111 {
00112     Account budgetAccount;
00113     budgetAccount.setBudget(USD(10));
00114 
00115     Account commitmentAccount, spendAccount;
00116 
00117     ShadowAccount shadowCommitmentAccount;
00118     ShadowAccount shadowSpendAccount;
00119 
00120     commitmentAccount.setBalance(budgetAccount, USD(2));
00121 
00122     BOOST_CHECK_EQUAL(budgetAccount.balance, USD(8));
00123     BOOST_CHECK_EQUAL(commitmentAccount.balance, USD(2));
00124 
00125     shadowCommitmentAccount.syncFromMaster(commitmentAccount);
00126     shadowSpendAccount.syncFromMaster(spendAccount);
00127 
00128     BOOST_CHECK_EQUAL(shadowCommitmentAccount.balance, USD(2));
00129     BOOST_CHECK_EQUAL(shadowSpendAccount.balance, USD(0));
00130 
00131 
00132     auto doBidding = [&] ()
00133         {
00134             bool auth1 = shadowCommitmentAccount.authorizeBid("ad1", USD(1));
00135             bool auth2 = shadowCommitmentAccount.authorizeBid("ad2", USD(1));
00136             bool auth3 = shadowCommitmentAccount.authorizeBid("ad3", USD(1));
00137 
00138             BOOST_CHECK_EQUAL(auth1, true);
00139             BOOST_CHECK_EQUAL(auth2, true);
00140             BOOST_CHECK_EQUAL(auth3, false);
00141     
00142             Amount detached = shadowCommitmentAccount.detachBid("ad1");
00143             BOOST_CHECK_EQUAL(detached, USD(1));
00144 
00145             shadowCommitmentAccount.cancelBid("ad2");
00146 
00147             shadowSpendAccount.commitDetachedBid(detached, USD(0.50), LineItems());
00148 
00149             shadowCommitmentAccount.syncToMaster(commitmentAccount);
00150             shadowSpendAccount.syncToMaster(spendAccount);
00151         };
00152 
00153     // Do the same kind of bid 5 times
00154     for (unsigned i = 0;  i < 5;  ++i) {
00155 
00156         doBidding();
00157 
00158         cerr << "budget" << budgetAccount << endl;
00159         cerr << "spend " << spendAccount << endl;
00160         cerr << "commitment " << commitmentAccount << endl;
00161         cerr << "shadow spend" << shadowSpendAccount << endl;
00162         cerr << "shadow commitment" << shadowCommitmentAccount << endl;
00163 
00164         spendAccount.recuperateTo(budgetAccount);
00165 
00166         cerr << "after recuperation" << endl;
00167         cerr << "budget" << budgetAccount << endl;
00168         cerr << "spend " << spendAccount << endl;
00169    
00170         commitmentAccount.setBalance(budgetAccount, USD(2));
00171         
00172         cerr << "after setBalance" << endl;
00173         cerr << "budget" << budgetAccount << endl;
00174         cerr << "spend " << spendAccount << endl;
00175         cerr << "commitment " << commitmentAccount << endl;
00176 
00177         shadowCommitmentAccount.syncFromMaster(commitmentAccount);
00178         shadowSpendAccount.syncFromMaster(spendAccount);
00179 
00180         cerr << "after sync" << endl;
00181         cerr << "shadow spend" << shadowSpendAccount << endl;
00182         cerr << "shadow commitment" << shadowCommitmentAccount << endl;
00183 
00184         BOOST_CHECK_EQUAL(commitmentAccount.balance, USD(2));
00185         BOOST_CHECK_EQUAL(shadowCommitmentAccount.balance, USD(2));
00186         BOOST_CHECK_EQUAL(spendAccount.balance, USD(0));
00187         BOOST_CHECK_EQUAL(shadowSpendAccount.balance, USD(0));
00188     }
00189 }
00190 
00191 BOOST_AUTO_TEST_CASE( test_account_recycling )
00192 {
00193     Accounts accounts;
00194 
00195     AccountKey campaign("campaign");
00196     AccountKey strategy("campaign:strategy");
00197     AccountKey strategy2("campaign:strategy2");
00198     AccountKey spend("campaign:strategy:spend");
00199     AccountKey spend2("campaign:strategy2:spend");
00200 
00201     accounts.createBudgetAccount(campaign);
00202     accounts.createBudgetAccount(strategy);
00203     accounts.createBudgetAccount(strategy2);
00204     accounts.createSpendAccount(spend);
00205     accounts.createSpendAccount(spend2);
00206 
00207     // Top level budget of $10
00208     accounts.setBudget(campaign, USD(10));
00209 
00210     // Make $2 available in the strategy account
00211     accounts.setBalance(strategy, USD(2), AT_NONE);
00212     accounts.setBalance(strategy2, USD(2), AT_NONE);
00213     
00214     BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(6));
00215     BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(2));
00216     BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2));
00217 
00218     accounts.setBalance(spend, USD(1), AT_NONE);
00219     //accounts.setBalance(spend2, USD(1), AT_NONE);
00220 
00221     BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(6));
00222     BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(1));
00223     BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2));
00224     BOOST_CHECK_EQUAL(accounts.getBalance(spend), USD(1));
00225     BOOST_CHECK_EQUAL(accounts.getBalance(spend2), USD(0));
00226 
00227     accounts.setBalance(spend, USD(1), AT_NONE);
00228     //accounts.setBalance(spend2, USD(1), AT_NONE);
00229 
00230     BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(6));
00231     BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(1));
00232     BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2));
00233     BOOST_CHECK_EQUAL(accounts.getBalance(spend), USD(1));
00234     BOOST_CHECK_EQUAL(accounts.getBalance(spend2), USD(0));
00235 
00236     accounts.setBalance(strategy, USD(2), AT_NONE);
00237     //accounts.setBalance(strategy2, USD(2), AT_NONE);
00238 
00239     BOOST_CHECK_EQUAL(accounts.getBalance(campaign), USD(5));
00240     BOOST_CHECK_EQUAL(accounts.getBalance(strategy), USD(2));
00241     BOOST_CHECK_EQUAL(accounts.getBalance(strategy2), USD(2));
00242     BOOST_CHECK_EQUAL(accounts.getBalance(spend), USD(1));
00243     BOOST_CHECK_EQUAL(accounts.getBalance(spend2), USD(0));
00244 }
00245 
00246 BOOST_AUTO_TEST_CASE( test_accounts )
00247 {
00248     Accounts accounts;
00249 
00250     AccountKey budget("budget");
00251     AccountKey commitment("budget:commitment");
00252     AccountKey spend("budget:spend");
00253 
00254     ShadowAccounts shadow;
00255 
00256     accounts.createBudgetAccount(budget);
00257     accounts.createSpendAccount(commitment);
00258     accounts.createSpendAccount(spend);
00259 
00260     // Top level budget of $10
00261     accounts.setBudget(budget, USD(10));
00262 
00263     // Make $2 available in the commitment account
00264     accounts.setBalance(commitment, USD(2), AT_SPEND);
00265     
00266     BOOST_CHECK_EQUAL(accounts.getBalance(budget), USD(8));
00267     BOOST_CHECK_EQUAL(accounts.getBalance(commitment), USD(2));
00268 
00269     shadow.activateAccount(commitment);
00270     shadow.activateAccount(spend);
00271 
00272     auto doBidding = [&] ()
00273         {
00274             shadow.syncFrom(accounts);
00275 
00276             bool auth1 = shadow.authorizeBid(commitment, "ad1", USD(1));
00277             bool auth2 = shadow.authorizeBid(commitment, "ad2", USD(1));
00278             bool auth3 = shadow.authorizeBid(commitment, "ad3", USD(1));
00279 
00280             BOOST_CHECK_EQUAL(auth1, true);
00281             BOOST_CHECK_EQUAL(auth2, true);
00282             BOOST_CHECK_EQUAL(auth3, false);
00283 
00284             shadow.checkInvariants();
00285 
00286             Amount detached = shadow.detachBid(commitment, "ad1");
00287             BOOST_CHECK_EQUAL(detached, USD(1));
00288 
00289             shadow.checkInvariants();
00290 
00291             shadow.cancelBid(commitment, "ad2");
00292 
00293             shadow.checkInvariants();
00294 
00295             shadow.commitDetachedBid(spend, detached, USD(0.50), LineItems());
00296 
00297             shadow.syncTo(accounts);
00298 
00299             accounts.checkInvariants();
00300 
00301             cerr << accounts.getAccountSummary(budget) << endl;
00302     
00303         };
00304 
00305     // Do the same kind of bid 5 times
00306     for (unsigned i = 0;  i < 5;  ++i) {
00307 
00308         cerr << accounts.getAccountSummary(budget) << endl;
00309         cerr << accounts.getAccount(budget) << endl;
00310         cerr << accounts.getAccount(commitment) << endl;
00311         cerr << accounts.getAccount(spend) << endl;
00312 
00313         doBidding();
00314 
00315         //cerr << "budget" << budgetAccount << endl;
00316         //cerr << "spend " << spendAccount << endl;
00317         //cerr << "commitment " << commitmentAccount << endl;
00318     
00319         accounts.recuperate(spend);
00320 
00321         accounts.checkInvariants();
00322 
00323         //cerr << "after recuperation" << endl;
00324         //cerr << "budget" << budgetAccount << endl;
00325         //cerr << "spend " << spendAccount << endl;
00326    
00327         accounts.setBalance(commitment, USD(2), AT_SPEND);
00328         
00329         accounts.checkInvariants();
00330 
00331         //cerr << "after setBalance" << endl;
00332         //cerr << "budget" << budgetAccount << endl;
00333         //cerr << "spend " << spendAccount << endl;
00334         //cerr << "commitment " << commitmentAccount << endl;
00335     }
00336 
00337     cerr << accounts.getAccountSummary(budget) << endl;
00338 }
00339 
00340 BOOST_AUTO_TEST_CASE( test_multiple_bidder_threads )
00341 {
00342     Accounts master;
00343 
00344     AccountKey campaign("campaign");
00345     AccountKey strategy("campaign:strategy");
00346 
00347     // Create a budget for the campaign
00348     master.createBudgetAccount(strategy);
00349     master.setBudget(campaign, USD(10));
00350 
00351     // Do 1,000 topup transfers of one micro
00352 
00353     int nTopupThreads = 2;
00354     int nAddBudgetThreads = 2;
00355     int nBidThreads = 2; 
00356     //int nSpendThreads = 2;
00357     int numTransfersPerThread = 10000;
00358     int numAddBudgetsPerThread = 10;
00359 
00360     volatile bool finished = false;
00361 
00362     auto runTopupThread = [&] ()
00363         {
00364             while (!finished) {
00365                 master.setBalance(strategy, USD(0.10), AT_BUDGET);
00366             }
00367         };
00368 
00369     auto runAddBudgetThread = [&] ()
00370         {
00371             for (unsigned i = 0;  i < numAddBudgetsPerThread;  ++i) {
00372                 
00373                 AccountSummary summary = master.getAccountSummary(campaign);
00374                 cerr << summary << endl;
00375                 master.setBudget(campaign, summary.budget + USD(1));
00376 
00377                 ML::sleep(1.0);
00378             }
00379         };
00380 
00381     uint64_t numBidsCommitted = 0;
00382 
00383     ML::RingBufferSRMW<Amount> toCommitThread(1000000);
00384     
00385 
00386     auto runBidThread = [&] (int threadNum)
00387         {
00388             ShadowAccounts shadow;
00389             AccountKey account = strategy;
00390             account.push_back("bid" + to_string(threadNum));
00391 
00392             master.createSpendAccount(account);
00393             shadow.activateAccount(account);
00394             shadow.syncFrom(master);
00395 
00396             int done = 0;
00397             for (;  !finished;  ++done) {
00398                 string item = "item";
00399 
00400                 // Every little bit, do a sync and a re-up
00401                 if (done && done % 1000 == 0) {
00402                     shadow.syncTo(master);
00403                     master.setBalance(account, USD(0.10), AT_NONE);
00404                     shadow.syncFrom(master);
00405                     //cerr << "done " << done << " bids" << endl;
00406                 }
00407                 
00408                 // Authorize 10
00409                 if (!shadow.authorizeBid(account, item, MicroUSD(1))) {
00410                     continue;
00411                 }
00412 
00413                 // In half of the cases, we cancel.  In the other half, we
00414                 // transfer it off to the commit thread
00415 
00416                 if (done % 2 == 0) {
00417                     // Commit 1
00418                     shadow.commitBid(account, item, MicroUSD(1), LineItems());
00419                     ML::atomic_inc(numBidsCommitted);
00420                 }
00421                 else {
00422                     Amount amount = shadow.detachBid(account, item);
00423                     toCommitThread.push(amount);
00424                 }
00425             }
00426 
00427             shadow.sync(master);
00428 
00429             cerr << "finished shadow account with "
00430                  << done << " bids" << endl;
00431             cerr << master.getAccount(account) << endl;
00432 
00433         };
00434 
00435     auto runCommitThread = [&] (int threadNum)
00436         {
00437             ShadowAccounts shadow;
00438             AccountKey account = strategy;
00439             account.push_back("commit" + to_string(threadNum));
00440 
00441             master.createSpendAccount(account);
00442             shadow.activateAccount(account);
00443             shadow.syncFrom(master);
00444 
00445             while (!finished || toCommitThread.couldPop()) {
00446                 Amount amount;
00447                 if (toCommitThread.tryPop(amount, 0.1)) {
00448                     shadow.commitDetachedBid(account, amount, MicroUSD(1), LineItems());
00449                     ML::atomic_inc(numBidsCommitted);
00450                 }
00451                 shadow.syncTo(master);
00452             }
00453 
00454             shadow.syncTo(master);
00455             cerr << "done commit thread" << endl;
00456         };
00457 
00458     boost::thread_group budgetThreads;
00459 
00460     for (unsigned i = 0;  i < nAddBudgetThreads;  ++i)
00461         budgetThreads.create_thread(runAddBudgetThread);
00462 
00463     boost::thread_group bidThreads;
00464     for (unsigned i = 0;  i < nBidThreads;  ++i)
00465         bidThreads.create_thread(std::bind(runBidThread, i));
00466 
00467     for (unsigned i = 0;  i < nTopupThreads;  ++i)
00468         bidThreads.create_thread(runTopupThread);
00469 
00470     bidThreads.create_thread(std::bind(runCommitThread, 0));
00471     
00472 
00473     budgetThreads.join_all();
00474 
00475     finished = true;
00476 
00477     bidThreads.join_all();
00478 
00479     uint32_t amountAdded       = nAddBudgetThreads * numAddBudgetsPerThread;
00480     uint32_t amountTransferred = nTopupThreads * numTransfersPerThread;
00481 
00482     cerr << "numBidsCommitted = "  << numBidsCommitted << endl;
00483     cerr << "amountTransferred = " << amountTransferred << endl;
00484     cerr << "amountAdded =       " << amountAdded << endl;
00485 
00486     cerr << "campaign" << endl;
00487     cerr << master.getAccountSummary(campaign) << endl;
00488     cerr << master.getAccount(campaign) << endl; 
00489 
00490     cerr << "strategy" << endl;
00491     cerr << master.getAccountSummary(strategy) << endl;
00492     cerr << master.getAccount(strategy) << endl; 
00493 
00494 
00495 #if 0    
00496     RedisBanker banker("bankerTest", "b", s, redis);
00497     banker.sync();
00498     Json::Value status = banker.getCampaignStatusJson("testCampaign", "");
00499 
00500     cerr << status << endl;
00501 
00502 
00503 
00504 
00505     BOOST_CHECK_EQUAL(status["available"]["micro-USD"].asInt(), 1000000 - amountTransferred + amountAdded);
00506     BOOST_CHECK_EQUAL(status["strategies"][0]["available"]["micro-USD"].asInt(),
00507                       amountTransferred - numBidsCommitted);
00508     BOOST_CHECK_EQUAL(status["strategies"][0]["transferred"]["micro-USD"].asInt(),
00509                       amountTransferred);
00510     BOOST_CHECK_EQUAL(status["strategies"][0]["spent"]["micro-USD"].asInt(),
00511                       numBidsCommitted);
00512     BOOST_CHECK_EQUAL(status["spent"]["micro-USD"].asInt(), numBidsCommitted);
00513 
00514     //BOOST_CHECK_EQUAL(status["available"].
00515 #endif
00516 }
00517 
00518 BOOST_AUTO_TEST_CASE( test_recycling )
00519 {
00520     Accounts accounts;
00521     Account t, s, sp; /* "top", "sub" and "spend" */
00522 
00523     /* setup */
00524     accounts.createAccount({"t"}, AT_BUDGET);
00525     accounts.setBudget({"t"}, USD(666));
00526 
00527     accounts.setBalance({"t", "s"}, USD(10), AT_BUDGET);
00528 
00529     // s.setBalance(10)
00530     // -> as.budgetIncrease increased by 10
00531     s = accounts.getAccount({"t", "s"});
00532     BOOST_CHECK_EQUAL(s.budgetIncreases, USD(10));
00533 
00534     // s.setBalance(7)
00535     // -> t.recycledIn increased by 3 (total: 3)
00536     // and s.recycledOut increased by 3 (total: 3)
00537     accounts.setBalance({"t", "s"}, USD(7), AT_NONE);
00538     t = accounts.getAccount({"t"});
00539     BOOST_CHECK_EQUAL(t.recycledIn, USD(3));
00540     s = accounts.getAccount({"t", "s"});
00541     BOOST_CHECK_EQUAL(s.recycledOut, USD(3));
00542 
00543     // s.setBalance(8)
00544     // -> t.recycledOut increased by 1 (total: 1)
00545     // and s.recycledIn increased by 1 (total: 1)
00546     accounts.setBalance({"t", "s"}, USD(8), AT_NONE);
00547     t = accounts.getAccount({"t"});
00548     BOOST_CHECK_EQUAL(t.recycledOut, USD(1));
00549     s = accounts.getAccount({"t", "s"});
00550     BOOST_CHECK_EQUAL(s.recycledIn, USD(1));
00551 
00552     // sp.setBalance(5)
00553     // -> sp.budgetIncrease increased by 5 (total: 5),
00554     //    s.allocatedOut increased by 5 (total: 5)
00555     accounts.setBalance({"t", "s", "sp"}, USD(5), AT_SPEND);
00556     sp = accounts.getAccount({"t", "s", "sp"});
00557     BOOST_CHECK_EQUAL(sp.budgetIncreases, USD(5));
00558     s = accounts.getAccount({"t", "s"});
00559     BOOST_CHECK_EQUAL(s.allocatedOut, USD(5));
00560 
00561     /* mixup */
00562     // sp.setBalance(4)
00563     // -> sp.recycledOut by 1 (total: 1),
00564     // s.recycledIn increased by 1 (total: 2)
00565     accounts.setBalance({"t", "s", "sp"}, USD(4), AT_NONE);
00566     sp = accounts.getAccount({"t", "s", "sp"});
00567     BOOST_CHECK_EQUAL(sp.recycledOut, USD(1));
00568     s = accounts.getAccount({"t", "s"});
00569     BOOST_CHECK_EQUAL(s.recycledIn, USD(2));
00570 }
00571 
00572 BOOST_AUTO_TEST_CASE( test_getRecycledUp )
00573 {
00574     Accounts accounts;
00575     CurrencyPool recycledIn, recycledOut;
00576 
00577     /* setup */
00578     accounts.createAccount({"t"}, AT_BUDGET);
00579     accounts.setBudget({"t"}, USD(666));
00580 
00581     // s.setBalance(10)
00582     accounts.setBalance({"t", "s"}, USD(10), AT_BUDGET);
00583     accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut);
00584     BOOST_CHECK_EQUAL(recycledIn, USD(0));
00585     BOOST_CHECK_EQUAL(recycledOut, USD(0));
00586     
00587     // s.setBalance(7)
00588     // t.recycledIn == 3 but t.recycledIn(up) == 0
00589     // s.recycledOut(up) == 3
00590     accounts.setBalance({"t", "s"}, USD(7), AT_NONE);
00591     accounts.getRecycledUp({"t"}, recycledIn, recycledOut);
00592     BOOST_CHECK_EQUAL(recycledIn, USD(0));
00593     accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut);
00594     BOOST_CHECK_EQUAL(recycledOut, USD(3));
00595 
00596     // s.setBalance(8)
00597     // t.recycledOut == 1 but t.recycledOut(up) == 0
00598     // s.recycledIn == 1 and s.recycledIn(up) == 1
00599     accounts.setBalance({"t", "s"}, USD(8), AT_NONE);
00600     accounts.getRecycledUp({"t"}, recycledIn, recycledOut);
00601     BOOST_CHECK_EQUAL(recycledOut, USD(0));
00602     accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut);
00603     BOOST_CHECK_EQUAL(recycledIn, USD(1));
00604 
00605     // sp.setBalance(5)
00606     // sp.recycleX untouched and sp.budgetIncreases increased
00607     accounts.setBalance({"t", "s", "sp"}, USD(5), AT_SPEND);
00608     accounts.getRecycledUp({"t", "s", "sp"}, recycledIn, recycledOut);
00609     BOOST_CHECK_EQUAL(recycledIn, USD(0));
00610     BOOST_CHECK_EQUAL(recycledOut, USD(0));
00611 
00612     // sp.setBalance(4)
00613     // s.recycledIn == 2 but s.recycledIn(up) == 1
00614     // sp.recycledOut == 1 and sp.recycledOut(up) == 1
00615     accounts.setBalance({"t", "s", "sp"}, USD(4), AT_NONE);
00616     accounts.getRecycledUp({"t", "s"}, recycledIn, recycledOut);
00617     BOOST_CHECK_EQUAL(recycledIn, USD(1));
00618     accounts.getRecycledUp({"t", "s", "sp"}, recycledIn, recycledOut);
00619     BOOST_CHECK_EQUAL(recycledOut, USD(1));
00620 }
00621 
00622 /* ensure values of simple account summaries matches those in non-simple
00623    ones */
00624 BOOST_AUTO_TEST_CASE( test_account_simple_summaries )
00625 {
00626     Accounts accounts;
00627 
00628     /* NOTE: the accounts are not particularly consistent with one another */
00629     Json::Value jsonValue
00630         = Json::parse("{'adjustmentLineItems':{},"
00631                       "'adjustmentsIn':{},"
00632                       "'adjustmentsOut':{},"
00633                       "'allocatedIn':{},"
00634                       "'allocatedOut':{'USD/1M':46571708796},"
00635                       "'budgetDecreases':{},"
00636                       "'budgetIncreases':{'USD/1M':52947000000},"
00637                       "'commitmentsMade':{},"
00638                       "'commitmentsRetired':{},"
00639                       "'lineItems':{},"
00640                       "'md':{'objectType':'Account','version':1},"
00641                       "'recycledIn':{},"
00642                       "'recycledOut':{},"
00643                       "'spent':{},"
00644                       "'type':'budget'}");
00645     accounts.restoreAccount({"top"}, jsonValue);
00646     jsonValue = Json::parse("{'adjustmentLineItems':{},"
00647                             "'adjustmentsIn':{},"
00648                             "'adjustmentsOut':{},"
00649                             "'allocatedIn':{},"
00650                             "'allocatedOut':{'USD/1M':582053135},"
00651                             "'budgetDecreases':{},"
00652                             "'budgetIncreases':{'USD/1M':614502770},"
00653                             "'commitmentsMade':{},"
00654                             "'commitmentsRetired':{},"
00655                             "'lineItems':{},"
00656                             "'md':{'objectType':'Account','version':1},"
00657                             "'recycledIn':{},"
00658                             "'recycledOut':{},"
00659                             "'spent':{},"
00660                             "'type':'budget'}");
00661     accounts.restoreAccount({"top", "sub"}, jsonValue);
00662     jsonValue = Json::parse("{'adjustmentLineItems':{},"
00663                             "'adjustmentsIn':{},"
00664                             "'adjustmentsOut':{},"
00665                             "'allocatedIn':{},"
00666                             "'allocatedOut':{},"
00667                             "'budgetDecreases':{},"
00668                             "'budgetIncreases':{'USD/1M':582053135},"
00669                             "'commitmentsMade':{},"
00670                             "'commitmentsRetired':{},"
00671                             "'lineItems':{},"
00672                             "'md':{'objectType':'Account','version':1},"
00673                             "'recycledIn':{},"
00674                             "'recycledOut':{},"
00675                             "'spent':{'USD/1M':582053135},"
00676                             "'type':'spend'}");
00677     accounts.restoreAccount({"top", "sub", "spent"}, jsonValue);
00678 
00679     vector<string> aNames = { "top", "top:sub", "top:sub:spent" };
00680     for (const string & aName: aNames) {
00681         AccountSummary accountS
00682             = accounts.getAccountSummary(aName);
00683         Json::Value summary = accountS.toJson();
00684         BOOST_CHECK_EQUAL(summary["md"]["objectType"].asString(),
00685                           "AccountSummary");
00686         BOOST_CHECK_EQUAL(summary["md"]["version"].asInt(), 1);
00687 
00688         Json::Value simpleSummary = accountS.toJson(true);
00689         BOOST_CHECK_EQUAL(simpleSummary["md"]["objectType"].asString(),
00690                           "AccountSimpleSummary");
00691         BOOST_CHECK_EQUAL(simpleSummary["md"]["version"].asInt(), 1);
00692         vector<string> keys = {"budget", "spend", "available", "inFlight"};
00693         for (const string & key: keys) {
00694             BOOST_CHECK_EQUAL(summary[key], simpleSummary[key]);
00695         }
00696     }
00697 }
00698 
00699 /* ensure values of account summaries, both normal and simple, matches the
00700  * values of the account tree they represent */
00701 BOOST_AUTO_TEST_CASE( test_account_summary_values )
00702 {
00703     Accounts accounts;
00704 
00705     /* NOTE: the accounts are not particularly consistent with one another */
00706     Json::Value jsonValue
00707         = Json::parse("{'adjustmentLineItems':{},"
00708                       "'adjustmentsIn':{},"
00709                       "'adjustmentsOut':{},"
00710                       "'allocatedIn':{},"
00711                       "'allocatedOut':{'USD/1M':46571708796},"
00712                       "'budgetDecreases':{},"
00713                       "'budgetIncreases':{'USD/1M':52947000000},"
00714                       "'commitmentsMade':{},"
00715                       "'commitmentsRetired':{},"
00716                       "'lineItems':{},"
00717                       "'md':{'objectType':'Account','version':1},"
00718                       "'recycledIn':{},"
00719                       "'recycledOut':{},"
00720                       "'spent':{},"
00721                       "'type':'budget'}");
00722     accounts.restoreAccount({"top"}, jsonValue);
00723     jsonValue = Json::parse("{'adjustmentLineItems':{},"
00724                             "'adjustmentsIn':{},"
00725                             "'adjustmentsOut':{},"
00726                             "'allocatedIn':{},"
00727                             "'allocatedOut':{'USD/1M':582053135},"
00728                             "'budgetDecreases':{},"
00729                             "'budgetIncreases':{'USD/1M':614502770},"
00730                             "'commitmentsMade':{},"
00731                             "'commitmentsRetired':{},"
00732                             "'lineItems':{},"
00733                             "'md':{'objectType':'Account','version':1},"
00734                             "'recycledIn':{},"
00735                             "'recycledOut':{},"
00736                             "'spent':{},"
00737                             "'type':'budget'}");
00738     accounts.restoreAccount({"top", "sub"}, jsonValue);
00739     jsonValue = Json::parse("{'adjustmentLineItems':{},"
00740                             "'adjustmentsIn':{},"
00741                             "'adjustmentsOut':{},"
00742                             "'allocatedIn':{},"
00743                             "'allocatedOut':{},"
00744                             "'budgetDecreases':{},"
00745                             "'budgetIncreases':{'USD/1M':582053135},"
00746                             "'commitmentsMade':{},"
00747                             "'commitmentsRetired':{},"
00748                             "'lineItems':{},"
00749                             "'md':{'objectType':'Account','version':1},"
00750                             "'recycledIn':{},"
00751                             "'recycledOut':{},"
00752                             "'spent':{'USD/1M':582053135},"
00753                             "'type':'spend'}");
00754     accounts.restoreAccount({"top", "sub", "spent"}, jsonValue);
00755 
00756     AccountSummary summary = accounts.getAccountSummary({"top"}, 0);
00757     Json::Value normalValue = summary.toJson(false);
00758     Json::Value expected
00759         = Json::parse("{'account':"
00760                       " {'adjustmentLineItems':{},"
00761                       "  'adjustmentsIn':{},"
00762                       "  'adjustmentsOut':{},"
00763                       "  'allocatedIn':{},"
00764                       "  'allocatedOut': {'USD/1M':46571708796},"
00765                       "  'budgetDecreases':{},"
00766                       "  'budgetIncreases': {'USD/1M':52947000000},"
00767                       "  'commitmentsMade':{},"
00768                       "  'commitmentsRetired':{},"
00769                       "  'lineItems':{},"
00770                       "  'md': {'objectType':'Account','version':1},"
00771                       "  'recycledIn':{},"
00772                       "  'recycledOut':{},"
00773                       "  'spent':{},"
00774                       "  'type':'budget'},"
00775                       " 'available': {'USD/1M':52364946865},"
00776                       " 'budget': {'USD/1M':52947000000},"
00777                       " 'inFlight':{},"
00778                       " 'md': {'objectType':'AccountSummary',"
00779                       "        'version':1},"
00780                       " 'spent': {'USD/1M':582053135}}");
00781     BOOST_CHECK_EQUAL(normalValue, expected);
00782 
00783     Json::Value simpleValue = summary.toJson(true);
00784     expected = Json::parse("{'available': {'USD/1M':52364946865},"
00785                            " 'budget': {'USD/1M':52947000000},"
00786                            " 'inFlight': {},"
00787                            " 'md': {'objectType':'AccountSimpleSummary',"
00788                            "        'version':1},"
00789                            " 'spent': {'USD/1M':582053135}}");
00790     BOOST_CHECK_EQUAL(simpleValue, expected);
00791 }
00792 
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator