#include "config.h"
#define main bookkeeper_main
int bookkeeper_main(int argc, char *argv[]);
#include "../bookkeeper.c"
#undef main

#include <assert.h>
#include <ccan/str/str.h>
#include <ccan/tal/str/str.h>
#include <common/amount.h>
#include <common/json_parse.h>
#include <common/setup.h>

/* AUTOGENERATED MOCKS START */
/* Generated stub for account_get_chain_events */
struct chain_event **account_get_chain_events(const tal_t *ctx UNNEEDED,
					      const struct bkpr *bkpr UNNEEDED,
					      struct command *cmd UNNEEDED,
					      struct account *acct UNNEEDED)
{ fprintf(stderr, "account_get_chain_events called!\n"); abort(); }
/* Generated stub for account_get_chain_fees */
struct onchain_fee **account_get_chain_fees(const tal_t *ctx UNNEEDED,
					    const struct bkpr *bkpr UNNEEDED,
					    const char *acct_name UNNEEDED)
{ fprintf(stderr, "account_get_chain_fees called!\n"); abort(); }
/* Generated stub for account_get_channel_events */
struct channel_event **account_get_channel_events(const tal_t *ctx UNNEEDED,
						  const struct bkpr *bkpr UNNEEDED,
						  struct command *cmd UNNEEDED,
						  struct account *acct UNNEEDED)
{ fprintf(stderr, "account_get_channel_events called!\n"); abort(); }
/* Generated stub for account_get_credit_debit */
bool account_get_credit_debit(const struct bkpr *bkpr UNNEEDED,
			      struct command *cmd UNNEEDED,
			      const char *acct_name UNNEEDED,
			      struct amount_msat *credit UNNEEDED,
			      struct amount_msat *debit UNNEEDED)
{ fprintf(stderr, "account_get_credit_debit called!\n"); abort(); }
/* Generated stub for account_onchain_closeheight */
u64 account_onchain_closeheight(const struct bkpr *bkpr UNNEEDED,
				struct command *cmd UNNEEDED,
				const struct account *acct UNNEEDED)
{ fprintf(stderr, "account_onchain_closeheight called!\n"); abort(); }
/* Generated stub for account_update_closeheight */
void account_update_closeheight(struct command *cmd UNNEEDED,
				struct account *acct UNNEEDED,
				u64 close_height UNNEEDED)
{ fprintf(stderr, "account_update_closeheight called!\n"); abort(); }
/* Generated stub for add_payment_hash_description */
void add_payment_hash_description(struct command *cmd UNNEEDED,
				  struct bkpr *bkpr UNNEEDED,
				  const struct sha256 *payment_hash UNNEEDED,
				  const char *desc UNNEEDED)
{ fprintf(stderr, "add_payment_hash_description called!\n"); abort(); }
/* Generated stub for add_utxo_description */
void add_utxo_description(struct command *cmd UNNEEDED,
			  struct bkpr *bkpr UNNEEDED,
			  const struct bitcoin_outpoint *outpoint UNNEEDED,
			  const char *desc UNNEEDED)
{ fprintf(stderr, "add_utxo_description called!\n"); abort(); }
/* Generated stub for aux_command */
struct command *aux_command(const struct command *cmd)

{ fprintf(stderr, "aux_command called!\n"); abort(); }
/* Generated stub for channel_apy_sum */
 bool channel_apy_sum(struct channel_apy *sum_apy UNNEEDED,
					const struct channel_apy *entry UNNEEDED)
{ fprintf(stderr, "channel_apy_sum called!\n"); abort(); }
/* Generated stub for command_check_done */
struct command_result *command_check_done(struct command *cmd)

{ fprintf(stderr, "command_check_done called!\n"); abort(); }
/* Generated stub for command_check_only */
bool command_check_only(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_check_only called!\n"); abort(); }
/* Generated stub for command_deprecated_in_ok */
bool command_deprecated_in_ok(struct command *cmd UNNEEDED,
			      const char *param UNNEEDED,
			      const char *depr_start UNNEEDED,
			      const char *depr_end UNNEEDED)
{ fprintf(stderr, "command_deprecated_in_ok called!\n"); abort(); }
/* Generated stub for command_dev_apis */
bool command_dev_apis(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_dev_apis called!\n"); abort(); }
/* Generated stub for command_fail */
struct command_result *command_fail(struct command *cmd UNNEEDED, enum jsonrpc_errcode code UNNEEDED,
				    const char *fmt UNNEEDED, ...)

{ fprintf(stderr, "command_fail called!\n"); abort(); }
/* Generated stub for command_filter_ptr */
struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); }
/* Generated stub for command_finished */
struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response)

{ fprintf(stderr, "command_finished called!\n"); abort(); }
/* Generated stub for command_log */
void command_log(struct command *cmd UNNEEDED, enum log_level level UNNEEDED,
		 const char *fmt UNNEEDED, ...)

{ fprintf(stderr, "command_log called!\n"); abort(); }
/* Generated stub for command_param_failed */
struct command_result *command_param_failed(void)
{ fprintf(stderr, "command_param_failed called!\n"); abort(); }
/* Generated stub for command_set_usage */
void command_set_usage(struct command *cmd UNNEEDED, const char *usage UNNEEDED)
{ fprintf(stderr, "command_set_usage called!\n"); abort(); }
/* Generated stub for command_still_pending */
struct command_result *command_still_pending(struct command *cmd)

{ fprintf(stderr, "command_still_pending called!\n"); abort(); }
/* Generated stub for command_usage_only */
bool command_usage_only(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_usage_only called!\n"); abort(); }
/* Generated stub for compute_channel_apys */
struct channel_apy **compute_channel_apys(const tal_t *ctx UNNEEDED,
					  const struct bkpr *bkpr UNNEEDED,
					  struct command *cmd UNNEEDED,
					  u64 start_time UNNEEDED,
					  u64 end_time UNNEEDED,
					  u32 current_blockheight UNNEEDED)
{ fprintf(stderr, "compute_channel_apys called!\n"); abort(); }
/* Generated stub for csv_filename */
const char *csv_filename(const tal_t *ctx UNNEEDED, const struct csv_fmt *fmt UNNEEDED)
{ fprintf(stderr, "csv_filename called!\n"); abort(); }
/* Generated stub for csv_list_fmts */
const char *csv_list_fmts(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "csv_list_fmts called!\n"); abort(); }
/* Generated stub for csv_match_token */
const struct csv_fmt *csv_match_token(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
{ fprintf(stderr, "csv_match_token called!\n"); abort(); }
/* Generated stub for csv_print_income_events */
char *csv_print_income_events(const tal_t *ctx UNNEEDED,
			      const struct csv_fmt *csvfmt UNNEEDED,
			      const char *filename UNNEEDED,
			      struct income_event **evs UNNEEDED)
{ fprintf(stderr, "csv_print_income_events called!\n"); abort(); }
/* Generated stub for do_bkpr_report */
struct command_result *do_bkpr_report(struct command *cmd UNNEEDED,
				      struct report_info *info UNNEEDED)
{ fprintf(stderr, "do_bkpr_report called!\n"); abort(); }
/* Generated stub for find_account */
struct account *find_account(const struct bkpr *bkpr UNNEEDED,
			     const char *name UNNEEDED)
{ fprintf(stderr, "find_account called!\n"); abort(); }
/* Generated stub for find_account_onchain_fees */
struct fee_sum **find_account_onchain_fees(const tal_t *ctx UNNEEDED,
					   const struct bkpr *bkpr UNNEEDED,
					   const struct account *acct UNNEEDED)
{ fprintf(stderr, "find_account_onchain_fees called!\n"); abort(); }
/* Generated stub for find_chain_events_bytxid */
struct chain_event **find_chain_events_bytxid(const tal_t *ctx UNNEEDED,
					      const struct bkpr *bkpr UNNEEDED,
					      struct command *cmd UNNEEDED,
					      const struct bitcoin_txid *txid UNNEEDED)
{ fprintf(stderr, "find_chain_events_bytxid called!\n"); abort(); }
/* Generated stub for find_close_account_name */
const char *find_close_account_name(const tal_t *ctx UNNEEDED,
				    const struct bkpr *bkpr UNNEEDED,
				    struct command *cmd UNNEEDED,
				    const struct bitcoin_txid *txid UNNEEDED)
{ fprintf(stderr, "find_close_account_name called!\n"); abort(); }
/* Generated stub for find_or_create_account */
struct account *find_or_create_account(struct command *cmd UNNEEDED,
				       struct bkpr *bkpr UNNEEDED,
				       const char *name UNNEEDED)
{ fprintf(stderr, "find_or_create_account called!\n"); abort(); }
/* Generated stub for find_txo_chain */
bool find_txo_chain(const tal_t *ctx UNNEEDED,
		    const struct bkpr *bkpr UNNEEDED,
		    struct command *cmd UNNEEDED,
		    const struct account *acct UNNEEDED,
		    struct txo_set ***sets UNNEEDED)
{ fprintf(stderr, "find_txo_chain called!\n"); abort(); }
/* Generated stub for forward_error */
struct command_result *forward_error(struct command *cmd UNNEEDED,
				     const char *method UNNEEDED,
				     const char *buf UNNEEDED,
				     const jsmntok_t *error UNNEEDED,
				     void *arg)

{ fprintf(stderr, "forward_error called!\n"); abort(); }
/* Generated stub for get_chain_events_by_id */
struct chain_event **get_chain_events_by_id(const tal_t *ctx UNNEEDED,
					    const struct bkpr *bkpr UNNEEDED,
					    struct command *cmd UNNEEDED,
					    const struct sha256 *id UNNEEDED)
{ fprintf(stderr, "get_chain_events_by_id called!\n"); abort(); }
/* Generated stub for get_chain_events_by_outpoint */
struct chain_event **get_chain_events_by_outpoint(const tal_t *ctx UNNEEDED,
						  const struct bkpr *bkpr UNNEEDED,
						  struct command *cmd UNNEEDED,
						  const struct bitcoin_outpoint *outpoint UNNEEDED)
{ fprintf(stderr, "get_chain_events_by_outpoint called!\n"); abort(); }
/* Generated stub for get_chain_fees_by_txid */
struct onchain_fee **get_chain_fees_by_txid(const tal_t *ctx UNNEEDED,
					    const struct bkpr *bkpr UNNEEDED,
					    const struct bitcoin_txid *txid UNNEEDED)
{ fprintf(stderr, "get_chain_fees_by_txid called!\n"); abort(); }
/* Generated stub for get_channel_events_by_id */
struct channel_event **get_channel_events_by_id(const tal_t *ctx UNNEEDED,
						const struct bkpr *bkpr UNNEEDED,
						struct command *cmd UNNEEDED,
						const struct sha256 *id UNNEEDED)
{ fprintf(stderr, "get_channel_events_by_id called!\n"); abort(); }
/* Generated stub for init_accounts */
struct accounts *init_accounts(const tal_t *ctx UNNEEDED, struct command *init_cmd UNNEEDED)
{ fprintf(stderr, "init_accounts called!\n"); abort(); }
/* Generated stub for init_blockheights */
struct blockheights *init_blockheights(const tal_t *ctx UNNEEDED,
				       struct command *init_cmd UNNEEDED)
{ fprintf(stderr, "init_blockheights called!\n"); abort(); }
/* Generated stub for init_descriptions */
struct descriptions *init_descriptions(const tal_t *ctx UNNEEDED,
				       struct command *init_cmd UNNEEDED)
{ fprintf(stderr, "init_descriptions called!\n"); abort(); }
/* Generated stub for init_onchain_fees */
struct onchain_fees *init_onchain_fees(const tal_t *ctx UNNEEDED,
				       struct command *init_cmd UNNEEDED)
{ fprintf(stderr, "init_onchain_fees called!\n"); abort(); }
/* Generated stub for init_rebalances */
struct rebalances *init_rebalances(const tal_t *ctx UNNEEDED,
				   struct command *init_cmd UNNEEDED)
{ fprintf(stderr, "init_rebalances called!\n"); abort(); }
/* Generated stub for json_add_chain_event */
void json_add_chain_event(struct json_stream *out UNNEEDED,
			  const struct bkpr *bkpr UNNEEDED,
                          struct chain_event *ev UNNEEDED)
{ fprintf(stderr, "json_add_chain_event called!\n"); abort(); }
/* Generated stub for json_add_channel_apy */
void json_add_channel_apy(struct json_stream *res UNNEEDED,
			  const struct channel_apy *apy UNNEEDED)
{ fprintf(stderr, "json_add_channel_apy called!\n"); abort(); }
/* Generated stub for json_add_channel_event */
void json_add_channel_event(struct json_stream *out UNNEEDED,
			    const struct bkpr *bkpr UNNEEDED,
			    struct channel_event *ev UNNEEDED)
{ fprintf(stderr, "json_add_channel_event called!\n"); abort(); }
/* Generated stub for json_add_income_event */
void json_add_income_event(struct json_stream *str UNNEEDED, struct income_event *ev UNNEEDED)
{ fprintf(stderr, "json_add_income_event called!\n"); abort(); }
/* Generated stub for json_add_onchain_fee */
void json_add_onchain_fee(struct json_stream *out UNNEEDED,
			  const struct bkpr *bkpr UNNEEDED,
			  const struct onchain_fee *fee UNNEEDED)
{ fprintf(stderr, "json_add_onchain_fee called!\n"); abort(); }
/* Generated stub for jsonrpc_request_start_ */
struct out_req *jsonrpc_request_start_(struct command *cmd UNNEEDED,
				       const char *method UNNEEDED,
				       const char *id_prefix UNNEEDED,
				       const char *filter UNNEEDED,
				       struct command_result *(*cb)(struct command *command UNNEEDED,
								    const char *methodname UNNEEDED,
								    const char *buf UNNEEDED,
								    const jsmntok_t *result UNNEEDED,
								    void *arg) UNNEEDED,
				       struct command_result *(*errcb)(struct command *command UNNEEDED,
								       const char *methodname UNNEEDED,
								       const char *buf UNNEEDED,
								       const jsmntok_t *result UNNEEDED,
								       void *arg) UNNEEDED,
				       void *arg)

{ fprintf(stderr, "jsonrpc_request_start_ called!\n"); abort(); }
/* Generated stub for jsonrpc_request_sync */
const jsmntok_t *jsonrpc_request_sync(const tal_t *ctx UNNEEDED,
				      struct command *cmd UNNEEDED,
				      const char *method UNNEEDED,
				      const struct json_out *params TAKES UNNEEDED,
				      const char **resp UNNEEDED)
{ fprintf(stderr, "jsonrpc_request_sync called!\n"); abort(); }
/* Generated stub for jsonrpc_set_datastore_ */
struct command_result *jsonrpc_set_datastore_(struct command *cmd UNNEEDED,
					      const char **keys UNNEEDED,
					      const void *value UNNEEDED,
					      int len_or_str UNNEEDED,
					      const char *mode UNNEEDED,
					      struct command_result *(*cb)(struct command *command UNNEEDED,
									   const char *method UNNEEDED,
									   const char *buf UNNEEDED,
									   const jsmntok_t *result UNNEEDED,
									   void *arg) UNNEEDED,
					      struct command_result *(*errcb)(struct command *command UNNEEDED,
									      const char *method UNNEEDED,
									      const char *buf UNNEEDED,
									      const jsmntok_t *result UNNEEDED,
									      void *arg) UNNEEDED,
					      void *arg)

{ fprintf(stderr, "jsonrpc_set_datastore_ called!\n"); abort(); }
/* Generated stub for jsonrpc_stream_success */
struct json_stream *jsonrpc_stream_success(struct command *cmd)

{ fprintf(stderr, "jsonrpc_stream_success called!\n"); abort(); }
/* Generated stub for list_accounts */
struct account **list_accounts(const tal_t *ctx UNNEEDED, const struct bkpr *bkpr UNNEEDED)
{ fprintf(stderr, "list_accounts called!\n"); abort(); }
/* Generated stub for list_chain_events */
struct chain_event **list_chain_events(const tal_t *ctx UNNEEDED,
				       const struct bkpr *bkpr UNNEEDED,
				       struct command *cmd UNNEEDED)
{ fprintf(stderr, "list_chain_events called!\n"); abort(); }
/* Generated stub for list_chain_fees */
struct onchain_fee **list_chain_fees(const tal_t *ctx UNNEEDED, const struct bkpr *bkpr UNNEEDED)
{ fprintf(stderr, "list_chain_fees called!\n"); abort(); }
/* Generated stub for list_channel_events */
struct channel_event **list_channel_events(const tal_t *ctx UNNEEDED,
					   const struct bkpr *bkpr UNNEEDED,
					   struct command *cmd UNNEEDED)
{ fprintf(stderr, "list_channel_events called!\n"); abort(); }
/* Generated stub for list_income_events */
struct income_event **list_income_events(const tal_t *ctx UNNEEDED,
					 const struct bkpr *bkpr UNNEEDED,
					 struct command *cmd UNNEEDED,
					 u64 start_time UNNEEDED,
					 u64 end_time UNNEEDED,
					 bool consolidate_fees UNNEEDED)
{ fprintf(stderr, "list_income_events called!\n"); abort(); }
/* Generated stub for maybe_closeout_external_deposits */
void maybe_closeout_external_deposits(struct command *cmd UNNEEDED,
				      struct bkpr *bkpr UNNEEDED,
				      const struct bitcoin_txid *txid UNNEEDED,
				      u32 blockheight UNNEEDED)
{ fprintf(stderr, "maybe_closeout_external_deposits called!\n"); abort(); }
/* Generated stub for maybe_record_rebalance */
void maybe_record_rebalance(struct command *cmd UNNEEDED,
			    struct bkpr *bkpr UNNEEDED,
			    const struct channel_event *out UNNEEDED)
{ fprintf(stderr, "maybe_record_rebalance called!\n"); abort(); }
/* Generated stub for maybe_update_account */
void maybe_update_account(struct command *cmd UNNEEDED,
			  struct account *acct UNNEEDED,
			  struct chain_event *e UNNEEDED,
			  const enum mvt_tag *tags UNNEEDED,
			  u32 closed_count UNNEEDED,
			  struct node_id *peer_id UNNEEDED)
{ fprintf(stderr, "maybe_update_account called!\n"); abort(); }
/* Generated stub for maybe_update_onchain_fees */
char *maybe_update_onchain_fees(const tal_t *ctx UNNEEDED,
				struct command *cmd UNNEEDED,
				struct bkpr *bkpr UNNEEDED,
			        struct bitcoin_txid *txid UNNEEDED)
{ fprintf(stderr, "maybe_update_onchain_fees called!\n"); abort(); }
/* Generated stub for new_channel_apy */
struct channel_apy *new_channel_apy(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "new_channel_apy called!\n"); abort(); }
/* Generated stub for notification_handled */
struct command_result *notification_handled(struct command *cmd)

{ fprintf(stderr, "notification_handled called!\n"); abort(); }
/* Generated stub for param_escape_format */
struct command_result *param_escape_format(struct command *cmd UNNEEDED, const char *name UNNEEDED,
					   const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
					   enum escape_format **escape UNNEEDED)
{ fprintf(stderr, "param_escape_format called!\n"); abort(); }
/* Generated stub for param_report_format */
struct command_result *param_report_format(struct command *cmd UNNEEDED, const char *name UNNEEDED,
					   const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
					   struct report_format **format UNNEEDED)
{ fprintf(stderr, "param_report_format called!\n"); abort(); }
/* Generated stub for plugin_broken_cb */
struct command_result *plugin_broken_cb(struct command *cmd UNNEEDED,
					const char *method UNNEEDED,
					const char *buf UNNEEDED,
					const jsmntok_t *result UNNEEDED,
					void *arg UNNEEDED)
{ fprintf(stderr, "plugin_broken_cb called!\n"); abort(); }
/* Generated stub for plugin_err */
void   plugin_err(struct plugin *p UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "plugin_err called!\n"); abort(); }
/* Generated stub for plugin_get_data_ */
void *plugin_get_data_(struct plugin *plugin UNNEEDED)
{ fprintf(stderr, "plugin_get_data_ called!\n"); abort(); }
/* Generated stub for plugin_log */
void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "plugin_log called!\n"); abort(); }
/* Generated stub for plugin_main */
void   plugin_main(char *argv[] UNNEEDED,
					const char *(*init)(struct command *init_cmd UNNEEDED,
							    const char *buf UNNEEDED,
							    const jsmntok_t *) UNNEEDED,
					void *data TAKES UNNEEDED,
					const enum plugin_restartability restartability UNNEEDED,
					bool init_rpc UNNEEDED,
					struct feature_set *features STEALS UNNEEDED,
					const struct plugin_command *commands TAKES UNNEEDED,
					size_t num_commands UNNEEDED,
					const struct plugin_notification *notif_subs TAKES UNNEEDED,
					size_t num_notif_subs UNNEEDED,
					const struct plugin_hook *hook_subs TAKES UNNEEDED,
					size_t num_hook_subs UNNEEDED,
					const char **notif_topics TAKES UNNEEDED,
					size_t num_notif_topics UNNEEDED,
					...)
{ fprintf(stderr, "plugin_main called!\n"); abort(); }
/* Generated stub for rpc_scan_datastore_hex */
const char *rpc_scan_datastore_hex(const tal_t *ctx UNNEEDED,
				   struct command *cmd UNNEEDED,
				   const char **keys UNNEEDED,
				   ...)
{ fprintf(stderr, "rpc_scan_datastore_hex called!\n"); abort(); }
/* Generated stub for send_outreq */
struct command_result *send_outreq(const struct out_req *req UNNEEDED)
{ fprintf(stderr, "send_outreq called!\n"); abort(); }
/* Generated stub for update_channel_onchain_fees */
char *update_channel_onchain_fees(const tal_t *ctx UNNEEDED,
				  struct command *cmd UNNEEDED,
				  struct bkpr *bkpr UNNEEDED,
				  struct account *acct UNNEEDED)
{ fprintf(stderr, "update_channel_onchain_fees called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

static struct bkpr *make_bkpr(const tal_t *ctx,
			      const char *name, unsigned int minor_unit)
{
	struct bkpr *bkpr = tal(ctx, struct bkpr);
	struct iso4217_name_and_divisor *currency
		= tal(bkpr, struct iso4217_name_and_divisor);
	currency->name = name;
	currency->minor_unit = minor_unit;
	bkpr->currency = currency;
	bkpr->currency_rates = tal(bkpr, currencymap_t);
	uintmap_init(bkpr->currency_rates);
	return bkpr;
}

static void add_rate(struct bkpr *bkpr, u64 ts, u64 raw_rate, u32 duration)
{
	struct currencyrate *crate = tal(bkpr->currency_rates, struct currencyrate);
	crate->raw_rate = raw_rate;
	crate->duration = duration;
	uintmap_add(bkpr->currency_rates, ts, crate);
}

/* USD at $50,000/BTC: raw_rate = 5,000,000 cents/BTC, minor_unit=2, mul=100 */
#define USD_RATE 5000000ULL
#define TS 1000ULL

/* No rate added yet -> returns NULL */
static void test_no_rate(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	assert(currencyrate_str(ctx, bkpr, TS, NULL) == NULL);
	tal_free(bkpr);
}

/* Timestamp before any rate -> NULL */
static void test_before_rate(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	assert(currencyrate_str(ctx, bkpr, TS - 1, NULL) == NULL);
	tal_free(bkpr);
}

/* Timestamp after rate expiry -> NULL */
static void test_expired_rate(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	assert(currencyrate_str(ctx, bkpr, TS + 3600, NULL) == NULL);
	tal_free(bkpr);
}

/* NULL msat: returns the rate for 1 BTC directly, no msat math */
static void test_null_msat(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	/* raw_rate=5,000,000; intpart=50000, fracpart=0 */
	assert(streq(currencyrate_str(ctx, bkpr, TS + 1, NULL), "50000.00"));
	tal_free(bkpr);
}

/* 1 BTC: should match the NULL-msat rate */
static void test_one_btc(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	/* MSAT_PER_BTC * 5,000,000 / MSAT_PER_BTC = 5,000,000 -> "50000.00" */
	struct amount_msat msat = amount_msat(MSAT_PER_BTC);
	assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "50000.00"));
	tal_free(bkpr);
}

/* 0.01 BTC = 1e9 msat: 1e9 * 5e6 / 1e11 = 50,000 cents -> "500.00" */
static void test_fractional_btc(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	struct amount_msat msat = amount_msat(MSAT_PER_BTC / 100);
	assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "500.00"));
	tal_free(bkpr);
}

/* 100 sat = 100,000 msat: 100,000 * 5,000,000 / 1e11 = 5 cents -> "0.05" */
static void test_small_amount(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	struct amount_msat msat = amount_msat(100 * MSAT_PER_SAT);
	assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "0.05"));
	tal_free(bkpr);
}

/* Overflow: msat > rate.  2000 BTC = 2e14 msat.
 * 2e14 * 5e6 = 1e21 > u64_max, so we divide msat first:
 * (2e14 / 1e11) * 5e6 = 2000 * 5,000,000 = 1e10 cents -> "100000000.00" */
static void test_overflow_msat_larger(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
	add_rate(bkpr, TS, USD_RATE, 3600);
	struct amount_msat msat = amount_msat(2000 * MSAT_PER_BTC);
	assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "100000000.00"));
	tal_free(bkpr);
}

/* Overflow: rate > msat.  Hypothetical 0-decimal currency at 5e12 units/BTC.
 * 20,000 sat = 2e7 msat; 2e7 * 5e12 = 1e20 > u64_max, so divide rate first:
 * 2e7 * (5e12 / 1e11) = 2e7 * 50 = 1e9 -> "1000000000" */
static void test_overflow_rate_larger(const tal_t *ctx)
{
	struct bkpr *bkpr = make_bkpr(ctx, "BIG", 0);
	add_rate(bkpr, TS, 5000000000000ULL, 3600);
	struct amount_msat msat = amount_msat(20000 * MSAT_PER_SAT);
	assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "1000000000"));
	tal_free(bkpr);
}

int main(int argc, char *argv[])
{
	const tal_t *ctx = tal(NULL, char);

	common_setup(argv[0]);

	test_no_rate(ctx);
	test_before_rate(ctx);
	test_expired_rate(ctx);
	test_null_msat(ctx);
	test_one_btc(ctx);
	test_fractional_btc(ctx);
	test_small_amount(ctx);
	test_overflow_msat_larger(ctx);
	test_overflow_rate_larger(ctx);

	tal_free(ctx);
	common_shutdown();
}
