LCOV - code coverage report
Current view: top level - tests/unit - test_when_expr.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 232 232
Test Date: 2026-05-07 15:53:07 Functions: 100.0 % 34 34

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "when_expr.h"
       3              : #include "raii.h"
       4              : #include <stdlib.h>
       5              : #include <string.h>
       6              : #include <time.h>
       7              : 
       8              : /* ── when_parse tests ──────────────────────────────────────────────── */
       9              : 
      10            1 : static void test_parse_null_empty(void) {
      11            1 :     ASSERT(when_parse(NULL) == NULL, "NULL expr → NULL");
      12            1 :     ASSERT(when_parse("") == NULL,   "empty expr → NULL");
      13            1 :     ASSERT(when_parse("   ") == NULL, "whitespace-only → NULL");
      14              : }
      15              : 
      16            1 : static void test_parse_single_atom(void) {
      17            1 :     WhenNode *n = when_parse("from:*@github.com");
      18            1 :     ASSERT(n != NULL, "single from atom parses");
      19            1 :     ASSERT(n->type == WN_FROM, "node type is FROM");
      20            1 :     ASSERT(strcmp(n->pattern, "*@github.com") == 0, "pattern correct");
      21            1 :     when_node_free(n);
      22              : }
      23              : 
      24            1 : static void test_parse_all_fields(void) {
      25              :     WhenNode *n;
      26              : 
      27            1 :     n = when_parse("to:*boss*");
      28            1 :     ASSERT(n && n->type == WN_TO, "to: field");
      29            1 :     when_node_free(n);
      30              : 
      31            1 :     n = when_parse("subject:*urgent*");
      32            1 :     ASSERT(n && n->type == WN_SUBJECT, "subject: field");
      33            1 :     when_node_free(n);
      34              : 
      35            1 :     n = when_parse("label:UNREAD");
      36            1 :     ASSERT(n && n->type == WN_LABEL, "label: field");
      37            1 :     when_node_free(n);
      38              : 
      39            1 :     n = when_parse("body:*password*");
      40            1 :     ASSERT(n && n->type == WN_BODY, "body: field");
      41            1 :     when_node_free(n);
      42              : 
      43            1 :     n = when_parse("age-gt:30");
      44            1 :     ASSERT(n && n->type == WN_AGE_GT && n->age_val == 30, "age-gt: field");
      45            1 :     when_node_free(n);
      46              : 
      47            1 :     n = when_parse("age-lt:7");
      48            1 :     ASSERT(n && n->type == WN_AGE_LT && n->age_val == 7, "age-lt: field");
      49            1 :     when_node_free(n);
      50              : }
      51              : 
      52            1 : static void test_parse_not(void) {
      53            1 :     WhenNode *n = when_parse("!from:*spam*");
      54            1 :     ASSERT(n != NULL, "NOT parses");
      55            1 :     ASSERT(n->type == WN_NOT, "root is NOT");
      56            1 :     ASSERT(n->left != NULL && n->left->type == WN_FROM, "child is FROM");
      57            1 :     when_node_free(n);
      58              : }
      59              : 
      60            1 : static void test_parse_and(void) {
      61            1 :     WhenNode *n = when_parse("from:*@a.hu* and subject:*news*");
      62            1 :     ASSERT(n != NULL, "AND parses");
      63            1 :     ASSERT(n->type == WN_AND, "root is AND");
      64            1 :     ASSERT(n->left->type == WN_FROM, "left is FROM");
      65            1 :     ASSERT(n->right->type == WN_SUBJECT, "right is SUBJECT");
      66            1 :     when_node_free(n);
      67              : }
      68              : 
      69            1 : static void test_parse_or(void) {
      70            1 :     WhenNode *n = when_parse("from:*@a.hu* or from:*@b.hu*");
      71            1 :     ASSERT(n != NULL, "OR parses");
      72            1 :     ASSERT(n->type == WN_OR, "root is OR");
      73            1 :     when_node_free(n);
      74              : }
      75              : 
      76            1 : static void test_parse_precedence(void) {
      77              :     /* "A or B and C" → "A or (B and C)" */
      78            1 :     WhenNode *n = when_parse("from:*@a.hu* or from:*@b.hu* and !label:UNREAD");
      79            1 :     ASSERT(n != NULL, "complex expr parses");
      80            1 :     ASSERT(n->type == WN_OR, "root is OR (lower prec)");
      81            1 :     ASSERT(n->right->type == WN_AND, "right subtree is AND");
      82            1 :     ASSERT(n->right->right->type == WN_NOT, "NOT binds tightest");
      83            1 :     when_node_free(n);
      84              : }
      85              : 
      86            1 : static void test_parse_parens(void) {
      87            1 :     WhenNode *n = when_parse("(from:*@a.hu* or from:*@b.hu*) and !label:UNREAD");
      88            1 :     ASSERT(n != NULL, "parens expr parses");
      89            1 :     ASSERT(n->type == WN_AND, "parens override: root is AND");
      90            1 :     ASSERT(n->left->type == WN_OR, "left is OR group");
      91            1 :     when_node_free(n);
      92              : }
      93              : 
      94            1 : static void test_parse_double_not(void) {
      95            1 :     WhenNode *n = when_parse("!!from:*@a.hu*");
      96            1 :     ASSERT(n != NULL, "double NOT parses");
      97            1 :     ASSERT(n->type == WN_NOT, "outer NOT");
      98            1 :     ASSERT(n->left->type == WN_NOT, "inner NOT");
      99            1 :     when_node_free(n);
     100              : }
     101              : 
     102            1 : static void test_parse_error_unknown_field(void) {
     103            1 :     WhenNode *n = when_parse("unknownfield:*x*");
     104            1 :     ASSERT(n == NULL, "unknown field → parse error → NULL");
     105              : }
     106              : 
     107            1 : static void test_parse_error_missing_rparen(void) {
     108            1 :     WhenNode *n = when_parse("(from:*@a.hu*");
     109            1 :     ASSERT(n == NULL, "missing ) → parse error → NULL");
     110              : }
     111              : 
     112              : /* ── when_eval tests ──────────────────────────────────────────────── */
     113              : 
     114            1 : static void test_eval_null_ast_always_matches(void) {
     115            1 :     int r = when_eval(NULL, "a@b.com", "Hi", NULL, NULL, NULL, 0);
     116            1 :     ASSERT(r == 1, "NULL AST matches everything");
     117              : }
     118              : 
     119            1 : static void test_eval_from(void) {
     120            1 :     WhenNode *n = when_parse("from:*@github.com");
     121            1 :     ASSERT(n != NULL, "parse ok");
     122            1 :     ASSERT(when_eval(n, "notifications@github.com", "PR", NULL, NULL, NULL, 0) == 1, "from match");
     123            1 :     ASSERT(when_eval(n, "user@gmail.com",           "PR", NULL, NULL, NULL, 0) == 0, "from no match");
     124            1 :     when_node_free(n);
     125              : }
     126              : 
     127            1 : static void test_eval_subject(void) {
     128            1 :     WhenNode *n = when_parse("subject:*URGENT*");
     129            1 :     ASSERT(n != NULL, "parse ok");
     130            1 :     ASSERT(when_eval(n, NULL, "This is URGENT", NULL, NULL, NULL, 0) == 1, "subject match");
     131            1 :     ASSERT(when_eval(n, NULL, "Normal mail",    NULL, NULL, NULL, 0) == 0, "subject no match");
     132            1 :     when_node_free(n);
     133              : }
     134              : 
     135            1 : static void test_eval_label(void) {
     136            1 :     WhenNode *n = when_parse("label:UNREAD");
     137            1 :     ASSERT(n != NULL, "parse ok");
     138            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, "INBOX,UNREAD",  NULL, 0) == 1, "label present");
     139            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, "INBOX,GitHub",  NULL, 0) == 0, "label absent");
     140            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL,            NULL, 0) == 0, "NULL labels csv");
     141            1 :     when_node_free(n);
     142              : }
     143              : 
     144            1 : static void test_eval_not(void) {
     145            1 :     WhenNode *n = when_parse("!from:*@spam.com*");
     146            1 :     ASSERT(n != NULL, "parse ok");
     147            1 :     ASSERT(when_eval(n, "user@legit.org", NULL, NULL, NULL, NULL, 0) == 1, "NOT: non-spam matches");
     148            1 :     ASSERT(when_eval(n, "ad@spam.com",    NULL, NULL, NULL, NULL, 0) == 0, "NOT: spam rejected");
     149            1 :     when_node_free(n);
     150              : }
     151              : 
     152            1 : static void test_eval_and(void) {
     153            1 :     WhenNode *n = when_parse("from:*@a.hu* and subject:*news*");
     154            1 :     ASSERT(n != NULL, "parse ok");
     155            1 :     ASSERT(when_eval(n, "x@a.hu", "*news* report", NULL, NULL, NULL, 0) == 1, "AND: both match");
     156            1 :     ASSERT(when_eval(n, "x@a.hu", "hello",         NULL, NULL, NULL, 0) == 0, "AND: subj fails");
     157            1 :     ASSERT(when_eval(n, "x@b.hu", "*news*",        NULL, NULL, NULL, 0) == 0, "AND: from fails");
     158            1 :     when_node_free(n);
     159              : }
     160              : 
     161            1 : static void test_eval_or(void) {
     162            1 :     WhenNode *n = when_parse("from:*@a.hu* or from:*@b.hu*");
     163            1 :     ASSERT(n != NULL, "parse ok");
     164            1 :     ASSERT(when_eval(n, "x@a.hu", NULL, NULL, NULL, NULL, 0) == 1, "OR: first branch");
     165            1 :     ASSERT(when_eval(n, "x@b.hu", NULL, NULL, NULL, NULL, 0) == 1, "OR: second branch");
     166            1 :     ASSERT(when_eval(n, "x@c.hu", NULL, NULL, NULL, NULL, 0) == 0, "OR: neither");
     167            1 :     when_node_free(n);
     168              : }
     169              : 
     170            1 : static void test_eval_case_insensitive(void) {
     171            1 :     WhenNode *n = when_parse("from:*@GITHUB.com");
     172            1 :     ASSERT(n != NULL, "parse ok");
     173            1 :     ASSERT(when_eval(n, "notifications@github.com", NULL, NULL, NULL, NULL, 0) == 1,
     174              :            "case-insensitive match");
     175            1 :     when_node_free(n);
     176              : }
     177              : 
     178            1 : static void test_eval_age_gt(void) {
     179            1 :     WhenNode *n = when_parse("age-gt:10");
     180            1 :     ASSERT(n != NULL, "parse ok");
     181            1 :     time_t old  = time(NULL) - 15 * 86400;
     182            1 :     time_t fresh = time(NULL) - 5  * 86400;
     183            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, NULL, old)   == 1, "age > 10 days matches");
     184            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, NULL, fresh) == 0, "age < 10 days no match");
     185            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, NULL, 0)     == 0, "unknown date no match");
     186            1 :     when_node_free(n);
     187              : }
     188              : 
     189            1 : static void test_eval_age_lt(void) {
     190            1 :     WhenNode *n = when_parse("age-lt:7");
     191            1 :     ASSERT(n != NULL, "parse ok");
     192            1 :     time_t fresh = time(NULL) - 3 * 86400;
     193            1 :     time_t old   = time(NULL) - 9 * 86400;
     194            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, NULL, fresh) == 1, "age < 7 days matches");
     195            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, NULL, old)   == 0, "age > 7 days no match");
     196            1 :     when_node_free(n);
     197              : }
     198              : 
     199            1 : static void test_eval_body_null(void) {
     200            1 :     WhenNode *n = when_parse("body:*password*");
     201            1 :     ASSERT(n != NULL, "parse ok");
     202            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, "please enter your password", 0) == 1, "body match");
     203            1 :     ASSERT(when_eval(n, NULL, NULL, NULL, NULL, NULL, 0) == 0, "NULL body no match");
     204            1 :     when_node_free(n);
     205              : }
     206              : 
     207            1 : static void test_eval_complex(void) {
     208            1 :     WhenNode *n = when_parse("(from:*@a.hu* or from:*@b.hu*) and !label:UNREAD");
     209            1 :     ASSERT(n != NULL, "complex parse ok");
     210            1 :     ASSERT(when_eval(n, "x@a.hu", NULL, NULL, "INBOX",       NULL, 0) == 1, "from-a, no UNREAD");
     211            1 :     ASSERT(when_eval(n, "x@b.hu", NULL, NULL, "INBOX",       NULL, 0) == 1, "from-b, no UNREAD");
     212            1 :     ASSERT(when_eval(n, "x@a.hu", NULL, NULL, "INBOX,UNREAD",NULL, 0) == 0, "from-a, has UNREAD");
     213            1 :     ASSERT(when_eval(n, "x@c.hu", NULL, NULL, "INBOX",       NULL, 0) == 0, "other from");
     214            1 :     when_node_free(n);
     215              : }
     216              : 
     217              : /* ── when_from_flat tests ────────────────────────────────────────── */
     218              : 
     219            1 : static void test_from_flat_null_all(void) {
     220            1 :     char *w = when_from_flat(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0);
     221            1 :     ASSERT(w == NULL, "all-null flat → NULL");
     222              : }
     223              : 
     224            1 : static void test_from_flat_single(void) {
     225            2 :     RAII_STRING char *w = when_from_flat("*@github.com", NULL, NULL, NULL,
     226              :                                           NULL, NULL, NULL, NULL, 0, 0);
     227            1 :     ASSERT(w != NULL, "single from_flat not NULL");
     228            1 :     ASSERT(strcmp(w, "from:*@github.com") == 0, "single from_flat value");
     229              : }
     230              : 
     231            1 : static void test_from_flat_and_chain(void) {
     232            2 :     RAII_STRING char *w = when_from_flat("*@a.hu*", "*news*", NULL, NULL,
     233              :                                           NULL, NULL, NULL, NULL, 0, 0);
     234            1 :     ASSERT(w != NULL, "and-chain not NULL");
     235            1 :     ASSERT(strcmp(w, "from:*@a.hu* and subject:*news*") == 0, "and-chain value");
     236              : }
     237              : 
     238            1 : static void test_from_flat_negated(void) {
     239            2 :     RAII_STRING char *w = when_from_flat(NULL, NULL, NULL, NULL,
     240              :                                           "*@spam.com*", NULL, NULL, NULL, 0, 0);
     241            1 :     ASSERT(w != NULL, "negated from_flat not NULL");
     242            1 :     ASSERT(strcmp(w, "!from:*@spam.com*") == 0, "negated value");
     243              : }
     244              : 
     245            1 : static void test_from_flat_age(void) {
     246            2 :     RAII_STRING char *w = when_from_flat(NULL, NULL, NULL, NULL,
     247              :                                           NULL, NULL, NULL, NULL, 30, 0);
     248            1 :     ASSERT(w != NULL, "age-gt flat not NULL");
     249            1 :     ASSERT(strcmp(w, "age-gt:30") == 0, "age-gt value");
     250              : }
     251              : 
     252              : /* ── when_from_conds tests ──────────────────────────────────────── */
     253              : 
     254            1 : static void test_from_conds_empty(void) {
     255            1 :     char *w = when_from_conds(NULL, 0, 1);
     256            1 :     ASSERT(w == NULL, "empty conds → NULL");
     257              : }
     258              : 
     259            1 : static void test_from_conds_or(void) {
     260            1 :     WhenCond cs[] = {
     261              :         { "from", "*@a.hu*", 0 },
     262              :         { "from", "*@b.hu*", 0 },
     263              :         { "from", "*@c.hu*", 0 },
     264              :     };
     265            2 :     RAII_STRING char *w = when_from_conds(cs, 3, 1);
     266            1 :     ASSERT(w != NULL, "OR conds not NULL");
     267            1 :     ASSERT(strcmp(w, "from:*@a.hu* or from:*@b.hu* or from:*@c.hu*") == 0, "OR chain value");
     268              : }
     269              : 
     270            1 : static void test_from_conds_and(void) {
     271            1 :     WhenCond cs[] = {
     272              :         { "from",    "*@a.hu*", 0 },
     273              :         { "subject", "*news*",  0 },
     274              :     };
     275            2 :     RAII_STRING char *w = when_from_conds(cs, 2, 0);
     276            1 :     ASSERT(w != NULL, "AND conds not NULL");
     277            1 :     ASSERT(strcmp(w, "from:*@a.hu* and subject:*news*") == 0, "AND chain value");
     278              : }
     279              : 
     280            1 : static void test_from_conds_negated(void) {
     281            1 :     WhenCond cs[] = {
     282              :         { "from", "*@spam.com*", 1 },
     283              :     };
     284            2 :     RAII_STRING char *w = when_from_conds(cs, 1, 0);
     285            1 :     ASSERT(w != NULL, "negated cond not NULL");
     286            1 :     ASSERT(strcmp(w, "!from:*@spam.com*") == 0, "negated cond value");
     287              : }
     288              : 
     289              : /* Round-trip: build from_conds string, parse it, eval it */
     290            1 : static void test_roundtrip_or_eval(void) {
     291            1 :     WhenCond cs[] = {
     292              :         { "from", "*@a.hu*", 0 },
     293              :         { "from", "*@b.hu*", 0 },
     294              :     };
     295            2 :     RAII_STRING char *w = when_from_conds(cs, 2, 1);
     296            1 :     ASSERT(w != NULL, "roundtrip: conds → string ok");
     297            1 :     WhenNode *tree = when_parse(w);
     298            1 :     ASSERT(tree != NULL, "roundtrip: parse ok");
     299            1 :     ASSERT(when_eval(tree, "x@a.hu", NULL, NULL, NULL, NULL, 0) == 1, "roundtrip: a matches");
     300            1 :     ASSERT(when_eval(tree, "x@b.hu", NULL, NULL, NULL, NULL, 0) == 1, "roundtrip: b matches");
     301            1 :     ASSERT(when_eval(tree, "x@c.hu", NULL, NULL, NULL, NULL, 0) == 0, "roundtrip: c no match");
     302            1 :     when_node_free(tree);
     303              : }
     304              : 
     305              : /* ── Registration ───────────────────────────────────────────────── */
     306              : 
     307            1 : void test_when_expr(void) {
     308            1 :     RUN_TEST(test_parse_null_empty);
     309            1 :     RUN_TEST(test_parse_single_atom);
     310            1 :     RUN_TEST(test_parse_all_fields);
     311            1 :     RUN_TEST(test_parse_not);
     312            1 :     RUN_TEST(test_parse_and);
     313            1 :     RUN_TEST(test_parse_or);
     314            1 :     RUN_TEST(test_parse_precedence);
     315            1 :     RUN_TEST(test_parse_parens);
     316            1 :     RUN_TEST(test_parse_double_not);
     317            1 :     RUN_TEST(test_parse_error_unknown_field);
     318            1 :     RUN_TEST(test_parse_error_missing_rparen);
     319              : 
     320            1 :     RUN_TEST(test_eval_null_ast_always_matches);
     321            1 :     RUN_TEST(test_eval_from);
     322            1 :     RUN_TEST(test_eval_subject);
     323            1 :     RUN_TEST(test_eval_label);
     324            1 :     RUN_TEST(test_eval_not);
     325            1 :     RUN_TEST(test_eval_and);
     326            1 :     RUN_TEST(test_eval_or);
     327            1 :     RUN_TEST(test_eval_case_insensitive);
     328            1 :     RUN_TEST(test_eval_age_gt);
     329            1 :     RUN_TEST(test_eval_age_lt);
     330            1 :     RUN_TEST(test_eval_body_null);
     331            1 :     RUN_TEST(test_eval_complex);
     332              : 
     333            1 :     RUN_TEST(test_from_flat_null_all);
     334            1 :     RUN_TEST(test_from_flat_single);
     335            1 :     RUN_TEST(test_from_flat_and_chain);
     336            1 :     RUN_TEST(test_from_flat_negated);
     337            1 :     RUN_TEST(test_from_flat_age);
     338              : 
     339            1 :     RUN_TEST(test_from_conds_empty);
     340            1 :     RUN_TEST(test_from_conds_or);
     341            1 :     RUN_TEST(test_from_conds_and);
     342            1 :     RUN_TEST(test_from_conds_negated);
     343            1 :     RUN_TEST(test_roundtrip_or_eval);
     344            1 : }
        

Generated by: LCOV version 2.0-1