dependency_services: Don't download archives on apply (#3352)

diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index 0e731f4..47dcfa4 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -13,9 +13,9 @@
 import 'package:yaml_edit/yaml_edit.dart';
 
 import '../command.dart';
-import '../entrypoint.dart';
 import '../exceptions.dart';
 import '../io.dart';
+import '../lock_file.dart';
 import '../log.dart' as log;
 import '../package.dart';
 import '../package_name.dart';
@@ -275,10 +275,10 @@
 }
 
 enum UpgradeType {
-  /// Only upgrade pubspec.lock
+  /// Only upgrade pubspec.lock.
   compatible,
 
-  /// Unlock at most one dependency in pubspec.yaml
+  /// Unlock at most one dependency in pubspec.yaml.
   singleBreaking,
 
   /// Unlock any dependencies in pubspec.yaml needed for getting the
@@ -292,7 +292,7 @@
 
   @override
   String get description =>
-      'Output a machine digestible listing of all dependencies';
+      'Updates pubspec.yaml and pubspec.lock according to input.';
 
   @override
   bool get takesArguments => true;
@@ -362,17 +362,32 @@
       }
     }
 
-    if (pubspecEditor.edits.isNotEmpty) {
-      writeTextFile(entrypoint.pubspecPath, pubspecEditor.toString());
-    }
-    if (lockFileEditor != null && lockFileEditor.edits.isNotEmpty) {
-      writeTextFile(entrypoint.lockFilePath, lockFileEditor.toString());
-    }
+    final updatedLockfile = lockFileEditor == null
+        ? null
+        : LockFile.parse(lockFileEditor.toString(), cache.sources);
     await log.warningsOnlyUnlessTerminal(
       () async {
-        // This will fail if the new configuration does not resolve.
-        await Entrypoint(directory, cache).acquireDependencies(SolveType.get,
-            analytics: null, generateDotPackages: false);
+        final updatedPubspec = pubspecEditor.toString();
+        // Resolve versions, this will update transitive dependencies that were
+        // not passed in the input. And also counts as a validation of the input
+        // by ensuring the resolution is valid.
+        //
+        // We don't use `acquireDependencies` as that downloads all the archives
+        // to cache.
+        // TODO: Handle HTTP exceptions gracefully!
+        final solveResult = await resolveVersions(
+          SolveType.get,
+          cache,
+          Package.inMemory(Pubspec.parse(updatedPubspec, cache.sources)),
+          lockFile: updatedLockfile,
+        );
+        if (pubspecEditor.edits.isNotEmpty) {
+          writeTextFile(entrypoint.pubspecPath, updatedPubspec);
+        }
+        // Only if we originally had a lock-file we write the resulting lockfile back.
+        if (lockFileEditor != null) {
+          entrypoint.saveLockFile(solveResult);
+        }
       },
     );
     // Dummy message.
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 202580c..e854d52 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -313,7 +313,7 @@
     }
     if (!dryRun) {
       await result.downloadCachedPackages(cache);
-      _saveLockFile(result);
+      saveLockFile(result);
     }
     if (onlyReportSuccessOrFailure) {
       log.message('Got dependencies$suffix.');
@@ -844,7 +844,7 @@
   ///
   /// Will use Windows line endings (`\r\n`) if a `pubspec.lock` exists, and
   /// uses that.
-  void _saveLockFile(SolveResult result) {
+  void saveLockFile(SolveResult result) {
     _lockFile = result.lockFile;
 
     final windowsLineEndings = fileExists(lockFilePath) &&
diff --git a/test/dependency_services/dependency_services_test.dart b/test/dependency_services/dependency_services_test.dart
index 694e229..ed21a19 100644
--- a/test/dependency_services/dependency_services_test.dart
+++ b/test/dependency_services/dependency_services_test.dart
@@ -8,6 +8,7 @@
 import 'package:path/path.dart' as p;
 import 'package:pub/src/io.dart';
 import 'package:pub_semver/pub_semver.dart';
+import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
@@ -110,7 +111,7 @@
   });
 
   testWithGolden('Removing transitive', (context) async {
-    (await servePackages())
+    final server = (await servePackages())
       ..serve('foo', '1.2.3', deps: {'transitive': '^1.0.0'})
       ..serve('foo', '2.2.3')
       ..serve('transitive', '1.0.0');
@@ -124,6 +125,7 @@
       })
     ]).create();
     await pubGet();
+    server.dontAllowDownloads();
     await listReportApply(context, [
       _PackageVersion('foo', Version.parse('2.2.3')),
       _PackageVersion('transitive', null)
@@ -146,6 +148,7 @@
       ..serve('bar', '1.2.3')
       ..serve('bar', '2.2.3')
       ..serve('boo', '1.2.3');
+
     await d.dir(appPath, [
       d.pubspec({
         'name': 'app',
@@ -160,6 +163,8 @@
     server.serve('foo', '1.2.4');
     server.serve('boo', '1.2.4');
 
+    server.dontAllowDownloads();
+
     await listReportApply(context, [
       _PackageVersion('foo', Version.parse('1.2.4')),
     ], reportAssertions: (report) {
@@ -171,7 +176,7 @@
   });
 
   testWithGolden('Adding transitive', (context) async {
-    (await servePackages())
+    final server = (await servePackages())
       ..serve('foo', '1.2.3')
       ..serve('foo', '2.2.3', deps: {'transitive': '^1.0.0'})
       ..serve('transitive', '1.0.0');
@@ -185,6 +190,8 @@
       })
     ]).create();
     await pubGet();
+    server.dontAllowDownloads();
+
     await listReportApply(context, [
       _PackageVersion('foo', Version.parse('2.2.3')),
       _PackageVersion('transitive', Version.parse('1.0.0'))
@@ -225,6 +232,9 @@
       ..serve('foo', '3.0.1', deps: {'bar': '^2.0.0'})
       ..serve('bar', '2.0.0', deps: {'foo': '^3.0.0'})
       ..serve('baz', '1.1.0');
+
+    server.dontAllowDownloads();
+
     await listReportApply(context, [
       _PackageVersion('foo', Version.parse('3.0.1'),
           constraint: VersionConstraint.parse('^3.0.0')),
@@ -259,3 +269,17 @@
         if (constraint != null) 'constraint': constraint.toString()
       };
 }
+
+extension on PackageServer {
+  ///Check that nothing is downloaded.
+  void dontAllowDownloads() {
+    // This testing logic is a bit fragile, if we change the pattern for pattern
+    // for the download URL then this will pass silently. There isn't much we
+    // can / should do about it. Just accept the limitations, and remove it if
+    // the test becomes useless.
+    handle(RegExp(r'/.+\.tar\.gz'), (request) {
+      return shelf.Response.notFound(
+          'This test should not download archives! Requested ${request.url}');
+    });
+  }
+}