diff --git a/src/Microsoft.ComponentDetection.Common/Telemetry/Records/GoReplaceTelemetryRecord.cs b/src/Microsoft.ComponentDetection.Common/Telemetry/Records/GoReplaceTelemetryRecord.cs new file mode 100644 index 000000000..f9fa2320c --- /dev/null +++ b/src/Microsoft.ComponentDetection.Common/Telemetry/Records/GoReplaceTelemetryRecord.cs @@ -0,0 +1,10 @@ +namespace Microsoft.ComponentDetection.Common.Telemetry.Records; + +public class GoReplaceTelemetryRecord : BaseDetectionTelemetryRecord +{ + public override string RecordName => "GoReplace"; + + public string GoModPathAndVersion { get; set; } + + public string GoModReplacement { get; set; } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs b/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs index 12849535e..dcc80390e 100644 --- a/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/go/GoComponentWithReplaceDetector.cs @@ -27,19 +27,22 @@ public class GoComponentWithReplaceDetector : FileComponentDetector, IExperiment private readonly ICommandLineInvocationService commandLineInvocationService; private readonly IEnvironmentVariableService envVarService; + private readonly IFileUtilityService fileUtilityService; public GoComponentWithReplaceDetector( IComponentStreamEnumerableFactory componentStreamEnumerableFactory, IObservableDirectoryWalkerFactory walkerFactory, ICommandLineInvocationService commandLineInvocationService, IEnvironmentVariableService envVarService, - ILogger logger) + ILogger logger, + IFileUtilityService fileUtilityService) { this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; this.Scanner = walkerFactory; this.commandLineInvocationService = commandLineInvocationService; this.envVarService = envVarService; this.Logger = logger; + this.fileUtilityService = fileUtilityService; } public override string Id => "GoWithReplace"; @@ -50,7 +53,7 @@ public GoComponentWithReplaceDetector( public override IEnumerable SupportedComponentTypes { get; } = [ComponentType.Go]; - public override int Version => 1; + public override int Version => 2; protected override Task> OnPrepareDetectionAsync( IObservable processRequests, @@ -246,7 +249,7 @@ private async Task UseGoCliToScanAsync(string location, ISingleFileCompone return false; } - this.RecordBuildDependencies(goDependenciesProcess.StdOut, singleFileComponentRecorder); + this.RecordBuildDependencies(goDependenciesProcess.StdOut, singleFileComponentRecorder, projectRootDirectory.FullName); var generateGraphProcess = await this.commandLineInvocationService.ExecuteCommandAsync("go", null, workingDirectory: projectRootDirectory, new List { "mod", "graph" }.ToArray()); if (generateGraphProcess.ExitCode == 0) @@ -422,7 +425,7 @@ private bool IsModuleInBuildList(ISingleFileComponentRecorder singleFileComponen return singleFileComponentRecorder.GetComponent(component.Id) != null; } - private void RecordBuildDependencies(string goListOutput, ISingleFileComponentRecorder singleFileComponentRecorder) + private void RecordBuildDependencies(string goListOutput, ISingleFileComponentRecorder singleFileComponentRecorder, string projectRootDirectoryFullName) { var goBuildModules = new List(); var reader = new JsonTextReader(new StringReader(goListOutput)) @@ -430,6 +433,8 @@ private void RecordBuildDependencies(string goListOutput, ISingleFileComponentRe SupportMultipleContent = true, }; + using var record = new GoReplaceTelemetryRecord(); + while (reader.Read()) { var serializer = new JsonSerializer(); @@ -440,16 +445,40 @@ private void RecordBuildDependencies(string goListOutput, ISingleFileComponentRe foreach (var dependency in goBuildModules) { + var dependencyName = $"{dependency.Path} {dependency.Version}"; + if (dependency.Main) { // main is the entry point module (superfluous as we already have the file location) continue; } + if (dependency.Replace?.Path != null && dependency.Replace.Version == null) + { + var dirName = projectRootDirectoryFullName; + var combinedPath = Path.Combine(dirName, dependency.Replace.Path, "go.mod"); + var goModFilePath = Path.GetFullPath(combinedPath); + if (this.fileUtilityService.Exists(goModFilePath)) + { + this.Logger.LogInformation("go Module {GoModule} is being replaced with module at path {GoModFilePath}", dependencyName, goModFilePath); + record.GoModPathAndVersion = dependencyName; + record.GoModReplacement = goModFilePath; + continue; + } + + this.Logger.LogWarning("go.mod file {GoModFilePath} does not exist in the relative path given for replacement", goModFilePath); + record.GoModPathAndVersion = goModFilePath; + record.GoModReplacement = null; + } + GoComponent goComponent; - if (dependency.Replace != null) + if (dependency.Replace?.Path != null && dependency.Replace.Version != null) { + var dependencyReplacementName = $"{dependency.Replace.Path} {dependency.Replace.Version}"; goComponent = new GoComponent(dependency.Replace.Path, dependency.Replace.Version); + this.Logger.LogInformation("go Module {GoModule} being replaced with module {GoModuleReplacement}", dependencyName, dependencyReplacementName); + record.GoModPathAndVersion = dependencyName; + record.GoModReplacement = dependencyReplacementName; } else { diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs index 67789938e..0a86880f0 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/GoComponentWithReplaceDetectorTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.ComponentDetection.Detectors.Tests; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.ComponentDetection.Detectors.Go; using Microsoft.ComponentDetection.TestsUtilities; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -19,6 +20,8 @@ public class GoComponentWithReplaceDetectorTests : BaseDetectorTest commandLineMock; private readonly Mock envVarService; + private readonly Mock fileUtilityServiceMock; + private readonly Mock> mockLogger; public GoComponentWithReplaceDetectorTests() { @@ -30,6 +33,10 @@ public GoComponentWithReplaceDetectorTests() this.envVarService = new Mock(); this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(true); this.DetectorTestUtility.AddServiceMock(this.envVarService); + this.fileUtilityServiceMock = new Mock(); + this.DetectorTestUtility.AddServiceMock(this.fileUtilityServiceMock); + this.mockLogger = new Mock>(); + this.DetectorTestUtility.AddServiceMock(this.mockLogger); } [TestMethod] @@ -582,4 +589,460 @@ public async Task TestGoDetector_GoCliRequiresEnvVarToRunAsync() this.commandLineMock.Verify(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny(), It.IsAny()), Times.Never); } + + [TestMethod] + public async Task TestGoDetector_GoGraphReplaceWithRelativePathAsync() + { + var buildDependencies = @"{ + ""Path"": ""some-package"", + ""Version"": ""v1.2.3"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""test"", + ""Version"": ""v2.0.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""other"", + ""Version"": ""v1.2.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""a"", + ""Version"": ""v1.5.0"", + ""Time"": ""2020-05-19T17:02:07Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + ""Replace"": { + ""Path"": ""C:\\test\\module\\"", + ""Version"": null, + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + } +}"; + + var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 a@v1.5.0"; + this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "list", "-mod=readonly", "-m", "-json", "all" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = buildDependencies, + }); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "mod", "graph" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = goGraph, + }); + + this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false); + + this.fileUtilityServiceMock.Setup(fs => fs.Exists(It.IsAny())) + .Returns(true); + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("go.mod", string.Empty) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDetectedComponents(); + detectedComponents.Should().HaveCount(3); + detectedComponents.Should().NotContain(component => component.Component.Id == "a v1.5.0 - Go"); + detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.3 - Go"); + this.mockLogger.Verify( + logger => logger.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("go Module a v1.5.0 is being replaced with module at path")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [TestMethod] + public async Task TestGoDetector_GoGraphReplaceWithRelativePathDoesNotContainGoModFileAsync() + { + var buildDependencies = @"{ + ""Path"": ""some-package"", + ""Version"": ""v1.2.3"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""test"", + ""Version"": ""v2.0.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""other"", + ""Version"": ""v1.2.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""a"", + ""Version"": ""v1.5.0"", + ""Time"": ""2020-05-19T17:02:07Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + ""Replace"": { + ""Path"": ""C:\\test\\module\\"", + ""Version"": null, + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + } +}"; + + var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 a@v1.5.0"; + this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "list", "-mod=readonly", "-m", "-json", "all" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = buildDependencies, + }); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "mod", "graph" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = goGraph, + }); + + this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false); + + this.fileUtilityServiceMock.Setup(fs => fs.Exists(It.IsAny())) + .Returns(false); + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("go.mod", string.Empty) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDetectedComponents(); + detectedComponents.Should().HaveCount(4); + detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "a v1.5.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.3 - Go"); + this.mockLogger.Verify( + logger => logger.Log( + LogLevel.Warning, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("does not exist in the relative path given for replacement")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [TestMethod] + public async Task TestGoDetector_GoGraphReplaceMultipleReplaceModulesAsync() + { + var buildDependencies = @"{ + ""Path"": ""some-package"", + ""Version"": ""v1.2.3"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""test"", + ""Version"": ""v2.0.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""other"", + ""Version"": ""v1.2.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + ""Replace"": { + ""Path"": ""C:\\test\\component\\"", + ""Version"": null, + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\component\\go.mod"", + ""GoVersion"": ""1.15"", + } + +}" + "\n" + @"{ + ""Path"": ""github"", + ""Version"": ""v1.5.0"", + ""Time"": ""2020-05-19T17:02:07Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + ""Replace"": { + ""Path"": ""C:\\test\\module\\"", + ""Version"": null, + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\module\\go.mod"", + ""GoVersion"": ""1.11"", + } +}"; + + var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 github@v1.5.0"; + this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "list", "-mod=readonly", "-m", "-json", "all" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = buildDependencies, + }); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "mod", "graph" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = goGraph, + }); + + this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false); + + this.fileUtilityServiceMock.Setup(fs => fs.Exists(It.IsAny())) + .Returns(true); + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("go.mod", string.Empty) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDetectedComponents(); + detectedComponents.Should().HaveCount(2); + detectedComponents.Should().NotContain(component => component.Component.Id == "a v1.5.0 - Go"); + detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go"); + detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.2.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.3 - Go"); + this.mockLogger.Verify( + logger => logger.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("go Module other v1.2.0 is being replaced with module at path")), + It.IsAny(), + It.IsAny>()), + Times.Once); + this.mockLogger.Verify( + logger => logger.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString().Contains("go Module github v1.5.0 is being replaced with module at path")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [TestMethod] + public async Task TestGoDetector_GoGraphReplaceNoPathAsync() + { + var buildDependencies = @"{ + ""Path"": ""some-package"", + ""Version"": ""v1.2.3"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""test"", + ""Version"": ""v2.0.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""other"", + ""Version"": ""v1.2.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""github"", + ""Version"": ""v1.5.0"", + ""Time"": ""2020-05-19T17:02:07Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + ""Replace"": { + ""Path"": null, + ""Version"": ""v1.18"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\module\\go.mod"", + ""GoVersion"": ""1.11"", + } +}"; + + var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 github@v1.5.0"; + this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "list", "-mod=readonly", "-m", "-json", "all" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = buildDependencies, + }); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "mod", "graph" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = goGraph, + }); + + this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false); + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("go.mod", string.Empty) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDetectedComponents(); + detectedComponents.Should().HaveCount(4); + detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go"); + detectedComponents.Should().NotContain(component => component.Component.Id == "github v1.18 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "github v1.5.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "github v1.5.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.3 - Go"); + this.mockLogger.Verify( + logger => logger.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString().Equals("go Module github v1.5.0 being replaced with module github v1.18")), + It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [TestMethod] + public async Task TestGoDetector_GoGraphReplacePathAndVersionAsync() + { + var buildDependencies = @"{ + ""Path"": ""some-package"", + ""Version"": ""v1.2.3"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" +}" + "\n" + @"{ + ""Path"": ""test"", + ""Version"": ""v2.0.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""other"", + ""Version"": ""v1.2.0"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"" + +}" + "\n" + @"{ + ""Path"": ""github"", + ""Version"": ""v1.5.0"", + ""Time"": ""2020-05-19T17:02:07Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\go.mod"", + ""GoVersion"": ""1.11"", + ""Replace"": { + ""Path"": ""github"", + ""Version"": ""v1.18"", + ""Time"": ""2021-12-06T23:04:27Z"", + ""Indirect"": true, + ""GoMod"": ""C:\\test\\module\\go.mod"", + ""GoVersion"": ""1.11"", + } +}"; + + var goGraph = "example.com/mainModule some-package@v1.2.3\nsome-package@v1.2.3 other@v1.0.0\nsome-package@v1.2.3 other@v1.2.0\ntest@v2.0.0 github@v1.5.0"; + this.commandLineMock.Setup(x => x.CanCommandBeLocatedAsync("go", null, It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "list", "-mod=readonly", "-m", "-json", "all" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = buildDependencies, + }); + + this.commandLineMock.Setup(x => x.ExecuteCommandAsync("go", null, It.IsAny(), new[] { "mod", "graph" })) + .ReturnsAsync(new CommandLineExecutionResult + { + ExitCode = 0, + StdOut = goGraph, + }); + + this.envVarService.Setup(x => x.IsEnvironmentVariableValueTrue("DisableGoCliScan")).Returns(false); + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("go.mod", string.Empty) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDetectedComponents(); + detectedComponents.Should().HaveCount(4); + detectedComponents.Should().NotContain(component => component.Component.Id == "github v1.5.0 - Go"); + detectedComponents.Should().NotContain(component => component.Component.Id == "other v1.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "github v1.18 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "other v1.2.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "test v2.0.0 - Go"); + detectedComponents.Should().ContainSingle(component => component.Component.Id == "some-package v1.2.3 - Go"); + this.mockLogger.Verify( + logger => logger.Log( + LogLevel.Information, + It.IsAny(), + It.Is((v, t) => v.ToString().Equals("go Module github v1.5.0 being replaced with module github v1.18")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } }