oto/apps/runner/test/oto_agent_bootstrap_script_test.dart
toki 86afabb3eb refactor(runner): 런타임 패키지를 앱 하위로 이동한다
독립 control plane 구성을 위해 기존 Dart CLI/runtime을 runner 앱 경계로 옮기고, 후속 client/core/root 작업을 같은 마일스톤 task group에 연결한다.
2026-06-05 06:34:45 +09:00

154 lines
6 KiB
Dart

import 'dart:io';
import 'package:test/test.dart';
void main() {
final scriptFile = File('assets/script/shell/oto_agent_bootstrap.sh');
group('Linux Bootstrap Script Contract Tests', () {
late String content;
setUpAll(() async {
expect(await scriptFile.exists(), isTrue, reason: 'Bootstrap script file must exist');
content = await scriptFile.readAsString();
});
test('should contain required flags', () {
expect(content, contains('--edge-url'));
expect(content, contains('--agent-id'));
expect(content, contains('--enrollment-token'));
expect(content, contains('--release-base-url'));
});
test('should contain optional/default flags and paths', () {
expect(content, contains('--agent-alias'));
expect(content, contains('--install-dir'));
expect(content, contains('--config-path'));
expect(content, contains('--workspace-root'));
expect(content, contains('--log-dir'));
expect(content, contains('--no-background'));
expect(content, contains('.oto/bin'));
expect(content, contains('.oto/agent/config.yaml'));
});
test('should support correct architecture assets', () {
expect(content, contains('oto-linux-x64.tar.gz'));
expect(content, contains('oto-linux-arm64.tar.gz'));
});
test('should write expected keys to configuration yaml', () {
expect(content, contains('agent:'));
expect(content, contains('id:'));
expect(content, contains('alias:'));
expect(content, contains('enrollment_token:'));
expect(content, contains('edge:'));
expect(content, contains('url:'));
expect(content, contains('runtime:'));
expect(content, contains('install_dir:'));
expect(content, contains('workspace_root:'));
expect(content, contains('log_dir:'));
});
test('should contain agent runtime commands and background behavior', () {
expect(content, contains('agent run'));
expect(content, contains('--config'));
expect(content, contains('nohup'));
expect(content, contains('oto-agent.pid'));
});
});
group('Linux Bootstrap Script Functional & Regressions Tests', () {
test('should fail when release base URL is not HTTPS', () async {
final result = await Process.run('bash', [
scriptFile.absolute.path,
'--edge-url', 'https://edge.example.com',
'--agent-id', 'test-agent',
'--enrollment-token', 'secret-token-123',
'--release-base-url', 'http://example.com/release', // HTTP URL
]);
expect(result.exitCode, isNot(0));
expect(result.stderr.toString(), contains('--release-base-url must use https://'));
expect(result.stdout.toString(), isNot(contains('Downloading OTO agent')));
// enrollment token이 출력에 노출되지 않는지 확인
expect(result.stdout.toString(), isNot(contains('secret-token-123')));
expect(result.stderr.toString(), isNot(contains('secret-token-123')));
});
test('should write PID to standard agent state path when custom config-path is specified', () async {
final tempDir = await Directory.systemTemp.createTemp('oto_bootstrap_test_');
try {
final tempHome = Directory('${tempDir.path}/home');
await tempHome.create(recursive: true);
// 1. 가짜 OTO 실행 파일 생성
final fakeOtoSource = Directory('${tempDir.path}/fake_oto_src');
await fakeOtoSource.create(recursive: true);
final fakeOtoFile = File('${fakeOtoSource.path}/oto');
await fakeOtoFile.writeAsString('#!/bin/sh\nwhile true; do sleep 1; done\n');
await Process.run('chmod', ['+x', fakeOtoFile.path]);
// 2. 가짜 tar.gz 아카이브 생성
final fakeTarGz = File('${tempDir.path}/oto-linux-x64.tar.gz');
await Process.run('tar', ['-czf', fakeTarGz.path, '-C', fakeOtoSource.path, 'oto']);
// 3. 가짜 curl 스크립트 생성
final fakeBinDir = Directory('${tempDir.path}/bin');
await fakeBinDir.create(recursive: true);
final fakeCurl = File('${fakeBinDir.path}/curl');
await fakeCurl.writeAsString('''#!/bin/sh
out_file=""
while [ \$# -gt 0 ]; do
if [ "\$1" = "-o" ]; then
out_file="\$2"
break
fi
shift
done
if [ -n "\$out_file" ]; then
cp "${fakeTarGz.path}" "\$out_file"
fi
''');
await Process.run('chmod', ['+x', fakeCurl.path]);
// 4. custom config path 지정
final customConfigDir = Directory('${tempDir.path}/custom_config');
await customConfigDir.create(recursive: true);
final customConfigPath = '${customConfigDir.path}/my-config.yaml';
// PATH 환경변수에 fakeBinDir 등록, HOME을 tempHome으로 설정
final env = Map<String, String>.from(Platform.environment);
env['PATH'] = '${fakeBinDir.path}:${env['PATH']}';
env['HOME'] = tempHome.path;
final result = await Process.run('bash', [
scriptFile.absolute.path,
'--edge-url', 'https://edge.example.com',
'--agent-id', 'test-agent',
'--enrollment-token', 'secret-token-123',
'--release-base-url', 'https://example.com/release',
'--config-path', customConfigPath,
], environment: env);
expect(result.exitCode, 0, reason: 'Script failed with: ${result.stderr}');
// 5. 검증
final configFile = File(customConfigPath);
expect(await configFile.exists(), isTrue);
final expectedPidFile = File('${tempHome.path}/.oto/agent/oto-agent.pid');
expect(await expectedPidFile.exists(), isTrue, reason: 'PID file should exist in standard agent state path');
final unexpectedPidFile = File('${customConfigDir.path}/oto-agent.pid');
expect(await unexpectedPidFile.exists(), isFalse, reason: 'PID file should not exist in custom config path directory');
final pidStr = await expectedPidFile.readAsString();
final pid = int.tryParse(pidStr.trim());
if (pid != null) {
Process.killPid(pid);
}
} finally {
await tempDir.delete(recursive: true);
}
});
});
}