#include #include #include #include "invoice.h" static int failures; static void check(int cond, const char *what) { if (!cond) { printf("FAIL %s\n", what); failures++; } else { printf("ok %s\n", what); } } static void eq64(const char *what, int64_t got, int64_t want) { if (got != want) { printf("FAIL %s: got %lld want %lld\n", what, (long long)got, (long long)want); failures++; } else { printf("ok %s: %lld\n", what, (long long)got); } } static const struct invoice_line synthetic_lines[] = { { "", "Utvecklingsarbete\nPeriod 2026-01-01 tom 2026-01-31", 61000, "tim", 120000, 7320000, "", "25", 0 }, { "A-1", "Konsult", 12500, "tim", 80000, 1000000, "Omvänd moms", "rc", 0 }, }; static void make_doc(struct invoice_doc *d) { memset(d, 0, sizeof *d); d->seller.name = "Testleverantör AB"; d->seller.address = "Testvägen 1"; d->seller.postal_code = "12345"; d->seller.city = "Teststad"; d->seller.phone = "+46(0)70 - 00 00 000"; d->seller.email = "test@example.invalid"; d->seller.org_nr = "559000-0000"; d->seller.vat_nr = "SE559000000001"; d->seller.bankgiro = "0000-0000"; d->customer.name = "Testkund AB"; d->customer.address = "Kundgatan 2"; d->customer.postal_code = "54321"; d->customer.city = "Kundstad"; d->customer.vat_nr = "SE556000000001"; d->number = 1001; d->ocr = "100102"; d->invoice_date = "2026-02-01"; d->due_date = "2026-03-03"; d->delivery_date = "2026-02-01"; d->our_ref = "Test Person"; d->your_ref = "Kund Person"; d->payment_days = 30; d->lines = synthetic_lines; d->nlines = 2; } static void test_totals(void) { static const struct invoice_line lines[] = { { "", "a", 1000, "st", 1000, 100000, "", "25", 0 }, { "", "b", 1000, "st", 1000, 50000, "", "12", 0 }, { "", "c", 1000, "st", 1000, 10000, "", "6", 0 }, { "", "d", 1000, "st", 1000, 25000, "", "rc", 0 }, { "", "e", 1000, "st", 1000, 15000, "", "0", 0 }, { "", "f", 1000, "st", 1000, 4000, "", "eu", 0 }, }; struct invoice_doc d; struct invoice_totals t; make_doc(&d); d.lines = lines; d.nlines = sizeof lines / sizeof lines[0]; invoice_totals(&d, &t); eq64("net", t.net_ore, 204000); eq64("vat", t.vat_ore, 31600); eq64("total", t.total_ore, 235600); eq64("net 25", t.net_25, 100000); eq64("net 12", t.net_12, 50000); eq64("net 6", t.net_6, 10000); eq64("net free", t.net_free, 44000); { static const struct invoice_line half[] = { { "", "half", 1000, "st", 100, 50, "", "25", 0 }, }; d.lines = half; d.nlines = 1; invoice_totals(&d, &t); eq64("vat half up", t.vat_ore, 13); } invoice_totals(NULL, &t); eq64("null doc net", t.net_ore, 0); eq64("null doc total", t.total_ore, 0); invoice_totals(&d, NULL); check(1, "null totals accepted"); } static void test_ocr(void) { check(invoice_ocr_check("7992739871") == 3, "luhn canonical"); check(invoice_ocr_check("804") == 5, "luhn 804"); check(invoice_ocr_check("13006") == 2, "luhn 13006"); check(invoice_ocr_check("23506") == 9, "luhn 23506"); check(invoice_ocr_check("0") == 0, "luhn zero"); check(invoice_ocr_check("") == 0, "luhn empty"); check(invoice_ocr_check("12a") == -1, "luhn non-digit"); check(invoice_ocr_check(NULL) == -1, "luhn null"); } static int contains(const unsigned char *data, size_t len, const char *needle) { size_t n = strlen(needle); if (n > len) return 0; for (size_t i = 0; i + n <= len; i++) if (memcmp(data + i, needle, n) == 0) return 1; return 0; } static void test_render(const char *path) { struct invoice_doc d; struct invoice_totals t; unsigned char *data = NULL; size_t len = 0; make_doc(&d); invoice_totals(&d, &t); eq64("synthetic net", t.net_ore, 8320000); eq64("synthetic vat", t.vat_ore, 1830000); eq64("synthetic total", t.total_ore, 10150000); check(invoice_render_pdf(&d, &data, &len) == 0, "render returns 0"); check(data != NULL && len > 1000, "render bytes"); if (data) { check(memcmp(data, "%PDF-1.4", 8) == 0, "render header"); check(contains(data, len, "Testleverant"), "render seller text"); check(contains(data, len, "Testkund AB"), "render customer text"); check(contains(data, len, "Summa att betala SEK"), "render summary"); check(contains(data, len, "Test Person"), "render our ref"); } if (path && data) { FILE *f = fopen(path, "wb"); if (!f) { printf("FAIL cannot write %s\n", path); failures++; } else { fwrite(data, 1, len, f); fclose(f); printf("wrote %s (%zu bytes)\n", path, len); } } free(data); { unsigned char *bad = NULL; size_t n = 0; check(invoice_render_pdf(NULL, &bad, &n) == -1, "render null doc"); check(bad == NULL && n == 0, "render null clears out"); check(invoice_render_pdf(&d, NULL, &n) == -1, "render null out"); check(invoice_render_pdf(&d, &bad, NULL) == -1, "render null len"); d.lines = NULL; d.nlines = 1; check(invoice_render_pdf(&d, &bad, &n) == -1, "render null lines"); } { struct invoice_doc many; static struct invoice_line rows[40]; unsigned char *bad = NULL; size_t n = 0; make_doc(&many); for (size_t i = 0; i < 40; i++) rows[i] = synthetic_lines[0]; many.lines = rows; many.nlines = 40; check(invoice_render_pdf(&many, &bad, &n) == -1, "render overflows"); } } int main(int argc, char **argv) { test_totals(); test_ocr(); test_render(argc > 1 ? argv[1] : NULL); if (failures) printf("%d failures\n", failures); else puts("all ok"); return failures ? 1 : 0; }