dependabot-nuget 0.265.0 → 0.266.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +6 -6
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalysisResult.cs +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +73 -77
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +21 -8
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +24 -8
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +33 -16
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +44 -25
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +8 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +2 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +8 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +18 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +2 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationResult.cs +5 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +62 -22
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +19 -1
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +6 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +448 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/CompatibilityCheckerTests.cs +23 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +2 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +148 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +1 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +81 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +27 -7
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +32 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +87 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +88 -0
  30. data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -0
  31. data/lib/dependabot/nuget/file_fetcher.rb +1 -1
  32. data/lib/dependabot/nuget/metadata_finder.rb +160 -2
  33. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +3 -0
  34. data/lib/dependabot/nuget/native_helpers.rb +34 -3
  35. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +1 -0
  36. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +3 -0
  37. metadata +11 -7
@@ -142,4 +142,27 @@ public class CompatibilityCheckerTests
142
142
 
143
143
  Assert.False(result);
144
144
  }
145
+
146
+ [Theory]
147
+ [InlineData("netstandard2.0")]
148
+ [InlineData("net472")]
149
+ [InlineData("net6.0")]
150
+ [InlineData("net7.0")]
151
+ [InlineData("net8.0")]
152
+ public void EverythingIsCompatibleWithAnyVersion0Framework(string projectFramework)
153
+ {
154
+ var package = new PackageIdentity("Dependency", NuGetVersion.Parse("1.0.0"));
155
+ ImmutableArray<NuGetFramework> projectFrameworks = [NuGetFramework.Parse(projectFramework)];
156
+ var isDevDependency = false;
157
+ ImmutableArray<NuGetFramework> packageFrameworks = [NuGetFramework.Parse("Any,Version=v0.0")];
158
+
159
+ var result = CompatibilityChecker.PerformCheck(
160
+ package,
161
+ projectFrameworks,
162
+ isDevDependency,
163
+ packageFrameworks,
164
+ new Logger(verbose: false));
165
+
166
+ Assert.True(result);
167
+ }
145
168
  }
@@ -40,6 +40,8 @@ public class DiscoveryWorkerTestBase
40
40
  ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
41
41
  ValidateProjectResults(expectedResult.Projects, actualResult.Projects);
42
42
  Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
43
+ Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
44
+ Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
43
45
 
44
46
  return;
45
47
 
@@ -1,3 +1,7 @@
1
+ using System.Text.Json;
2
+
3
+ using NuGetUpdater.Core.Discover;
4
+
1
5
  using Xunit;
2
6
 
3
7
  namespace NuGetUpdater.Core.Test.Discover;
@@ -55,6 +59,54 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
55
59
  );
56
60
  }
57
61
 
62
+ [Fact]
63
+ public async Task TestDependencyWithTrailingSpacesInAttribute()
64
+ {
65
+ await TestDiscoveryAsync(
66
+ packages:
67
+ [
68
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "9.0.1", "net8.0"),
69
+ ],
70
+ workspacePath: "src",
71
+ files: new[]
72
+ {
73
+ ("src/project.csproj", """
74
+ <Project Sdk="Microsoft.NET.Sdk">
75
+ <PropertyGroup>
76
+ <TargetFramework>net8.0</TargetFramework>
77
+ <SomePackageVersion>9.0.1</SomePackageVersion>
78
+ </PropertyGroup>
79
+
80
+ <ItemGroup>
81
+ <PackageReference Include=" Some.Package " Version="$(SomePackageVersion)" />
82
+ </ItemGroup>
83
+ </Project>
84
+ """)
85
+ },
86
+ expectedResult: new()
87
+ {
88
+ Path = "src",
89
+ Projects = [
90
+ new()
91
+ {
92
+ FilePath = "project.csproj",
93
+ TargetFrameworks = ["net8.0"],
94
+ ReferencedProjectPaths = [],
95
+ ExpectedDependencyCount = 2,
96
+ Dependencies = [
97
+ new("Microsoft.NET.Sdk", null, DependencyType.MSBuildSdk),
98
+ new("Some.Package", "9.0.1", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true)
99
+ ],
100
+ Properties = [
101
+ new("SomePackageVersion", "9.0.1", "src/project.csproj"),
102
+ new("TargetFramework", "net8.0", "src/project.csproj"),
103
+ ]
104
+ }
105
+ ]
106
+ }
107
+ );
108
+ }
109
+
58
110
  [Fact]
59
111
  public async Task TestPackageConfig()
60
112
  {
@@ -322,4 +374,100 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
322
374
  }
323
375
  );
324
376
  }
377
+
378
+ [Fact]
379
+ public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
380
+ {
381
+ using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync([]);
382
+ var discoveryResultPath = Path.Combine(temporaryDirectory.DirectoryPath, DiscoveryWorker.DiscoveryResultFileName);
383
+ await DiscoveryWorker.WriteResultsAsync(temporaryDirectory.DirectoryPath, discoveryResultPath, new()
384
+ {
385
+ ErrorType = ErrorType.AuthenticationFailure,
386
+ ErrorDetails = "<some package feed>",
387
+ Path = "/",
388
+ Projects = [],
389
+ });
390
+ var discoveryContents = await File.ReadAllTextAsync(discoveryResultPath);
391
+
392
+ // raw result file should look like this:
393
+ // {
394
+ // ...
395
+ // "ErrorType": "AuthenticationFailure",
396
+ // "ErrorDetails": "<some package feed>",
397
+ // ...
398
+ // }
399
+ var jsonDocument = JsonDocument.Parse(discoveryContents);
400
+ var errorType = jsonDocument.RootElement.GetProperty("ErrorType");
401
+ var errorDetails = jsonDocument.RootElement.GetProperty("ErrorDetails");
402
+
403
+ Assert.Equal("AuthenticationFailure", errorType.GetString());
404
+ Assert.Equal("<some package feed>", errorDetails.GetString());
405
+ }
406
+
407
+ [Fact]
408
+ public async Task ReportsPrivateSourceAuthenticationFailure()
409
+ {
410
+ static (int, string) TestHttpHandler(string uriString)
411
+ {
412
+ var uri = new Uri(uriString, UriKind.Absolute);
413
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
414
+ return uri.PathAndQuery switch
415
+ {
416
+ // initial request is good
417
+ "/index.json" => (200, $$"""
418
+ {
419
+ "version": "3.0.0",
420
+ "resources": [
421
+ {
422
+ "@id": "{{baseUrl}}/download",
423
+ "@type": "PackageBaseAddress/3.0.0"
424
+ },
425
+ {
426
+ "@id": "{{baseUrl}}/query",
427
+ "@type": "SearchQueryService"
428
+ },
429
+ {
430
+ "@id": "{{baseUrl}}/registrations",
431
+ "@type": "RegistrationsBaseUrl"
432
+ }
433
+ ]
434
+ }
435
+ """),
436
+ // all other requests are unauthorized
437
+ _ => (401, "{}"),
438
+ };
439
+ }
440
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
441
+ await TestDiscoveryAsync(
442
+ workspacePath: "",
443
+ files:
444
+ [
445
+ ("project.csproj", """
446
+ <Project Sdk="Microsoft.NET.Sdk">
447
+ <PropertyGroup>
448
+ <TargetFramework>net8.0</TargetFramework>
449
+ </PropertyGroup>
450
+ <ItemGroup>
451
+ <PackageReference Include="Some.Package" Version="1.2.3" />
452
+ </ItemGroup>
453
+ </Project>
454
+ """),
455
+ ("NuGet.Config", $"""
456
+ <configuration>
457
+ <packageSources>
458
+ <clear />
459
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
460
+ </packageSources>
461
+ </configuration>
462
+ """),
463
+ ],
464
+ expectedResult: new()
465
+ {
466
+ ErrorType = ErrorType.AuthenticationFailure,
467
+ ErrorDetails = $"({http.BaseUrl.TrimEnd('/')}/index.json)",
468
+ Path = "",
469
+ Projects = [],
470
+ }
471
+ );
472
+ }
325
473
  }
@@ -4,7 +4,7 @@ using NuGetUpdater.Core.Discover;
4
4
 
5
5
  namespace NuGetUpdater.Core.Test.Discover;
6
6
 
7
- public record ExpectedWorkspaceDiscoveryResult
7
+ public record ExpectedWorkspaceDiscoveryResult : NativeResult
8
8
  {
9
9
  public required string Path { get; init; }
10
10
  public bool IsSuccess { get; init; } = true;
@@ -245,7 +245,7 @@ namespace NuGetUpdater.Core.Test
245
245
  );
246
246
  }
247
247
 
248
- private Stream GetZipStream()
248
+ public Stream GetZipStream()
249
249
  {
250
250
  if (_stream is null)
251
251
  {
@@ -0,0 +1,81 @@
1
+ using System.Net;
2
+ using System.Net.Sockets;
3
+ using System.Text;
4
+
5
+ namespace NuGetUpdater.Core.Test
6
+ {
7
+ public class TestHttpServer : IDisposable
8
+ {
9
+ private readonly Func<string, (int, byte[])> _requestHandler;
10
+ private readonly HttpListener _listener;
11
+ private bool _runServer = true;
12
+
13
+ public string BaseUrl { get; }
14
+
15
+ private TestHttpServer(string baseurl, Func<string, (int, byte[])> requestHandler)
16
+ {
17
+ BaseUrl = baseurl;
18
+ _requestHandler = requestHandler;
19
+ _listener = new HttpListener();
20
+ _listener.Prefixes.Add(baseurl);
21
+ }
22
+
23
+ private void Start()
24
+ {
25
+ _listener.Start();
26
+ Task.Factory.StartNew(HandleResponses);
27
+ }
28
+
29
+ public void Dispose()
30
+ {
31
+ _runServer = false;
32
+ _listener.Stop();
33
+ }
34
+
35
+ private async Task HandleResponses()
36
+ {
37
+ while (_runServer)
38
+ {
39
+ var context = await _listener.GetContextAsync();
40
+ var (statusCode, response) = _requestHandler(context.Request.Url!.AbsoluteUri);
41
+ context.Response.StatusCode = statusCode;
42
+ await context.Response.OutputStream.WriteAsync(response);
43
+ context.Response.Close();
44
+ }
45
+ }
46
+
47
+ private static readonly object PortGate = new();
48
+
49
+ public static TestHttpServer CreateTestServer(Func<string, (int, byte[])> requestHandler)
50
+ {
51
+ // static lock to ensure the port is not recycled after `FindFreePort()` and before we can start the real server
52
+ lock (PortGate)
53
+ {
54
+ var port = FindFreePort();
55
+ var baseUrl = $"http://localhost:{port}/";
56
+ var server = new TestHttpServer(baseUrl, requestHandler);
57
+ server.Start();
58
+ return server;
59
+ }
60
+ }
61
+
62
+ public static TestHttpServer CreateTestStringServer(Func<string, (int, string)> requestHandler)
63
+ {
64
+ Func<string, (int, byte[])> bytesRequestHandler = url =>
65
+ {
66
+ var (statusCode, response) = requestHandler(url);
67
+ return (statusCode, Encoding.UTF8.GetBytes(response));
68
+ };
69
+ return CreateTestServer(bytesRequestHandler);
70
+ }
71
+
72
+ private static int FindFreePort()
73
+ {
74
+ var tcpListener = new TcpListener(IPAddress.Loopback, 0);
75
+ tcpListener.Start();
76
+ var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
77
+ tcpListener.Stop();
78
+ return port;
79
+ }
80
+ }
81
+ }
@@ -1,3 +1,7 @@
1
+ using System.Text.Json;
2
+
3
+ using NuGetUpdater.Core.Updater;
4
+
1
5
  using Xunit;
2
6
 
3
7
  namespace NuGetUpdater.Core.Test.Update;
@@ -88,7 +92,8 @@ public abstract class UpdateWorkerTestBase : TestBase
88
92
  TestFile[]? additionalFiles = null,
89
93
  TestFile[]? additionalFilesExpected = null,
90
94
  MockNuGetPackage[]? packages = null,
91
- string projectFilePath = "test-project.csproj")
95
+ string projectFilePath = "test-project.csproj",
96
+ UpdateOperationResult? expectedResult = null)
92
97
  => TestUpdateForProject(
93
98
  dependencyName,
94
99
  oldVersion,
@@ -98,7 +103,8 @@ public abstract class UpdateWorkerTestBase : TestBase
98
103
  isTransitive,
99
104
  additionalFiles,
100
105
  additionalFilesExpected,
101
- packages);
106
+ packages,
107
+ expectedResult);
102
108
 
103
109
  protected static async Task TestUpdateForProject(
104
110
  string dependencyName,
@@ -109,7 +115,8 @@ public abstract class UpdateWorkerTestBase : TestBase
109
115
  bool isTransitive = false,
110
116
  TestFile[]? additionalFiles = null,
111
117
  TestFile[]? additionalFilesExpected = null,
112
- MockNuGetPackage[]? packages = null)
118
+ MockNuGetPackage[]? packages = null,
119
+ UpdateOperationResult? expectedResult = null)
113
120
  {
114
121
  additionalFiles ??= [];
115
122
  additionalFilesExpected ??= [];
@@ -130,16 +137,29 @@ public abstract class UpdateWorkerTestBase : TestBase
130
137
  // run update
131
138
  var worker = new UpdaterWorker(new Logger(verbose: true));
132
139
  var projectPath = placeFilesInSrc ? $"src/{projectFilePath}" : projectFilePath;
133
- await worker.RunAsync(temporaryDirectory, projectPath, dependencyName, oldVersion, newVersion, isTransitive);
140
+ var updateResultFile = Path.Combine(temporaryDirectory, "update-result.json");
141
+ await worker.RunAsync(temporaryDirectory, projectPath, dependencyName, oldVersion, newVersion, isTransitive, updateResultFile);
142
+ var actualResultContents = await File.ReadAllTextAsync(updateResultFile);
143
+ var actualResult = JsonSerializer.Deserialize<UpdateOperationResult>(actualResultContents, UpdaterWorker.SerializerOptions);
144
+ if (expectedResult is { })
145
+ {
146
+ ValidateUpdateOperationResult(expectedResult, actualResult!);
147
+ }
134
148
  });
135
149
 
136
- var expectedResult = additionalFilesExpected.Prepend((projectFilePath, expectedProjectContents)).ToArray();
150
+ var expectedResultFiles = additionalFilesExpected.Prepend((projectFilePath, expectedProjectContents)).ToArray();
137
151
  if (placeFilesInSrc)
138
152
  {
139
- expectedResult = expectedResult.Select(er => ($"src/{er.Item1}", er.Item2)).ToArray();
153
+ expectedResultFiles = expectedResultFiles.Select(er => ($"src/{er.Item1}", er.Item2)).ToArray();
140
154
  }
141
155
 
142
- AssertContainsFiles(expectedResult, actualResult);
156
+ AssertContainsFiles(expectedResultFiles, actualResult);
157
+ }
158
+
159
+ protected static void ValidateUpdateOperationResult(UpdateOperationResult expectedResult, UpdateOperationResult actualResult)
160
+ {
161
+ Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
162
+ Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
143
163
  }
144
164
 
145
165
  protected static Task TestNoChangeforSolution(
@@ -1,3 +1,7 @@
1
+ using System.Text.Json;
2
+
3
+ using NuGetUpdater.Core.Updater;
4
+
1
5
  using Xunit;
2
6
 
3
7
  namespace NuGetUpdater.Core.Test.Update;
@@ -6,6 +10,34 @@ public partial class UpdateWorkerTests
6
10
  {
7
11
  public class Mixed : UpdateWorkerTestBase
8
12
  {
13
+ [Fact]
14
+ public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
15
+ {
16
+ using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync([]);
17
+ var result = new UpdateOperationResult()
18
+ {
19
+ ErrorType = ErrorType.AuthenticationFailure,
20
+ ErrorDetails = "<some package feed>",
21
+ };
22
+ var resultFilePath = Path.Combine(temporaryDirectory.DirectoryPath, "update-result.json");
23
+ await UpdaterWorker.WriteResultFile(result, resultFilePath, new Logger(false));
24
+ var resultContent = await File.ReadAllTextAsync(resultFilePath);
25
+
26
+ // raw result file should look like this:
27
+ // {
28
+ // ...
29
+ // "ErrorType": "AuthenticationFailure",
30
+ // "ErrorDetails": "<some package feed>",
31
+ // ...
32
+ // }
33
+ var jsonDocument = JsonDocument.Parse(resultContent);
34
+ var errorType = jsonDocument.RootElement.GetProperty("ErrorType");
35
+ var errorDetails = jsonDocument.RootElement.GetProperty("ErrorDetails");
36
+
37
+ Assert.Equal("AuthenticationFailure", errorType.GetString());
38
+ Assert.Equal("<some package feed>", errorDetails.GetString());
39
+ }
40
+
9
41
  [Fact]
10
42
  public async Task ForPackagesProject_UpdatePackageReference_InBuildProps()
11
43
  {
@@ -1,3 +1,5 @@
1
+ using NuGetUpdater.Core.Updater;
2
+
1
3
  using Xunit;
2
4
 
3
5
  namespace NuGetUpdater.Core.Test.Update;
@@ -1420,6 +1422,87 @@ public partial class UpdateWorkerTests
1420
1422
  """);
1421
1423
  }
1422
1424
 
1425
+ [Fact]
1426
+ public async Task ReportsPrivateSourceAuthenticationFailure()
1427
+ {
1428
+ static (int, string) TestHttpHandler(string uriString)
1429
+ {
1430
+ var uri = new Uri(uriString, UriKind.Absolute);
1431
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
1432
+ return uri.PathAndQuery switch
1433
+ {
1434
+ _ => (401, "{}"), // everything is unauthorized
1435
+ };
1436
+ }
1437
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
1438
+ await TestUpdateForProject("Some.Package", "1.0.0", "1.1.0",
1439
+ // existing
1440
+ projectContents: """
1441
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
1442
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
1443
+ <PropertyGroup>
1444
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
1445
+ </PropertyGroup>
1446
+ <ItemGroup>
1447
+ <None Include="packages.config" />
1448
+ </ItemGroup>
1449
+ <ItemGroup>
1450
+ <Reference Include="Some.Package, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
1451
+ <HintPath>packages\Some.Package.1.0.0\lib\net45\Some.Package.dll</HintPath>
1452
+ <Private>True</Private>
1453
+ </Reference>
1454
+ </ItemGroup>
1455
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
1456
+ </Project>
1457
+ """,
1458
+ packagesConfigContents: """
1459
+ <packages>
1460
+ <package id="Some.Package" version="1.0.0" targetFramework="net45" />
1461
+ </packages>
1462
+ """,
1463
+ additionalFiles:
1464
+ [
1465
+ ("NuGet.Config", $"""
1466
+ <configuration>
1467
+ <packageSources>
1468
+ <clear />
1469
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
1470
+ </packageSources>
1471
+ </configuration>
1472
+ """)
1473
+ ],
1474
+ // expected
1475
+ expectedProjectContents: """
1476
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
1477
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
1478
+ <PropertyGroup>
1479
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
1480
+ </PropertyGroup>
1481
+ <ItemGroup>
1482
+ <None Include="packages.config" />
1483
+ </ItemGroup>
1484
+ <ItemGroup>
1485
+ <Reference Include="Some.Package, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
1486
+ <HintPath>packages\Some.Package.1.0.0\lib\net45\Some.Package.dll</HintPath>
1487
+ <Private>True</Private>
1488
+ </Reference>
1489
+ </ItemGroup>
1490
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
1491
+ </Project>
1492
+ """,
1493
+ expectedPackagesConfigContents: """
1494
+ <packages>
1495
+ <package id="Some.Package" version="1.0.0" targetFramework="net45" />
1496
+ </packages>
1497
+ """,
1498
+ expectedResult: new()
1499
+ {
1500
+ ErrorType = ErrorType.AuthenticationFailure,
1501
+ ErrorDetails = $"({http.BaseUrl.TrimEnd('/')}/index.json)",
1502
+ }
1503
+ );
1504
+ }
1505
+
1423
1506
  protected static Task TestUpdateForProject(
1424
1507
  string dependencyName,
1425
1508
  string oldVersion,
@@ -1430,7 +1513,8 @@ public partial class UpdateWorkerTests
1430
1513
  string expectedPackagesConfigContents,
1431
1514
  (string Path, string Content)[]? additionalFiles = null,
1432
1515
  (string Path, string Content)[]? additionalFilesExpected = null,
1433
- MockNuGetPackage[]? packages = null)
1516
+ MockNuGetPackage[]? packages = null,
1517
+ UpdateOperationResult? expectedResult = null)
1434
1518
  {
1435
1519
  var realizedAdditionalFiles = new List<(string Path, string Content)>
1436
1520
  {
@@ -1458,7 +1542,8 @@ public partial class UpdateWorkerTests
1458
1542
  expectedProjectContents,
1459
1543
  additionalFiles: realizedAdditionalFiles.ToArray(),
1460
1544
  additionalFilesExpected: realizedAdditionalFilesExpected.ToArray(),
1461
- packages: packages);
1545
+ packages: packages,
1546
+ expectedResult: expectedResult);
1462
1547
  }
1463
1548
  }
1464
1549
  }
@@ -1,5 +1,8 @@
1
1
  using System.Linq;
2
2
  using System.Text;
3
+ using System.Text.Json;
4
+
5
+ using NuGetUpdater.Core.Updater;
3
6
 
4
7
  using Xunit;
5
8
 
@@ -2898,5 +2901,90 @@ public partial class UpdateWorkerTests
2898
2901
  """
2899
2902
  );
2900
2903
  }
2904
+
2905
+ [Fact]
2906
+ public async Task UpdatePackageWithWhitespaceInTheXMLAttributeValue()
2907
+ {
2908
+ await TestUpdateForProject("Some.Package", "1.0.0", "1.1.0",
2909
+ packages:
2910
+ [
2911
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
2912
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.1.0", "net8.0"),
2913
+ ],
2914
+ projectContents: """
2915
+ <Project Sdk="Microsoft.NET.Sdk">
2916
+ <PropertyGroup>
2917
+ <TargetFramework>net8.0</TargetFramework>
2918
+ </PropertyGroup>
2919
+ <ItemGroup>
2920
+ <PackageReference Include=" Some.Package " Version="1.0.0" />
2921
+ </ItemGroup>
2922
+ </Project>
2923
+ """,
2924
+ expectedProjectContents: """
2925
+ <Project Sdk="Microsoft.NET.Sdk">
2926
+ <PropertyGroup>
2927
+ <TargetFramework>net8.0</TargetFramework>
2928
+ </PropertyGroup>
2929
+ <ItemGroup>
2930
+ <PackageReference Include=" Some.Package " Version="1.1.0" />
2931
+ </ItemGroup>
2932
+ </Project>
2933
+ """
2934
+ );
2935
+ }
2936
+
2937
+ [Fact]
2938
+ public async Task ReportsPrivateSourceAuthenticationFailure()
2939
+ {
2940
+ static (int, string) TestHttpHandler(string uriString)
2941
+ {
2942
+ var uri = new Uri(uriString, UriKind.Absolute);
2943
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
2944
+ return uri.PathAndQuery switch
2945
+ {
2946
+ _ => (401, "{}"), // everything is unauthorized
2947
+ };
2948
+ }
2949
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
2950
+ await TestUpdateForProject("Some.Package", "1.0.0", "1.1.0",
2951
+ projectContents: """
2952
+ <Project Sdk="Microsoft.NET.Sdk">
2953
+ <PropertyGroup>
2954
+ <TargetFramework>net8.0</TargetFramework>
2955
+ </PropertyGroup>
2956
+ <ItemGroup>
2957
+ <PackageReference Include="Some.Package" Version="1.0.0" />
2958
+ </ItemGroup>
2959
+ </Project>
2960
+ """,
2961
+ additionalFiles:
2962
+ [
2963
+ ("NuGet.Config", $"""
2964
+ <configuration>
2965
+ <packageSources>
2966
+ <clear />
2967
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
2968
+ </packageSources>
2969
+ </configuration>
2970
+ """)
2971
+ ],
2972
+ expectedProjectContents: """
2973
+ <Project Sdk="Microsoft.NET.Sdk">
2974
+ <PropertyGroup>
2975
+ <TargetFramework>net8.0</TargetFramework>
2976
+ </PropertyGroup>
2977
+ <ItemGroup>
2978
+ <PackageReference Include="Some.Package" Version="1.0.0" />
2979
+ </ItemGroup>
2980
+ </Project>
2981
+ """,
2982
+ expectedResult: new()
2983
+ {
2984
+ ErrorType = ErrorType.AuthenticationFailure,
2985
+ ErrorDetails = $"({http.BaseUrl.TrimEnd('/')}/index.json)",
2986
+ }
2987
+ );
2988
+ }
2901
2989
  }
2902
2990
  }
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/nuget/version"
5
+ require "dependabot/nuget/native_helpers"
5
6
  require "sorbet-runtime"
6
7
 
7
8
  module Dependabot
@@ -11,6 +12,8 @@ module Dependabot
11
12
 
12
13
  sig { params(json: T::Hash[String, T.untyped]).returns(DependencyAnalysis) }
13
14
  def self.from_json(json)
15
+ Dependabot::Nuget::NativeHelpers.ensure_no_errors(json)
16
+
14
17
  updated_version = T.let(json.fetch("UpdatedVersion"), String)
15
18
  can_update = T.let(json.fetch("CanUpdate"), T::Boolean)
16
19
  version_comes_from_multi_dependency_property = T.let(json.fetch("VersionComesFromMultiDependencyProperty"),
@@ -43,7 +43,7 @@ module Dependabot
43
43
  ).void
44
44
  end
45
45
  def initialize(source:, credentials:, repo_contents_path: nil, options: {})
46
- super(source: source, credentials: credentials, repo_contents_path: repo_contents_path, options: options)
46
+ super
47
47
 
48
48
  @sln_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
49
49
  @sln_project_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))