oto/apps/runner/test/oto_agent_bootstrap_ps1_test.dart

138 lines
5 KiB
Dart

import 'dart:io';
import 'package:test/test.dart';
void main() {
final scriptFile = File('assets/script/powershell/oto_agent_bootstrap.ps1');
group('Windows PowerShell Bootstrap Script Contract Tests', () {
late String content;
setUpAll(() async {
expect(
await scriptFile.exists(),
isTrue,
reason: 'PowerShell bootstrap script file must exist',
);
content = await scriptFile.readAsString();
});
test('should contain required flags', () {
expect(content, contains('--token'));
expect(content, contains('--server-url'));
expect(content, contains('--agent-id'));
expect(content, isNot(contains('--enrollment-token')));
expect(content, contains('--release-base-url'));
});
test('should contain optional flags', () {
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'));
});
test(
'should support Windows x64 and arm64 zip assets via dynamic arch combination',
() {
expect(content, contains("'x64'"));
expect(content, contains("'arm64'"));
expect(content, contains('oto-windows-'));
expect(content, contains('.zip'));
expect(content, contains(r'"oto-windows-$archName.zip"'));
},
);
test('should detect architecture via PROCESSOR_ARCHITECTURE', () {
expect(content, contains('PROCESSOR_ARCHITECTURE'));
expect(content, contains('AMD64'));
expect(content, contains('ARM64'));
expect(content, contains("'x64'"));
expect(content, contains("'arm64'"));
});
test('should fail with unsupported architecture message', () {
expect(content, contains('Unsupported architecture:'));
});
test('should validate HTTPS for release-base-url', () {
expect(content, contains('https://'));
expect(content, contains('--release-base-url must use https://'));
});
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('server:'));
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 escape YAML single-quoted config values', () {
expect(content, contains(r'''.Replace("'", "''")'''));
expect(content, contains(r"id: '$agentIdYaml'"));
expect(content, contains(r"enrollment_token: '$enrollmentTokenYaml'"));
expect(content, contains(r"serverUrlYaml = $serverUrl.Replace("));
});
test('should contain agent runtime command and background behavior', () {
expect(content, contains('agent run'));
expect(content, contains('--config'));
expect(content, contains('oto-agent.pid'));
});
test(
'should use separate stdout and stderr log files for background process',
() {
expect(content, contains('RedirectStandardOutput'));
expect(content, contains('RedirectStandardError'));
expect(content, contains('oto-agent.out.log'));
expect(content, contains('oto-agent.err.log'));
},
);
test('should contain binary verification before background execution', () {
expect(content, contains('Verifying OTO binary...'));
expect(content, contains('Start-Process'));
expect(content, contains('--version'));
expect(content, contains('ExitCode -ne 0'));
expect(content, contains('Error: OTO binary verification failed.'));
});
test('should apply ACL hardening to config file after write', () {
expect(content, contains('SetAccessRuleProtection'));
expect(content, contains('FileSystemAccessRule'));
expect(content, contains('Set-Acl'));
});
test('should not expose enrollment token in write/output statements', () {
final lines = content.split('\n');
for (final line in lines) {
final trimmed = line.trim();
// Skip the config write block that intentionally contains the token key
if (trimmed.contains('enrollment_token:') &&
trimmed.contains(r'"$enrollmentToken"')) {
continue;
}
// No Write-Host / Write-Error / Write-Output line should directly print
// the enrollment token variable outside config file construction.
if (trimmed.startsWith('Write-Host') ||
trimmed.startsWith('Write-Error') ||
trimmed.startsWith('Write-Output')) {
expect(
trimmed,
isNot(contains(r'$enrollmentToken')),
reason:
'Output statement must not expose enrollment token: $trimmed',
);
}
}
});
});
}