Add "flutter downgrade" command (#50506)

diff --git a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
index 98922e0..74596c3 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/version_test.dart
@@ -8,11 +8,13 @@
 
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/version.dart';
 import 'package:flutter_tools/src/version.dart';
 import 'package:mockito/mockito.dart';
 import 'package:process/process.dart';
+import 'package:flutter_tools/src/globals.dart' as globals;
 
 import '../../src/common.dart';
 import '../../src/context.dart';
@@ -20,10 +22,18 @@
 
 void main() {
   group('version', () {
+    MockStdio mockStdio;
+
     setUpAll(() {
       Cache.disableLocking();
     });
 
+    setUp(() {
+      mockStdio = MockStdio();
+      when(mockStdio.stdinHasTerminal).thenReturn(false);
+      when(mockStdio.hasTerminal).thenReturn(false);
+    });
+
     testUsingContext('version ls', () async {
       final VersionCommand command = VersionCommand();
       await createTestCommandRunner(command).run(<String>[
@@ -33,11 +43,18 @@
       expect(testLogger.statusText, equals('v10.0.0\r\nv20.0.0\n'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(),
+      Stdio: () => mockStdio,
     });
 
-    testUsingContext('version switch', () async {
+    testUsingContext('version switch prompt is accepted', () async {
+      when(mockStdio.stdinHasTerminal).thenReturn(true);
       const String version = '10.0.0';
       final VersionCommand command = VersionCommand();
+      when(globals.terminal.promptForCharInput(<String>['y', 'n'],
+        logger: anyNamed('logger'),
+        prompt: 'Are you sure you want to proceed?')
+      ).thenAnswer((Invocation invocation) async => 'y');
+
       await createTestCommandRunner(command).run(<String>[
         'version',
         '--no-pub',
@@ -46,6 +63,29 @@
       expect(testLogger.statusText, contains('Switching Flutter to version $version'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(),
+      Stdio: () => mockStdio,
+      AnsiTerminal: () => MockTerminal(),
+    });
+
+    testUsingContext('version switch prompt is declined', () async {
+      when(mockStdio.stdinHasTerminal).thenReturn(true);
+      const String version = '10.0.0';
+      final VersionCommand command = VersionCommand();
+      when(globals.terminal.promptForCharInput(<String>['y', 'n'],
+        logger: anyNamed('logger'),
+        prompt: 'Are you sure you want to proceed?')
+      ).thenAnswer((Invocation invocation) async => 'n');
+
+      await createTestCommandRunner(command).run(<String>[
+        'version',
+        '--no-pub',
+        version,
+      ]);
+      expect(testLogger.statusText, isNot(contains('Switching Flutter to version $version')));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => MockProcessManager(),
+      Stdio: () => mockStdio,
+      AnsiTerminal: () => MockTerminal(),
     });
 
     testUsingContext('version switch, latest commit query fails', () async {
@@ -59,6 +99,7 @@
       expect(testLogger.errorText, contains('git failed'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(latestCommitFails: true),
+      Stdio: () => mockStdio,
     });
 
     testUsingContext('latest commit is parsable when query fails', () {
@@ -69,6 +110,7 @@
       );
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(latestCommitFails: true),
+      Stdio: () => mockStdio,
     });
 
     testUsingContext('switch to not supported version without force', () async {
@@ -82,6 +124,7 @@
       expect(testLogger.errorText, contains('Version command is not supported in'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(),
+      Stdio: () => mockStdio,
     });
 
     testUsingContext('switch to not supported version with force', () async {
@@ -96,6 +139,7 @@
       expect(testLogger.statusText, contains('Switching Flutter to version $version with force'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(),
+      Stdio: () => mockStdio,
     });
 
     testUsingContext('tool exit on confusing version', () async {
@@ -111,6 +155,7 @@
       );
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(),
+      Stdio: () => mockStdio,
     });
 
     testUsingContext("exit tool if can't get the tags", () async {
@@ -123,10 +168,13 @@
       }
     }, overrides: <Type, Generator>{
       ProcessManager: () => MockProcessManager(failGitTag: true),
+      Stdio: () => mockStdio,
     });
   });
 }
 
+class MockTerminal extends Mock implements AnsiTerminal {}
+class MockStdio extends Mock implements Stdio {}
 class MockProcessManager extends Mock implements ProcessManager {
   MockProcessManager({
     this.failGitTag = false,