summaryrefslogtreecommitdiff
path: root/tests/invoice_check.c
blob: 01b5ca3faa73326039b32a23248eb74dd1fd2f33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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", 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;
}