oto/apps/runner/test/oto_agent_cli_test.dart
toki e418925e61 feat(control-plane): runner 등록 계약을 연결한다
OTO Server가 runner 등록 상태를 직접 소유하고 Dart runner가 Server registration 계약으로 전환되어야 Control Plane 분리 마이그레이션을 이어갈 수 있다.
2026-06-05 15:34:17 +09:00

197 lines
5.3 KiB
Dart

import 'dart:io';
import 'package:test/test.dart';
import 'package:oto/cli/cli.dart';
import 'package:oto/cli/commands/command_agent.dart';
import 'package:oto/oto/agent/agent_config.dart';
import 'package:oto/oto/agent/agent_runner.dart';
class FakeAgentRunner implements AgentRunner {
AgentConfig? lastConfig;
int callCount = 0;
Future<void> Function(AgentConfig)? onRun;
@override
Future<void> run(AgentConfig config) async {
lastConfig = config;
callCount++;
if (onRun != null) {
await onRun!(config);
}
}
}
void main() {
const validBootstrapYaml = '''
agent:
id: "agent-abc"
alias: "my-agent"
enrollment_token: "token-123"
edge:
url: "https://edge.toki-labs.com:8443"
runtime:
install_dir: "/usr/bin"
workspace_root: "/var/oto"
log_dir: "/var/log/oto"
''';
const validServerYaml = '''
agent:
id: "agent-abc"
alias: "my-agent"
enrollment_token: "token-123"
server:
url: "https://oto-core.toki-labs.com:8443"
runtime:
install_dir: "/usr/bin"
workspace_root: "/var/oto"
log_dir: "/var/log/oto"
''';
late File tempFile;
setUp(() {
tempFile = File('${Directory.systemTemp.path}/agent_config_cli_test.yaml');
tempFile.writeAsStringSync(validBootstrapYaml);
CLI.logFunc = (msg) {};
CLI.initialize('oto', [], []);
});
tearDown(() {
if (tempFile.existsSync()) {
tempFile.deleteSync();
}
});
test('CommandAgent parses run config and calls runner', () async {
final runner = FakeAgentRunner();
final command = CommandAgent(runner: runner);
await command.execute(['run', '--config', tempFile.path]);
expect(runner.callCount, 1);
expect(runner.lastConfig, isNotNull);
expect(runner.lastConfig!.agent.id, 'agent-abc');
expect(runner.lastConfig!.edge.url, 'https://edge.toki-labs.com:8443');
expect(runner.lastConfig!.runtime.installDir, '/usr/bin');
});
test('CommandAgent parses server config and calls runner', () async {
tempFile.writeAsStringSync(validServerYaml);
final runner = FakeAgentRunner();
final command = CommandAgent(runner: runner);
await command.execute(['run', '--config', tempFile.path]);
expect(runner.callCount, 1);
expect(runner.lastConfig, isNotNull);
expect(
runner.lastConfig!.server.url,
'https://oto-core.toki-labs.com:8443',
);
});
test(
'CommandAgent parses run config with equals option and calls runner',
() async {
final runner = FakeAgentRunner();
final command = CommandAgent(runner: runner);
await command.execute(['run', '--config=${tempFile.path}']);
expect(runner.callCount, 1);
expect(runner.lastConfig!.agent.id, 'agent-abc');
},
);
test('CommandAgent rejects unknown subcommand', () async {
final runner = FakeAgentRunner();
final command = CommandAgent(runner: runner);
await command.execute(['unknown_sub']);
expect(runner.callCount, 0);
});
test('CommandAgent rejects missing config parameter', () async {
final runner = FakeAgentRunner();
final command = CommandAgent(runner: runner);
await command.execute(['run']);
expect(runner.callCount, 0);
});
test('CommandAgent sets exit code zero on accepted registration', () async {
final runner = FakeAgentRunner();
runner.onRun = (config) async {
// success
};
final command = CommandAgent(runner: runner);
final initialExitCode = exitCode;
exitCode = 0;
await command.execute(['run', '--config', tempFile.path]);
expect(runner.callCount, 1);
expect(exitCode, 0);
exitCode = initialExitCode;
});
test('CommandAgent sets exit code ten on rejected registration', () async {
final runner = FakeAgentRunner();
runner.onRun = (config) async {
throw RegistrationException('Rejected by server');
};
final command = CommandAgent(runner: runner);
final initialExitCode = exitCode;
exitCode = 0;
await command.execute(['run', '--config', tempFile.path]);
expect(runner.callCount, 1);
expect(exitCode, 10);
exitCode = initialExitCode;
});
// Integration tests calling actual binary
test('actual bin agent help lists run usage', () async {
final result = await Process.run('dart', [
'run',
'bin/main.dart',
'agent',
'-h',
]);
expect(result.exitCode, 0, reason: result.stderr.toString());
final stdoutStr = result.stdout.toString();
expect(stdoutStr, contains('agent'));
expect(stdoutStr, contains('run --config <path>'));
expect(stdoutStr, contains('Manage the OTO agent'));
});
test('actual bin agent run help works', () async {
final result = await Process.run('dart', [
'run',
'bin/main.dart',
'agent',
'run',
'-h',
]);
expect(result.exitCode, 0, reason: result.stderr.toString());
final stdoutStr = result.stdout.toString();
expect(stdoutStr, contains('agent run --config <path>'));
expect(stdoutStr, contains('Run the OTO agent daemon'));
});
test('actual bin agent with no parameters shows root help', () async {
final result = await Process.run('dart', ['run', 'bin/main.dart', 'agent']);
expect(result.exitCode, 0, reason: result.stderr.toString());
final stdoutStr = result.stdout.toString();
expect(stdoutStr, contains('Usage:'));
expect(stdoutStr, contains('agent run --config <path>'));
});
}