cln/plugins/bkpr/test/run-parse_report.c
Rusty Russell 90b861076d bkpr-report: enhance tag syntax from {tag:replacement} to {tag?if-set:if-not-set}
This is more powerful, but we need to do this before release.  It lets
us do things like: {credit?+{credit}:0} to only put a `+` in front of
no-zero credit, and {txid?TXID {txid}:Outpoint {outpoint}} to print
either `TXID xxx` or `Outpoint xxx`.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 14:54:20 +10:00

288 lines
8.9 KiB
C

#include "config.h"
#include "../report.c"
#include <common/setup.h>
#include <common/utils.h>
#include <stdio.h>
/* AUTOGENERATED MOCKS START */
/* Generated stub for bkpr_of */
struct bkpr *bkpr_of(struct plugin *plugin UNNEEDED)
{ fprintf(stderr, "bkpr_of 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_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_usage_only */
bool command_usage_only(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_usage_only called!\n"); abort(); }
/* Generated stub for currencyrate_str */
const char *currencyrate_str(const tal_t *ctx UNNEEDED,
const struct bkpr *bkpr UNNEEDED,
u64 timestamp UNNEEDED,
const struct amount_msat *msat UNNEEDED)
{ fprintf(stderr, "currencyrate_str 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_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(); }
/* AUTOGENERATED MOCKS END */
static void test_parse_report_format_simple(void)
{
struct report_format *f;
const char *err, *start = "hello {tag} world";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(f);
assert(streq(start, ""));
assert(tal_count(f->fmt) == 3);
assert(f->fmt[0] == NULL);
assert(streq(f->str[0], "hello "));
assert(f->ifset[0] == NULL);
assert(f->ifnotset[0] == NULL);
assert(f->fmt[1] == report_fmt_tag);
assert(f->str[1] == NULL);
assert(f->ifset[1] == NULL);
assert(f->ifnotset[1] == NULL);
assert(f->fmt[2] == NULL);
assert(streq(f->str[2], " world"));
assert(f->ifset[2] == NULL);
assert(f->ifnotset[2] == NULL);
}
static void test_parse_report_format_escaped_open_brace(void)
{
struct report_format *f;
const char *err, *start = "a{{b";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(f);
assert(streq(start, ""));
assert(tal_count(f->fmt) == 2);
assert(f->fmt[0] == NULL);
assert(f->fmt[1] == NULL);
assert(tal_count(f->str) == 2);
assert(streq(f->str[0], "a{"));
assert(streq(f->str[1], "b"));
assert(tal_count(f->ifnotset) == 2);
assert(f->ifnotset[0] == NULL);
assert(f->ifnotset[1] == NULL);
}
/* {description:NONE} => description, or NONE if absent */
static void test_parse_report_format_ifnotset_simple(void)
{
struct report_format *f;
const char *err, *start = "{description:NONE}";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(f);
assert(streq(start, ""));
assert(tal_count(f->fmt) == 1);
assert(f->fmt[0] == report_fmt_desc);
assert(f->str[0] == NULL);
assert(f->ifset[0] == NULL);
assert(f->ifnotset[0] != NULL);
assert(tal_count(f->ifnotset[0]->fmt) == 1);
assert(f->ifnotset[0]->fmt[0] == NULL);
assert(streq(f->ifnotset[0]->str[0], "NONE"));
assert(f->ifnotset[0]->ifnotset[0] == NULL);
}
/* {description:{txid:{outpoint:NONE}}} => nested if-not-set */
static void test_parse_report_format_ifnotset_nested(void)
{
struct report_format *f, *a1, *a2;
const char *err, *start = "{description:{txid:{outpoint:NONE}}}";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(f);
assert(streq(start, ""));
assert(tal_count(f->fmt) == 1);
assert(f->fmt[0] == report_fmt_desc);
assert(f->str[0] == NULL);
assert(f->ifset[0] == NULL);
assert(f->ifnotset[0] != NULL);
a1 = f->ifnotset[0];
assert(tal_count(a1->fmt) == 1);
assert(a1->fmt[0] == report_fmt_txid);
assert(a1->str[0] == NULL);
assert(a1->ifset[0] == NULL);
assert(a1->ifnotset[0] != NULL);
a2 = a1->ifnotset[0];
assert(tal_count(a2->fmt) == 1);
assert(a2->fmt[0] == report_fmt_outpoint);
assert(a2->str[0] == NULL);
assert(a2->ifset[0] == NULL);
assert(a2->ifnotset[0] != NULL);
assert(tal_count(a2->ifnotset[0]->fmt) == 1);
assert(a2->ifnotset[0]->fmt[0] == NULL);
assert(streq(a2->ifnotset[0]->str[0], "NONE"));
assert(a2->ifnotset[0]->ifnotset[0] == NULL);
}
/* {description:{txid}X}Y => if-not-set with suffix */
static void test_parse_report_format_ifnotset_with_suffix(void)
{
struct report_format *f, *a1;
const char *err, *start = "{description:{txid}X}Y";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(f);
assert(streq(start, ""));
assert(tal_count(f->fmt) == 2);
assert(f->fmt[0] == report_fmt_desc);
assert(f->ifset[0] == NULL);
assert(f->ifnotset[0] != NULL);
assert(f->fmt[1] == NULL);
assert(streq(f->str[1], "Y"));
a1 = f->ifnotset[0];
assert(tal_count(a1->fmt) == 2);
assert(a1->fmt[0] == report_fmt_txid);
assert(a1->ifnotset[0] == NULL);
assert(a1->fmt[1] == NULL);
assert(streq(a1->str[1], "X"));
}
/* {credit?+{credit}:0} => if-set and if-not-set */
static void test_parse_report_format_ifset_and_ifnotset(void)
{
struct report_format *f, *s1;
const char *err, *start = "{credit?+{credit}:0}";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(f);
assert(streq(start, ""));
assert(tal_count(f->fmt) == 1);
assert(f->fmt[0] == report_fmt_credit);
assert(f->str[0] == NULL);
assert(f->ifset[0] != NULL);
assert(f->ifnotset[0] != NULL);
/* if-set: "+{credit}" */
s1 = f->ifset[0];
assert(tal_count(s1->fmt) == 2);
assert(s1->fmt[0] == NULL);
assert(streq(s1->str[0], "+"));
assert(s1->fmt[1] == report_fmt_credit);
assert(s1->ifset[1] == NULL);
assert(s1->ifnotset[1] == NULL);
/* if-not-set: "0" */
assert(tal_count(f->ifnotset[0]->fmt) == 1);
assert(f->ifnotset[0]->fmt[0] == NULL);
assert(streq(f->ifnotset[0]->str[0], "0"));
}
static void test_parse_report_format_unknown_tag(void)
{
struct report_format *f;
const char *err, *start = "{nope}";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(!f);
assert(err);
assert(strstr(err, "Unknown tag nope"));
}
static void test_parse_report_format_unterminated_tag(void)
{
struct report_format *f;
const char *err, *start = "{description";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(!f);
assert(err);
assert(strstr(err, "Unterminated tag"));
}
static void test_parse_report_format_unterminated_nested_alt(void)
{
struct report_format *f;
const char *err, *start = "{description:{txid}";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(!f);
assert(err);
assert(strstr(err, "Unterminated tag"));
}
static void test_parse_report_format_unknown_nested_tag(void)
{
struct report_format *f;
const char *err, *start = "{description:{nope}}";
f = parse_report_format(tmpctx, &start, '\0', '\0', &err);
assert(!f);
assert(err);
assert(strstr(err, "Unknown tag nope"));
}
int main(int argc, char *argv[])
{
common_setup(argv[0]);
test_parse_report_format_simple();
test_parse_report_format_escaped_open_brace();
test_parse_report_format_ifnotset_simple();
test_parse_report_format_ifnotset_nested();
test_parse_report_format_ifnotset_with_suffix();
test_parse_report_format_ifset_and_ifnotset();
test_parse_report_format_unknown_tag();
test_parse_report_format_unterminated_tag();
test_parse_report_format_unterminated_nested_alt();
test_parse_report_format_unknown_nested_tag();
common_shutdown();
}