2024-09-27 23:41:06 +03:00
|
|
|
namespace Microsoft.ComponentDetection.Detectors.Tests;
|
2023-02-23 23:28:08 +03:00
|
|
|
|
2023-01-31 19:43:20 +03:00
|
|
|
using System.Linq;
|
2021-11-19 17:07:50 +03:00
|
|
|
using FluentAssertions;
|
|
|
|
using Microsoft.ComponentDetection.Common.DependencyGraph;
|
|
|
|
using Microsoft.ComponentDetection.Contracts;
|
|
|
|
using Microsoft.ComponentDetection.Contracts.TypedComponent;
|
|
|
|
using Microsoft.ComponentDetection.Detectors.Npm;
|
|
|
|
using Microsoft.ComponentDetection.Detectors.Tests.Utilities;
|
2023-02-23 23:28:08 +03:00
|
|
|
using Microsoft.Extensions.Logging;
|
2021-11-19 17:07:50 +03:00
|
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
using Moq;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
|
|
[TestClass]
|
|
|
|
[TestCategory("Governance/All")]
|
|
|
|
[TestCategory("Governance/ComponentDetection")]
|
|
|
|
public class NpmUtilitiesTests
|
|
|
|
{
|
|
|
|
private Mock<ILogger> loggerMock;
|
|
|
|
|
|
|
|
[TestInitialize]
|
|
|
|
public void TestInitialize()
|
|
|
|
{
|
2022-08-04 23:24:13 +03:00
|
|
|
this.loggerMock = new Mock<ILogger>();
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void TestGetTypedComponent()
|
|
|
|
{
|
2022-08-16 19:06:13 +03:00
|
|
|
var json = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""async"": {
|
|
|
|
""version"": ""2.3.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var j = JObject.Parse(json);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-04 23:24:13 +03:00
|
|
|
var componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().NotBeNull();
|
|
|
|
componentFromJProperty.Type.Should().Be(ComponentType.Npm);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var npmComponent = (NpmComponent)componentFromJProperty;
|
2023-10-27 21:04:04 +03:00
|
|
|
npmComponent.Name.Should().Be("async");
|
|
|
|
npmComponent.Version.Should().Be("2.3.0");
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void TestGetTypedComponent_FailsOnMalformed()
|
|
|
|
{
|
2022-08-16 19:06:13 +03:00
|
|
|
var json = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""async"": {
|
|
|
|
""version"": ""NOTAVERSION"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var j = JObject.Parse(json);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-04 23:24:13 +03:00
|
|
|
var componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().BeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void TestGetTypedComponent_FailsOnInvalidPackageName()
|
|
|
|
{
|
2022-08-16 19:06:13 +03:00
|
|
|
var jsonInvalidCharacter = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""async<"": {
|
|
|
|
""version"": ""1.0.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var j = JObject.Parse(jsonInvalidCharacter);
|
2022-08-04 23:24:13 +03:00
|
|
|
var componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().BeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var jsonUrlName = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""http://thisis/my/packagename"": {
|
|
|
|
""version"": ""1.0.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
|
|
|
j = JObject.Parse(jsonUrlName);
|
2022-08-04 23:24:13 +03:00
|
|
|
componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().BeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var jsonInvalidInitialCharacter1 = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""_async"": {
|
|
|
|
""version"": ""1.0.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
|
|
|
j = JObject.Parse(jsonInvalidInitialCharacter1);
|
2022-08-04 23:24:13 +03:00
|
|
|
componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().BeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var jsonInvalidInitialCharacter2 = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
"".async"": {
|
|
|
|
""version"": ""1.0.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
|
|
|
j = JObject.Parse(jsonInvalidInitialCharacter2);
|
2022-08-04 23:24:13 +03:00
|
|
|
componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().BeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
var longPackageName = new string('a', 214);
|
2022-08-16 19:06:13 +03:00
|
|
|
var jsonLongName = $@"{{
|
2021-11-19 17:07:50 +03:00
|
|
|
""{longPackageName}"": {{
|
|
|
|
""version"": ""1.0.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k=""
|
|
|
|
}},
|
|
|
|
}}";
|
|
|
|
|
|
|
|
j = JObject.Parse(jsonLongName);
|
2022-08-04 23:24:13 +03:00
|
|
|
componentFromJProperty = NpmComponentUtilities.GetTypedComponent(j.Children<JProperty>().Single(), "registry.npmjs.org", this.loggerMock.Object);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentFromJProperty.Should().BeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void TestTryParseNpmVersion()
|
|
|
|
{
|
2022-08-16 19:06:13 +03:00
|
|
|
var parsed = NpmComponentUtilities.TryParseNpmVersion("registry.npmjs.org", "archiver", "https://registry.npmjs.org/archiver-2.1.1.tgz", out var parsedVersion);
|
2023-10-27 21:04:04 +03:00
|
|
|
parsed.Should().BeTrue();
|
|
|
|
parsedVersion.ToString().Should().Be("2.1.1");
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
parsed = NpmComponentUtilities.TryParseNpmVersion("registry.npmjs.org", "archiver", "notavalidurl", out parsedVersion);
|
2023-10-27 21:04:04 +03:00
|
|
|
parsed.Should().BeFalse();
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void TestTraverseAndGetRequirementsAndDependencies()
|
|
|
|
{
|
2022-08-16 19:06:13 +03:00
|
|
|
var json = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""archiver"": {
|
|
|
|
""version"": ""2.3.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k="",
|
|
|
|
""dependencies"": {
|
|
|
|
""archiver-utils"": {
|
|
|
|
""version"": ""1.3.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/archiver-utils/-/archiver-utils-1.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-PRT306DRK/NZUaVL07iuqH7nWPg=""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
|
|
|
var jsonChildren = JObject.Parse(json).Children<JProperty>();
|
|
|
|
var currentDependency = jsonChildren.Single();
|
|
|
|
var dependencyLookup = jsonChildren.ToDictionary(dependency => dependency.Name);
|
|
|
|
|
2022-08-04 23:24:13 +03:00
|
|
|
var typedComponent = NpmComponentUtilities.GetTypedComponent(currentDependency, "registry.npmjs.org", this.loggerMock.Object);
|
2022-08-16 19:06:13 +03:00
|
|
|
var componentRecorder = new ComponentRecorder();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
var singleFileComponentRecorder1 = componentRecorder.CreateSingleFileComponentRecorder("/this/is/a/test/path/");
|
|
|
|
var singleFileComponentRecorder2 = componentRecorder.CreateSingleFileComponentRecorder("/this/is/a/different/path/");
|
|
|
|
|
|
|
|
NpmComponentUtilities.TraverseAndRecordComponents(currentDependency, singleFileComponentRecorder1, typedComponent, typedComponent);
|
|
|
|
NpmComponentUtilities.TraverseAndRecordComponents(currentDependency, singleFileComponentRecorder2, typedComponent, typedComponent);
|
|
|
|
|
2024-09-27 23:41:06 +03:00
|
|
|
componentRecorder.GetDetectedComponents().Should().ContainSingle();
|
2023-10-27 21:04:04 +03:00
|
|
|
componentRecorder.GetComponent(typedComponent.Id).Should().NotBeNull();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
var graph1 = componentRecorder.GetDependencyGraphsByLocation()["/this/is/a/test/path/"];
|
|
|
|
var graph2 = componentRecorder.GetDependencyGraphsByLocation()["/this/is/a/different/path/"];
|
|
|
|
|
2024-09-27 23:41:06 +03:00
|
|
|
graph1.GetExplicitReferencedDependencyIds(typedComponent.Id).Should().Contain(typedComponent.Id);
|
|
|
|
graph2.GetExplicitReferencedDependencyIds(typedComponent.Id).Should().Contain(typedComponent.Id);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentRecorder.GetEffectiveDevDependencyValue(typedComponent.Id).GetValueOrDefault(true).Should().BeFalse();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var json1 = @"{
|
2021-11-19 17:07:50 +03:00
|
|
|
""test"": {
|
|
|
|
""version"": ""2.0.0"",
|
|
|
|
""resolved"": ""https://mseng.pkgs.visualstudio.com/_packaging/VsoMicrosoftExternals/npm/registry/async/-/async-2.3.0.tgz"",
|
|
|
|
""integrity"": ""sha1-EBPRBRBH3TIP4k5JTVxm7K9hR9k="",
|
|
|
|
""dev"": ""true""
|
|
|
|
},
|
|
|
|
}";
|
|
|
|
|
|
|
|
var jsonChildren1 = JObject.Parse(json1).Children<JProperty>();
|
|
|
|
var currentDependency1 = jsonChildren1.Single();
|
|
|
|
var dependencyLookup1 = jsonChildren1.ToDictionary(dependency => dependency.Name);
|
|
|
|
|
2022-08-04 23:24:13 +03:00
|
|
|
var typedComponent1 = NpmComponentUtilities.GetTypedComponent(currentDependency1, "registry.npmjs.org", this.loggerMock.Object);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
NpmComponentUtilities.TraverseAndRecordComponents(currentDependency1, singleFileComponentRecorder2, typedComponent1, typedComponent1);
|
|
|
|
|
2024-09-27 23:41:06 +03:00
|
|
|
componentRecorder.GetDetectedComponents().Should().HaveCount(2);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
2024-09-27 23:41:06 +03:00
|
|
|
graph2.GetExplicitReferencedDependencyIds(typedComponent1.Id).Should().Contain(typedComponent1.Id);
|
2023-10-27 21:04:04 +03:00
|
|
|
componentRecorder.GetEffectiveDevDependencyValue(typedComponent1.Id).GetValueOrDefault(false).Should().BeTrue();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
NpmComponentUtilities.TraverseAndRecordComponents(currentDependency1, singleFileComponentRecorder2, typedComponent, typedComponent1, parentComponentId: typedComponent1.Id);
|
|
|
|
|
2024-09-27 23:41:06 +03:00
|
|
|
componentRecorder.GetDetectedComponents().Should().HaveCount(2);
|
2021-11-19 17:07:50 +03:00
|
|
|
var explicitlyReferencedDependencyIds = graph2.GetExplicitReferencedDependencyIds(typedComponent.Id);
|
2023-10-27 21:04:04 +03:00
|
|
|
explicitlyReferencedDependencyIds.Should().Contain(typedComponent.Id);
|
|
|
|
explicitlyReferencedDependencyIds.Should().Contain(typedComponent1.Id);
|
|
|
|
explicitlyReferencedDependencyIds.Should().HaveCount(2);
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void AddOrUpdateDetectedComponent_NewComponent_ComponentAdded()
|
|
|
|
{
|
|
|
|
var expectedDetectedComponent = new DetectedComponent(new NpmComponent("test", "1.0.0"));
|
|
|
|
var expectedDetectedDevComponent = new DetectedComponent(new NpmComponent("test2", "1.0.0"));
|
|
|
|
|
2022-08-16 19:06:13 +03:00
|
|
|
var componentRecorder = new ComponentRecorder();
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
var addedComponent1 = NpmComponentUtilities.AddOrUpdateDetectedComponent(
|
2022-08-20 02:56:29 +03:00
|
|
|
componentRecorder.CreateSingleFileComponentRecorder("path1"), expectedDetectedComponent.Component, isDevDependency: false);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
var addedComponent2 = NpmComponentUtilities.AddOrUpdateDetectedComponent(
|
2022-08-20 02:56:29 +03:00
|
|
|
componentRecorder.CreateSingleFileComponentRecorder("path2"), expectedDetectedDevComponent.Component, isDevDependency: true);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
addedComponent1.Should().BeEquivalentTo(expectedDetectedComponent, options => options.Excluding(obj => obj.DependencyRoots));
|
|
|
|
addedComponent2.Should().BeEquivalentTo(expectedDetectedDevComponent, options => options.Excluding(obj => obj.DependencyRoots));
|
|
|
|
|
2024-09-27 23:41:06 +03:00
|
|
|
componentRecorder.GetDetectedComponents().Should().HaveCount(2);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
var nonDevComponent = componentRecorder.GetComponent(expectedDetectedComponent.Component.Id);
|
|
|
|
nonDevComponent.Should().BeEquivalentTo(expectedDetectedComponent.Component);
|
|
|
|
componentRecorder.GetEffectiveDevDependencyValue(nonDevComponent.Id).Should().Be(false);
|
|
|
|
componentRecorder.ForOneComponent(nonDevComponent.Id, grouping => grouping.AllFileLocations.Should().BeEquivalentTo("path1"));
|
|
|
|
|
|
|
|
var devComponent = componentRecorder.GetComponent(expectedDetectedDevComponent.Component.Id);
|
|
|
|
devComponent.Should().BeEquivalentTo(expectedDetectedDevComponent.Component);
|
|
|
|
componentRecorder.GetEffectiveDevDependencyValue(devComponent.Id).Should().Be(true);
|
|
|
|
componentRecorder.ForOneComponent(devComponent.Id, grouping => grouping.AllFileLocations.Should().BeEquivalentTo("path2"));
|
|
|
|
}
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
|
|
|
public void AddOrUpdateDetectedComponent_ComponentExistAsDevDependencyNewUpdateIsNoDevDependency_DevDependencyIsUpdatedToFalse()
|
2023-01-05 20:10:56 +03:00
|
|
|
{
|
2021-11-19 17:07:50 +03:00
|
|
|
var detectedComponent = new DetectedComponent(new NpmComponent("name", "1.0"))
|
|
|
|
{
|
|
|
|
DevelopmentDependency = true,
|
|
|
|
};
|
|
|
|
|
2023-02-23 23:28:08 +03:00
|
|
|
var componentRecorder = new ComponentRecorder(new Mock<ILogger>().Object);
|
2021-11-19 17:07:50 +03:00
|
|
|
var singleFileComponentRecorder = componentRecorder.CreateSingleFileComponentRecorder("path");
|
|
|
|
singleFileComponentRecorder.RegisterUsage(detectedComponent);
|
|
|
|
|
|
|
|
var updatedDetectedComponent = NpmComponentUtilities.AddOrUpdateDetectedComponent(
|
2022-08-20 02:56:29 +03:00
|
|
|
componentRecorder.CreateSingleFileComponentRecorder("path"), detectedComponent.Component, isDevDependency: false);
|
2021-11-19 17:07:50 +03:00
|
|
|
|
|
|
|
componentRecorder.GetEffectiveDevDependencyValue(detectedComponent.Component.Id).Should().BeFalse();
|
|
|
|
componentRecorder.GetEffectiveDevDependencyValue(updatedDetectedComponent.Component.Id).Should().BeFalse();
|
|
|
|
}
|
2023-04-07 22:13:44 +03:00
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
public void GetModuleName_ReturnsAsExpected()
|
|
|
|
{
|
|
|
|
var testCases = new[]
|
|
|
|
{
|
2023-04-13 00:57:08 +03:00
|
|
|
("test", "test"), ("@types/test", "@types/test"), ("node_modules/test", "test"),
|
|
|
|
("node_modules/@types/test", "@types/test"), ("node_modules/root/node_modules/test", "test"),
|
2023-04-07 22:13:44 +03:00
|
|
|
("node_modules/root/node_modules/@types/test", "@types/test"),
|
|
|
|
("node_modules/rootA/node_modules/rootB/node_modules/test", "test"),
|
|
|
|
("node_modules/rootA/node_modules/rootB/node_modules/@types/test", "@types/test"),
|
|
|
|
};
|
|
|
|
|
|
|
|
foreach (var (path, expectedModuleName) in testCases)
|
|
|
|
{
|
|
|
|
NpmComponentUtilities.GetModuleName(path).Should().Be(expectedModuleName);
|
|
|
|
}
|
|
|
|
}
|
2021-11-19 17:07:50 +03:00
|
|
|
}
|