diff --git a/src/Microsoft.ComponentDetection.Contracts/Internal/NpmAuthor.cs b/src/Microsoft.ComponentDetection.Contracts/Internal/NpmAuthor.cs new file mode 100644 index 000000000..5c9891a5c --- /dev/null +++ b/src/Microsoft.ComponentDetection.Contracts/Internal/NpmAuthor.cs @@ -0,0 +1,17 @@ +using System; + +namespace Microsoft.ComponentDetection.Contracts.Internal +{ + public class NpmAuthor + { + public NpmAuthor(string name, string email = null) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Email = string.IsNullOrEmpty(email) ? null : email; + } + + public string Name { get; set; } + + public string Email { get; set; } + } +} diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs index 6a83342ea..afad4cdf8 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs @@ -1,4 +1,5 @@ -using PackageUrl; +using Microsoft.ComponentDetection.Contracts.Internal; +using PackageUrl; namespace Microsoft.ComponentDetection.Contracts.TypedComponent { @@ -9,11 +10,12 @@ private NpmComponent() /* Reserved for deserialization */ } - public NpmComponent(string name, string version, string hash = null) + public NpmComponent(string name, string version, string hash = null, NpmAuthor author = null) { Name = ValidateRequiredInput(name, nameof(Name), nameof(ComponentType.Npm)); Version = ValidateRequiredInput(version, nameof(Version), nameof(ComponentType.Npm)); Hash = hash; // Not required; only found in package-lock.json, not package.json + Author = author; } public string Name { get; set; } @@ -22,6 +24,8 @@ public NpmComponent(string name, string version, string hash = null) public string Hash { get; set; } + public NpmAuthor Author { get; set; } + public override ComponentType Type => ComponentType.Npm; public override string Id => $"{Name} {Version} - {Type}"; diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs index 65e2d87db..cfbcdf15e 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs @@ -9,16 +9,19 @@ private NuGetComponent() /* Reserved for deserialization */ } - public NuGetComponent(string name, string version) + public NuGetComponent(string name, string version, string[] authors = null) { Name = ValidateRequiredInput(name, nameof(Name), nameof(ComponentType.NuGet)); Version = ValidateRequiredInput(version, nameof(Version), nameof(ComponentType.NuGet)); + Authors = authors; } public string Name { get; set; } public string Version { get; set; } + public string[] Authors { get; set; } + public override ComponentType Type => ComponentType.NuGet; public override string Id => $"{Name} {Version} - {Type}"; diff --git a/src/Microsoft.ComponentDetection.Detectors/npm/NpmComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/npm/NpmComponentDetector.cs index 334e7a27b..b4c951bbb 100644 --- a/src/Microsoft.ComponentDetection.Detectors/npm/NpmComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/npm/NpmComponentDetector.cs @@ -8,6 +8,7 @@ using Microsoft.ComponentDetection.Contracts.TypedComponent; using Newtonsoft.Json.Linq; using NuGet.Versioning; +using System.Text.RegularExpressions; namespace Microsoft.ComponentDetection.Detectors.Npm { @@ -81,16 +82,68 @@ protected virtual bool ProcessIndividualPackageJTokens(string filePath, ISingleF { var name = packageJToken["name"].ToString(); var version = packageJToken["version"].ToString(); - + var authorToken = packageJToken["author"]; + if (!SemanticVersion.TryParse(version, out _)) { Logger.LogWarning($"Unable to parse version \"{version}\" for package \"{name}\" found at path \"{filePath}\". This may indicate an invalid npm package component and it will not be registered."); return false; } - var detectedComponent = new DetectedComponent(new NpmComponent(name, version)); - singleFileComponentRecorder.RegisterUsage(detectedComponent); + NpmComponent npmComponent = new NpmComponent(name, version, author: GetAuthor(authorToken, name, filePath)); + + singleFileComponentRecorder.RegisterUsage(new DetectedComponent(npmComponent)); return true; } + + private NpmAuthor GetAuthor(JToken authorToken, string packageName, string filePath) + { + var authorString = authorToken?.ToString(); + if (string.IsNullOrEmpty(authorString)) + { + return null; + } + + string authorName; + string authorEmail; + string authorSingleStringPattern = @"^(?([^<(]+?)?)[ \t]*(?:<(?([^>(]+?))>)?[ \t]*(?:\(([^)]+?)\)|$)"; + Match authorMatch = new Regex(authorSingleStringPattern).Match(authorString); + + /* + * for parsing author in Json Format + * for e.g. + * "author": { + * "name": "John Doe", + * "email": "johndoe@outlook.com", + * "name": "https://jd.com", + */ + if (authorToken.HasValues) + { + authorName = authorToken["name"]?.ToString(); + authorEmail = authorToken["email"]?.ToString(); + + /* + * for parsing author in single string format. + * for e.g. + * "author": "John Doe https://jd.com" + */ + } else if (authorMatch.Success) + { + authorName = authorMatch.Groups["name"].ToString().Trim(); + authorEmail = authorMatch.Groups["email"].ToString().Trim(); + } else + { + Logger.LogWarning("Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered."); + return null; + } + + if (string.IsNullOrEmpty(authorName)) + { + Logger.LogWarning("Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered."); + return null; + } + + return new NpmAuthor(authorName, authorEmail); + } } } \ No newline at end of file diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs index 89e7af193..19aaece6a 100644 --- a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs @@ -10,6 +10,7 @@ using Microsoft.ComponentDetection.Contracts; using Microsoft.ComponentDetection.Contracts.Internal; using Microsoft.ComponentDetection.Contracts.TypedComponent; +using MoreLinq; using NuGet.Versioning; namespace Microsoft.ComponentDetection.Detectors.NuGet @@ -106,6 +107,8 @@ private async Task ProcessFile(ProcessRequest processRequest) string name = metadataNode["id"].InnerText; string version = metadataNode["version"].InnerText; + string[] authors = metadataNode["authors"]?.InnerText.Split(",").Select(author => author.Trim()).ToArray(); + if (!NuGetVersion.TryParse(version, out NuGetVersion parsedVer)) { Logger.LogInfo($"Version '{version}' from {stream.Location} could not be parsed as a NuGet version"); @@ -113,7 +116,7 @@ private async Task ProcessFile(ProcessRequest processRequest) return; } - TypedComponent component = new NuGetComponent(name, version); + NuGetComponent component = new NuGetComponent(name, version, authors); singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component)); } catch (Exception e) diff --git a/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs b/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs index d9be70d59..0b59ba3c0 100644 --- a/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs +++ b/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs @@ -1,5 +1,6 @@ using System; using FluentAssertions; +using Microsoft.ComponentDetection.Contracts.Internal; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -28,25 +29,32 @@ public void TypedComponent_Serialization_Other() [TestMethod] public void TypedComponent_Serialization_NuGet() { - TypedComponent.TypedComponent tc = new NuGetComponent("SomeNuGetComponent", "1.2.3"); + string testComponentName = "SomeNuGetComponent"; + string testVersion = "1.2.3"; + string[] testAuthors = { "John Doe", "Jane Doe" }; + TypedComponent.TypedComponent tc = new NuGetComponent(testComponentName, testVersion, testAuthors); var result = JsonConvert.SerializeObject(tc); var deserializedTC = JsonConvert.DeserializeObject(result); deserializedTC.Should().BeOfType(typeof(NuGetComponent)); var nugetComponent = (NuGetComponent)deserializedTC; - nugetComponent.Name.Should().Be("SomeNuGetComponent"); - nugetComponent.Version.Should().Be("1.2.3"); + nugetComponent.Name.Should().Be(testComponentName); + nugetComponent.Version.Should().Be(testVersion); + nugetComponent.Authors.Should().BeEquivalentTo(testAuthors); } [TestMethod] public void TypedComponent_Serialization_Npm() { - TypedComponent.TypedComponent tc = new NpmComponent("SomeNpmComponent", "1.2.3"); - var result = JsonConvert.SerializeObject(tc); + NpmAuthor npmAuthor = new Internal.NpmAuthor("someAuthorName", "someAuthorEmail"); + NpmComponent npmCompObj = new NpmComponent("SomeNpmComponent", "1.2.3"); + npmCompObj.Author = npmAuthor; + var result = JsonConvert.SerializeObject(npmCompObj); var deserializedTC = JsonConvert.DeserializeObject(result); deserializedTC.Should().BeOfType(typeof(NpmComponent)); var npmComponent = (NpmComponent)deserializedTC; npmComponent.Name.Should().Be("SomeNpmComponent"); npmComponent.Version.Should().Be("1.2.3"); + npmComponent.Author.Should().BeEquivalentTo(npmAuthor); } [TestMethod] diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NpmDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NpmDetectorTests.cs new file mode 100644 index 000000000..9dc5c1f5b --- /dev/null +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NpmDetectorTests.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.ComponentDetection.Common.DependencyGraph; +using Microsoft.ComponentDetection.Contracts; +using Microsoft.ComponentDetection.Contracts.TypedComponent; +using Microsoft.ComponentDetection.Detectors.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Microsoft.ComponentDetection.TestsUtilities; + +using static Microsoft.ComponentDetection.Detectors.Tests.Utilities.TestUtilityExtensions; + +namespace Microsoft.ComponentDetection.Detectors.Tests +{ + [TestClass] + [TestCategory("Governance/All")] + [TestCategory("Governance/ComponentDetection")] + public class NpmDetectorTests + { + private Mock loggerMock; + private Mock pathUtilityService; + private ComponentRecorder componentRecorder; + private DetectorTestUtility detectorTestUtility = DetectorTestUtilityCreator.Create(); + private List packageJsonSearchPattern = new List { "package.json" }; + + [TestInitialize] + public void TestInitialize() + { + loggerMock = new Mock(); + pathUtilityService = new Mock(); + pathUtilityService.Setup(x => x.GetParentDirectory(It.IsAny())).Returns((string path) => Path.GetDirectoryName(path)); + componentRecorder = new ComponentRecorder(); + } + + [TestMethod] + public async Task TestNpmDetector_NameAndVersionDetected() + { + string componentName = GetRandomString(); + string version = NewRandomVersion(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForNameAndVersion(componentName, version); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + Assert.AreEqual(1, detectedComponents.Count()); + Assert.AreEqual(detectedComponents.First().Component.Type, ComponentType.Npm); + Assert.AreEqual(((NpmComponent)detectedComponents.First().Component).Name, componentName); + Assert.AreEqual(((NpmComponent)detectedComponents.First().Component).Version, version); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNameAndEmailDetected_AuthorInJsonFormat() + { + string authorName = GetRandomString(); + string authorEmail = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailInJsonFormat(authorName, authorEmail); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.AreEqual(authorName, ((NpmComponent)detectedComponents.First().Component).Author.Name); + Assert.AreEqual(authorEmail, ((NpmComponent)detectedComponents.First().Component).Author.Email); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNameDetectedWhenEmailIsNotPresent_AuthorInJsonFormat() + { + string authorName = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailInJsonFormat(authorName, null); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.AreEqual(authorName, ((NpmComponent)detectedComponents.First().Component).Author.Name); + Assert.IsNull(((NpmComponent)detectedComponents.First().Component).Author.Email); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNameAndAuthorEmailDetected_WhenAuthorNameAndEmailAndUrlIsPresent_AuthorAsSingleString() + { + string authorName = GetRandomString(); + string authorEmail = GetRandomString(); + string authroUrl = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailAsSingleString(authorName, authorEmail, authroUrl); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.AreEqual(authorName, ((NpmComponent)detectedComponents.First().Component).Author.Name); + Assert.AreEqual(authorEmail, ((NpmComponent)detectedComponents.First().Component).Author.Email); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNameDetected_WhenEmailNotPresentAndUrlIsPresent_AuthorAsSingleString() + { + string authorName = GetRandomString(); + string authroUrl = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailAsSingleString(authorName, null, authroUrl); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.AreEqual(authorName, ((NpmComponent)detectedComponents.First().Component).Author.Name); + Assert.IsNull(((NpmComponent)detectedComponents.First().Component).Author.Email); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNull_WhenAuthorMalformed_AuthorAsSingleString() + { + string authorName = GetRandomString(); + string authroUrl = GetRandomString(); + string authorEmail = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesMalformedAuthorAsSingleString(authorName, authorEmail, authroUrl); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.IsNull(((NpmComponent)detectedComponents.First().Component).Author); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNameDetected_WhenEmailNotPresentAndUrlNotPresent_AuthorAsSingleString() + { + string authorName = GetRandomString(); + string authroUrl = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailAsSingleString(authorName); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.AreEqual(authorName, ((NpmComponent)detectedComponents.First().Component).Author.Name); + Assert.IsNull(((NpmComponent)detectedComponents.First().Component).Author.Email); + } + + [TestMethod] + public async Task TestNpmDetector_AuthorNameAndAuthorEmailDetected_WhenUrlNotPresent_AuthorAsSingleString() + { + string authorName = GetRandomString(); + string authorEmail = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailAsSingleString(authorName, authorEmail); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.AreEqual(authorName, ((NpmComponent)detectedComponents.First().Component).Author.Name); + Assert.AreEqual(authorEmail, ((NpmComponent)detectedComponents.First().Component).Author.Email); + } + + [TestMethod] + public async Task TestNpmDetector_NullAuthor_WhenAuthorNameIsNullOrEmpty_AuthorAsJson() + { + string authorName = string.Empty; + string authorEmail = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailInJsonFormat(authorName, authorEmail); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.IsNull(((NpmComponent)detectedComponents.First().Component).Author); + } + + [TestMethod] + public async Task TestNpmDetector_NullAuthor_WhenAuthorNameIsNullOrEmpty_AuthorAsSingleString() + { + string authorName = string.Empty; + string authorEmail = GetRandomString(); + var (packageJsonName, packageJsonContents, packageJsonPath) = + NpmTestUtilities.GetPackageJsonNoDependenciesForAuthorAndEmailAsSingleString(authorName, authorEmail); + var detector = new NpmComponentDetector(); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithDetector(detector) + .WithFile(packageJsonName, packageJsonContents, packageJsonSearchPattern, fileLocation: packageJsonPath) + .ExecuteDetector(); + + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + var detectedComponents = componentRecorder.GetDetectedComponents(); + AssertDetectedComponentCount(detectedComponents, 1); + AssertNpmComponent(detectedComponents); + Assert.IsNull(((NpmComponent)detectedComponents.First().Component).Author); + } + + private static void AssertDetectedComponentCount(IEnumerable detectedComponents, int expectedCount) + { + Assert.AreEqual(expectedCount, detectedComponents.Count()); + } + + private static void AssertNpmComponent(IEnumerable detectedComponents) + { + Assert.AreEqual(detectedComponents.First().Component.Type, ComponentType.Npm); + } + } +} diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NpmTestUtilities.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NpmTestUtilities.cs index 41ab83fa8..5f3b3a9ad 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/NpmTestUtilities.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NpmTestUtilities.cs @@ -92,6 +92,104 @@ public static (string, string, string) GetPackageJsonOneRoot(string componentNam return ("package.json", packageJsonTemplate, Path.Combine(Path.GetTempPath(), "package.json")); } + public static (string, string, string) GetPackageJsonNoDependenciesForNameAndVersion(string packageName, string packageVersion) + { + string packagejson = @"{{ + ""name"": ""{0}"", + ""version"": ""{1}"" + }}"; + var packageJsonTemplate = string.Format(packagejson, packageName, packageVersion); + return ("package.json", packageJsonTemplate, Path.Combine(Path.GetTempPath(), "package.json")); + } + + public static (string, string, string) GetPackageJsonNoDependenciesForAuthorAndEmailInJsonFormat( + string authorName, string authorEmail = null) + { + string packagejson; + if (authorEmail != null) + { + packagejson = @"{{ + ""name"": ""test"", + ""version"": ""0.0.0"", + ""author"": {{ + ""name"": ""{0}"", + ""email"": ""{1}"" + }} + }}"; + } else + { + packagejson = @"{{ + ""name"": ""test"", + ""version"": ""0.0.0"", + ""author"": {{ + ""name"": ""{0}"", + }} + }}"; + } + + var packageJsonTemplate = string.Format(packagejson, authorName, authorEmail); + return ("package.json", packageJsonTemplate, Path.Combine(Path.GetTempPath(), "package.json")); + } + + public static (string, string, string) GetPackageJsonNoDependenciesForAuthorAndEmailAsSingleString( + string authorName, string authorEmail = null, string authorUrl = null) + { + string packagejson = @"{{{{ + ""name"": ""test"", + ""version"": ""0.0.0"", + ""author"": {0} + }}}}"; + string author; + + if (authorEmail != null && authorUrl != null) + { + author = @"""{0} <{1}> ({2})"""; + } else if (authorEmail == null && authorUrl != null) + { + author = @"""{0} ({2})"""; + } else if (authorEmail != null && authorUrl == null) + { + author = @"""{0} <{1}>"""; + } else + { + author = @"""{0}"""; + } + + var packageJsonTemplate = string.Format(string.Format(packagejson, author), authorName, authorEmail, authorUrl); + return ("package.json", packageJsonTemplate, Path.Combine(Path.GetTempPath(), "package.json")); + } + + public static (string, string, string) GetPackageJsonNoDependenciesMalformedAuthorAsSingleString( + string authorName, string authorEmail = null, string authorUrl = null) + { + string packagejson = @"{{{{ + ""name"": ""test"", + ""version"": ""0.0.0"", + ""author"": {0} + }}}}"; + string author; + + if (authorEmail != null && authorUrl != null) + { + author = @"""{0} <{1} ({2})"""; + } + else if (authorEmail == null && authorUrl != null) + { + author = @"""{0} ({2}"""; + } + else if (authorEmail != null && authorUrl == null) + { + author = @"""{0} <{1}"""; + } + else + { + author = @"""{0}"""; + } + + var packageJsonTemplate = string.Format(string.Format(packagejson, author), authorName, authorEmail, authorUrl); + return ("package.json", packageJsonTemplate, Path.Combine(Path.GetTempPath(), "package.json")); + } + public static (string, string, string) GetWellFormedPackageLock2(string lockFileName, string rootName0 = null, string rootVersion0 = null, string rootName2 = null, string rootVersion2 = null, string packageName0 = "test", string packageName1 = null, string packageName3 = null) { string packageLockJson = @"{{ diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs index 206bf6026..a88b0238f 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Microsoft.ComponentDetection.TestsUtilities; +using Microsoft.ComponentDetection.Contracts.TypedComponent; namespace Microsoft.ComponentDetection.Detectors.Tests { @@ -41,20 +42,51 @@ public async Task TestNuGetDetectorWithNoFiles_ReturnsSuccessfully() [TestMethod] public async Task TestNugetDetector_ReturnsValidNuspecComponent() { - var nuspec = NugetTestUtilities.GetRandomValidNuSpecComponent(); + var testComponentName = "TestComponentName"; + var testVersion = "1.2.3"; + var testAuthors = new string[] { "author 1", "author 2" }; + var nuspec = NugetTestUtilities.GetValidNuspec(testComponentName, testVersion, testAuthors); var (scanResult, componentRecorder) = await detectorTestUtility .WithFile("*.nuspec", nuspec) .ExecuteDetector(); - Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); - Assert.AreEqual(1, componentRecorder.GetDetectedComponents().Count()); + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode, "Result code does Not match."); + Assert.AreEqual(1, componentRecorder.GetDetectedComponents().Count(), "Componet count does not match"); + var detectedComponent = componentRecorder.GetDetectedComponents().First().Component; + Assert.AreEqual(Contracts.TypedComponent.ComponentType.NuGet, detectedComponent.Type); + var nuGetComponent = (NuGetComponent)detectedComponent; + Assert.AreEqual(testComponentName, nuGetComponent.Name, "Component name does not match."); + Assert.AreEqual(testVersion, nuGetComponent.Version, "Component version does not match."); + CollectionAssert.AreEqual(testAuthors, nuGetComponent.Authors, "Authors does not match."); + } + + [TestMethod] + public async Task TestNugetDetector_ReturnsValidNuspecComponent_SingleAuthor() + { + var testComponentName = "TestComponentName"; + var testVersion = "1.2.3"; + var testAuthors = new string[] { "author 1" }; + var nuspec = NugetTestUtilities.GetValidNuspec(testComponentName, testVersion, testAuthors); + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithFile("*.nuspec", nuspec) + .ExecuteDetector(); + + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode, "Result code does Not match."); + Assert.AreEqual(1, componentRecorder.GetDetectedComponents().Count(), "Componet count does not match"); + var detectedComponent = componentRecorder.GetDetectedComponents().First().Component; + Assert.AreEqual(Contracts.TypedComponent.ComponentType.NuGet, detectedComponent.Type); + var nuGetComponent = (NuGetComponent)detectedComponent; + Assert.AreEqual(testComponentName, nuGetComponent.Name, "Component name does not match."); + Assert.AreEqual(testVersion, nuGetComponent.Version, "Component version does not match."); + CollectionAssert.AreEqual(testAuthors, nuGetComponent.Authors, "Authors does not match."); } [TestMethod] public async Task TestNugetDetector_ReturnsValidNupkgComponent() { - var nupkg = await NugetTestUtilities.ZipNupkgComponent("test.nupkg", NugetTestUtilities.GetRandomValidNuPkgComponent()); + var nupkg = await NugetTestUtilities.ZipNupkgComponent("test.nupkg", NugetTestUtilities.GetRandomValidNuspec()); var (scanResult, componentRecorder) = await detectorTestUtility .WithFile("test.nupkg", nupkg) @@ -68,7 +100,7 @@ public async Task TestNugetDetector_ReturnsValidNupkgComponent() public async Task TestNugetDetector_ReturnsValidMixedComponent() { var nuspec = NugetTestUtilities.GetRandomValidNuSpecComponent(); - var nupkg = await NugetTestUtilities.ZipNupkgComponent("test.nupkg", NugetTestUtilities.GetRandomValidNuPkgComponent()); + var nupkg = await NugetTestUtilities.ZipNupkgComponent("test.nupkg", NugetTestUtilities.GetRandomValidNuspec()); var (scanResult, componentRecorder) = await detectorTestUtility .WithFile("test.nuspec", nuspec) @@ -82,7 +114,7 @@ public async Task TestNugetDetector_ReturnsValidMixedComponent() [TestMethod] public async Task TestNugetDetector_HandlesMalformedComponentsInComponentList() { - var validNupkg = await NugetTestUtilities.ZipNupkgComponent("test.nupkg", NugetTestUtilities.GetRandomValidNuPkgComponent()); + var validNupkg = await NugetTestUtilities.ZipNupkgComponent("test.nupkg", NugetTestUtilities.GetRandomValidNuspec()); var malformedNupkg = await NugetTestUtilities.ZipNupkgComponent("malformed.nupkg", NugetTestUtilities.GetRandomMalformedNuPkgComponent()); var nuspec = NugetTestUtilities.GetRandomValidNuSpecComponent(); diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NugetTestUtilities.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NugetTestUtilities.cs index df387cb0e..245fffe30 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/NugetTestUtilities.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NugetTestUtilities.cs @@ -17,20 +17,20 @@ public static class NugetTestUtilities { public static string GetRandomValidNuSpecComponent() { - string componentName = Guid.NewGuid().ToString("N"); + string componentName = GetRandomString(); string componentSpecFileName = $"{componentName}.nuspec"; string componentSpecPath = Path.Combine(Path.GetTempPath(), componentSpecFileName); - var template = GetTemplatedNuspec(componentName, NewRandomVersion()); + var template = GetTemplatedNuspec(componentName, NewRandomVersion(), new string[] { GetRandomString(), GetRandomString() }); return template; } public static IComponentStream GetRandomValidNuSpecComponentStream() { - string componentName = Guid.NewGuid().ToString("N"); + string componentName = GetRandomString(); string componentSpecFileName = $"{componentName}.nuspec"; string componentSpecPath = Path.Combine(Path.GetTempPath(), componentSpecFileName); - var template = GetTemplatedNuspec(componentName, NewRandomVersion()); + var template = GetTemplatedNuspec(componentName, NewRandomVersion(), new string[] { GetRandomString(), GetRandomString() }); var mock = new Mock(); mock.SetupGet(x => x.Stream).Returns(template.ToStream()); @@ -52,13 +52,18 @@ public static IComponentStream GetValidNuGetConfig(string repositoryPath) return mock.Object; } - public static string GetRandomValidNuPkgComponent() + public static string GetRandomValidNuspec() { - string componentName = Guid.NewGuid().ToString("N"); - var template = GetTemplatedNuspec(componentName, NewRandomVersion()); + string componentName = GetRandomString(); + var template = GetTemplatedNuspec(componentName, NewRandomVersion(), new string[] { GetRandomString(), GetRandomString() }); return template; } + public static string GetValidNuspec(string componentName, string version, string[] authors) + { + return GetTemplatedNuspec(componentName, version, authors); + } + public static async Task ZipNupkgComponent(string filename, string content) { var stream = new MemoryStream(); @@ -79,13 +84,13 @@ public static async Task ZipNupkgComponent(string filename, string conte public static string GetRandomMalformedNuPkgComponent() { - string componentName = Guid.NewGuid().ToString("N"); - var template = GetTemplatedNuspec(componentName, NewRandomVersion()); + string componentName = GetRandomString(); + var template = GetTemplatedNuspec(componentName, NewRandomVersion(), new string[] { GetRandomString(), GetRandomString() }); template = template.Replace("", ""); return template; } - private static string GetTemplatedNuspec(string id, string version) + private static string GetTemplatedNuspec(string id, string version, string[] authors) { string nuspec = @" @@ -94,7 +99,7 @@ private static string GetTemplatedNuspec(string id, string version) {0} {1} - + {2} @@ -102,7 +107,7 @@ private static string GetTemplatedNuspec(string id, string version) "; - return string.Format(nuspec, id, version); + return string.Format(nuspec, id, version, string.Join(",", authors)); } private static string GetTemplatedNuGetConfig(string repositoryPath) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestUtilityExtensions.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestUtilityExtensions.cs index 257cfca00..4b7b53ecb 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestUtilityExtensions.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestUtilityExtensions.cs @@ -13,5 +13,10 @@ public static string NewRandomVersion() RandomNumberGenerator.GetInt32(0, 1000)) .ToString(); } + + public static string GetRandomString() + { + return Guid.NewGuid().ToString("N"); + } } }