// ignore_for_file: depend_on_referenced_packages, library_private_types_in_public_api import 'dart:convert'; import 'dart:io'; import 'package:dart_framework/platform/process.dart'; import 'package:oto/cli/commands/install/regist_path.dart'; import 'package:oto/cli/printer.dart'; import 'package:oto/oto/core/system_runtime.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; // Fake classes to avoid duplicate definition problems class _ShellCall { final String shell; final String? workspace; final bool printStdout; final bool printStderr; final Converter, String>? decoder; _ShellCall(this.shell, this.workspace, this.printStdout, this.printStderr, this.decoder); } class _RunExecutableCall { final String exe; final List args; final String? workingDirectory; final Encoding? stdoutEncoding; final Encoding? stderrEncoding; _RunExecutableCall(this.exe, this.args, this.workingDirectory, this.stdoutEncoding, this.stderrEncoding); } class FakeProcessData extends ProcessData { FakeProcessData() : super(false, false) { exitCode = 0; } @override Future waitForExit() async {} @override Process get process => throw UnsupportedError('FakeProcessData has no real process'); } class FakeSystemRuntime implements SystemRuntime { final List<_ShellCall> startShellCalls = []; final List<_ShellCall> runShellCalls = []; final List<_RunExecutableCall> runExecutableCalls = []; final List killedPids = []; final bool _isWindows; final bool _isMacOS; final bool _isLinux; final Map _environment; FakeSystemRuntime({ bool isWindows = false, bool isMacOS = false, bool isLinux = true, Map environment = const {}, }) : _isWindows = isWindows, _isMacOS = isMacOS, _isLinux = isLinux, _environment = environment; @override bool get isWindows => _isWindows; @override bool get isMacOS => _isMacOS; @override bool get isLinux => _isLinux; @override Map get environment => _environment; @override Future startShell( StringBuffer shell, { String? workspace, bool printStdout = true, bool printStderr = true, Converter, String>? decoder, LogHandler? logHandler, }) async { startShellCalls.add(_ShellCall( shell.toString(), workspace, printStdout, printStderr, decoder)); return FakeProcessData(); } @override Future runShell( StringBuffer shell, { String? workspace, bool printStdout = true, bool printStderr = true, Converter, String>? decoder, LogHandler? logHandler, }) async { runShellCalls.add(_ShellCall( shell.toString(), workspace, printStdout, printStderr, decoder)); return ProcessResult(0, 0, '', ''); } @override Future runExecutable( String exe, List args, { String? workingDirectory, Encoding? stdoutEncoding, Encoding? stderrEncoding, }) async { runExecutableCalls.add(_RunExecutableCall( exe, args, workingDirectory, stdoutEncoding, stderrEncoding)); return ProcessResult(0, 0, '', ''); } @override Future startExecutable( String program, List args, { String? workingDirectory, ProcessStartMode mode = ProcessStartMode.normal, }) async { throw UnsupportedError('FakeSystemRuntime does not return a real Process'); } @override bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) { killedPids.add(pid); return true; } } void main() { group('Printer Runtime Rollout Tests', () { test('unix printer delegates script execution to system runtime', () async { final fakeRuntime = FakeSystemRuntime( isWindows: false, isMacOS: false, isLinux: true, ); final printer = Printer.unix(runtime: fakeRuntime); await printer.printCLI(['hello', 'world']); expect(fakeRuntime.runExecutableCalls, hasLength(1)); final call = fakeRuntime.runExecutableCalls.first; expect(call.exe, 'bash'); expect(call.args, hasLength(1)); expect(call.stdoutEncoding, utf8); expect(call.stderrEncoding, utf8); final tempFilePath = call.args.first; expect(tempFilePath, contains('temp_')); expect(tempFilePath, endsWith('.sh')); final tempFile = File(tempFilePath); expect(tempFile.existsSync(), isFalse); // printer deletes it }); test('windows printer delegates script execution to system runtime', () async { final fakeRuntime = FakeSystemRuntime( isWindows: true, isMacOS: false, isLinux: false, ); final printer = Printer.windows(runtime: fakeRuntime); await printer.printCLI(['hello', 'world']); expect(fakeRuntime.runExecutableCalls, hasLength(1)); final call = fakeRuntime.runExecutableCalls.first; expect(call.exe, 'cmd'); expect(call.args, ['/C', contains('temp_')]); final tempFilePath = call.args[1]; expect(tempFilePath, endsWith('.bat')); final tempFile = File(tempFilePath); expect(tempFile.existsSync(), isFalse); // printer deletes it }); }); group('RegistPath Runtime Rollout Tests', () { late Directory tempDir; setUp(() async { tempDir = await Directory.systemTemp.createTemp('regist_path_test'); }); tearDown(() async { await tempDir.delete(recursive: true); }); test('regist_path osx sources profiles through system runtime', () async { final fakeRuntime = FakeSystemRuntime( isWindows: false, isMacOS: true, isLinux: false, environment: {'HOME': tempDir.path}, ); final registPath = RegistPath.create(runtime: fakeRuntime); final zprofileFile = File(path.join(tempDir.path, '.zprofile')); final bashProfileFile = File(path.join(tempDir.path, '.bash_profile')); await registPath.add('/path/to/my/bin'); expect(zprofileFile.existsSync(), isTrue); expect(bashProfileFile.existsSync(), isTrue); final zprofileContent = await zprofileFile.readAsString(); expect(zprofileContent, contains('export PATH=\${PATH}:/path/to/my/bin')); expect(fakeRuntime.runShellCalls, hasLength(1)); expect(fakeRuntime.runShellCalls.first.shell, contains('source')); // remove test await registPath.remove('/path/to/my/bin'); final zprofileContentAfter = await zprofileFile.readAsString(); expect(zprofileContentAfter, isNot(contains('/path/to/my/bin'))); }); test('regist_path windows delegates setx through system runtime', () async { final fakeRuntime = FakeSystemRuntime( isWindows: true, isMacOS: false, isLinux: false, environment: {'UserProfile': tempDir.path}, ); final registPath = RegistPath.create(runtime: fakeRuntime); await registPath.add('/path/to/win/bin'); expect(fakeRuntime.runShellCalls, hasLength(1)); expect(fakeRuntime.runShellCalls.first.shell, contains('setx path "%PathUser%;/path/to/win/bin')); fakeRuntime.runShellCalls.clear(); // remove test (windows path removal creates a temporary path.txt in user profile) final pathTxtFile = File(path.join(tempDir.path, 'path.txt')); await pathTxtFile.writeAsString('some_old_path;/path/to/win/bin'); await registPath.remove('/path/to/win/bin'); // The temporary file pathTxt should be deleted after processing expect(pathTxtFile.existsSync(), isFalse); // There should be two shell calls: first to query/write path.txt, second to setx the new path. expect(fakeRuntime.runShellCalls, hasLength(2)); expect(fakeRuntime.runShellCalls[1].shell, contains('setx path "some_old_path"')); }); }); group('CLI Version and Unknown Command Tests', () { late String workingDir; setUp(() { var current = Directory.current.path; if (current.endsWith('runner')) { workingDir = current; } else { workingDir = path.join(current, 'apps/runner'); } }); test('should return version information with exit code 0 on --version', () async { final result = await Process.run( 'dart', ['run', 'bin/main.dart', '--version'], workingDirectory: workingDir, ); expect(result.exitCode, 0); expect(result.stdout.toString(), contains('oto version 1.0.0')); }); test('should return version information with exit code 0 on -v', () async { final result = await Process.run( 'dart', ['run', 'bin/main.dart', '-v'], workingDirectory: workingDir, ); expect(result.exitCode, 0); expect(result.stdout.toString(), contains('oto version 1.0.0')); }); test('should return non-zero exit code on unknown command', () async { final result = await Process.run( 'dart', ['run', 'bin/main.dart', 'nonexistent-command-999'], workingDirectory: workingDir, ); expect(result.exitCode, isNot(0)); expect(result.stdout.toString(), contains('command does not exist')); }); }); }