oto/test/oto_core_test.dart
toki ac1c42448e refactor: split build_ios and git commands, add cli styling, improve pipeline system
- Split BuildIOS command into build_ios_archive and build_ios_build
- Split Git command into git_branch, git_remote, git_revision, git_stash
- Add cli_style.dart and printer.dart for consistent CLI output
- Add macos_signing.dart for macOS/iOS code signing
- Refactor pipeline system (pipeline, pipeline_condition, pipeline_contain, etc.)
- Update tag_system, defined_data, pipeline_data
- Update application and command files for new structure
- Add tests for new command catalog and core functionality
- Update README with new features
2026-05-20 20:50:59 +09:00

199 lines
6.1 KiB
Dart

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('<!property.items>');
expect(listValue, isA<List>());
expect(listValue, equals([1, 2, 3]));
final mapValue = Command.replaceTagValue('<!property.config>');
expect(mapValue, isA<Map>());
expect(mapValue, equals({'a': 1}));
final embedded = Command.replaceTagValue('values=<!property.items>');
expect(embedded, isA<String>());
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<String, dynamic> props,
List<String> 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': '<!property.env> == 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'] = <dynamic>[];
final ctx = makeCtx({'items': [10, 20, 30]}, ['print']);
final result = Pipeline.pipelineInitialize([
{
'foreach': {
'iterator': '<!property.items>',
'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': '<!property.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);
});
}