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', ); } } }); }); }