diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-20 15:08:58 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-20 15:08:58 +0200 |
| commit | fb21e8efa65662dcb6c2cc7ed693dea91888c3a7 (patch) | |
| tree | 43f0662b2c61dd596e2be78fdd107463e8aa9591 /tests/invoice_check.c | |
| parent | 480640a747b81cb39deb1ec24e6e199a9c837fbe (diff) | |
| download | bokf-fb21e8efa65662dcb6c2cc7ed693dea91888c3a7.tar.gz bokf-fb21e8efa65662dcb6c2cc7ed693dea91888c3a7.zip | |
invoice: render the document as PDF
Diffstat (limited to 'tests/invoice_check.c')
| -rw-r--r-- | tests/invoice_check.c | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/tests/invoice_check.c b/tests/invoice_check.c new file mode 100644 index 0000000..46b1621 --- /dev/null +++ b/tests/invoice_check.c @@ -0,0 +1,205 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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" }, + { "A-1", "Konsult", 12500, "tim", 80000, 1000000, "Omvänd moms", "rc" }, +}; + +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" }, + { "", "b", 1000, "st", 1000, 50000, "", "12" }, + { "", "c", 1000, "st", 1000, 10000, "", "6" }, + { "", "d", 1000, "st", 1000, 25000, "", "rc" }, + { "", "e", 1000, "st", 1000, 15000, "", "0" }, + { "", "f", 1000, "st", 1000, 4000, "", "eu" }, + }; + 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" }, + }; + + 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; +} |
