Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
19e0644
Require rpc fixture store setup to succeed.
evoskuil Sep 29, 2026
849bf75
Add faulted store fixtures and bitcoind store fault tests.
evoskuil Sep 29, 2026
06cf26f
Add error, settings, merkle, native, admin and server node tests.
evoskuil Sep 29, 2026
89c89b4
Add bitcoind rest spenttxouts, origin, and zmq transaction tests.
evoskuil Sep 29, 2026
dc2a736
Add bitcoind parser, serializer, and descriptor tests.
evoskuil Sep 29, 2026
198e172
Add electrum version, history limit, notification and parameter tests.
evoskuil Sep 29, 2026
a18e932
Add electrum request parser and outpoint subscription limit tests.
evoskuil Sep 29, 2026
022f991
Add electrum estimatefee invalid mode test.
evoskuil Sep 29, 2026
f18e930
Add esplora request validation, broadcast, unconfirmed and header-onl…
evoskuil Sep 29, 2026
3a216d1
Add btcd cfilter, node, outpoint filter and watch limit tests.
evoskuil Sep 29, 2026
329a785
Add bitcoind scanblocks, wait, chain tips and filter tests.
evoskuil Sep 29, 2026
4604227
Add bitcoind psbt, utility, network, origin and submission tests.
evoskuil Sep 29, 2026
0a15cbe
Add bitcoind decodepsbt unknown global test.
evoskuil Sep 29, 2026
1407786
Add bitcoind store fault tests.
evoskuil Sep 29, 2026
27e3634
Add bitcoind psbt field, unspendable scan and broadcast tests.
evoskuil Sep 29, 2026
072db03
Remove bitcoind misplaced store test.
evoskuil Sep 29, 2026
8c3395e
Add electrum store fault, credential and transport tests.
evoskuil Sep 29, 2026
dd4343f
Add esplora, native and btcd faulted store tests.
evoskuil Sep 29, 2026
e78945b
Add btcd dispatch failure, rescan and admin event unsubscribe tests.
evoskuil Sep 29, 2026
7581474
Add bitcoind transaction, scan, combine and utxo update helper tests.
evoskuil Sep 29, 2026
65f3c21
Remove electrum subscription test over a faulted store that is platfo…
evoskuil Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,140 @@ BOOST_AUTO_TEST_CASE(error_t__code__server_error__true_expected_message)
BOOST_REQUIRE_EQUAL(ec.message(), "server_error");
}

// electrum::translate

BOOST_AUTO_TEST_CASE(error_t__electrum_translate__success__success)
{
BOOST_REQUIRE(!error::electrum::translate(error::success, error::electrum::bad_request));
}

BOOST_AUTO_TEST_CASE(error_t__electrum_translate__electrum_code__unchanged)
{
const code ec{ error::electrum::server_busy };
BOOST_REQUIRE(error::electrum::translate(ec, error::electrum::bad_request) == ec);
}

BOOST_AUTO_TEST_CASE(error_t__electrum_translate__depth_limited__excessive_history)
{
const code ec{ database::error::depth_limited };
BOOST_REQUIRE(error::electrum::translate(ec, error::electrum::bad_request) == error::electrum::excessive_history);
}

BOOST_AUTO_TEST_CASE(error_t__electrum_translate__database_code__daemon_error)
{
const code ec{ database::error::integrity };
BOOST_REQUIRE(error::electrum::translate(ec, error::electrum::bad_request) == error::electrum::daemon_error);
}

BOOST_AUTO_TEST_CASE(error_t__electrum_translate__other_code__failure)
{
const code ec{ error::not_found };
BOOST_REQUIRE(error::electrum::translate(ec, error::electrum::bad_request) == error::electrum::bad_request);
}

// btcd::translate

BOOST_AUTO_TEST_CASE(error_t__btcd_translate__success__success)
{
BOOST_REQUIRE(!error::btcd::translate(error::success, error::btcd::misc_error));
}

BOOST_AUTO_TEST_CASE(error_t__btcd_translate__btcd_code__unchanged)
{
const code ec{ error::btcd::invalid_parameter };
BOOST_REQUIRE(error::btcd::translate(ec, error::btcd::misc_error) == ec);
}

BOOST_AUTO_TEST_CASE(error_t__btcd_translate__database_code__internal_error)
{
const code ec{ database::error::integrity };
BOOST_REQUIRE(error::btcd::translate(ec, error::btcd::misc_error) == error::btcd::internal_error);
}

BOOST_AUTO_TEST_CASE(error_t__btcd_translate__other_code__failure)
{
const code ec{ error::not_found };
BOOST_REQUIRE(error::btcd::translate(ec, error::btcd::misc_error) == error::btcd::misc_error);
}

// bitcoind::translate

BOOST_AUTO_TEST_CASE(error_t__bitcoind_translate__success__success)
{
BOOST_REQUIRE(!error::bitcoind::translate(error::success, error::bitcoind::misc_error));
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_translate__bitcoind_code__unchanged)
{
const code ec{ error::bitcoind::invalid_parameter };
BOOST_REQUIRE(error::bitcoind::translate(ec, error::bitcoind::misc_error) == ec);
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_translate__database_code__internal_error)
{
const code ec{ database::error::integrity };
BOOST_REQUIRE(error::bitcoind::translate(ec, error::bitcoind::misc_error) == error::bitcoind::internal_error);
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_translate__other_code__failure)
{
const code ec{ error::not_found };
BOOST_REQUIRE(error::bitcoind::translate(ec, error::bitcoind::misc_error) == error::bitcoind::misc_error);
}

// bitcoind::reject

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__node_orphan_block__prev_blk_not_found)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(node::error::orphan_block), "prev-blk-not-found");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__node_orphan_header__prev_blk_not_found)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(node::error::orphan_header), "prev-blk-not-found");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__node_duplicate_block__duplicate)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(node::error::duplicate_block), "duplicate");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__node_duplicate_header__duplicate)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(node::error::duplicate_header), "duplicate");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__empty_transaction__bad_txns_vin_empty)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(system::error::empty_transaction), "bad-txns-vin-empty");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__unmapped_transaction_code__message)
{
const code ec{ system::error::double_spend };
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(ec), ec.message());
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__invalid_proof_of_work__high_hash)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(system::error::invalid_proof_of_work), "high-hash");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__block_weight_limit__bad_blk_weight)
{
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(system::error::block_weight_limit), "bad-blk-weight");
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__unmapped_block_code__message)
{
const code ec{ system::error::checkpoint_conflict };
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(ec), ec.message());
}

BOOST_AUTO_TEST_CASE(error_t__bitcoind_reject__other_code__message)
{
const code ec{ error::not_found };
BOOST_REQUIRE_EQUAL(error::bitcoind::reject(ec), "not_found");
}

BOOST_AUTO_TEST_SUITE_END()
15 changes: 15 additions & 0 deletions test/mocks/blocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ bool setup_broadcast_store(query_t& query) NOEXCEPT
query.push_confirmed(query.to_header(block1c.hash()), true);
}

// A faulted store: block 3 is confirmed without its transactions.
bool setup_unassociated_store(query_t& query) NOEXCEPT
{
return setup_three_block_store(query) &&
query.set(block3.header(), database::context{ 0, 3, 0 }, {}, false) &&
query.push_confirmed(query.to_header(block3_hash), false);
}

// A faulted store: the confirmed top is not a header.
bool setup_dangling_store(query_t& query) NOEXCEPT
{
return setup_three_block_store(query) &&
query.push_confirmed(database::header_link{ 42 }, false);
}

bool setup_three_block_unconfirmed_address_store(query_t& query) NOEXCEPT
{
return query.initialize(genesis) &&
Expand Down
2 changes: 2 additions & 0 deletions test/mocks/blocks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ bool setup_three_block_witness_store(query_t& query) NOEXCEPT;
bool setup_three_block_confirmed_address_store(query_t& query) NOEXCEPT;
bool setup_three_block_unconfirmed_address_store(query_t& query) NOEXCEPT;
bool setup_broadcast_store(query_t& query) NOEXCEPT;
bool setup_unassociated_store(query_t& query) NOEXCEPT;
bool setup_dangling_store(query_t& query) NOEXCEPT;

// TODO: move.
boost::json::value parse_json(std::string_view value) NOEXCEPT;
Expand Down
31 changes: 31 additions & 0 deletions test/parsers/bitcoind_query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,35 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_query__malformed_count__false)
BOOST_REQUIRE(!bitcoind_query(out, target));
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_query__empty_count__false)
{
request_t out{};
const auto target = "/rest/headers/" + test_hash + ".json?count=";
BOOST_REQUIRE(!bitcoind_target(out, target));
BOOST_REQUIRE(!bitcoind_query(out, target));
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_query__empty_target__false)
{
request_t out{};
BOOST_REQUIRE(!bitcoind_target(out, "/rest/chaininfo.json"));
BOOST_REQUIRE(!bitcoind_query(out, ""));
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_query__no_params__false)
{
request_t out{};
BOOST_REQUIRE(!bitcoind_query(out, "/rest/headers/" + test_hash + ".json?count=7"));
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_query__offset_and_size__overlaid)
{
request_t out{};
const auto target = "/rest/blockpart/" + test_hash + ".bin?offset=80&size=1";
BOOST_REQUIRE(!bitcoind_target(out, target));
BOOST_REQUIRE(bitcoind_query(out, target));
BOOST_REQUIRE_EQUAL(std::get<uint32_t>(params_of(out).at("offset").value()), 80u);
BOOST_REQUIRE_EQUAL(std::get<uint32_t>(params_of(out).at("size").value()), 1u);
}

BOOST_AUTO_TEST_SUITE_END()
80 changes: 80 additions & 0 deletions test/parsers/bitcoind_target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,84 @@ BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockpart__block_part)
BOOST_REQUIRE_EQUAL(media_of(object), to_value(media_type::application_octet_stream));
}

// error paths

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__tx_no_hash__missing_hash)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/tx"), server::error::missing_hash);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__tx_no_extension__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/tx/" + test_hash), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__tx_not_hex__invalid_hash)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/tx/nothex.json"), server::error::invalid_hash);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockhashbyheight_no_extension__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockhashbyheight/5"), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilter_no_extension__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilter/basic/" + test_hash), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilter_not_hex__invalid_hash)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilter/basic/nothex.json"), server::error::invalid_hash);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_no_type__missing_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilterheaders"), server::error::missing_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_extended__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilterheaders/extended/" + test_hash + ".json"), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_invalid_count__invalid_number)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilterheaders/basic/abc/" + test_hash + ".json"), server::error::invalid_number);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_no_extension__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilterheaders/basic/" + test_hash), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__blockfilterheaders_not_hex__invalid_hash)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/blockfilterheaders/basic/nothex.json"), server::error::invalid_hash);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__getutxos_no_extension__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/getutxos/" + test_hash + "-0"), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__bitcoind_target__spenttxouts_no_extension__invalid_target)
{
request_t out{};
BOOST_REQUIRE_EQUAL(bitcoind_target(out, "/rest/spenttxouts/" + test_hash), server::error::invalid_target);
}

BOOST_AUTO_TEST_SUITE_END()
6 changes: 6 additions & 0 deletions test/parsers/esplora_target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ BOOST_AUTO_TEST_CASE(parsers__esplora_target__block_txid_missing_index__missing_
BOOST_REQUIRE_EQUAL(esplora_target(out, "/block/0000000000000000000000000000000000000000000000000000000000000042/txid"), server::error::missing_position);
}

BOOST_AUTO_TEST_CASE(parsers__esplora_target__block_txid_invalid_index__invalid_number)
{
request_t out{};
BOOST_REQUIRE_EQUAL(esplora_target(out, "/block/0000000000000000000000000000000000000000000000000000000000000042/txid/x"), server::error::invalid_number);
}

BOOST_AUTO_TEST_CASE(parsers__esplora_target__block_missing_hash__missing_hash)
{
request_t out{};
Expand Down
19 changes: 19 additions & 0 deletions test/parsers/native_target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ BOOST_AUTO_TEST_CASE(parsers__native_target__invalid_target__invalid_target)
BOOST_REQUIRE_EQUAL(native_target(out, "/v3/invalid"), server::error::invalid_target);
}

BOOST_AUTO_TEST_CASE(parsers__native_target__top_invalid_subcomponent__invalid_subcomponent)
{
request_t out{};
BOOST_REQUIRE_EQUAL(native_target(out, "/v3/top/invalid"), server::error::invalid_subcomponent);
}

BOOST_AUTO_TEST_CASE(parsers__native_target__input_invalid_subcomponent__invalid_subcomponent)
{
const std::string path = "/v3/input/0000000000000000000000000000000000000000000000000000000000000000/3/invalid";
request_t out{};
BOOST_REQUIRE_EQUAL(native_target(out, path), server::error::invalid_subcomponent);
}

BOOST_AUTO_TEST_CASE(parsers__native_target__block_missing_id_type__missing_id_type)
{
request_t out{};
BOOST_REQUIRE_EQUAL(native_target(out, "/v3/block"), server::error::missing_id_type);
}

// configuration

BOOST_AUTO_TEST_CASE(parsers__native_target__configuration_valid__expected)
Expand Down
38 changes: 38 additions & 0 deletions test/protocols/admin/admin_diagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ BOOST_AUTO_TEST_CASE(admin__event_subscribe__json__previous_zero)
BOOST_REQUIRE_EQUAL(response.at("previous").as_int64(), 0);
}

// dispatch (http)
// ----------------------------------------------------------------------------

BOOST_AUTO_TEST_CASE(admin__dispatch__invalid_target__bad_request)
{
BOOST_REQUIRE(get_status("/v1/bogus") == status::bad_request);
}

BOOST_AUTO_TEST_CASE(admin__dispatch__html__bad_request)
{
BOOST_REQUIRE(get_status("/v1/log/subscribe?filter=1&format=html") == status::bad_request);
}

// subscribe (websockets)
// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -155,6 +168,16 @@ BOOST_AUTO_TEST_CASE(admin__ws_log_subscribe__html__error_eof)
BOOST_REQUIRE(ws_dropped("/v1/log/subscribe?filter=1&format=html"));
}

BOOST_AUTO_TEST_CASE(admin__ws_dispatch__invalid_target__ignored)
{
BOOST_REQUIRE(!ws_upgrade());
ws_send("/v1/bogus");

const auto ack = ws_get_json("/v1/log/subscribe?filter=1024");
REQUIRE_NO_THROW_TRUE(ack.at("previous").is_int64());
BOOST_REQUIRE_EQUAL(ack.at("previous").as_int64(), 0);
}

// streams (websockets)
// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -248,4 +271,19 @@ BOOST_AUTO_TEST_CASE(admin__ws_event_subscribe__unfiltered_event__not_notified)
BOOST_REQUIRE_EQUAL(frame.at("value").as_int64(), 2);
}

BOOST_AUTO_TEST_CASE(admin__ws_event_subscribe__unsubscribed__not_notified)
{
BOOST_REQUIRE(!ws_upgrade());
BOOST_REQUIRE_EQUAL(ws_get_json("/v1/event/subscribe?filter=8").at("previous").as_int64(), 0);
BOOST_REQUIRE_EQUAL(ws_get_json("/v1/event/subscribe?filter=0").at("previous").as_int64(), 8);
BOOST_REQUIRE_EQUAL(ws_get_json("/v1/log/subscribe?filter=1024").at("previous").as_int64(), 0);

fire(node::events::block_archived, 1);
write(10, "marker");

const auto frame = ws_receive_json();
REQUIRE_NO_THROW_TRUE(frame.at("message").is_string());
BOOST_REQUIRE_EQUAL(frame.at("message").as_string(), "marker");
}

BOOST_AUTO_TEST_SUITE_END()
Loading
Loading