aboutsummaryrefslogtreecommitdiff
path: root/tests/invoice_check.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-20 15:08:58 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-20 15:08:58 +0200
commitfb21e8efa65662dcb6c2cc7ed693dea91888c3a7 (patch)
tree43f0662b2c61dd596e2be78fdd107463e8aa9591 /tests/invoice_check.c
parent480640a747b81cb39deb1ec24e6e199a9c837fbe (diff)
downloadbokf-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.c205
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;
+}