import 'package:oto/oto/application.dart'; import 'package:oto/oto/commands/command.dart'; import 'package:oto/oto/commands/command_registry.dart'; import 'package:oto/oto/core/execution_context.dart'; import 'package:oto/oto/data/command_data.dart'; import 'package:oto/oto/pipeline/pipeline.dart'; import 'package:test/test.dart'; void main() { setUp(() { Application.instance.property = {}; Application.instance.commandStates = {}; Application.instance.dataCommandMap = {}; }); test('parses minimal build yaml into DataBuild', () { const yaml = ''' property: workspace: . commands: - command: Print id: hello param: hi pipeline: id: main workflow: - exe: hello '''; final map = Application.getMapFromYamlA(yaml); expect(map, isNotNull); final build = DataBuild.fromJson(map!); expect(build.property!['workspace'], '.'); expect(build.commands, hasLength(1)); expect(build.commands.first.id, 'hello'); expect(build.commands.first.command, CommandType.Print); expect(build.pipeline!.id, 'main'); expect(build.pipeline!.workflow, hasLength(1)); }); test('tag system preserves single tag object values', () { Application.instance.property['items'] = [1, 2, 3]; Application.instance.property['config'] = {'a': 1}; final listValue = Command.replaceTagValue(''); expect(listValue, isA()); expect(listValue, equals([1, 2, 3])); final mapValue = Command.replaceTagValue(''); expect(mapValue, isA()); expect(mapValue, equals({'a': 1})); final embedded = Command.replaceTagValue('values='); expect(embedded, isA()); expect(embedded, contains('1')); }); test('pipeline validation rejects missing command id', () { final result = Pipeline.pipelineInitialize([ {'exe': 'doesNotExist'} ]); expect(result.enable, isFalse); expect(result.message, contains('doesNotExist')); }); // ── Explicit context regression tests ────────────────────────────────────── // Each test puts a different value in the singleton vs. the explicit context // and asserts that flow-control uses the explicit context exclusively. ExecutionContext makeCtx(Map props, List commandIds) { registerAllCommands(); final ctx = ExecutionContext(); ctx.property.addAll(props); for (final id in commandIds) { final cmd = DataCommand() ..command = CommandType.Print ..id = id ..param = {'message': id}; ctx.dataCommandMap[id] = cmd; } return ctx; } test('PipelineIf evaluates condition from explicit context not singleton', () async { // Singleton: env = 'dev' → on-false; explicit: env = 'prod' → on-true Application.instance.property['env'] = 'dev'; final ctx = makeCtx({'env': 'prod'}, ['onTrue', 'onFalse']); final result = Pipeline.pipelineInitialize([ { 'if': { 'condition-string': ' == prod', 'on-true': [ {'exe': 'onTrue'} ], 'on-false': [ {'exe': 'onFalse'} ], } } ], context: ctx); expect(result.enable, isTrue, reason: result.message); await result.pipeline!.execute(); expect(ctx.commandStates['onTrue'], CommandState.complete); expect(ctx.commandStates['onFalse'], CommandState.ready); }); test('PipelineForeach iterates list from explicit context not singleton', () async { // Singleton: items = [] (empty); explicit: items = [10, 20, 30] Application.instance.property['items'] = []; final ctx = makeCtx({'items': [10, 20, 30]}, ['print']); final result = Pipeline.pipelineInitialize([ { 'foreach': { 'iterator': '', 'setValue': '<@property.current>', 'on-do': [ {'exe': 'print'} ], } } ], context: ctx); expect(result.enable, isTrue, reason: result.message); await result.pipeline!.execute(); // Loop ran 3 times using explicit context; last assigned value = 30 expect(ctx.property['current'], 30); // Singleton must not have been modified expect(Application.instance.property.containsKey('current'), isFalse); }); test('PipelineContain evaluates target from explicit context not singleton', () async { // Singleton: target = 'xyz' (not in list); explicit: target = 'foo' (in list) Application.instance.property['target'] = 'xyz'; final ctx = makeCtx({'target': 'foo'}, ['onTrue', 'onFalse']); final result = Pipeline.pipelineInitialize([ { 'contain': { 'target': '', 'list': ['foo', 'bar'], 'on-true': [ {'exe': 'onTrue'} ], 'on-false': [ {'exe': 'onFalse'} ], } } ], context: ctx); expect(result.enable, isTrue, reason: result.message); await result.pipeline!.execute(); expect(ctx.commandStates['onTrue'], CommandState.complete); expect(ctx.commandStates['onFalse'], CommandState.ready); }); test( 'PipelineSwitch validate uses explicit context command map not singleton', () { // Singleton dataCommandMap is empty; only explicit context has the command. registerAllCommands(); final ctx = ExecutionContext(); final cmd = DataCommand() ..command = CommandType.Print ..id = 'deployProd' ..param = {'message': 'prod'}; ctx.dataCommandMap['deployProd'] = cmd; final result = Pipeline.pipelineInitialize([ { 'switch': { 'condition-string': 'prod', 'cases': [ { 'value': 'prod', 'tasks': [ {'exe': 'deployProd'} ], } ], } } ], context: ctx); // Should succeed: deployProd exists in explicit context. // Would fail if singleton (empty) context were used. expect(result.enable, isTrue, reason: result.message); }); }