text: #undef NDEBUG
  #include"Boss/Mod/SwapManager.hpp"
  #include"Boss/Msg/AcceptSwapQuotation.hpp"
  #include"Boss/Msg/DbResource.hpp"
  #include"Boss/Msg/JsonCout.hpp"
  #include"Boss/Msg/ListfundsResult.hpp"
  #include"Boss/Msg/PayInvoice.hpp"
  #include"Boss/Msg/ProvideSwapQuotation.hpp"
  #include"Boss/Msg/RequestNewaddr.hpp"
  #include"Boss/Msg/ResponseNewaddr.hpp"
  #include"Boss/Msg/SolicitSwapQuotation.hpp"
  #include"Boss/Msg/SwapCreation.hpp"
  #include"Boss/Msg/SwapRequest.hpp"
  #include"Boss/Msg/SwapResponse.hpp"
  #include"Ev/Io.hpp"
  #include"Ev/foreach.hpp"
  #include"Ev/start.hpp"
  #include"Ev/yield.hpp"
  #include"Json/Out.hpp"
  #include"S/Bus.hpp"
  #include"Sqlite3.hpp"
  #include"Util/make_unique.hpp"
  #include"Uuid.hpp"
  #include<assert.h>
  #include<iostream>
  #include<memory>
  #include<queue>
  #include<set>
  #include<sstream>
  #include<unordered_map>
  #include<unordered_set>
  
  namespace {
  
  /*-----------------------------------------------------------------------------
  Mocks
  -----------------------------------------------------------------------------*/
  
  class MockLog {
  public:
  	MockLog(S::Bus& bus) {
  		bus.subscribe<Boss::Msg::JsonCout
  			     >([](Boss::Msg::JsonCout const& j) {
  			std::cout << j.obj.output() << std::endl;
  			return Ev::lift();
  		});
  	}
  };
  class MockNewaddr {
  private:
  	std::queue<std::string> addresses;
  public:
  	MockNewaddr(S::Bus& bus) {
  		bus.subscribe<Boss::Msg::RequestNewaddr
  			     >([ this, &bus
  			       ](Boss::Msg::RequestNewaddr const& r) {
  			assert(!addresses.empty());
  			auto addr = addresses.front();
  			addresses.pop();
  			return bus.raise(Boss::Msg::ResponseNewaddr{
  				addr, r.requester
  			});
  		});
  	}
  	void add(std::string addr) {
  		addresses.emplace(std::move(addr));
  	}
  	bool empty() const { return addresses.empty(); }
  };
  struct MockProvider {};
  std::vector<std::unique_ptr<MockProvider>> providers;
  class MockSwapQuotation {
  private:
  	/* A vector, since we want to check what happens when multiple
  	 * quotations exist.
  	 */
  	std::queue<std::vector<Ln::Amount>> fees;
  public:
  	MockSwapQuotation(S::Bus& bus) {
  		using Boss::Msg::ProvideSwapQuotation;
  		using Boss::Msg::SolicitSwapQuotation;
  		bus.subscribe<SolicitSwapQuotation
  			     >([ this
  			       , &bus
  			       ](SolicitSwapQuotation const& s) {
  			assert(!fees.empty());
  			auto set = std::move(fees.front());
  			fees.pop();
  
  			/* Ensure there are enough providers.  */
  			while (set.size() > providers.size()) {
  				providers.push_back(
  					Util::make_unique<MockProvider>()
  				);
  			}
  			/* Create the messages.  */
  			auto messages = std::vector<ProvideSwapQuotation>();
  			for (auto i = std::size_t(0); i < set.size(); ++i) {
  				messages.emplace_back(ProvideSwapQuotation{
  					set[i],
  					s.solicitor,
  					providers[i].get()
  				});
  			}
  			/* Send them.  */
  			auto f = [&bus](ProvideSwapQuotation q) {
  				return bus.raise(std::move(q));
  			};
  			return Ev::foreach(f, std::move(messages));
  		});
  	}
  
  	void add(std::vector<Ln::Amount> set) {
  		fees.emplace(std::move(set));
  	}
  	bool empty() const { return fees.empty(); }
  };
  
  class MockSwapCreation {
  public:
  	struct Entry {
  		/* The expected address, if not match, fail.  */
  		std::string address;
  		/* Details.  */
  		bool success;
  		std::string invoice;
  		Sha256::Hash hash;
  		std::uint32_t timeout;
  	};
  private:
  	std::queue<Entry> entries;
  	std::set<std::string> invoices;
  public:
  	MockSwapCreation(S::Bus& bus) {
  		using Boss::Msg::AcceptSwapQuotation;
  		using Boss::Msg::PayInvoice;
  		using Boss::Msg::SwapCreation;
  		bus.subscribe<AcceptSwapQuotation
  			     >([ this
  			       , &bus
  			       ](AcceptSwapQuotation const& q) {
  			assert(!entries.empty());
  			auto entry = std::move(entries.front());
  			entries.pop();
  
  			/* Validate.  */
  			assert(q.onchain_address == entry.address);
  			auto found = false;
  			for (auto const& p : providers) {
  				if (p.get() == q.provider) {
  					found = true;
  					break;
  				}
  			}
  			assert(found);
  
  			/* If success, add invoice to set of invoices
  			 * we expect.  */
  			if (entry.success)
  				invoices.insert(entry.invoice);
  
  			return bus.raise(SwapCreation{
  				entry.success,
  				entry.invoice, entry.hash, entry.timeout,
  				q.solicitor, q.provider
  			});
  		});
  		bus.subscribe<PayInvoice
  			     >([this](PayInvoice const& p) {
  			auto it = invoices.find(p.invoice);
  			assert(it != invoices.end());
  			invoices.erase(it);
  			return Ev::lift();
  		});
  	}
  
  	void add(Entry e) {
  		entries.emplace(std::move(e));
  	}
  	bool okay() const {
  		return entries.empty() && invoices.empty();
  	}
  };
  class MockListfunds {
  private:
  	S::Bus& bus;
  public:
  	MockListfunds(S::Bus& bus_) : bus(bus_) { }
  	Ev::Io<void>
  	listfunds(std::vector<std::pair< std::string
  					, Ln::Amount
  					>> const& addr_amount) {
  		/* Generate first via Json::Out.  */
  		auto outputs = Json::Out();
  		auto arr = outputs.start_array();
  		for (auto const& e : addr_amount) {
  			auto obj = arr.start_object();
  			obj.field("address", e.first);
  			obj.field("amount_msat", std::string(e.second));
  			obj.end_object();
  		}
  		arr.end_array();
  		/* Convert to Jsmn::Object.  */
  		auto outputs_s = outputs.output();
  		auto is = std::istringstream(std::move(outputs_s));
  		auto outputs_j = Jsmn::Object();
  		is >> outputs_j;
  
  		return bus.raise(Boss::Msg::ListfundsResult{
  			std::move(outputs_j), Jsmn::Object()
  		});
  	}
  };
  class MockRequester {
  private:
  	S::Bus& bus;
  	Sqlite3::Db db;
  	std::unordered_set<Uuid> pendings;
  	std::unordered_map<Uuid, bool> finished;
  
  public:
  	MockRequester(S::Bus& bus_) : bus(bus_) {
  		bus.subscribe<Boss::Msg::DbResource
  			     >([this](Boss::Msg::DbResource const& r) {
  			db = r.db;
  			return Ev::lift();
  		});
  		bus.subscribe<Boss::Msg::SwapResponse
  			     >([this](Boss::Msg::SwapResponse const& r) {
  			auto tx = std::move(*r.dbtx);
  
  			auto it = pendings.find(r.id);
  			assert(it != pendings.end());
  			pendings.erase(it);
  
  			auto it_m = finished.find(r.id);
  			assert(it_m == finished.end());
  			finished[r.id] = r.success;
  
  			tx.commit();
  			return Ev::lift();
  		});
  	}
  
  	Ev::Io<void> start_swap(Uuid uuid, Ln::Amount min, Ln::Amount max) {
  		return db.transact().then([this, uuid, min, max
  					  ](Sqlite3::Tx tx) {
  			pendings.insert(uuid);
  			auto sh_tx = std::make_shared<Sqlite3::Tx>(
  				std::move(tx)
  			);
  			return bus.raise(Boss::Msg::SwapRequest{
  				sh_tx, uuid, min, max
  			}).then([this, sh_tx]() {
  				assert(sh_tx);
  				assert(!*sh_tx);
  				return Ev::lift();
  			});
  		});
  	}
  
  	void expect_finished(Uuid uuid, bool expect_success) {
  		auto it = finished.find(uuid);
  		assert(it != finished.end());
  		assert(it->second == expect_success);
  	}
  };
  
  Ev::Io<void> wait() {
  	auto act = Ev::yield();
  	for (auto i = std::size_t(0); i < 200; ++i)
  		act = std::move(act).then([]() { return Ev::yield(); });
  	return act;
  }
  
  /*-----------------------------------------------------------------------------
  Test Harness
  -----------------------------------------------------------------------------*/
  
  S::Bus bus;
  Sqlite3::Db db(":memory:");
  MockLog logger(bus);
  MockNewaddr addresses(bus);
  MockSwapQuotation quotations(bus);
  MockSwapCreation swapsetups(bus);
  MockListfunds onchain(bus);
  MockRequester requester(bus);
  /* Object under test.  */
  Boss::Mod::SwapManager swapper(bus);
  
  /*-----------------------------------------------------------------------------
  Tests
  -----------------------------------------------------------------------------*/
  
  Ev::Io<void> test_simple() {
  
  	Uuid uuid = Uuid::random();
  
  	return Ev::lift().then([&, uuid]() {
  		addresses.add("address1");
  		quotations.add({Ln::Amount::sat(1000), Ln::Amount::sat(100)});
  		/* Fail the first provider, but succeed the next one.  */
  		swapsetups.add({"address1", false, "", Sha256::Hash(), 0});
  		swapsetups.add({"address1", true, "invoice1"
  			       , Sha256::Hash("ec8103d3f57a561fd213bf6bb45b2cd0e1b37738232c7d1fd13b1a7729bf0982")
  			       , 12345
  			       });
  
  		return requester.start_swap( uuid
  					   , Ln::Amount::btc(0.001)
  					   , Ln::Amount::btc(0.012)
  					   );
  	}).then([&]() {
  		return wait();
  	}).then([&]() {
  		assert(addresses.empty());
  		assert(quotations.empty());
  		assert(swapsetups.okay());
  
  		return onchain.listfunds({ { "unrelated-address"
  					   , Ln::Amount::sat(42)
  					   }
  					 });
  	}).then([&]() {
  		return onchain.listfunds({ { "unrelated-address"
  					   , Ln::Amount::sat(42)
  					   }
  					 , { "address1"
  					   , Ln::Amount::sat(12100)
  					   }
  					 });
  	}).then([&]() {
  		return wait();
  	}).then([&, uuid]() {
  		requester.expect_finished(uuid, true);
  		return Ev::lift();
  	});
  }
  
  }
  
  /*-----------------------------------------------------------------------------
  Setup
  -----------------------------------------------------------------------------*/
  
  int main() {
  	auto code = Ev::lift().then([&]() {
  		return bus.raise(Boss::Msg::DbResource{db});
  	}).then([&]() {
  		return test_simple();
  	}).then([&]() {
  		return Ev::lift(0);
  	});
  
  	return Ev::start(code);
  }
  