dependabot-nuget 0.258.0 → 0.259.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +2 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs +2 -2
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +255 -191
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +63 -35
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +107 -14
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +9 -5
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +18 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +6 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +6 -2
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +6 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.PackagesConfig.cs +11 -21
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Proj.cs +95 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +204 -62
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +64 -45
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +419 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj +1 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +7 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +77 -19
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs +120 -91
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +132 -97
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +93 -75
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +45 -42
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +1089 -956
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +1624 -1291
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +296 -293
  27. data/helpers/lib/NuGetUpdater/global.json +6 -0
  28. data/lib/dependabot/nuget/file_parser.rb +4 -5
  29. data/lib/dependabot/nuget/file_updater.rb +1 -1
  30. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +7 -2
  31. data/lib/dependabot/nuget/update_checker/property_updater.rb +1 -0
  32. data/lib/dependabot/nuget/update_checker/version_finder.rb +2 -3
  33. data/lib/dependabot/nuget/update_checker.rb +1 -0
  34. metadata +8 -5
@@ -0,0 +1,419 @@
1
+ using System.IO.Compression;
2
+ using System.Security.Cryptography;
3
+ using System.Text;
4
+ using System.Text.Json.Nodes;
5
+ using System.Text.RegularExpressions;
6
+ using System.Xml;
7
+ using System.Xml.Linq;
8
+ using System.Xml.XPath;
9
+
10
+ using Microsoft.CodeAnalysis;
11
+ using Microsoft.CodeAnalysis.CSharp;
12
+ using Microsoft.CodeAnalysis.Emit;
13
+
14
+ namespace NuGetUpdater.Core.Test
15
+ {
16
+ public record MockNuGetPackage(
17
+ string Id,
18
+ string Version,
19
+ XElement[]? AdditionalMetadata = null,
20
+ (string? TargetFramework, (string Id, string Version)[] Packages)[]? DependencyGroups = null,
21
+ (string Path, byte[] Content)[]? Files = null)
22
+ {
23
+ private static readonly XNamespace Namespace = "http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd";
24
+ private static readonly XmlWriterSettings WriterSettings = new()
25
+ {
26
+ Encoding = Encoding.UTF8,
27
+ Indent = true,
28
+ };
29
+
30
+ private XDocument? _nuspec;
31
+ private Stream? _stream;
32
+
33
+ public void WriteToDirectory(string localPackageSourcePath)
34
+ {
35
+ string cachePath = Path.Join(localPackageSourcePath, "_nupkg_cache");
36
+ string nupkgPath = Path.Join(cachePath, $"{Id}.{Version}.nupkg");
37
+ Directory.CreateDirectory(cachePath);
38
+ Stream stream = GetZipStream();
39
+ using (FileStream fileStream = new(nupkgPath, FileMode.Create))
40
+ {
41
+ stream.CopyTo(fileStream);
42
+ }
43
+
44
+ // add the package to the local feed; this is equivalent to running
45
+ // nuget add <nupkgPath> -source <localPackageSourcePath>
46
+ // but running that in-process locks the files, so we have to do it manually
47
+ // the end result is 4 files:
48
+ // .nupkg.metadata // a JSON object with the package's content hash and some other fields
49
+ // <id>.<version>.nupkg // the package itself
50
+ // <id>.<version>.nupkg.sha512 // the SHA512 hash of the package
51
+ // <id>.nuspec // the package's nuspec file
52
+ string expandedPath = Path.Join(localPackageSourcePath, Id.ToLowerInvariant(), Version);
53
+ Directory.CreateDirectory(expandedPath);
54
+ File.Copy(nupkgPath, Path.Join(expandedPath, $"{Id}.{Version}.nupkg".ToLowerInvariant()));
55
+ using XmlWriter writer = XmlWriter.Create(Path.Join(expandedPath, $"{Id}.nuspec".ToLowerInvariant()), WriterSettings);
56
+ GetNuspec().WriteTo(writer);
57
+ using SHA512 sha512 = SHA512.Create();
58
+ byte[] hash = sha512.ComputeHash(File.ReadAllBytes(nupkgPath));
59
+ string hashString = Convert.ToBase64String(hash);
60
+ File.WriteAllText(Path.Join(expandedPath, $"{Id}.{Version}.nupkg.sha512".ToLowerInvariant()), hashString);
61
+ JsonObject metadata = new()
62
+ {
63
+ ["version"] = 2,
64
+ ["contentHash"] = hashString,
65
+ ["source"] = null,
66
+ };
67
+ File.WriteAllText(Path.Join(expandedPath, ".nupkg.metadata"), metadata.ToString());
68
+ }
69
+
70
+ /// <summary>
71
+ /// Creates a mock NuGet package with a single assembly in the appropriate `lib/` directory. The assembly will
72
+ /// be empty.
73
+ /// </summary>
74
+ public static MockNuGetPackage CreateSimplePackage(string id, string version, string targetFramework, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
75
+ {
76
+ return new(
77
+ id,
78
+ version,
79
+ AdditionalMetadata: null,
80
+ DependencyGroups: dependencyGroups,
81
+ Files:
82
+ [
83
+ ($"lib/{targetFramework}/{id}.dll", Array.Empty<byte>())
84
+ ]
85
+ );
86
+ }
87
+
88
+ /// <summary>
89
+ /// Creates a mock NuGet package with a single assembly in the appropriate `lib/` directory. The assembly will
90
+ /// contain the appropriate `AssemblyVersion` attribute and nothing else.
91
+ /// </summary>
92
+ public static MockNuGetPackage CreatePackageWithAssembly(string id, string version, string targetFramework, string assemblyVersion, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
93
+ {
94
+ return new(
95
+ id,
96
+ version,
97
+ AdditionalMetadata: null,
98
+ DependencyGroups: dependencyGroups,
99
+ Files:
100
+ [
101
+ ($"lib/{targetFramework}/{id}.dll", CreateAssembly(id, assemblyVersion))
102
+ ]
103
+ );
104
+ }
105
+
106
+ /// <summary>
107
+ /// Creates a mock NuGet package with empty analyzer assemblies for both C# and VB.
108
+ /// </summary>
109
+ public static MockNuGetPackage CreateAnalyzerPackage(string id, string version, (string? TargetFramework, (string Id, string Version)[] Packages)[]? dependencyGroups = null)
110
+ {
111
+ return new(
112
+ id,
113
+ version,
114
+ AdditionalMetadata:
115
+ [
116
+ new XElement("developmentDependency", "true"),
117
+ ],
118
+ DependencyGroups: dependencyGroups,
119
+ Files:
120
+ [
121
+ ($"analyzers/dotnet/cs/{id}.dll", Array.Empty<byte>()),
122
+ ($"analyzers/dotnet/vb/{id}.dll", Array.Empty<byte>()),
123
+ ]
124
+ );
125
+ }
126
+
127
+ public static MockNuGetPackage CreateDotNetToolPackage(string id, string version, string targetFramework)
128
+ {
129
+ return new(
130
+ id,
131
+ version,
132
+ AdditionalMetadata:
133
+ [
134
+ new XElement("packageTypes",
135
+ new XElement("packageType",
136
+ new XAttribute("name", "DotnetTool")
137
+ )
138
+ )
139
+ ],
140
+ Files:
141
+ [
142
+ ($"tools/{targetFramework}/any/DotnetToolSettings.xml", Encoding.UTF8.GetBytes($"""
143
+ <DotNetCliTool Version="1">
144
+ <Commands>
145
+ <Command Name="{id}" EntryPoint="{id}.dll" Runner="dotnet" />
146
+ </Commands>
147
+ </DotNetCliTool>
148
+ """)),
149
+ ($"tools/{targetFramework}/any/{id}.dll", Array.Empty<byte>()),
150
+ ]
151
+ );
152
+ }
153
+
154
+ public static MockNuGetPackage CreateMSBuildSdkPackage(string id, string version, string? sdkPropsContent = null, string? sdkTargetsContent = null)
155
+ {
156
+ sdkPropsContent ??= """
157
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
158
+ </Project>
159
+ """;
160
+ sdkTargetsContent ??= """
161
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
162
+ </Project>
163
+ """;
164
+ return new(
165
+ id,
166
+ version,
167
+ AdditionalMetadata:
168
+ [
169
+ new XElement("packageTypes",
170
+ new XElement("packageType",
171
+ new XAttribute("name", "MSBuildSdk")
172
+ )
173
+ )
174
+ ],
175
+ Files:
176
+ [
177
+ ("Sdk/Sdk.props", Encoding.UTF8.GetBytes(sdkPropsContent)),
178
+ ("Sdk/Sdk.targets", Encoding.UTF8.GetBytes(sdkTargetsContent)),
179
+ ]
180
+ );
181
+ }
182
+
183
+ private XDocument GetNuspec()
184
+ {
185
+ if (_nuspec is null)
186
+ {
187
+ _nuspec = new XDocument(
188
+ new XElement(Namespace + "package",
189
+ new XElement(Namespace + "metadata",
190
+ new XElement(Namespace + "id", Id),
191
+ new XElement(Namespace + "version", Version),
192
+ new XElement(Namespace + "authors", "MockNuGetPackage"),
193
+ new XElement(Namespace + "description", "Mock NuGet package"),
194
+ AdditionalMetadata?.Select(a => WithNamespace(a, Namespace)),
195
+ new XElement(Namespace + "dependencies",
196
+ // dependencies with no target framework
197
+ DependencyGroups?.Where(g => g.TargetFramework is null).SelectMany(g =>
198
+ g.Packages.Select(p =>
199
+ new XElement(Namespace + "dependency",
200
+ new XAttribute("id", p.Id),
201
+ new XAttribute("version", p.Version)
202
+ )
203
+ )
204
+ ),
205
+ // dependencies with a target framework
206
+ DependencyGroups?.Where(g => g.TargetFramework is not null).Select(g =>
207
+ new XElement(Namespace + "group",
208
+ new XAttribute("targetFramework", g.TargetFramework!),
209
+ g.Packages.Select(p =>
210
+ new XElement(Namespace + "dependency",
211
+ new XAttribute("id", p.Id),
212
+ new XAttribute("version", p.Version)
213
+ )
214
+ )
215
+ )
216
+ )
217
+ )
218
+ )
219
+ )
220
+ );
221
+ }
222
+
223
+ return _nuspec;
224
+ }
225
+
226
+ private static XElement WithNamespace(XElement element, XNamespace ns)
227
+ {
228
+ return new XElement(ns + element.Name.LocalName,
229
+ element.Attributes(),
230
+ element.Nodes().Select(n =>
231
+ {
232
+ if (n is XElement e)
233
+ {
234
+ return WithNamespace(e, ns);
235
+ }
236
+
237
+ return n;
238
+ })
239
+ );
240
+ }
241
+
242
+ private Stream GetZipStream()
243
+ {
244
+ if (_stream is null)
245
+ {
246
+ XDocument nuspec = GetNuspec();
247
+ _stream = new MemoryStream();
248
+ using ZipArchive zip = new(_stream, ZipArchiveMode.Create, leaveOpen: true);
249
+ ZipArchiveEntry nuspecEntry = zip.CreateEntry($"{Id}.nuspec");
250
+ using (Stream contentStream = nuspecEntry.Open())
251
+ using (XmlWriter writer = XmlWriter.Create(contentStream, WriterSettings))
252
+ {
253
+ nuspec.WriteTo(writer);
254
+ }
255
+
256
+ foreach (var file in Files ?? [])
257
+ {
258
+ ZipArchiveEntry fileEntry = zip.CreateEntry(file.Path);
259
+ using Stream contentStream = fileEntry.Open();
260
+ contentStream.Write(file.Content, 0, file.Content.Length);
261
+ }
262
+ }
263
+
264
+ _stream.Seek(0, SeekOrigin.Begin);
265
+ return _stream;
266
+ }
267
+
268
+ private static byte[] CreateAssembly(string assemblyName, string assemblyVersion)
269
+ {
270
+ CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary);
271
+ CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, options: compilationOptions)
272
+ .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
273
+ .AddSyntaxTrees(CSharpSyntaxTree.ParseText($"[assembly: System.Reflection.AssemblyVersionAttribute(\"{assemblyVersion}\")]"));
274
+ MemoryStream assemblyStream = new();
275
+ EmitResult emitResult = compilation.Emit(assemblyStream);
276
+ if (!emitResult.Success)
277
+ {
278
+ throw new Exception($"Unable to create test assembly:\n\t{string.Join("\n\t", emitResult.Diagnostics.ToString())}");
279
+ }
280
+
281
+ return assemblyStream.ToArray();
282
+ }
283
+
284
+ // some well-known packages
285
+ public static MockNuGetPackage CentralPackageVersionsPackage =>
286
+ CreateMSBuildSdkPackage(
287
+ "Microsoft.Build.CentralPackageVersions",
288
+ "2.1.3",
289
+ sdkTargetsContent: """
290
+ <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
291
+ <!-- this is a simplified version of this package used for testing -->
292
+ <PropertyGroup>
293
+ <CentralPackagesFile Condition=" '$(CentralPackagesFile)' == '' ">$([MSBuild]::GetPathOfFileAbove('Packages.props', $(MSBuildProjectDirectory)))</CentralPackagesFile>
294
+ </PropertyGroup>
295
+ <Import Project="$(CentralPackagesFile)" Condition="Exists('$(CentralPackagesFile)')" />
296
+ </Project>
297
+ """
298
+ );
299
+
300
+ private static readonly Lazy<string> BundledVersionsPropsPath = new(() =>
301
+ {
302
+ // we need to find the file `Microsoft.NETCoreSdk.BundledVersions.props` in the SDK directory
303
+
304
+ DirectoryInfo projectDir = Directory.CreateTempSubdirectory("bundled_versions_props_path_discovery_");
305
+ try
306
+ {
307
+ // get the sdk version
308
+ string projectPath = Path.Combine(projectDir.FullName, "project.csproj");
309
+ File.WriteAllText(projectPath, """
310
+ <Project Sdk="Microsoft.NET.Sdk">
311
+ <Target Name="_ReportCurrentSdkVersion">
312
+ <Message Text="_CurrentSdkVersion=$(NETCoreSdkVersion)" Importance="High" />
313
+ </Target>
314
+ </Project>
315
+ """
316
+ );
317
+ var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", $"msbuild {projectPath} /t:_ReportCurrentSdkVersion").Result;
318
+ if (exitCode != 0)
319
+ {
320
+ throw new Exception($"Failed to report the current SDK version:\n{stdout}\n{stderr}");
321
+ }
322
+
323
+ MatchCollection matches = Regex.Matches(stdout, "_CurrentSdkVersion=(?<SdkVersion>.*)$", RegexOptions.Multiline);
324
+ if (matches.Count == 0)
325
+ {
326
+ throw new Exception($"Failed to find the current SDK version in the output:\n{stdout}");
327
+ }
328
+
329
+ string sdkVersionString = matches.First().Groups["SdkVersion"].Value.Trim();
330
+
331
+ // find the actual SDK directory
332
+ string privateCoreLibPath = typeof(object).Assembly.Location; // e.g., C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
333
+ string sdkDirectory = Path.Combine(Path.GetDirectoryName(privateCoreLibPath)!, "..", "..", "..", "sdk", sdkVersionString); // e.g., C:\Program Files\dotnet\sdk\8.0.204
334
+ string bundledVersionsPropsPath = Path.Combine(sdkDirectory, "Microsoft.NETCoreSdk.BundledVersions.props");
335
+ FileInfo normalizedPath = new(bundledVersionsPropsPath);
336
+ return normalizedPath.FullName;
337
+ }
338
+ finally
339
+ {
340
+ projectDir.Delete(recursive: true);
341
+ }
342
+ });
343
+
344
+ private static readonly Dictionary<string, MockNuGetPackage> WellKnownPackages = new();
345
+ public static MockNuGetPackage WellKnownReferencePackage(string packageName, string targetFramework, (string Path, byte[] Content)[]? files = null)
346
+ {
347
+ string key = $"{packageName}/{targetFramework}";
348
+ if (!WellKnownPackages.ContainsKey(key))
349
+ {
350
+ // for the current SDK, the file `Microsoft.NETCoreSdk.BundledVersions.props` contains the version of the
351
+ // `Microsoft.WindowsDesktop.App.Ref` package that will be needed to build, so we find it by TFM
352
+ XDocument propsDocument = XDocument.Load(BundledVersionsPropsPath.Value);
353
+ XElement? matchingFrameworkElement = propsDocument.XPathSelectElement(
354
+ $"""
355
+ /Project/ItemGroup/KnownFrameworkReference
356
+ [
357
+ @Include='{packageName}' and
358
+ @TargetingPackName='{packageName}.Ref' and
359
+ @TargetFramework='{targetFramework}'
360
+ ]
361
+ """);
362
+ if (matchingFrameworkElement is null)
363
+ {
364
+ throw new Exception($"Unable to find {packageName}.Ref version for target framework '{targetFramework}'");
365
+ }
366
+
367
+ string expectedVersion = matchingFrameworkElement.Attribute("TargetingPackVersion")!.Value;
368
+ return new(
369
+ $"{packageName}.Ref",
370
+ expectedVersion,
371
+ AdditionalMetadata:
372
+ [
373
+ new XElement("packageTypes",
374
+ new XElement("packageType",
375
+ new XAttribute("name", "DotnetPlatform")
376
+ )
377
+ )
378
+ ],
379
+ Files: files
380
+ );
381
+ }
382
+
383
+ return WellKnownPackages[key];
384
+ }
385
+
386
+ public static MockNuGetPackage[] CommonPackages { get; } =
387
+ [
388
+ CreateSimplePackage("NETStandard.Library", "2.0.3", "netstandard2.0"),
389
+ new MockNuGetPackage("Microsoft.NETFramework.ReferenceAssemblies", "1.0.3"),
390
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net6.0"),
391
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net7.0"),
392
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net8.0"),
393
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net6.0",
394
+ [
395
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
396
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="6.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
397
+ </FileList>
398
+ """))
399
+ ]),
400
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net7.0",
401
+ [
402
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
403
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="7.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
404
+ </FileList>
405
+ """))
406
+ ]),
407
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net8.0",
408
+ [
409
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
410
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="8.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
411
+ </FileList>
412
+ """))
413
+ ]),
414
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net6.0"),
415
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net7.0"),
416
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0"),
417
+ ];
418
+ }
419
+ }
@@ -12,6 +12,7 @@
12
12
 
13
13
  <ItemGroup>
14
14
  <PackageReference Include="DiffPlex" />
15
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
15
16
  <PackageReference Include="Microsoft.NET.Test.Sdk" />
16
17
  <PackageReference Include="xunit" />
17
18
  <PackageReference Include="xunit.runner.visualstudio" />
@@ -50,9 +50,14 @@ public sealed class TemporaryDirectory : IDisposable
50
50
  var parentDirectory = Path.GetDirectoryName(temporaryDirectory.DirectoryPath)!;
51
51
 
52
52
  // prevent directory crawling
53
- await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Build.props"), "<Project />");
53
+ await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Build.props"), """
54
+ <Project>
55
+ <PropertyGroup>
56
+ <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
57
+ </PropertyGroup>
58
+ </Project>
59
+ """);
54
60
  await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Build.targets"), "<Project />");
55
- await File.WriteAllTextAsync(Path.Combine(parentDirectory, "Directory.Packages.props"), "<Project />");
56
61
 
57
62
  foreach (var (path, contents) in fileContents)
58
63
  {
@@ -1,8 +1,3 @@
1
- using System;
2
- using System.IO;
3
- using System.Linq;
4
- using System.Threading.Tasks;
5
-
6
1
  using Xunit;
7
2
 
8
3
  namespace NuGetUpdater.Core.Test.Update;
@@ -20,11 +15,12 @@ public abstract class UpdateWorkerTestBase : TestBase
20
15
  string projectContents,
21
16
  bool isTransitive = false,
22
17
  TestFile[]? additionalFiles = null,
18
+ MockNuGetPackage[]? packages = null,
23
19
  string projectFilePath = "test-project.csproj")
24
20
  {
25
21
  return useSolution
26
- ? TestNoChangeforSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], isTransitive, additionalFiles)
27
- : TestNoChangeforProject(dependencyName, oldVersion, newVersion, projectContents, isTransitive, additionalFiles, projectFilePath);
22
+ ? TestNoChangeforSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], isTransitive, additionalFiles, packages)
23
+ : TestNoChangeforProject(dependencyName, oldVersion, newVersion, projectContents, isTransitive, additionalFiles, packages, projectFilePath);
28
24
  }
29
25
 
30
26
  protected static Task TestUpdate(
@@ -37,11 +33,12 @@ public abstract class UpdateWorkerTestBase : TestBase
37
33
  bool isTransitive = false,
38
34
  TestFile[]? additionalFiles = null,
39
35
  TestFile[]? additionalFilesExpected = null,
36
+ MockNuGetPackage[]? packages = null,
40
37
  string projectFilePath = "test-project.csproj")
41
38
  {
42
39
  return useSolution
43
- ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], projectFilesExpected: [(projectFilePath, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected)
44
- : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile: (projectFilePath, projectContents), expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected);
40
+ ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [(projectFilePath, projectContents)], projectFilesExpected: [(projectFilePath, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected, packages)
41
+ : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile: (projectFilePath, projectContents), expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, packages);
45
42
  }
46
43
 
47
44
  protected static Task TestUpdate(
@@ -53,11 +50,12 @@ public abstract class UpdateWorkerTestBase : TestBase
53
50
  string expectedProjectContents,
54
51
  bool isTransitive = false,
55
52
  TestFile[]? additionalFiles = null,
56
- TestFile[]? additionalFilesExpected = null)
53
+ TestFile[]? additionalFilesExpected = null,
54
+ MockNuGetPackage[]? packages = null)
57
55
  {
58
56
  return useSolution
59
- ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [projectFile], projectFilesExpected: [(projectFile.Path, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected)
60
- : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected);
57
+ ? TestUpdateForSolution(dependencyName, oldVersion, newVersion, projectFiles: [projectFile], projectFilesExpected: [(projectFile.Path, expectedProjectContents)], isTransitive, additionalFiles, additionalFilesExpected, packages)
58
+ : TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected, packages);
61
59
  }
62
60
 
63
61
  protected static Task TestNoChangeforProject(
@@ -67,6 +65,7 @@ public abstract class UpdateWorkerTestBase : TestBase
67
65
  string projectContents,
68
66
  bool isTransitive = false,
69
67
  TestFile[]? additionalFiles = null,
68
+ MockNuGetPackage[]? packages = null,
70
69
  string projectFilePath = "test-project.csproj")
71
70
  => TestUpdateForProject(
72
71
  dependencyName,
@@ -76,7 +75,8 @@ public abstract class UpdateWorkerTestBase : TestBase
76
75
  expectedProjectContents: projectContents,
77
76
  isTransitive,
78
77
  additionalFiles,
79
- additionalFilesExpected: additionalFiles);
78
+ additionalFilesExpected: additionalFiles,
79
+ packages: packages);
80
80
 
81
81
  protected static Task TestUpdateForProject(
82
82
  string dependencyName,
@@ -87,6 +87,7 @@ public abstract class UpdateWorkerTestBase : TestBase
87
87
  bool isTransitive = false,
88
88
  TestFile[]? additionalFiles = null,
89
89
  TestFile[]? additionalFilesExpected = null,
90
+ MockNuGetPackage[]? packages = null,
90
91
  string projectFilePath = "test-project.csproj")
91
92
  => TestUpdateForProject(
92
93
  dependencyName,
@@ -96,7 +97,8 @@ public abstract class UpdateWorkerTestBase : TestBase
96
97
  expectedProjectContents,
97
98
  isTransitive,
98
99
  additionalFiles,
99
- additionalFilesExpected);
100
+ additionalFilesExpected,
101
+ packages);
100
102
 
101
103
  protected static async Task TestUpdateForProject(
102
104
  string dependencyName,
@@ -106,21 +108,36 @@ public abstract class UpdateWorkerTestBase : TestBase
106
108
  string expectedProjectContents,
107
109
  bool isTransitive = false,
108
110
  TestFile[]? additionalFiles = null,
109
- TestFile[]? additionalFilesExpected = null)
111
+ TestFile[]? additionalFilesExpected = null,
112
+ MockNuGetPackage[]? packages = null)
110
113
  {
111
114
  additionalFiles ??= [];
112
115
  additionalFilesExpected ??= [];
113
116
 
117
+ var placeFilesInSrc = packages is not null;
118
+
114
119
  var projectFilePath = projectFile.Path;
115
120
  var testFiles = new[] { projectFile }.Concat(additionalFiles).ToArray();
121
+ if (placeFilesInSrc)
122
+ {
123
+ testFiles = testFiles.Select(f => ($"src/{f.Path}", f.Content)).ToArray();
124
+ }
116
125
 
117
126
  var actualResult = await RunUpdate(testFiles, async temporaryDirectory =>
118
127
  {
128
+ await MockNuGetPackagesInDirectory(packages, temporaryDirectory);
129
+
130
+ // run update
119
131
  var worker = new UpdaterWorker(new Logger(verbose: true));
120
- await worker.RunAsync(temporaryDirectory, projectFilePath, dependencyName, oldVersion, newVersion, isTransitive);
132
+ var projectPath = placeFilesInSrc ? $"src/{projectFilePath}" : projectFilePath;
133
+ await worker.RunAsync(temporaryDirectory, projectPath, dependencyName, oldVersion, newVersion, isTransitive);
121
134
  });
122
135
 
123
136
  var expectedResult = additionalFilesExpected.Prepend((projectFilePath, expectedProjectContents)).ToArray();
137
+ if (placeFilesInSrc)
138
+ {
139
+ expectedResult = expectedResult.Select(er => ($"src/{er.Item1}", er.Item2)).ToArray();
140
+ }
124
141
 
125
142
  AssertContainsFiles(expectedResult, actualResult);
126
143
  }
@@ -131,7 +148,8 @@ public abstract class UpdateWorkerTestBase : TestBase
131
148
  string newVersion,
132
149
  TestFile[] projectFiles,
133
150
  bool isTransitive = false,
134
- TestFile[]? additionalFiles = null)
151
+ TestFile[]? additionalFiles = null,
152
+ MockNuGetPackage[]? packages = null)
135
153
  => TestUpdateForSolution(
136
154
  dependencyName,
137
155
  oldVersion,
@@ -140,7 +158,8 @@ public abstract class UpdateWorkerTestBase : TestBase
140
158
  projectFilesExpected: projectFiles,
141
159
  isTransitive,
142
160
  additionalFiles,
143
- additionalFilesExpected: additionalFiles);
161
+ additionalFilesExpected: additionalFiles,
162
+ packages: packages);
144
163
 
145
164
  protected static async Task TestUpdateForSolution(
146
165
  string dependencyName,
@@ -150,7 +169,8 @@ public abstract class UpdateWorkerTestBase : TestBase
150
169
  TestFile[] projectFilesExpected,
151
170
  bool isTransitive = false,
152
171
  TestFile[]? additionalFiles = null,
153
- TestFile[]? additionalFilesExpected = null)
172
+ TestFile[]? additionalFilesExpected = null,
173
+ MockNuGetPackage[]? packages = null)
154
174
  {
155
175
  additionalFiles ??= [];
156
176
  additionalFilesExpected ??= [];
@@ -191,6 +211,8 @@ public abstract class UpdateWorkerTestBase : TestBase
191
211
 
192
212
  var actualResult = await RunUpdate(testFiles, async temporaryDirectory =>
193
213
  {
214
+ await MockNuGetPackagesInDirectory(packages, temporaryDirectory);
215
+
194
216
  var slnPath = Path.Combine(temporaryDirectory, slnName);
195
217
  var worker = new UpdaterWorker(new Logger(verbose: true));
196
218
  await worker.RunAsync(temporaryDirectory, slnPath, dependencyName, oldVersion, newVersion, isTransitive);
@@ -201,6 +223,42 @@ public abstract class UpdateWorkerTestBase : TestBase
201
223
  AssertContainsFiles(expectedResult, actualResult);
202
224
  }
203
225
 
226
+ public static async Task MockNuGetPackagesInDirectory(MockNuGetPackage[]? packages, string temporaryDirectory)
227
+ {
228
+ if (packages is not null)
229
+ {
230
+ string localFeedPath = Path.Join(temporaryDirectory, "local-feed");
231
+ Directory.CreateDirectory(localFeedPath);
232
+ MockNuGetPackage[] allPackages = packages.Concat(MockNuGetPackage.CommonPackages).ToArray();
233
+
234
+ // write all packages to disk
235
+ foreach (MockNuGetPackage package in allPackages)
236
+ {
237
+ package.WriteToDirectory(localFeedPath);
238
+ }
239
+
240
+ // override various nuget locations
241
+ foreach (var envName in new[] { "NUGET_PACKAGES", "NUGET_HTTP_CACHE_PATH", "NUGET_SCRATCH", "NUGET_PLUGINS_CACHE_PATH" })
242
+ {
243
+ string dir = Path.Join(temporaryDirectory, envName);
244
+ Directory.CreateDirectory(dir);
245
+ Environment.SetEnvironmentVariable(envName, dir);
246
+ }
247
+
248
+ // ensure only the test feed is used
249
+ await File.WriteAllTextAsync(Path.Join(temporaryDirectory, "NuGet.Config"), $"""
250
+ <?xml version="1.0" encoding="utf-8"?>
251
+ <configuration>
252
+ <packageSources>
253
+ <clear />
254
+ <add key="local-feed" value="{localFeedPath}" />
255
+ </packageSources>
256
+ </configuration>
257
+ """
258
+ );
259
+ }
260
+ }
261
+
204
262
  protected static async Task<TestFile[]> RunUpdate(TestFile[] files, Func<string, Task> action)
205
263
  {
206
264
  // write initial files