dependabot-nuget 0.290.0 → 0.292.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/.editorconfig +1 -0
  3. data/helpers/lib/NuGetUpdater/Directory.Build.props +1 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +15 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +2 -2
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +1 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +2 -1
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +87 -3
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +1 -1
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +14 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +19 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadRequirementException.cs +9 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +39 -8
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +111 -17
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +2 -2
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +2 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +19 -11
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +2 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +31 -5
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +1 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/BadRequirement.cs +10 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +1 -1
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +3 -2
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +1 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs +1 -4
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -2
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdateNotPossible.cs +1 -1
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +9 -3
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +3 -2
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +43 -18
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +1 -1
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +12 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +0 -7
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +48 -17
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +2 -2
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +45 -7
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +4 -4
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +60 -2
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +10 -1
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +56 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +41 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +2 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +1 -1
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +2 -1
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +76 -40
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +20 -2
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +2 -2
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +251 -0
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +6 -6
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +63 -5
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +277 -73
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/ProjectHelperTests.cs +65 -0
  59. data/helpers/lib/NuGetUpdater/global.json +1 -1
  60. data/lib/dependabot/nuget/file_fetcher.rb +1 -0
  61. data/lib/dependabot/nuget/file_parser.rb +90 -0
  62. data/lib/dependabot/nuget/language.rb +98 -0
  63. data/lib/dependabot/nuget/native_helpers.rb +25 -0
  64. data/lib/dependabot/nuget/package_manager.rb +51 -0
  65. metadata +12 -6
@@ -1,6 +1,5 @@
1
1
  using NuGet.Versioning;
2
2
 
3
- using NuGetUpdater.Core.Analyze;
4
3
  using NuGetUpdater.Core.Run;
5
4
  using NuGetUpdater.Core.Run.ApiModel;
6
5
  using NuGetUpdater.Core.Test.Utilities;
@@ -228,37 +227,33 @@ public class SerializationTests
228
227
  Assert.False(experimentsManager.UseDirectDiscovery);
229
228
  }
230
229
 
231
- [Fact]
232
- public async Task DeserializeExperimentsManager_UnsupportedJobFileShape_InfoIsReportedAndEmptyExperimentSetIsReturned()
230
+ [Theory]
231
+ [MemberData(nameof(DeserializeErrorTypesData))]
232
+ public void SerializeError(JobErrorBase error, string expectedSerialization)
233
233
  {
234
- // arrange
235
- using var tempDir = new TemporaryDirectory();
236
- var jobFilePath = Path.Combine(tempDir.DirectoryPath, "job.json");
237
- var jobContent = """
238
- {
239
- "this-is-not-a-job-and-parsing-will-fail-but-an-empty-experiment-set-should-sill-be-returned": {
240
- }
241
- }
242
- """;
243
- await File.WriteAllTextAsync(jobFilePath, jobContent);
244
- var capturingTestLogger = new CapturingTestLogger();
245
-
246
- // act - this is the entrypoint the update command uses to parse the job file
247
- var experimentsManager = await ExperimentsManager.FromJobFileAsync(jobFilePath, capturingTestLogger);
234
+ if (error is UnknownError unknown)
235
+ {
236
+ // special case the exception's call stack to make it testable
237
+ unknown.Details["error-backtrace"] = "TEST-BACKTRACE";
238
+ }
248
239
 
249
- // assert
250
- Assert.False(experimentsManager.UseLegacyDependencySolver);
251
- Assert.False(experimentsManager.UseDirectDiscovery);
252
- Assert.Single(capturingTestLogger.Messages, m => m.Contains("Error deserializing job file"));
240
+ var actual = HttpApiHandler.Serialize(error);
241
+ Assert.Equal(expectedSerialization, actual);
253
242
  }
254
243
 
255
244
  [Fact]
256
- public void SerializeError()
245
+ public void SerializeError_AllErrorTypesHaveSerializationTests()
257
246
  {
258
- var error = new JobRepoNotFound("some message");
259
- var actual = HttpApiHandler.Serialize(error);
260
- var expected = """{"data":{"error-type":"job_repo_not_found","error-details":{"message":"some message"}}}""";
261
- Assert.Equal(expected, actual);
247
+ var untestedTypes = typeof(JobErrorBase).Assembly.GetTypes()
248
+ .Where(t => t.IsSubclassOf(typeof(JobErrorBase)))
249
+ .ToHashSet();
250
+ foreach (object?[] data in DeserializeErrorTypesData())
251
+ {
252
+ var testedErrorType = data[0]!.GetType();
253
+ untestedTypes.Remove(testedErrorType);
254
+ }
255
+
256
+ Assert.Empty(untestedTypes.Select(t => t.Name));
262
257
  }
263
258
 
264
259
  [Fact]
@@ -503,7 +498,9 @@ public class SerializationTests
503
498
  "repo": "some/repo"
504
499
  },
505
500
  "commit-message-options": {
506
- "prefix": "[SECURITY] "
501
+ "prefix": "[SECURITY] ",
502
+ "prefix-development": null,
503
+ "include-scope": true
507
504
  }
508
505
  }
509
506
  }
@@ -511,7 +508,58 @@ public class SerializationTests
511
508
  var jobWrapper = RunWorker.Deserialize(jsonWrapperJson)!;
512
509
  Assert.Equal("[SECURITY] ", jobWrapper.Job.CommitMessageOptions!.Prefix);
513
510
  Assert.Null(jobWrapper.Job.CommitMessageOptions!.PrefixDevelopment);
514
- Assert.Null(jobWrapper.Job.CommitMessageOptions!.IncludeScope);
511
+ Assert.True(jobWrapper.Job.CommitMessageOptions!.IncludeScope);
512
+ }
513
+
514
+ public static IEnumerable<object?[]> DeserializeErrorTypesData()
515
+ {
516
+ yield return
517
+ [
518
+ new BadRequirement("some message"),
519
+ """
520
+ {"data":{"error-type":"illformed_requirement","error-details":{"message":"some message"}}}
521
+ """
522
+ ];
523
+
524
+ yield return
525
+ [
526
+ new DependencyFileNotFound("some message", "/some/file"),
527
+ """
528
+ {"data":{"error-type":"dependency_file_not_found","error-details":{"message":"some message","file-path":"/some/file"}}}
529
+ """
530
+ ];
531
+
532
+ yield return
533
+ [
534
+ new JobRepoNotFound("some message"),
535
+ """
536
+ {"data":{"error-type":"job_repo_not_found","error-details":{"message":"some message"}}}
537
+ """
538
+ ];
539
+
540
+ yield return
541
+ [
542
+ new PrivateSourceAuthenticationFailure(["url1", "url2"]),
543
+ """
544
+ {"data":{"error-type":"private_source_authentication_failure","error-details":{"source":"(url1|url2)"}}}
545
+ """
546
+ ];
547
+
548
+ yield return
549
+ [
550
+ new UnknownError(new Exception("some message"), "JOB-ID"),
551
+ """
552
+ {"data":{"error-type":"unknown_error","error-details":{"error-class":"Exception","error-message":"some message","error-backtrace":"TEST-BACKTRACE","package-manager":"nuget","job-id":"JOB-ID"}}}
553
+ """
554
+ ];
555
+
556
+ yield return
557
+ [
558
+ new UpdateNotPossible(["dep1", "dep2"]),
559
+ """
560
+ {"data":{"error-type":"update_not_possible","error-details":{"dependencies":["dep1","dep2"]}}}
561
+ """
562
+ ];
515
563
  }
516
564
 
517
565
  public static IEnumerable<object?[]> DeserializeAllowedUpdatesData()
@@ -596,16 +644,4 @@ public class SerializationTests
596
644
  }
597
645
  ];
598
646
  }
599
-
600
- private class CapturingTestLogger : ILogger
601
- {
602
- private readonly List<string> _messages = new();
603
-
604
- public IReadOnlyList<string> Messages => _messages;
605
-
606
- public void LogRaw(string message)
607
- {
608
- _messages.Add(message);
609
- }
610
- }
611
647
  }
@@ -47,7 +47,7 @@ public abstract class UpdateWorkerTestBase : TestBase
47
47
  {
48
48
  return useSolution
49
49
  ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], projectFilesExpected: [(projectFilePath, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected, packages, experimentsManager)
50
- : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile: (projectFilePath, projectContents), expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, packages, experimentsManager);
50
+ : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile: (projectFilePath, projectContents), expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, additionalChecks: null, packages: packages, experimentsManager: experimentsManager);
51
51
  }
52
52
 
53
53
  protected static Task TestUpdate(
@@ -65,7 +65,7 @@ public abstract class UpdateWorkerTestBase : TestBase
65
65
  {
66
66
  return useSolution
67
67
  ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [projectFile], projectFilesExpected: [(projectFile.Path, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected, packages, experimentsManager)
68
- : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, packages, experimentsManager);
68
+ : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, additionalChecks: null, packages: packages, experimentsManager: experimentsManager);
69
69
  }
70
70
 
71
71
  protected static Task TestNoChangeforProject(
@@ -101,6 +101,7 @@ public abstract class UpdateWorkerTestBase : TestBase
101
101
  bool isTransitive = false,
102
102
  TestFile[]? additionalFiles = null,
103
103
  TestFile[]? additionalFilesExpected = null,
104
+ Action<string>? additionalChecks = null,
104
105
  MockNuGetPackage[]? packages = null,
105
106
  ExperimentsManager? experimentsManager = null,
106
107
  string projectFilePath = "test-project.csproj",
@@ -114,6 +115,7 @@ public abstract class UpdateWorkerTestBase : TestBase
114
115
  isTransitive,
115
116
  additionalFiles,
116
117
  additionalFilesExpected,
118
+ additionalChecks,
117
119
  packages,
118
120
  experimentsManager,
119
121
  expectedResult);
@@ -127,6 +129,7 @@ public abstract class UpdateWorkerTestBase : TestBase
127
129
  bool isTransitive = false,
128
130
  TestFile[]? additionalFiles = null,
129
131
  TestFile[]? additionalFilesExpected = null,
132
+ Action<string>? additionalChecks = null,
130
133
  MockNuGetPackage[]? packages = null,
131
134
  ExperimentsManager? experimentsManager = null,
132
135
  ExpectedUpdateOperationResult? expectedResult = null)
@@ -156,6 +159,21 @@ public abstract class UpdateWorkerTestBase : TestBase
156
159
  {
157
160
  ValidateUpdateOperationResult(expectedResult, actualResult!);
158
161
  }
162
+ else if ((actualResult.ErrorType ?? ErrorType.None) != ErrorType.None)
163
+ {
164
+ throw new Exception($"Result indicates failure: ErrorType={actualResult.ErrorType}, ErrorDetails={actualResult.ErrorDetails}");
165
+ }
166
+
167
+ if (additionalChecks is not null)
168
+ {
169
+ var sourcesDirectory = temporaryDirectory;
170
+ if (placeFilesInSrc)
171
+ {
172
+ sourcesDirectory = Path.Combine(temporaryDirectory, "src");
173
+ }
174
+
175
+ additionalChecks(sourcesDirectory);
176
+ }
159
177
  });
160
178
 
161
179
  var expectedResultFiles = additionalFilesExpected.Prepend((projectFilePath, expectedProjectContents)).ToArray();
@@ -69,10 +69,10 @@ public partial class UpdateWorkerTests
69
69
  <PropertyGroup>
70
70
  <TargetFramework>netstandard2.0</TargetFramework>
71
71
  </PropertyGroup>
72
-
72
+
73
73
  <ItemGroup>
74
74
  <PackageReference Include="Some.Package" Version="13.0.3" />
75
- </ItemGroup>>
75
+ </ItemGroup>
76
76
  </Project>
77
77
  """,
78
78
  additionalFiles:
@@ -0,0 +1,251 @@
1
+ using Xunit;
2
+
3
+ namespace NuGetUpdater.Core.Test.Update;
4
+
5
+ public partial class UpdateWorkerTests
6
+ {
7
+ public class LockFile : UpdateWorkerTestBase
8
+ {
9
+ [Fact]
10
+ public async Task UpdateSingleDependency()
11
+ {
12
+ await TestUpdateForProject("Some.Package", "1.0.0", "2.0.0",
13
+ packages:
14
+ [
15
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
16
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0"),
17
+ ],
18
+ // initial
19
+ projectContents: $"""
20
+ <Project Sdk="Microsoft.NET.Sdk">
21
+ <PropertyGroup>
22
+ <TargetFramework>net8.0</TargetFramework>
23
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
24
+ </PropertyGroup>
25
+
26
+ <ItemGroup>
27
+ <PackageReference Include="Some.Package" Version="1.0.0" />
28
+ </ItemGroup>
29
+ </Project>
30
+ """,
31
+ additionalFiles:
32
+ [
33
+ ("packages.lock.json", "{}")
34
+ ],
35
+ // expected
36
+ expectedProjectContents: $"""
37
+ <Project Sdk="Microsoft.NET.Sdk">
38
+ <PropertyGroup>
39
+ <TargetFramework>net8.0</TargetFramework>
40
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
41
+ </PropertyGroup>
42
+
43
+ <ItemGroup>
44
+ <PackageReference Include="Some.Package" Version="2.0.0" />
45
+ </ItemGroup>
46
+ </Project>
47
+ """,
48
+ additionalChecks: path =>
49
+ {
50
+ var lockContents = File.ReadAllText(Path.Combine(path, "packages.lock.json"));
51
+ Assert.Contains("\"resolved\": \"2.0.0\"", lockContents);
52
+ }
53
+ );
54
+ }
55
+
56
+ [Fact]
57
+ public async Task UpdateSingleDependency_CentralPackageManagement()
58
+ {
59
+ await TestUpdateForProject("Some.Package", "1.0.0", "2.0.0",
60
+ packages:
61
+ [
62
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
63
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0"),
64
+ ],
65
+ // initial
66
+ projectContents: $"""
67
+ <Project Sdk="Microsoft.NET.Sdk">
68
+ <PropertyGroup>
69
+ <TargetFramework>net8.0</TargetFramework>
70
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
71
+ </PropertyGroup>
72
+
73
+ <ItemGroup>
74
+ <PackageReference Include="Some.Package" />
75
+ </ItemGroup>
76
+ </Project>
77
+ """,
78
+ additionalFiles:
79
+ [
80
+ ("packages.lock.json", "{}"),
81
+ ("Directory.Packages.props", """
82
+ <Project>
83
+ <PropertyGroup>
84
+ <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
85
+ </PropertyGroup>
86
+
87
+ <ItemGroup>
88
+ <PackageVersion Include="Some.Package" Version="1.0.0" />
89
+ </ItemGroup>
90
+ </Project>
91
+ """)
92
+ ],
93
+ // expected
94
+ expectedProjectContents: $"""
95
+ <Project Sdk="Microsoft.NET.Sdk">
96
+ <PropertyGroup>
97
+ <TargetFramework>net8.0</TargetFramework>
98
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
99
+ </PropertyGroup>
100
+
101
+ <ItemGroup>
102
+ <PackageReference Include="Some.Package" />
103
+ </ItemGroup>
104
+ </Project>
105
+ """,
106
+ additionalFilesExpected:
107
+ [
108
+ ("Directory.Packages.props", """
109
+ <Project>
110
+ <PropertyGroup>
111
+ <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
112
+ </PropertyGroup>
113
+
114
+ <ItemGroup>
115
+ <PackageVersion Include="Some.Package" Version="2.0.0" />
116
+ </ItemGroup>
117
+ </Project>
118
+ """)
119
+ ],
120
+ additionalChecks: path =>
121
+ {
122
+ var lockContents = File.ReadAllText(Path.Combine(path, "packages.lock.json"));
123
+ Assert.Contains("\"resolved\": \"2.0.0\"", lockContents);
124
+ }
125
+ );
126
+ }
127
+
128
+ [Fact]
129
+ public async Task UpdateSingleDependency_WindowsSpecific()
130
+ {
131
+ await TestUpdateForProject("Some.Package", "1.0.0", "2.0.0",
132
+ packages:
133
+ [
134
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
135
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0"),
136
+ ],
137
+ // initial
138
+ projectContents: $"""
139
+ <Project Sdk="Microsoft.NET.Sdk">
140
+ <PropertyGroup>
141
+ <TargetFramework>net8.0-windows</TargetFramework>
142
+ <UseWindowsForms>true</UseWindowsForms>
143
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
144
+ </PropertyGroup>
145
+
146
+ <ItemGroup>
147
+ <PackageReference Include="Some.Package" Version="1.0.0" />
148
+ </ItemGroup>
149
+ </Project>
150
+ """,
151
+ additionalFiles:
152
+ [
153
+ ("packages.lock.json", "{}")
154
+ ],
155
+ // expected
156
+ expectedProjectContents: $"""
157
+ <Project Sdk="Microsoft.NET.Sdk">
158
+ <PropertyGroup>
159
+ <TargetFramework>net8.0-windows</TargetFramework>
160
+ <UseWindowsForms>true</UseWindowsForms>
161
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
162
+ </PropertyGroup>
163
+
164
+ <ItemGroup>
165
+ <PackageReference Include="Some.Package" Version="2.0.0" />
166
+ </ItemGroup>
167
+ </Project>
168
+ """,
169
+ additionalChecks: path =>
170
+ {
171
+ var lockContents = File.ReadAllText(Path.Combine(path, "packages.lock.json"));
172
+ Assert.Contains("\"resolved\": \"2.0.0\"", lockContents);
173
+ }
174
+ );
175
+ }
176
+
177
+ [Fact]
178
+ public async Task UpdateSingleDependency_CentralPackageManagement_WindowsSpecific()
179
+ {
180
+ await TestUpdateForProject("Some.Package", "1.0.0", "2.0.0",
181
+ packages:
182
+ [
183
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
184
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0"),
185
+ ],
186
+ // initial
187
+ projectContents: $"""
188
+ <Project Sdk="Microsoft.NET.Sdk">
189
+ <PropertyGroup>
190
+ <TargetFramework>net8.0-windows</TargetFramework>
191
+ <UseWindowsForms>true</UseWindowsForms>
192
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
193
+ </PropertyGroup>
194
+
195
+ <ItemGroup>
196
+ <PackageReference Include="Some.Package" />
197
+ </ItemGroup>
198
+ </Project>
199
+ """,
200
+ additionalFiles:
201
+ [
202
+ ("packages.lock.json", "{}"),
203
+ ("Directory.Packages.props", """
204
+ <Project>
205
+ <PropertyGroup>
206
+ <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
207
+ </PropertyGroup>
208
+
209
+ <ItemGroup>
210
+ <PackageVersion Include="Some.Package" Version="1.0.0" />
211
+ </ItemGroup>
212
+ </Project>
213
+ """)
214
+ ],
215
+ // expected
216
+ expectedProjectContents: $"""
217
+ <Project Sdk="Microsoft.NET.Sdk">
218
+ <PropertyGroup>
219
+ <TargetFramework>net8.0-windows</TargetFramework>
220
+ <UseWindowsForms>true</UseWindowsForms>
221
+ <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
222
+ </PropertyGroup>
223
+
224
+ <ItemGroup>
225
+ <PackageReference Include="Some.Package" />
226
+ </ItemGroup>
227
+ </Project>
228
+ """,
229
+ additionalFilesExpected:
230
+ [
231
+ ("Directory.Packages.props", """
232
+ <Project>
233
+ <PropertyGroup>
234
+ <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
235
+ </PropertyGroup>
236
+
237
+ <ItemGroup>
238
+ <PackageVersion Include="Some.Package" Version="2.0.0" />
239
+ </ItemGroup>
240
+ </Project>
241
+ """)
242
+ ],
243
+ additionalChecks: path =>
244
+ {
245
+ var lockContents = File.ReadAllText(Path.Combine(path, "packages.lock.json"));
246
+ Assert.Contains("\"resolved\": \"2.0.0\"", lockContents);
247
+ }
248
+ );
249
+ }
250
+ }
251
+ }
@@ -495,7 +495,7 @@ public partial class UpdateWorkerTests
495
495
  MockNuGetPackage.CreateSimplePackage("Some.Package", "13.0.1", "net8.0"),
496
496
  ],
497
497
  projectContents: $"""
498
- <Project Sdk="Microsoft.NET.Sdk">">
498
+ <Project Sdk="Microsoft.NET.Sdk">
499
499
  <PropertyGroup>
500
500
  <TargetFramework>net8.0</TargetFramework>
501
501
  <SomePackageVersion>9.0.1</SomePackageVersion>
@@ -3075,14 +3075,14 @@ public partial class UpdateWorkerTests
3075
3075
  public async Task UpdatingTransitiveDependencyWithNewSolverCanUpdateJustTheTopLevelPackage()
3076
3076
  {
3077
3077
  // we've been asked to explicitly update a transitive dependency, but we can solve it by updating the top-level package instead
3078
- await TestUpdateForProject("Transitive.Package", "1.0.0", "2.0.0",
3078
+ await TestUpdateForProject("Transitive.Package", "7.0.0", "8.0.0",
3079
3079
  isTransitive: true,
3080
3080
  packages:
3081
3081
  [
3082
- MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0", [("net8.0", [("Transitive.Package", "[1.0.0]")])]),
3083
- MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0", [("net8.0", [("Transitive.Package", "[2.0.0]")])]),
3084
- MockNuGetPackage.CreateSimplePackage("Transitive.Package", "1.0.0", "net8.0"),
3085
- MockNuGetPackage.CreateSimplePackage("Transitive.Package", "2.0.0", "net8.0"),
3082
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0", [("net8.0", [("Transitive.Package", "[7.0.0]")])]),
3083
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0", [("net8.0", [("Transitive.Package", "[8.0.0]")])]),
3084
+ MockNuGetPackage.CreateSimplePackage("Transitive.Package", "7.0.0", "net8.0"),
3085
+ MockNuGetPackage.CreateSimplePackage("Transitive.Package", "8.0.0", "net8.0"),
3086
3086
  ],
3087
3087
  projectContents: """
3088
3088
  <Project Sdk="Microsoft.NET.Sdk">
@@ -1969,7 +1969,7 @@ public partial class UpdateWorkerTests
1969
1969
  <VSToolsPath Condition="'$(VSToolsPath)' == ''">C:\some\path\that\does\not\exist</VSToolsPath>
1970
1970
  </PropertyGroup>
1971
1971
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
1972
- <Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
1972
+ <Import Project="$(VSToolsPath)\SomeSubPath\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
1973
1973
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
1974
1974
  Other similar extension points exist, see Microsoft.Common.targets.
1975
1975
  <Target Name="BeforeBuild">
@@ -2050,7 +2050,7 @@ public partial class UpdateWorkerTests
2050
2050
  <VSToolsPath Condition="'$(VSToolsPath)' == ''">C:\some\path\that\does\not\exist</VSToolsPath>
2051
2051
  </PropertyGroup>
2052
2052
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
2053
- <Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
2053
+ <Import Project="$(VSToolsPath)\SomeSubPath\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
2054
2054
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
2055
2055
  Other similar extension points exist, see Microsoft.Common.targets.
2056
2056
  <Target Name="BeforeBuild">
@@ -2292,15 +2292,73 @@ public partial class UpdateWorkerTests
2292
2292
  }
2293
2293
 
2294
2294
  [Fact]
2295
- public async Task ReportsPrivateSourceAuthenticationFailure()
2295
+ public async Task MissingVisualStudioComponentTargetsAreReportedAsMissingFiles()
2296
2296
  {
2297
- static (int, string) TestHttpHandler(string uriString)
2297
+ using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync(
2298
+ [
2299
+ ("project.csproj", """
2300
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2301
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
2302
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Some.Visual.Studio.Component.props" />
2303
+ <PropertyGroup>
2304
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
2305
+ </PropertyGroup>
2306
+ <ItemGroup>
2307
+ <None Include="packages.config" />
2308
+ </ItemGroup>
2309
+ <ItemGroup>
2310
+ <Reference Include="Some.Package, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
2311
+ <HintPath>packages\Some.Package.1.0.0\lib\net45\Some.Package.dll</HintPath>
2312
+ <Private>True</Private>
2313
+ </Reference>
2314
+ </ItemGroup>
2315
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
2316
+ </Project>
2317
+ """),
2318
+ ("packages.config", """
2319
+ <packages>
2320
+ <package id="Some.Package" version="1.0.0" targetFramework="net45" />
2321
+ </packages>
2322
+ """),
2323
+ ("NuGet.Config", """
2324
+ <configuration>
2325
+ <packageSources>
2326
+ <clear />
2327
+ <add key="private_feed" value="packages" />
2328
+ </packageSources>
2329
+ </configuration>
2330
+ """)
2331
+ ]
2332
+ );
2333
+ MockNuGetPackage[] packages =
2334
+ [
2335
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net45"),
2336
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net45"),
2337
+ ];
2338
+ await MockNuGetPackagesInDirectory(packages, Path.Combine(temporaryDirectory.DirectoryPath, "packages"));
2339
+ var resultOutputPath = Path.Combine(temporaryDirectory.DirectoryPath, "result.json");
2340
+
2341
+ var worker = new UpdaterWorker(new ExperimentsManager(), new TestLogger());
2342
+ await worker.RunAsync(temporaryDirectory.DirectoryPath, "project.csproj", "Some.Package", "1.0.0", "1.1.0", isTransitive: false, resultOutputPath: resultOutputPath);
2343
+
2344
+ var resultContents = await File.ReadAllTextAsync(resultOutputPath);
2345
+ var result = JsonSerializer.Deserialize<UpdateOperationResult>(resultContents, UpdaterWorker.SerializerOptions)!;
2346
+ Assert.Equal(ErrorType.MissingFile, result.ErrorType);
2347
+ Assert.Equal("$(MSBuildExtensionsPath32)/Microsoft/VisualStudio/v$(VisualStudioVersion)/Some.Visual.Studio.Component.props", result.ErrorDetails!.ToString().NormalizePathToUnix());
2348
+ }
2349
+
2350
+ [Theory]
2351
+ [InlineData(401)]
2352
+ [InlineData(403)]
2353
+ public async Task ReportsPrivateSourceAuthenticationFailure(int httpStatusCode)
2354
+ {
2355
+ (int, string) TestHttpHandler(string uriString)
2298
2356
  {
2299
2357
  var uri = new Uri(uriString, UriKind.Absolute);
2300
2358
  var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
2301
2359
  return uri.PathAndQuery switch
2302
2360
  {
2303
- _ => (401, "{}"), // everything is unauthorized
2361
+ _ => (httpStatusCode, "{}"), // everything is unauthorized
2304
2362
  };
2305
2363
  }
2306
2364
  using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);