toBe(['traffic_in', 'traffic_out']) ->and($fetch['timestamp'])->toBe([ 'start_time' => 1700000400, 'end_time' => 1700001000, 'step' => 300, ]) ->and(array_keys($fetch['values'][0]))->toBe([1700000400, 1700000700, 1700001000]) ->and($fetch['values'][1][1700001000])->toBe('6.0') ->and($fetch['values'][0])->not->toHaveKey(1700001300); }); test('fetch output handles unknown, malformed, sparse, and empty rows safely', function () { $output = "value\n" . "not-a-time: 9\n" . "1700000400: NaN\n" . "malformed row\n" . "1700001000: -NaN\n"; $with_unknown = rrdtool_parse_fetch_output($output, 1700001000, true); $without = rrdtool_parse_fetch_output($output, 1700001000); $empty = rrdtool_parse_fetch_output("\n\n", 1700001000); expect($with_unknown['values'][0])->toBe([ 1700000400 => 'U', 1700001000 => 'U', ]) ->and($with_unknown['timestamp']['step'])->toBe(600) ->and($without)->not->toHaveKey('values') ->and($empty)->toBe([]); }); test('a single in-window row derives its observed step from the excluded trailing row', function () { $fetch = rrdtool_parse_fetch_output("value\n1700000400: 1\n1700000700: 2\n", 1700000400); expect($fetch['timestamp'])->toBe([ 'start_time' => 1700000400, 'end_time' => 1700000400, 'step' => 300, ]); });