not->toBeFalse('Failed to read lib/rrd.php'); $fnPos = strpos($src, 'function rrdtool_function_graph('); expect($fnPos)->not->toBeFalse('rrdtool_function_graph must exist in lib/rrd.php'); // Anchor on the integer-validation call by shape so a switch between // ctype_digit() and filter_var(FILTER_VALIDATE_INT) keeps passing. $matched = preg_match('/ctype_digit\s*\(|filter_var\s*\([^)]*FILTER_VALIDATE_INT/', $src, $m, PREG_OFFSET_CAPTURE, $fnPos); expect($matched)->toBe(1, 'integer validation of rra_id must exist inside rrdtool_function_graph'); $normPos = $m[0][1]; $boostPos = strpos($src, 'boost_graph_cache_check(', $fnPos); expect($boostPos)->not->toBeFalse('boost_graph_cache_check call must exist'); expect($normPos)->toBeLessThan($boostPos, 'validation must precede the first boost call'); }); test('normalization maps caller inputs to int or null', function () { // reports.php and graph_realtime.php pass '' expect(normalize_rra_id(''))->toBeNull(); // graph_image.php passes a validated numeric string expect(normalize_rra_id('3'))->toBe(3); // graphs.php debug passes int directly expect(normalize_rra_id(1))->toBe(1); expect(normalize_rra_id(null))->toBeNull(); expect(normalize_rra_id('all'))->toBeNull(); }); test('non-integer numerics fall back to best-fit instead of truncating', function () { expect(normalize_rra_id('1.5'))->toBeNull(); expect(normalize_rra_id('1e3'))->toBeNull(); expect(normalize_rra_id('-2'))->toBeNull(); expect(normalize_rra_id(' 3'))->toBeNull(); });