import 'dart:io'; import 'package:pipe/pipe.dart'; import 'package:pipe/src/cli.dart'; import 'package:test/test.dart'; /// Collects what a command writes to stdout. class _CapturedStdout implements Stdout { final StringBuffer buffer = StringBuffer(); @override void write(Object? object) => buffer.write(object); @override void writeln([Object? object = '']) => buffer.writeln(object); @override dynamic noSuchMethod(Invocation invocation) => null; } Future<(int?, String)> _run(List args) async { final out = _CapturedStdout(); final code = await IOOverrides.runZoned( () => PipeRunner().run(args), stdout: () => out, ); return (code, out.buffer.toString()); } const String _fixture = 'test/fixtures/stuck_loading'; void main() { late String model; setUpAll(() async { final dir = Directory.systemTemp.createTempSync('pipe-model'); model = '${dir.path}/model.json'; final (code, json) = await _run([ 'state', _fixture, '--format', 'json', '-c', '1', ]); expect(code, 0); File(model).writeAsStringSync(json); }); test('check --model reports exactly what a source check reports', () async { // The front-end seam as a separate process would use it: the model goes // through a file, and the rules must not notice. final (_, fromSource) = await _run([ 'check', _fixture, '--format', 'json', '-c', '1', ]); final (_, fromModel) = await _run([ 'check', '--model', model, '--format', 'json', ]); expect(fromModel, fromSource); }); test('check --model honours --rule and --fail-on', () async { final (code, output) = await _run([ 'check', '--model', model, '--rule', ruleEventWithoutHandler, '--fail-on', 'error', '--format', 'json', ]); expect(code, 1, reason: 'event-without-handler fires at error severity'); expect(output, contains(ruleEventWithoutHandler)); expect(output, isNot(contains(ruleDeadHandler))); }); test('check --model refuses --write-baseline', () async { final target = '${File(model).parent.path}/baseline.json'; await expectLater( _run(['check', '--model', model, '--write-baseline', target]), throwsA( isA().having( (error) => error.toString(), 'message', contains('--model'), ), ), ); expect(File(target).existsSync(), isFalse); }); test('check --model rejects a file that is not a model', () async { final dir = File(model).parent.path; File('$dir/array.json').writeAsStringSync('[1, 2]'); // An object missing required fields must be a clear error, not a crash. File('$dir/partial.json').writeAsStringSync('{"unions": [{"name": 1}]}'); for (final bad in ['array.json', 'partial.json']) { await expectLater( _run(['check', '--model', '$dir/$bad']), throwsA(isA()), reason: bad, ); } }); }