import 'dart:io'; import 'package:oto/cli/commands/command_exe.dart'; import 'package:oto/oto/application.dart'; import 'package:test/test.dart'; void main() { setUp(() { Application.instance.property = {}; Application.instance.commandStates = {}; Application.instance.dataCommandMap = {}; }); test('build returns failure instead of exiting on invalid yaml', () async { const invalidYaml = ''' property: workspace: . commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe: doesNotExist '''; final result = await Application.instance .build(BuildType.file, yamlContent: invalidYaml); expect(result.success, isFalse); expect(result.exitCode, 10); expect(result.error, isNotNull); }); test('file build returns success for print-only pipeline', () async { const yaml = ''' property: workspace: . commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isTrue); expect(result.exitCode, 0); }); test('file build can run twice in same process', () async { const yaml = ''' property: workspace: . commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe: hello '''; final first = await Application.instance.build(BuildType.file, yamlContent: yaml); final second = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(first.success, isTrue); expect(second.success, isTrue); }); test('build fails with message when YAML root is not a map', () async { const yaml = ''' - command: Print id: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), contains('Build YAML root must be a map')); }); test('build fails with message when property is not a map', () async { const yaml = ''' property: - bad commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), contains('Validate build yaml')); }); test('build fails with message when command id is not a string', () async { const yaml = ''' commands: - command: Print id: 123 param: message: hi pipeline: id: main workflow: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), contains('Validate command list')); }); test('build fails with message when command type is not a string', () async { const yaml = ''' commands: - command: 999 id: hello param: message: hi pipeline: id: main workflow: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), contains('Validate command list')); }); test('build fails with validation message when workflow key is not a string', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - 123: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('Converting object to an encodable object failed'))); expect(result.error.toString(), contains('workflow[0]')); }); test('build fails with validation message when exe-handle branch is missing', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe-handle: id: hello on-fail: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), contains('exe-handle syntax requires on-success')); }); // REVIEW_VALIDATE-1: exe command id type guard test( 'build fails with validation message when exe command id is not a string', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe: 123 '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('is not a subtype'))); expect(result.error.toString(), contains('exe')); }); // REVIEW_VALIDATE-1: wait-until-string condition type guard test( 'build fails with validation message when wait-until-string value is not a string', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - wait-until-string: 123 '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('is not a subtype'))); expect(result.error.toString(), contains('wait-until')); }); // REVIEW_VALIDATE-3: nested pipeline malformed cases test('build fails with validation message when exe-handle id is not a string', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: - exe-handle: id: 123 on-success: - exe: hello on-fail: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('is not a subtype'))); expect(result.error.toString(), contains('exe-handle')); expect(result.error.toString(), contains('id')); }); // REVIEW_REVIEW_VALIDATE-1: pipeline.id validation test('build fails with validation message when pipeline.id is missing', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: workflow: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('is not a subtype'))); expect(result.error.toString(), contains('pipeline.id')); }); test('build fails with validation message when pipeline.id is not a string', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: 123 workflow: - exe: hello '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('is not a subtype'))); expect(result.error.toString(), contains('pipeline.id')); }); test('build fails with validation message when workflow is empty', () async { const yaml = ''' commands: - command: Print id: hello param: message: hi pipeline: id: main workflow: [] '''; final result = await Application.instance.build(BuildType.file, yamlContent: yaml); expect(result.success, isFalse); expect(result.error.toString(), isNot(contains('Null check operator'))); expect(result.error.toString(), contains('pipeline.workflow')); }); test('CommandExe exe -f handles missing file without LateInitializationError', () async { final missing = '/tmp/oto_missing_${DateTime.now().microsecondsSinceEpoch}.yaml'; expect(File(missing).existsSync(), isFalse); final command = CommandExe(); final priorExitCode = exitCode; try { await command.execute(['-f', missing]); } finally { exitCode = priorExitCode; } }); }