Skip to content

Commit 0988024

Browse files
test: add unit tests for match filter, preprocess, and partition
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
1 parent 3e4bf13 commit 0988024

3 files changed

Lines changed: 648 additions & 0 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* Unit tests for syslog_build_match_filter().
7+
*
8+
* The function returns ['sql' => string, 'params' => array] and is the
9+
* central WHERE-clause builder used by removal, alert, and report code
10+
* paths. These tests cover every match type, the sql trust boundary,
11+
* edge inputs, and special characters.
12+
*/
13+
14+
require_once __DIR__ . '/../Helpers/GlobalStubs.php';
15+
require_once __DIR__ . '/../../functions.php';
16+
17+
beforeEach(function (): void {
18+
$GLOBALS['syslogdb_default'] = 'cacti_syslog';
19+
});
20+
21+
describe('syslog_build_match_filter — string equality types', function (): void {
22+
test('facility type: plain column uses equality placeholder', function (): void {
23+
$r = syslog_build_match_filter('facility', 'local0');
24+
expect($r['sql'])->toBe('facility = ?');
25+
expect($r['params'])->toBe(['local0']);
26+
});
27+
28+
test('facility type: facility_id column uses subquery', function (): void {
29+
$r = syslog_build_match_filter('facility', 'local0', 'facility_id');
30+
expect($r['sql'])->toContain('facility_id IN (SELECT DISTINCT facility_id FROM');
31+
expect($r['sql'])->toContain('syslog_facilities');
32+
expect($r['sql'])->toContain('WHERE facility = ?');
33+
expect($r['params'])->toBe(['local0']);
34+
});
35+
36+
test('host type: plain column uses equality placeholder', function (): void {
37+
$r = syslog_build_match_filter('host', '10.0.0.1');
38+
expect($r['sql'])->toBe('host = ?');
39+
expect($r['params'])->toBe(['10.0.0.1']);
40+
});
41+
42+
test('host type: host_id column uses subquery', function (): void {
43+
$r = syslog_build_match_filter('host', '10.0.0.1', 'host_id');
44+
expect($r['sql'])->toContain('host_id IN (SELECT DISTINCT host_id FROM');
45+
expect($r['sql'])->toContain('syslog_hosts');
46+
expect($r['sql'])->toContain('WHERE host = ?');
47+
expect($r['params'])->toBe(['10.0.0.1']);
48+
});
49+
50+
test('program type: plain column uses equality placeholder', function (): void {
51+
$r = syslog_build_match_filter('program', 'sshd');
52+
expect($r['sql'])->toBe('program = ?');
53+
expect($r['params'])->toBe(['sshd']);
54+
});
55+
56+
test('program type: program_id column uses subquery', function (): void {
57+
$r = syslog_build_match_filter('program', 'sshd', 'program_id');
58+
expect($r['sql'])->toContain('program_id IN (SELECT DISTINCT program_id FROM');
59+
expect($r['sql'])->toContain('syslog_programs');
60+
expect($r['sql'])->toContain('WHERE program = ?');
61+
expect($r['params'])->toBe(['sshd']);
62+
});
63+
});
64+
65+
describe('syslog_build_match_filter — substring match types', function (): void {
66+
test('messageb (starts with): appends trailing wildcard', function (): void {
67+
$r = syslog_build_match_filter('messageb', 'kernel:', 'message');
68+
expect($r['sql'])->toBe('message LIKE ?');
69+
expect($r['params'])->toBe(['kernel:%']);
70+
});
71+
72+
test('messagec (contains): wraps value in wildcards', function (): void {
73+
$r = syslog_build_match_filter('messagec', 'error', 'message');
74+
expect($r['sql'])->toBe('message LIKE ?');
75+
expect($r['params'])->toBe(['%error%']);
76+
});
77+
78+
test('messagee (ends with): prepends leading wildcard', function (): void {
79+
$r = syslog_build_match_filter('messagee', 'failed', 'message');
80+
expect($r['sql'])->toBe('message LIKE ?');
81+
expect($r['params'])->toBe(['%failed']);
82+
});
83+
84+
test('messageb uses default column when none supplied', function (): void {
85+
$r = syslog_build_match_filter('messageb', 'start');
86+
expect($r['sql'])->toBe('message LIKE ?');
87+
});
88+
89+
test('messagec uses explicit column override', function (): void {
90+
$r = syslog_build_match_filter('messagec', 'needle', 'logmessage');
91+
expect($r['sql'])->toBe('logmessage LIKE ?');
92+
expect($r['params'])->toBe(['%needle%']);
93+
});
94+
95+
test('messagee uses explicit column override', function (): void {
96+
$r = syslog_build_match_filter('messagee', 'tail', 'logtext');
97+
expect($r['sql'])->toBe('logtext LIKE ?');
98+
expect($r['params'])->toBe(['%tail']);
99+
});
100+
});
101+
102+
describe('syslog_build_match_filter — sql trust boundary', function (): void {
103+
/*
104+
* The 'sql' type is intentional: only Cacti administrators with console
105+
* access configure removal/alert rules, so arbitrary expressions are
106+
* permitted. The contract is that the raw value is wrapped in parens and
107+
* no params are bound.
108+
*/
109+
110+
test('sql type: wraps raw expression in parens, no bound params', function (): void {
111+
$r = syslog_build_match_filter('sql', 'host_id = 42 AND facility_id < 10');
112+
expect($r['sql'])->toBe('(host_id = 42 AND facility_id < 10)');
113+
expect($r['params'])->toBe([]);
114+
});
115+
116+
test('sql type: passes value through without escaping', function (): void {
117+
$expr = "message LIKE '%critical%'";
118+
$r = syslog_build_match_filter('sql', $expr);
119+
expect($r['sql'])->toBe('(' . $expr . ')');
120+
expect($r['params'])->toBe([]);
121+
});
122+
123+
test('sql type: empty expression wraps empty parens', function (): void {
124+
$r = syslog_build_match_filter('sql', '');
125+
expect($r['sql'])->toBe('()');
126+
expect($r['params'])->toBe([]);
127+
});
128+
});
129+
130+
describe('syslog_build_match_filter — empty and null-like values', function (): void {
131+
test('messageb with empty value produces trailing-wildcard-only param', function (): void {
132+
$r = syslog_build_match_filter('messageb', '');
133+
expect($r['params'])->toBe(['%']);
134+
});
135+
136+
test('messagec with empty value produces double-wildcard param', function (): void {
137+
$r = syslog_build_match_filter('messagec', '');
138+
expect($r['params'])->toBe(['%%']);
139+
});
140+
141+
test('messagee with empty value produces leading-wildcard-only param', function (): void {
142+
$r = syslog_build_match_filter('messagee', '');
143+
expect($r['params'])->toBe(['%']);
144+
});
145+
146+
test('host type with empty value still binds placeholder', function (): void {
147+
$r = syslog_build_match_filter('host', '');
148+
expect($r['sql'])->toBe('host = ?');
149+
expect($r['params'])->toBe(['']);
150+
});
151+
152+
test('unknown type returns empty sql and empty params', function (): void {
153+
$r = syslog_build_match_filter('nonexistent', 'value');
154+
expect($r['sql'])->toBe('');
155+
expect($r['params'])->toBe([]);
156+
});
157+
});
158+
159+
describe('syslog_build_match_filter — special characters in match values', function (): void {
160+
test('SQL metacharacters in messagec value are passed as literal param (not interpolated)', function (): void {
161+
$payload = "' OR '1'='1";
162+
$r = syslog_build_match_filter('messagec', $payload);
163+
// Value must appear verbatim in the param, not in the SQL fragment.
164+
expect($r['sql'])->not->toContain($payload);
165+
expect($r['params'][0])->toBe('%' . $payload . '%');
166+
});
167+
168+
test('backslash in messageb value is passed to param intact', function (): void {
169+
$r = syslog_build_match_filter('messageb', 'C:\\Windows\\');
170+
expect($r['params'][0])->toBe('C:\\Windows\\%');
171+
});
172+
173+
test('percent sign in messagec value is preserved in param', function (): void {
174+
$r = syslog_build_match_filter('messagec', '50% done');
175+
expect($r['params'][0])->toBe('%50% done%');
176+
});
177+
178+
test('underscore in messagee value is preserved in param', function (): void {
179+
$r = syslog_build_match_filter('messagee', 'auth_log');
180+
expect($r['params'][0])->toBe('%auth_log');
181+
});
182+
183+
test('host type with injection attempt binds raw value as param', function (): void {
184+
$payload = "1 OR 1=1--";
185+
$r = syslog_build_match_filter('host', $payload);
186+
expect($r['sql'])->toBe('host = ?');
187+
expect($r['params'])->toBe([$payload]);
188+
});
189+
190+
test('subquery path: facility_id with special characters binds raw value as param', function (): void {
191+
$payload = "'; DROP TABLE syslog;--";
192+
$r = syslog_build_match_filter('facility', $payload, 'facility_id');
193+
expect($r['sql'])->toContain('WHERE facility = ?');
194+
expect($r['params'])->toBe([$payload]);
195+
});
196+
});

0 commit comments

Comments
 (0)