dependabot-nuget 0.291.0 → 0.292.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) 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.Core/Analyze/AnalyzeWorker.cs +11 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +19 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadRequirementException.cs +9 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +39 -8
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +67 -12
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +1 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +28 -5
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +1 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/BadRequirement.cs +10 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +1 -1
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +3 -2
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +1 -2
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs +1 -4
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +1 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -2
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdateNotPossible.cs +1 -1
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +9 -3
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +1 -1
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +12 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/DependencyConflictResolver.cs +0 -7
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +8 -3
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +4 -4
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +60 -2
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +10 -1
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +56 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +1 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +76 -40
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +20 -2
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +2 -2
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.LockFile.cs +251 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +6 -6
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +63 -5
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +38 -20
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/ProjectHelperTests.cs +65 -0
  47. data/helpers/lib/NuGetUpdater/global.json +1 -1
  48. data/lib/dependabot/nuget/language.rb +21 -5
  49. data/lib/dependabot/nuget/native_helpers.rb +2 -0
  50. data/lib/dependabot/nuget/package_manager.rb +4 -4
  51. metadata +10 -6
@@ -12,6 +12,7 @@ namespace NuGetUpdater.Core.Run;
12
12
 
13
13
  public class RunWorker
14
14
  {
15
+ private readonly string _jobId;
15
16
  private readonly IApiHandler _apiHandler;
16
17
  private readonly ILogger _logger;
17
18
  private readonly IDiscoveryWorker _discoveryWorker;
@@ -25,8 +26,9 @@ public class RunWorker
25
26
  Converters = { new JsonStringEnumConverter(), new RequirementConverter(), new VersionConverter() },
26
27
  };
27
28
 
28
- public RunWorker(IApiHandler apiHandler, IDiscoveryWorker discoverWorker, IAnalyzeWorker analyzeWorker, IUpdaterWorker updateWorker, ILogger logger)
29
+ public RunWorker(string jobId, IApiHandler apiHandler, IDiscoveryWorker discoverWorker, IAnalyzeWorker analyzeWorker, IUpdaterWorker updateWorker, ILogger logger)
29
30
  {
31
+ _jobId = jobId;
30
32
  _apiHandler = apiHandler;
31
33
  _logger = logger;
32
34
  _discoveryWorker = discoverWorker;
@@ -87,9 +89,13 @@ public class RunWorker
87
89
  {
88
90
  error = new PrivateSourceAuthenticationFailure(lastUsedPackageSourceUrls);
89
91
  }
92
+ catch (BadRequirementException ex)
93
+ {
94
+ error = new BadRequirement(ex.Message);
95
+ }
90
96
  catch (MissingFileException ex)
91
97
  {
92
- error = new DependencyFileNotFound(ex.FilePath);
98
+ error = new DependencyFileNotFound("file not found", ex.FilePath);
93
99
  }
94
100
  catch (UpdateNotPossibleException ex)
95
101
  {
@@ -97,7 +103,7 @@ public class RunWorker
97
103
  }
98
104
  catch (Exception ex)
99
105
  {
100
- error = new UnknownError(ex.ToString());
106
+ error = new UnknownError(ex, _jobId);
101
107
  }
102
108
 
103
109
  if (error is not null)
@@ -11,7 +11,7 @@ internal static class LockFileUpdater
11
11
  var projectDirectory = Path.GetDirectoryName(projectPath)!;
12
12
  await MSBuildHelper.HandleGlobalJsonAsync(projectDirectory, repoRootPath, experimentsManager, async () =>
13
13
  {
14
- var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", "--force-evaluate", projectPath], projectDirectory, experimentsManager);
14
+ var (exitCode, stdout, stderr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(["restore", "--force-evaluate", "-p:EnableWindowsTargeting=true", projectPath], projectDirectory, experimentsManager);
15
15
  if (exitCode != 0)
16
16
  {
17
17
  logger.Error($" Lock file update failed.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
@@ -13,7 +13,18 @@ namespace NuGetUpdater.Core.Updater
13
13
  getContent: () => File.ReadAllText(projectFilePath),
14
14
  setContent: s => File.WriteAllText(projectFilePath, s),
15
15
  nodeFinder: doc => doc.Descendants()
16
- .FirstOrDefault(e => e.Name == "Import" && e.GetAttributeValue("Project") == @"$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets")
16
+ .Where(e => e.Name == "Import")
17
+ .FirstOrDefault(e =>
18
+ {
19
+ var projectPath = e.GetAttributeValue("Project");
20
+ if (projectPath is not null)
21
+ {
22
+ var projectFileName = Path.GetFileName(projectPath.NormalizePathToUnix());
23
+ return projectFileName.Equals("Microsoft.WebApplication.targets", StringComparison.OrdinalIgnoreCase);
24
+ }
25
+
26
+ return false;
27
+ })
17
28
  as XmlNodeSyntax,
18
29
  preProcessor: n =>
19
30
  {
@@ -395,7 +395,6 @@ public class PackageManager
395
395
  if (await AreAllParentsCompatibleAsync(existingPackages, existingPackage, targetFramework, projectDirectory, logger) == true)
396
396
  {
397
397
  existingPackage.CurrentVersion = dependencyOldVersion;
398
- string NewVersion = dependency.CurrentVersion;
399
398
  existingPackage.NewVersion = dependency.CurrentVersion;
400
399
  await UpdateVersion(existingPackages, existingPackage, targetFramework, projectDirectory, logger);
401
400
  }
@@ -593,12 +592,6 @@ public class PackageManager
593
592
  return null;
594
593
  }
595
594
 
596
- // If the current version of the parent is less than the current version of the dependency
597
- else if (CurrentVersion < currentVersionDependency)
598
- {
599
- return currentVersionDependency;
600
- }
601
-
602
595
  // 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
603
596
  for (NuGetVersion version = CurrentVersion; version <= latestVersion; version = NextPatch(version, versions))
604
597
  {
@@ -886,6 +886,7 @@ internal static partial class MSBuildHelper
886
886
  "The plugin credential provider could not acquire credentials",
887
887
  "401 (Unauthorized)",
888
888
  "error NU1301: Unable to load the service index for source",
889
+ "Response status code does not indicate success: 403",
889
890
  };
890
891
  if (unauthorizedMessageSnippets.Any(stdout.Contains))
891
892
  {
@@ -895,9 +896,13 @@ internal static partial class MSBuildHelper
895
896
 
896
897
  internal static string? GetMissingFile(string output)
897
898
  {
898
- var missingFilePattern = new Regex(@"The imported project \""(?<FilePath>.*)\"" was not found");
899
- var match = missingFilePattern.Match(output);
900
- if (match.Success)
899
+ var missingFilePatterns = new[]
900
+ {
901
+ new Regex(@"The imported project \""(?<FilePath>.*)\"" was not found"),
902
+ new Regex(@"The imported file \""(?<FilePath>.*)\"" does not exist"),
903
+ };
904
+ var match = missingFilePatterns.Select(p => p.Match(output)).Where(m => m.Success).FirstOrDefault();
905
+ if (match is not null)
901
906
  {
902
907
  return match.Groups["FilePath"].Value;
903
908
  }
@@ -77,11 +77,11 @@ internal static class ProjectHelper
77
77
  var itemPath = projectRootElement.Items
78
78
  .Where(i => i.ElementName.Equals("None", StringComparison.OrdinalIgnoreCase) ||
79
79
  i.ElementName.Equals("Content", StringComparison.OrdinalIgnoreCase))
80
- .Where(i => Path.GetFileName(i.Include).Equals(itemFileName, StringComparison.OrdinalIgnoreCase))
81
- .Select(i => Path.GetFullPath(Path.Combine(projectDirectory, i.Include)))
80
+ .Where(i => !string.IsNullOrEmpty(i.Include))
81
+ .Select(i => Path.GetFullPath(Path.Combine(projectDirectory, i.Include.NormalizePathToUnix())))
82
+ .Where(p => Path.GetFileName(p).Equals(itemFileName, StringComparison.OrdinalIgnoreCase))
82
83
  .Where(File.Exists)
83
- .FirstOrDefault()
84
- ?.NormalizePathToUnix();
84
+ .FirstOrDefault();
85
85
  return itemPath;
86
86
  }
87
87
 
@@ -95,6 +95,65 @@ public class CloneWorkerTests
95
95
  );
96
96
  }
97
97
 
98
+ [Fact]
99
+ public async Task JobFileParseErrorIsReported_InvalidJson()
100
+ {
101
+ // arrange
102
+ var testApiHandler = new TestApiHandler();
103
+ var testGitCommandHandler = new TestGitCommandHandler();
104
+ var cloneWorker = new CloneWorker("JOB-ID", testApiHandler, testGitCommandHandler);
105
+ using var testDirectory = new TemporaryDirectory();
106
+ var jobFilePath = Path.Combine(testDirectory.DirectoryPath, "job.json");
107
+ await File.WriteAllTextAsync(jobFilePath, "not json");
108
+
109
+ // act
110
+ var result = await cloneWorker.RunAsync(new FileInfo(jobFilePath), new DirectoryInfo(testDirectory.DirectoryPath));
111
+
112
+ // assert
113
+ Assert.Equal(1, result);
114
+ var expectedParseErrorObject = testApiHandler.ReceivedMessages.Single(m => m.Type == typeof(UnknownError));
115
+ var unknownError = (UnknownError)expectedParseErrorObject.Object;
116
+ Assert.Equal("JsonException", unknownError.Details["error-class"]);
117
+ }
118
+
119
+ [Fact]
120
+ public async Task JobFileParseErrorIsReported_BadRequirement()
121
+ {
122
+ // arrange
123
+ var testApiHandler = new TestApiHandler();
124
+ var testGitCommandHandler = new TestGitCommandHandler();
125
+ var cloneWorker = new CloneWorker("JOB-ID", testApiHandler, testGitCommandHandler);
126
+ using var testDirectory = new TemporaryDirectory();
127
+ var jobFilePath = Path.Combine(testDirectory.DirectoryPath, "job.json");
128
+
129
+ // write a job file with a valid shape, but invalid requirement
130
+ await File.WriteAllTextAsync(jobFilePath, """
131
+ {
132
+ "job": {
133
+ "source": {
134
+ "provider": "github",
135
+ "repo": "test/repo"
136
+ },
137
+ "security-advisories": [
138
+ {
139
+ "dependency-name": "Some.Dependency",
140
+ "affected-versions": ["not a valid requirement"]
141
+ }
142
+ ]
143
+ }
144
+ }
145
+ """);
146
+
147
+ // act
148
+ var result = await cloneWorker.RunAsync(new FileInfo(jobFilePath), new DirectoryInfo(testDirectory.DirectoryPath));
149
+
150
+ // assert
151
+ Assert.Equal(1, result);
152
+ var expectedParseErrorObject = testApiHandler.ReceivedMessages.Single(m => m.Type == typeof(BadRequirement));
153
+ var badRequirement = (BadRequirement)expectedParseErrorObject.Object;
154
+ Assert.Equal("not a valid requirement", badRequirement.Details["message"]);
155
+ }
156
+
98
157
  private class TestGitCommandHandlerWithOutputs : TestGitCommandHandler
99
158
  {
100
159
  private readonly string _stdout;
@@ -134,8 +193,7 @@ public class CloneWorkerTests
134
193
  // arrange
135
194
  var testApiHandler = new TestApiHandler();
136
195
  testGitCommandHandler ??= new TestGitCommandHandler();
137
- var testLogger = new TestLogger();
138
- var worker = new CloneWorker(testApiHandler, testGitCommandHandler, testLogger);
196
+ var worker = new CloneWorker("TEST-JOB-ID", testApiHandler, testGitCommandHandler);
139
197
 
140
198
  // act
141
199
  var job = new Job()
@@ -45,7 +45,16 @@ public class DiscoveryWorkerTestBase : TestBase
45
45
  ValidateProjectResults(expectedResult.Projects, actualResult.Projects, experimentsManager);
46
46
  Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
47
47
  Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
48
- Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
48
+ if (expectedResult.ErrorDetailsPattern is not null)
49
+ {
50
+ var errorDetails = actualResult.ErrorDetails?.ToString();
51
+ Assert.NotNull(errorDetails);
52
+ Assert.Matches(expectedResult.ErrorDetailsPattern, errorDetails);
53
+ }
54
+ else
55
+ {
56
+ Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
57
+ }
49
58
 
50
59
  return;
51
60
 
@@ -115,5 +115,61 @@ public partial class DiscoveryWorkerTests
115
115
  }
116
116
  );
117
117
  }
118
+
119
+ [Theory]
120
+ [InlineData(true)]
121
+ [InlineData(false)]
122
+ public async Task DiscoveryIsMergedWithPackageReferences(bool useDirectDiscovery)
123
+ {
124
+ await TestDiscoveryAsync(
125
+ experimentsManager: new ExperimentsManager() { UseDirectDiscovery = useDirectDiscovery },
126
+ packages:
127
+ [
128
+ MockNuGetPackage.CreateSimplePackage("Package.A", "1.0.0", "net46"),
129
+ MockNuGetPackage.CreateSimplePackage("Package.B", "2.0.0", "net46"),
130
+ ],
131
+ workspacePath: "src",
132
+ files: [
133
+ ("src/myproj.csproj", """
134
+ <Project Sdk="Microsoft.NET.Sdk">
135
+ <PropertyGroup>
136
+ <TargetFramework>net46</TargetFramework>
137
+ </PropertyGroup>
138
+ <ItemGroup>
139
+ <None Include="..\unexpected-directory\packages.config" />
140
+ <PackageReference Include="Package.B" Version="2.0.0" />
141
+ </ItemGroup>
142
+ </Project>
143
+ """),
144
+ ("unexpected-directory/packages.config", """
145
+ <?xml version="1.0" encoding="utf-8"?>
146
+ <packages>
147
+ <package id="Package.A" version="1.0.0" targetFramework="net46" />
148
+ </packages>
149
+ """),
150
+ ],
151
+ expectedResult: new()
152
+ {
153
+ Path = "src",
154
+ Projects = [
155
+ new()
156
+ {
157
+ FilePath = "myproj.csproj",
158
+ Properties = [new("TargetFramework", "net46", "src/myproj.csproj")],
159
+ TargetFrameworks = ["net46"],
160
+ Dependencies = [
161
+ new("Package.A", "1.0.0", DependencyType.PackagesConfig, TargetFrameworks: ["net46"]),
162
+ new("Package.B", "2.0.0", DependencyType.PackageReference, IsDirect: true, TargetFrameworks: ["net46"]),
163
+ ],
164
+ ReferencedProjectPaths = [],
165
+ ImportedFiles = [],
166
+ AdditionalFiles = [
167
+ "../unexpected-directory/packages.config"
168
+ ],
169
+ }
170
+ ],
171
+ }
172
+ );
173
+ }
118
174
  }
119
175
  }
@@ -12,6 +12,7 @@ public record ExpectedWorkspaceDiscoveryResult : NativeResult
12
12
  public int? ExpectedProjectCount { get; init; }
13
13
  public ExpectedDependencyDiscoveryResult? GlobalJson { get; init; }
14
14
  public ExpectedDependencyDiscoveryResult? DotNetToolsJson { get; init; }
15
+ public string? ErrorDetailsPattern { get; init; } = null;
15
16
  }
16
17
 
17
18
  public record ExpectedSdkProjectDiscoveryResult : ExpectedDependencyDiscoveryResult
@@ -1733,7 +1733,7 @@ public class RunWorkerTests
1733
1733
  analyzeWorker ??= new AnalyzeWorker(experimentsManager, logger);
1734
1734
  updaterWorker ??= new UpdaterWorker(experimentsManager, logger);
1735
1735
 
1736
- var worker = new RunWorker(testApiHandler, discoveryWorker, analyzeWorker, updaterWorker, logger);
1736
+ var worker = new RunWorker("TEST-JOB-ID", testApiHandler, discoveryWorker, analyzeWorker, updaterWorker, logger);
1737
1737
  var repoContentsPathDirectoryInfo = new DirectoryInfo(tempDirectory.DirectoryPath);
1738
1738
  var actualResult = await worker.RunAsync(job, repoContentsPathDirectoryInfo, "TEST-COMMIT-SHA");
1739
1739
  var actualApiMessages = testApiHandler.ReceivedMessages.ToArray();
@@ -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: