138 lines
5 KiB
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',
|
|
);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|