1700000000 1700003600 300 2 2 traffic_in traffic_out 17000000001234.567890.12 17000003002345.678901.23 XML; $result = rrdxport2array($xml); expect($result)->toBeArray() ->and($result['meta'])->toBeArray() ->and($result['meta']['start'])->toBe('1700000000') ->and($result['meta']['end'])->toBe('1700003600') ->and($result['meta']['step'])->toBe('300') ->and($result['meta']['rows'])->toBe('2') ->and($result['meta']['columns'])->toBe('2') ->and($result['meta']['legend'])->toBeArray() ->and($result['meta']['legend']['col1'])->toBe('traffic_in') ->and($result['meta']['legend']['col2'])->toBe('traffic_out') ->and($result['data'])->toBeArray() ->and($result['data'])->toHaveCount(2) ->and($result['data'][1]['timestamp'])->toBe('1700000000') ->and($result['data'][1]['col1'])->toBe('1234.56'); }); // --- rrdxport2array: rrdtool error XML has no meta key --- test('rrdxport2array returns no meta key for rrdtool error XML', function () { $xml = 'ERROR: opening rrd: No such file'; $result = rrdxport2array($xml); expect($result)->toBeArray() ->and(isset($result['meta']))->toBeFalse(); }); // --- rrdxport2array: empty string input returns array without meta --- test('rrdxport2array returns array without meta for empty input', function () { $result = rrdxport2array(''); expect($result)->toBeArray() ->and(isset($result['meta']))->toBeFalse(); }); // --- rrdxport2array: xport with zero data rows has meta but empty data --- test('rrdxport2array handles xport with no data rows', function () { $xml = <<<'XML' 1700000000 1700003600 300 0 1 traffic_in XML; $result = rrdxport2array($xml); expect($result)->toBeArray() ->and(isset($result['meta']))->toBeTrue() ->and($result['meta']['start'])->toBe('1700000000') ->and(empty($result['data']))->toBeTrue(); }); // --- rrdxport2array: strips non-XML preamble lines (rrdtool 1.2.30 bug) --- test('rrdxport2array strips non-XML preamble from rrdtool output', function () { $xml = "OK u:0.00 s:0.00 r:0.01\n" . '' . "\n" . "\n\n1700000000\n1700003600\n" . "300\n0\n1\n" . "\ncol\n\n\n\n\n"; $result = rrdxport2array($xml); expect(isset($result['meta']))->toBeTrue() ->and($result['meta']['start'])->toBe('1700000000'); }); // --- Export guard: mirrors the early-exit logic in graph_xport.php --- function xportShouldFail(mixed $xport_array): bool { return !is_array($xport_array) || !isset($xport_array['meta']['start']); } test('export guard rejects false return from rrdtool_function_xport', function () { expect(xportShouldFail(false))->toBeTrue(); }); test('export guard rejects empty array', function () { expect(xportShouldFail([]))->toBeTrue(); }); test('export guard rejects array with error key but no meta', function () { expect(xportShouldFail(['error' => 'some rrdtool error']))->toBeTrue(); }); test('export guard rejects meta without start key', function () { expect(xportShouldFail(['meta' => ['columns' => 1]]))->toBeTrue(); }); test('export guard passes valid xport structure', function () { $valid = [ 'meta' => [ 'start' => '1700000000', 'end' => '1700003600', 'step' => '300', 'rows' => '2', 'columns' => '1', ], 'data' => [], ]; expect(xportShouldFail($valid))->toBeFalse(); }); // --- rrd.php guard: mirrors the !isset($xport_array['meta']) check --- function xportMetaMissing(array $xport_array): bool { return !isset($xport_array['meta']); } test('rrd guard detects missing meta from error XML parse', function () { $errorResult = rrdxport2array( 'ERROR: ds not found' ); expect(xportMetaMissing($errorResult))->toBeTrue(); }); test('rrd guard passes when meta exists', function () { $xml = <<<'XML' 1700000000 1700003600 300 0 1 a XML; $result = rrdxport2array($xml); expect(xportMetaMissing($result))->toBeFalse(); });