dependabot-nuget 0.310.0 → 0.312.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
  3. data/helpers/lib/NuGetUpdater/DotNetPackageCorrelation.Test/DotNetPackageCorrelation.Test.csproj +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +2 -2
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj +1 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +7 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadResponseException.cs +12 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +121 -9
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ClosePullRequest.cs +13 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +20 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +1 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +26 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MessageBase.cs +1 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +12 -2
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatePullRequest.cs +16 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +19 -50
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +33 -7
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +49 -13
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +41 -11
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +13 -1
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +1 -2
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +6 -25
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +13 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/VersionFinderTests.cs +74 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +1 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +3 -9
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +3 -4
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +58 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +37 -2
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +116 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +231 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +32 -8
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +714 -11
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +44 -13
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +2 -39
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +9 -5
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +99 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationBaseTests.cs +3 -3
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +1 -13
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/LoggerTests.cs +0 -1
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +66 -8
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +1 -1
  44. data/helpers/lib/NuGetUpdater/global.json +1 -1
  45. metadata +30 -27
@@ -1,5 +1,7 @@
1
1
  using System.Collections.Immutable;
2
2
 
3
+ using NuGet.Versioning;
4
+
3
5
  using NuGetUpdater.Core.Run.ApiModel;
4
6
  using NuGetUpdater.Core.Updater;
5
7
 
@@ -7,34 +9,68 @@ namespace NuGetUpdater.Core.Run;
7
9
 
8
10
  public class PullRequestTextGenerator
9
11
  {
12
+ private const int MaxTitleLength = 70;
13
+
10
14
  public static string GetPullRequestTitle(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
11
15
  {
12
16
  // simple version looks like
13
17
  // Update Some.Package to 1.2.3
14
18
  // if multiple packages are updated to multiple versions, result looks like:
15
19
  // Update Package.A to 1.0.0, 2.0.0; Package.B to 3.0.0, 4.0.0
16
- var dependencySets = updateOperationsPerformed
17
- .GroupBy(d => d.DependencyName, StringComparer.OrdinalIgnoreCase)
18
- .OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase)
19
- .Select(g => new
20
- {
21
- Name = g.Key,
22
- Versions = g
23
- .Select(d => d.NewVersion)
24
- .OrderBy(v => v)
25
- .ToArray()
26
- })
27
- .ToArray();
20
+ var dependencySets = GetDependencySets(updateOperationsPerformed);
28
21
  var updatedPartTitles = dependencySets
29
22
  .Select(d => $"{d.Name} to {string.Join(", ", d.Versions.Select(v => v.ToString()))}")
30
23
  .ToArray();
31
24
  var title = $"{job.CommitMessageOptions?.Prefix}Update {string.Join("; ", updatedPartTitles)}";
25
+
26
+ // don't let the title get too long
27
+ if (title.Length > MaxTitleLength && updatedPartTitles.Length >= 3)
28
+ {
29
+ title = $"{job.CommitMessageOptions?.Prefix}Update {dependencySets[0].Name} and {dependencySets.Length - 1} other dependencies";
30
+ }
31
+
32
32
  return title;
33
33
  }
34
34
 
35
35
  public static string GetPullRequestCommitMessage(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
36
36
  {
37
- return GetPullRequestTitle(job, updateOperationsPerformed, dependencyGroupName);
37
+ // updating a single dependency looks like
38
+ // Update Some.Package to 1.2.3
39
+ // if multiple packages are updated, result looks like:
40
+ // Update:
41
+ // - Package.A to 1.0.0
42
+ // - Package.B to 2.0.0
43
+ var dependencySets = GetDependencySets(updateOperationsPerformed);
44
+ if (dependencySets.Length == 1)
45
+ {
46
+ var depName = dependencySets[0].Name;
47
+ var depVersions = dependencySets[0].Versions.Select(v => v.ToString());
48
+ return $"Update {dependencySets[0].Name} to {string.Join(", ", depVersions)}";
49
+ }
50
+
51
+ var updatedParts = dependencySets
52
+ .Select(d => $"- {d.Name} to {string.Join(", ", d.Versions.Select(v => v.ToString()))}")
53
+ .ToArray();
54
+ var message = string.Join("\n", ["Update:", .. updatedParts]);
55
+ return message;
56
+ }
57
+
58
+ private static (string Name, NuGetVersion[] Versions)[] GetDependencySets(ImmutableArray<UpdateOperationBase> updateOperationsPerformed)
59
+ {
60
+ var dependencySets = updateOperationsPerformed
61
+ .GroupBy(d => d.DependencyName, StringComparer.OrdinalIgnoreCase)
62
+ .OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase)
63
+ .Select(g =>
64
+ {
65
+ var name = g.Key;
66
+ var versions = g
67
+ .Select(d => d.NewVersion)
68
+ .OrderBy(v => v)
69
+ .ToArray();
70
+ return (name, versions);
71
+ })
72
+ .ToArray();
73
+ return dependencySets;
38
74
  }
39
75
 
40
76
  public static string GetPullRequestBody(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
@@ -113,7 +113,16 @@ public class RunWorker
113
113
  _logger.Info("Discovery JSON content:");
114
114
  _logger.Info(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
115
115
 
116
- // TODO: report errors
116
+ if (discoveryResult.Error is not null)
117
+ {
118
+ // this is unrecoverable
119
+ await _apiHandler.RecordUpdateJobError(discoveryResult.Error);
120
+ return new()
121
+ {
122
+ Base64DependencyFiles = [],
123
+ BaseCommitSha = baseCommitSha,
124
+ };
125
+ }
117
126
 
118
127
  // report dependencies
119
128
  var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult, repoContentsPath.FullName);
@@ -201,17 +210,30 @@ public class RunWorker
201
210
 
202
211
  var dependencyInfo = GetDependencyInfo(job, dependency);
203
212
  var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
204
- // TODO: log analysisResult
213
+ _logger.Info("Analysis content:");
214
+ _logger.Info(JsonSerializer.Serialize(analysisResult, AnalyzeWorker.SerializerOptions));
215
+
216
+ if (analysisResult.Error is not null)
217
+ {
218
+ await _apiHandler.RecordUpdateJobError(analysisResult.Error);
219
+ continue;
220
+ }
221
+
205
222
  if (analysisResult.CanUpdate)
206
223
  {
207
224
  if (!job.UpdatingAPullRequest)
208
225
  {
209
- var existingPullRequest = job.GetExistingPullRequestForDependency(analysisResult.UpdatedDependencies.First(d => d.Name.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)));
210
- if (existingPullRequest is not null)
226
+ var updatedDependencyFromAnalysis = analysisResult.UpdatedDependencies
227
+ .FirstOrDefault(d => d.Name.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase));
228
+ if (updatedDependencyFromAnalysis is not null)
211
229
  {
212
- await SendApiMessage(new PullRequestExistsForLatestVersion(dependency.Name, analysisResult.UpdatedVersion));
213
- unhandledPullRequestDependenciesSet.RemoveWhere(handled => handled.Count == 1 && handled.Contains(dependency.Name));
214
- continue;
230
+ var existingPullRequest = job.GetExistingPullRequestForDependency(updatedDependencyFromAnalysis);
231
+ if (existingPullRequest is not null)
232
+ {
233
+ await SendApiMessage(new PullRequestExistsForLatestVersion(dependency.Name, analysisResult.UpdatedVersion));
234
+ unhandledPullRequestDependenciesSet.RemoveWhere(handled => handled.Count == 1 && handled.Contains(dependency.Name));
235
+ continue;
236
+ }
215
237
  }
216
238
  }
217
239
 
@@ -240,8 +262,11 @@ public class RunWorker
240
262
  };
241
263
 
242
264
  var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, updateOperation.ProjectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
243
- // TODO: need to report if anything was actually updated
244
- if (updateResult.Error is null)
265
+ if (updateResult.Error is not null)
266
+ {
267
+ await _apiHandler.RecordUpdateJobError(updateResult.Error);
268
+ }
269
+ else
245
270
  {
246
271
  actualUpdatedDependencies.Add(updatedDependency);
247
272
  }
@@ -365,10 +390,15 @@ public class RunWorker
365
390
 
366
391
  private async Task SendApiMessage(MessageBase? message)
367
392
  {
393
+ if (message is null)
394
+ {
395
+ return;
396
+ }
397
+
398
+ var report = message.GetReport();
399
+ _logger.Info(report);
368
400
  switch (message)
369
401
  {
370
- case null:
371
- break;
372
402
  case JobErrorBase error:
373
403
  await _apiHandler.RecordUpdateJobError(error);
374
404
  break;
@@ -1,5 +1,6 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Text.Json;
3
+ using System.Text.RegularExpressions;
3
4
 
4
5
  using Microsoft.Language.Xml;
5
6
 
@@ -526,7 +527,7 @@ internal static class PackageReferenceUpdater
526
527
  /// <returns>The updated files.</returns>
527
528
  private static async Task<IEnumerable<string>> AddTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, ExperimentsManager experimentsManager, ILogger logger)
528
529
  {
529
- var updatedFiles = new[] { projectPath }; // assume this worked unless...
530
+ var updatedFiles = new List<string>() { projectPath }; // assume this worked unless...
530
531
  var projectDirectory = Path.GetDirectoryName(projectPath)!;
531
532
  await MSBuildHelper.HandleGlobalJsonAsync(projectDirectory, repoRootPath, experimentsManager, async () =>
532
533
  {
@@ -545,6 +546,17 @@ internal static class PackageReferenceUpdater
545
546
  updatedFiles = [];
546
547
  }
547
548
 
549
+ // output might contain a line like this:
550
+ // info : PackageReference for package 'Some.Package' added to 'C:\project.csproj' and PackageVersion added to central package management file 'C:\Directory.Packages.props'.
551
+ // we explicitly want to pull out this: ^^^^^^^^^^^^^^^^^^^^^^^^^^^
552
+ // so we can report all files updated on disk
553
+ var match = Regex.Match(stdout, @"added to central package management file '(?<CentralPackageManagementFilePath>[^']+)'");
554
+ if (match.Success)
555
+ {
556
+ var cpmFilePath = match.Groups["CentralPackageManagementFilePath"].Value;
557
+ updatedFiles.Add(cpmFilePath);
558
+ }
559
+
548
560
  return exitCode;
549
561
  }, logger, retainMSBuildSdks: true);
550
562
 
@@ -29,8 +29,7 @@ public abstract record UpdateOperationBase
29
29
  return string.Empty;
30
30
  }
31
31
 
32
- var separator = "\n ";
33
- var report = $"Performed the following updates:{separator}{string.Join(separator, updateMessages.Select(m => $"- {m}"))}";
32
+ var report = $"Performed the following updates:\n{string.Join("\n", updateMessages.Select(m => $"- {m}"))}";
34
33
  return report;
35
34
  }
36
35
 
@@ -465,11 +465,8 @@ public class PackageManager
465
465
  NuGetVersion latestVersion = versions.Where(v => !v.IsPrerelease).Max();
466
466
 
467
467
  // Loop from the current version to the latest version, use next patch as a limit (unless there's a limit) so it doesn't look for versions that don't exist
468
- for (NuGetVersion version = currentVersionParent; version <= latestVersion; version = NextPatch(version, versions))
468
+ for (NuGetVersion? version = currentVersionParent; version is not null && version <= latestVersion; version = NextPatch(version, versions))
469
469
  {
470
- string parentVersion = version.ToString();
471
- parent.NewVersion = parentVersion;
472
-
473
470
  // Check if the parent needs to be updated since the child isn't in the existing package list and the parent can update to a newer version to remove the dependency
474
471
  List<PackageToUpdate> dependencyListParentTemp = await GetDependenciesAsync(parent, targetFramework, projectDirectory, logger);
475
472
  PackageToUpdate parentDependencyTemp = dependencyListParentTemp.FirstOrDefault(p => string.Compare(p.PackageName, package.PackageName, StringComparison.OrdinalIgnoreCase) == 0);
@@ -477,7 +474,7 @@ public class PackageManager
477
474
  // If the newer package version of the parent has the same version as the parent's previous dependency, update
478
475
  if (parentDependencyTemp.CurrentVersion == package.CurrentVersion)
479
476
  {
480
- parent.NewVersion = parentVersion;
477
+ parent.NewVersion = version.ToString();
481
478
  parent.CurrentVersion = null;
482
479
  await UpdateVersion(existingPackages, parent, targetFramework, projectDirectory, logger);
483
480
  package.IsSpecific = true;
@@ -534,18 +531,10 @@ public class PackageManager
534
531
  }
535
532
 
536
533
  // Method to update a version to the next available version for a package
537
- public NuGetVersion NextPatch(NuGetVersion version, IEnumerable<NuGetVersion> allVersions)
534
+ private static NuGetVersion? NextPatch(NuGetVersion version, IEnumerable<NuGetVersion> allVersions)
538
535
  {
539
- var versions = allVersions.Where(v => v > version);
540
-
541
- if (!versions.Any())
542
- {
543
- // If there are no greater versions, return current version
544
- return version;
545
- }
546
-
547
- // Find smallest version in the versions
548
- return versions.Min();
536
+ var candidateVersions = allVersions.Where(v => v > version).ToArray();
537
+ return candidateVersions.Min();
549
538
  }
550
539
 
551
540
  // Method to find a compatible version with the child for the parent to update to
@@ -588,18 +577,10 @@ public class PackageManager
588
577
  }
589
578
 
590
579
  // Loop from the current version to the latest version, use next patch as a limit (unless there's a limit) so it doesn't look for versions that don't exist
591
- for (NuGetVersion version = CurrentVersion; version <= latestVersion; version = NextPatch(version, versions))
580
+ for (NuGetVersion? version = CurrentVersion; version is not null && version <= latestVersion; version = NextPatch(version, versions))
592
581
  {
593
582
  possibleParent.NewVersion = version.ToString();
594
583
 
595
- NuGetVersion nextPatch = NextPatch(version, versions);
596
-
597
- // If the next patch is the same as the CurrentVersion, then nothing is needed
598
- if (nextPatch == version)
599
- {
600
- return nextPatch;
601
- }
602
-
603
584
  // Check if there's compatibility with parent and dependency
604
585
  if (await IsCompatibleAsync(possibleParent, possibleDependency, targetFramework, nugetContext.CurrentDirectory, logger))
605
586
  {
@@ -1029,11 +1029,23 @@ internal static partial class MSBuildHelper
1029
1029
  new Regex(@"Unable to find package (?<PackageName>[^ ]+)\. No packages exist with this id in source\(s\): (?<PackageSource>.*)$", RegexOptions.Multiline),
1030
1030
  new Regex(@"Unable to find package (?<PackageName>[^ ]+) with version \((?<PackageVersion>[^)]+)\)"),
1031
1031
  new Regex(@"Could not resolve SDK ""(?<PackageName>[^ ]+)""\."),
1032
+ new Regex(@"Failed to fetch results from V2 feed at '.*FindPackagesById\(\)\?id='(?<PackageName>[^']+)'&semVerLevel=2\.0\.0' with following message : Response status code does not indicate success: 404\."),
1032
1033
  };
1033
1034
  var matches = patterns.Select(p => p.Match(output)).Where(m => m.Success);
1034
1035
  if (matches.Any())
1035
1036
  {
1036
- var packages = matches.Select(m => m.Groups["PackageName"].Value).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
1037
+ var packages = matches.Select(m =>
1038
+ {
1039
+ var packageName = m.Groups["PackageName"].Value;
1040
+ if (m.Groups.TryGetValue("PackageVersion", out var versionGroup))
1041
+ {
1042
+ packageName = $"{packageName}/{versionGroup.Value}";
1043
+ }
1044
+
1045
+ return packageName;
1046
+ })
1047
+ .Distinct(StringComparer.OrdinalIgnoreCase)
1048
+ .ToArray();
1037
1049
  throw new DependencyNotFoundException(packages);
1038
1050
  }
1039
1051
  }
@@ -1,9 +1,12 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text.Json;
2
3
 
3
4
  using NuGet.Frameworks;
4
5
  using NuGet.Versioning;
5
6
 
6
7
  using NuGetUpdater.Core.Analyze;
8
+ using NuGetUpdater.Core.Run;
9
+ using NuGetUpdater.Core.Run.ApiModel;
7
10
  using NuGetUpdater.Core.Test.Update;
8
11
  using NuGetUpdater.Core.Test.Utilities;
9
12
 
@@ -11,7 +14,7 @@ using Xunit;
11
14
 
12
15
  namespace NuGetUpdater.Core.Test.Analyze;
13
16
 
14
- public class VersionFinderTests
17
+ public class VersionFinderTests : TestBase
15
18
  {
16
19
  [Fact]
17
20
  public void VersionFilter_VersionInIgnoredVersions_ReturnsFalse()
@@ -223,4 +226,74 @@ public class VersionFinderTests
223
226
  var expected = new[] { "2.0.0" };
224
227
  AssertEx.Equal(expected, actual);
225
228
  }
229
+
230
+ [Fact]
231
+ public async Task FeedReturnsBadJson()
232
+ {
233
+ // arrange
234
+ using var http = TestHttpServer.CreateTestStringServer(url =>
235
+ {
236
+ var uri = new Uri(url, UriKind.Absolute);
237
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
238
+ return uri.PathAndQuery switch
239
+ {
240
+ // initial and search query are good, update should be possible...
241
+ "/index.json" => (200, $$"""
242
+ {
243
+ "version": "3.0.0",
244
+ "resources": [
245
+ {
246
+ "@id": "{{baseUrl}}/download",
247
+ "@type": "PackageBaseAddress/3.0.0"
248
+ },
249
+ {
250
+ "@id": "{{baseUrl}}/query",
251
+ "@type": "SearchQueryService"
252
+ },
253
+ {
254
+ "@id": "{{baseUrl}}/registrations",
255
+ "@type": "RegistrationsBaseUrl"
256
+ }
257
+ ]
258
+ }
259
+ """),
260
+ _ => (200, "") // empty string instead of expected JSON object
261
+ };
262
+ });
263
+ var feedUrl = $"{http.BaseUrl.TrimEnd('/')}/index.json";
264
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync(
265
+ ("NuGet.Config", $"""
266
+ <configuration>
267
+ <packageSources>
268
+ <clear />
269
+ <add key="private_feed" value="{feedUrl}" allowInsecureConnections="true" />
270
+ </packageSources>
271
+ </configuration>
272
+ """)
273
+ );
274
+
275
+ // act
276
+ var tfm = NuGetFramework.Parse("net9.0");
277
+ var dependencyInfo = new DependencyInfo
278
+ {
279
+ Name = "Some.Dependency",
280
+ Version = "1.0.0",
281
+ IsVulnerable = false,
282
+ IgnoredVersions = [],
283
+ Vulnerabilities = [],
284
+ };
285
+ var logger = new TestLogger();
286
+ var nugetContext = new NuGetContext(tempDir.DirectoryPath);
287
+ var exception = await Assert.ThrowsAsync<BadResponseException>(async () =>
288
+ {
289
+ await VersionFinder.GetVersionsAsync([tfm], dependencyInfo, nugetContext, logger, CancellationToken.None);
290
+ });
291
+ var error = JobErrorBase.ErrorFromException(exception, "TEST-JOB-ID", tempDir.DirectoryPath);
292
+
293
+ // assert
294
+ var expected = new PrivateSourceBadResponse([feedUrl]);
295
+ var expectedJson = JsonSerializer.Serialize(expected, RunWorker.SerializerOptions);
296
+ var actualJson = JsonSerializer.Serialize(error, RunWorker.SerializerOptions);
297
+ Assert.Equal(expectedJson, actualJson);
298
+ }
226
299
  }
@@ -111,7 +111,7 @@ public class CloneWorkerTests
111
111
 
112
112
  // assert
113
113
  Assert.Equal(1, result);
114
- var expectedParseErrorObject = testApiHandler.ReceivedMessages.Single(m => m.Type == typeof(UnknownError));
114
+ var expectedParseErrorObject = testApiHandler.ReceivedMessages.First(m => m.Type == typeof(UnknownError));
115
115
  var unknownError = (UnknownError)expectedParseErrorObject.Object;
116
116
  Assert.Equal("JsonException", unknownError.Details["error-class"]);
117
117
  }
@@ -832,10 +832,8 @@ public partial class DiscoveryWorkerTests
832
832
  {
833
833
  FilePath = "myproj.csproj",
834
834
  Dependencies = [
835
- new("Some.Package", "1.2.3.4", DependencyType.PackageReference, TargetFrameworks: ["net7.0"], IsDirect: true),
836
- new("Some.Package", "1.2.3.4", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true),
837
- new("Transitive.Dependency", "5.6.7.8", DependencyType.Unknown, TargetFrameworks: ["net7.0"], IsTransitive: true),
838
- new("Transitive.Dependency", "5.6.7.8", DependencyType.Unknown, TargetFrameworks: ["net8.0"], IsTransitive: true),
835
+ new("Some.Package", "1.2.3.4", DependencyType.PackageReference, TargetFrameworks: ["net7.0", "net8.0"], IsDirect: true),
836
+ new("Transitive.Dependency", "5.6.7.8", DependencyType.Unknown, TargetFrameworks: ["net7.0", "net8.0"], IsTransitive: true),
839
837
  ],
840
838
  Properties = [
841
839
  new("TargetFrameworks", "net7.0;net8.0", "myproj.csproj"),
@@ -1191,11 +1189,7 @@ public partial class DiscoveryWorkerTests
1191
1189
  {
1192
1190
  FilePath = "project.csproj",
1193
1191
  Dependencies = [
1194
- new("Some.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0-android"], IsDirect: true),
1195
- new("Some.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0-ios"], IsDirect: true),
1196
- new("Some.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0-maccatalyst"], IsDirect: true),
1197
- new("Some.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0-macos"], IsDirect: true),
1198
- new("Some.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0-windows"], IsDirect: true),
1192
+ new("Some.Package", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0-android", "net8.0-ios", "net8.0-maccatalyst", "net8.0-macos", "net8.0-windows"], IsDirect: true),
1199
1193
  ],
1200
1194
  Properties = [
1201
1195
  new("TargetFrameworks", "net8.0-ios;net8.0-android;net8.0-macos;net8.0-maccatalyst;net8.0-windows", @"src/project.csproj"),
@@ -27,7 +27,7 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
27
27
  {
28
28
  // this package ships with the SDK and is automatically added for F# projects but should be manually added here to make the test consistent
29
29
  // only direct package discovery finds this, though
30
- expectedDependencies.Add(new Dependency("FSharp.Core", "9.0.100", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true));
30
+ expectedDependencies.Add(new Dependency("FSharp.Core", MockNuGetPackage.FSharpCorePackageVersion.Value, DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true));
31
31
  }
32
32
 
33
33
  var experimentsManager = new ExperimentsManager() { UseDirectDiscovery = useDirectDiscovery };
@@ -673,8 +673,7 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
673
673
  FilePath = "src/project.csproj",
674
674
  TargetFrameworks = ["net7.0", "net8.0"],
675
675
  Dependencies = [
676
- new("Some.Package", "9.0.1", DependencyType.PackageReference, TargetFrameworks: ["net7.0"], IsDirect: true),
677
- new("Some.Package", "9.0.1", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true),
676
+ new("Some.Package", "9.0.1", DependencyType.PackageReference, TargetFrameworks: ["net7.0", "net8.0"], IsDirect: true),
678
677
  ],
679
678
  Properties = [
680
679
  new("TargetFrameworks", "net7.0;net8.0", "src/project.csproj")
@@ -1559,7 +1558,7 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
1559
1558
  {
1560
1559
  Path = "",
1561
1560
  Projects = [],
1562
- Error = new DependencyNotFound("Transitive.Dependency"),
1561
+ Error = new DependencyNotFound("Transitive.Dependency/>= 4.5.6"),
1563
1562
  }
1564
1563
  );
1565
1564
  }
@@ -1,4 +1,5 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text;
2
3
 
3
4
  using NuGetUpdater.Core.Discover;
4
5
  using NuGetUpdater.Core.Test.Update;
@@ -297,8 +298,7 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
297
298
  FilePath = "library.csproj",
298
299
  Dependencies =
299
300
  [
300
- new("Some.Dependency", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net7.0"], IsDirect: true),
301
- new("Some.Dependency", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true),
301
+ new("Some.Dependency", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net7.0", "net8.0"], IsDirect: true),
302
302
  ],
303
303
  ImportedFiles = [],
304
304
  Properties =
@@ -524,6 +524,62 @@ public class SdkProjectDiscoveryTests : DiscoveryWorkerTestBase
524
524
  );
525
525
  }
526
526
 
527
+ [Fact]
528
+ public async Task TransitiveDependenciesWithoutAssembliesAreReported()
529
+ {
530
+ await TestDiscoverAsync(
531
+ packages:
532
+ [
533
+ MockNuGetPackage.CreateSimplePackage("Some.Dependency", "1.2.3", "net9.0", [(null, [("Transitive.Dependency", "4.5.6")])]),
534
+ new MockNuGetPackage(
535
+ "Transitive.Dependency",
536
+ "4.5.6",
537
+ Files: [
538
+ ("build/Transitive.Dependency.targets", Encoding.UTF8.GetBytes("<Project />"))
539
+ ],
540
+ DependencyGroups: [(null, [("Super.Transitive.Dependency", "7.8.9")])]
541
+ ),
542
+ MockNuGetPackage.CreateSimplePackage("Super.Transitive.Dependency", "7.8.9", "net9.0"),
543
+ ],
544
+ startingDirectory: "src",
545
+ projectPath: "src/library.csproj",
546
+ files:
547
+ [
548
+ ("src/library.csproj", """
549
+ <Project Sdk="Microsoft.NET.Sdk">
550
+ <PropertyGroup>
551
+ <TargetFramework>net9.0</TargetFramework>
552
+ </PropertyGroup>
553
+ <ItemGroup>
554
+ <PackageReference Include="Some.Dependency" Version="1.2.3" />
555
+ </ItemGroup>
556
+ </Project>
557
+ """)
558
+ ],
559
+ expectedProjects:
560
+ [
561
+ new()
562
+ {
563
+ FilePath = "library.csproj",
564
+ Dependencies =
565
+ [
566
+ new("Some.Dependency", "1.2.3", DependencyType.PackageReference, TargetFrameworks: ["net9.0"], IsDirect: true),
567
+ new("Transitive.Dependency", "4.5.6", DependencyType.Unknown, TargetFrameworks: ["net9.0"], IsTransitive: true),
568
+ new("Super.Transitive.Dependency", "7.8.9", DependencyType.Unknown, TargetFrameworks: ["net9.0"], IsTransitive: true),
569
+ ],
570
+ ImportedFiles = [],
571
+ Properties =
572
+ [
573
+ new("TargetFramework", "net9.0", "src/library.csproj"),
574
+ ],
575
+ TargetFrameworks = ["net9.0"],
576
+ ReferencedProjectPaths = [],
577
+ AdditionalFiles = [],
578
+ },
579
+ ]
580
+ );
581
+ }
582
+
527
583
  private static async Task TestDiscoverAsync(string startingDirectory, string projectPath, TestFile[] files, ImmutableArray<ExpectedSdkProjectDiscoveryResult> expectedProjects, MockNuGetPackage[]? packages = null)
528
584
  {
529
585
  using var testDirectory = await TemporaryDirectory.CreateWithContentsAsync(files);
@@ -346,6 +346,15 @@ namespace NuGetUpdater.Core.Test
346
346
  }
347
347
  });
348
348
 
349
+ public static readonly Lazy<string> FSharpCorePackageVersion = new(() =>
350
+ {
351
+ var fsharpPropsPath = Path.Combine(Path.GetDirectoryName(BundledVersionsPropsPath.Value)!, "FSharp", "Microsoft.FSharp.Core.NetSdk.props");
352
+ var fsharpPropsDocument = XDocument.Load(fsharpPropsPath);
353
+ var fsharpCoreVersionElement = fsharpPropsDocument.XPathSelectElement("//*[name()='FSCorePackageVersion']")!;
354
+ var fsharpCoreVersion = fsharpCoreVersionElement.Value;
355
+ return fsharpCoreVersion;
356
+ });
357
+
349
358
  private static readonly Dictionary<string, MockNuGetPackage> WellKnownPackages = new();
350
359
  public static MockNuGetPackage WellKnownReferencePackage(string packageName, string targetFramework, (string Path, byte[] Content)[]? files = null)
351
360
  {
@@ -370,7 +379,7 @@ namespace NuGetUpdater.Core.Test
370
379
  }
371
380
 
372
381
  string expectedVersion = matchingFrameworkElement.Attribute("TargetingPackVersion")!.Value;
373
- return new(
382
+ WellKnownPackages[key] = new MockNuGetPackage(
374
383
  $"{packageName}.Ref",
375
384
  expectedVersion,
376
385
  AdditionalMetadata:
@@ -440,6 +449,33 @@ namespace NuGetUpdater.Core.Test
440
449
  return WellKnownPackages[key];
441
450
  }
442
451
 
452
+ public static MockNuGetPackage WellKnownWindowsSdkRefPackage(string windowsSdkVersion)
453
+ {
454
+ var packageName = "Microsoft.Windows.SDK.NET.Ref";
455
+ var key = $"{packageName}/{windowsSdkVersion}";
456
+ if (!WellKnownPackages.ContainsKey(key))
457
+ {
458
+ var propsDocument = XDocument.Load(BundledVersionsPropsPath.Value);
459
+ var sdkTpmElement = propsDocument.XPathSelectElement($"/Project/ItemGroup/WindowsSdkSupportedTargetPlatformVersion[@Include='{windowsSdkVersion}']")!;
460
+ var packageVersion = sdkTpmElement.Attribute("WindowsSdkPackageVersion")!.Value!;
461
+ var package = new MockNuGetPackage(packageName, packageVersion, Files: [
462
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
463
+ <FileList Name="Windows SDK .NET 6.0">
464
+ <!-- contents omitted -->
465
+ </FileList>
466
+ """)),
467
+ ("data/RuntimeList.xml", Encoding.UTF8.GetBytes("""
468
+ <FileList Name="Windows SDK .NET 6.0" TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="6.0" FrameworkName="Microsoft.Windows.SDK.NET.Ref">
469
+ <!-- contents omitted -->
470
+ </FileList>
471
+ """))
472
+ ]);
473
+ WellKnownPackages[key] = package;
474
+ }
475
+
476
+ return WellKnownPackages[key];
477
+ }
478
+
443
479
  public static MockNuGetPackage[] CommonPackages { get; } =
444
480
  [
445
481
  CreateSimplePackage("NETStandard.Library", "2.0.3", "netstandard2.0"),
@@ -456,7 +492,6 @@ namespace NuGetUpdater.Core.Test
456
492
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net7.0"),
457
493
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0"),
458
494
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net9.0"),
459
- WellKnownHostPackage("Microsoft.NETCore.App", "net8.0"),
460
495
  ];
461
496
  }
462
497
  }
@@ -13,7 +13,7 @@
13
13
  <ItemGroup>
14
14
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
15
15
  <PackageReference Include="Microsoft.NET.Test.Sdk" />
16
- <PackageReference Include="xunit" />
16
+ <PackageReference Include="xunit.v3" />
17
17
  <PackageReference Include="xunit.runner.visualstudio" />
18
18
  </ItemGroup>
19
19