First pass at cataloging .csproj files

Signed-off-by: Alan Pope <alan.pope@anchore.com>
This commit is contained in:
Alan Pope 2025-08-26 12:01:11 +01:00
parent 37b2c0391b
commit e5fd03d2f6
6 changed files with 791 additions and 0 deletions

View File

@ -126,6 +126,7 @@ func DefaultPackageTaskFactories() Factories {
pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#", pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#",
), ),
newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"), newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
newSimplePackageTaskFactory(dotnet.NewDotnetCsprojCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "python"), newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "python"),
newPackageTaskFactory( newPackageTaskFactory(
func(cfg CatalogingFactoryConfig) pkg.Cataloger { func(cfg CatalogingFactoryConfig) pkg.Cataloger {

View File

@ -29,3 +29,9 @@ func NewDotnetPackagesLockCataloger() pkg.Cataloger {
return generic.NewCataloger("dotnet-packages-lock-cataloger"). return generic.NewCataloger("dotnet-packages-lock-cataloger").
WithParserByGlobs(parseDotnetPackagesLock, "**/packages.lock.json") WithParserByGlobs(parseDotnetPackagesLock, "**/packages.lock.json")
} }
// NewDotnetCsprojCataloger returns a cataloger based on .csproj files.
func NewDotnetCsprojCataloger() pkg.Cataloger {
return generic.NewCataloger("dotnet-csproj-cataloger").
WithParserByGlobs(parseDotnetCsproj, "**/*.csproj")
}

View File

@ -0,0 +1,211 @@
package dotnet
import (
"testing"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func TestDotnetCsprojCataloger(t *testing.T) {
fixture := "test-fixtures/steeltoe-sample/WeatherForecast.csproj"
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
expected := []pkg.Package{
{
Name: "Steeltoe.Discovery.Eureka",
Version: "3.2.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Steeltoe.Discovery.Eureka@3.2.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Steeltoe.Discovery.Eureka",
Version: "3.2.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
{
Name: "Steeltoe.Extensions.Configuration.CloudFoundry",
Version: "3.2.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Steeltoe.Extensions.Configuration.CloudFoundry@3.2.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Steeltoe.Extensions.Configuration.CloudFoundry",
Version: "3.2.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
{
Name: "Steeltoe.Management.Endpoint",
Version: "3.2.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Steeltoe.Management.Endpoint@3.2.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Steeltoe.Management.Endpoint",
Version: "3.2.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
{
Name: "Microsoft.AspNetCore.OpenApi",
Version: "8.0.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Microsoft.AspNetCore.OpenApi@8.0.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Microsoft.AspNetCore.OpenApi",
Version: "8.0.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
{
Name: "Swashbuckle.AspNetCore",
Version: "6.5.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Swashbuckle.AspNetCore@6.5.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Swashbuckle.AspNetCore",
Version: "6.5.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
{
Name: "Serilog.AspNetCore",
Version: "8.0.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog.AspNetCore@8.0.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Serilog.AspNetCore",
Version: "8.0.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
{
Name: "Serilog.Sinks.Console",
Version: "5.0.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog.Sinks.Console@5.0.0",
Locations: fixtureLocationSet,
Metadata: pkg.DotnetDepsEntry{
Name: "Serilog.Sinks.Console",
Version: "5.0.0",
Path: "test-fixtures/steeltoe-sample",
Sha512: "",
HashPath: "",
},
},
}
pkgtest.TestCataloger(t, fixture, NewDotnetCsprojCataloger(), expected, nil)
}
func TestDotnetCsprojCataloger_GlopsMatch(t *testing.T) {
expectedPackages := []pkg.Package{
{
Name: "Serilog",
Version: "2.10.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog@2.10.0",
},
{
Name: "Serilog.Sinks.Console",
Version: "4.0.1",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1",
},
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
{
Name: "Humanizer",
Version: "2.14.1",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Humanizer@2.14.1",
},
{
Name: "Microsoft.Web.LibraryManager.Build",
Version: "2.1.175",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Microsoft.Web.LibraryManager.Build@2.1.175",
},
{
Name: "Steeltoe.Discovery.Eureka",
Version: "3.2.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Steeltoe.Discovery.Eureka@3.2.0",
},
{
Name: "Steeltoe.Extensions.Configuration.CloudFoundry",
Version: "3.2.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Steeltoe.Extensions.Configuration.CloudFoundry@3.2.0",
},
{
Name: "Steeltoe.Management.Endpoint",
Version: "3.2.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Steeltoe.Management.Endpoint@3.2.0",
},
{
Name: "Microsoft.AspNetCore.OpenApi",
Version: "8.0.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Microsoft.AspNetCore.OpenApi@8.0.0",
},
{
Name: "Swashbuckle.AspNetCore",
Version: "6.5.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Swashbuckle.AspNetCore@6.5.0",
},
{
Name: "Serilog.AspNetCore",
Version: "8.0.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog.AspNetCore@8.0.0",
},
}
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures").
Expects(expectedPackages, nil).
TestCataloger(t, NewDotnetCsprojCataloger())
}

View File

@ -0,0 +1,160 @@
package dotnet
import (
"context"
"encoding/xml"
"fmt"
"io"
"path/filepath"
"strings"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
// csprojProject represents the root element of a .csproj file
type csprojProject struct {
XMLName xml.Name `xml:"Project"`
Sdk string `xml:"Sdk,attr"`
ItemGroups []csprojItemGroup `xml:"ItemGroup"`
}
// csprojItemGroup represents an ItemGroup element containing references
type csprojItemGroup struct {
PackageReferences []csprojPackageReference `xml:"PackageReference"`
ProjectReferences []csprojProjectReference `xml:"ProjectReference"`
}
// csprojPackageReference represents a PackageReference element
type csprojPackageReference struct {
Include string `xml:"Include,attr"`
Version string `xml:"Version,attr"`
PrivateAssets string `xml:"PrivateAssets,attr"`
IncludeAssets string `xml:"IncludeAssets,attr"`
Condition string `xml:"Condition,attr"`
}
// csprojProjectReference represents a ProjectReference element
type csprojProjectReference struct {
Include string `xml:"Include,attr"`
Condition string `xml:"Condition,attr"`
}
func parseDotnetCsproj(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contents, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to read .csproj file: %w", err)
}
var project csprojProject
if err := xml.Unmarshal(contents, &project); err != nil {
return nil, nil, fmt.Errorf("unable to parse .csproj XML: %w", err)
}
var pkgs []pkg.Package
var relationships []artifact.Relationship
// Process PackageReference elements
for _, itemGroup := range project.ItemGroups {
for _, pkgRef := range itemGroup.PackageReferences {
// Skip packages that are build-time only or analyzers
if shouldSkipPackageReference(pkgRef) {
continue
}
p := buildPackageFromReference(pkgRef, reader.Location)
if p != nil {
pkgs = append(pkgs, *p)
}
}
// Process ProjectReference elements (if we want to include them as relationships)
for _, projRef := range itemGroup.ProjectReferences {
// ProjectReferences represent internal project dependencies
// We could create relationships here, but for now we skip them
// since they represent source-to-source dependencies within the same solution
_ = projRef
}
}
return pkgs, relationships, nil
}
// shouldSkipPackageReference determines if a package reference should be skipped
func shouldSkipPackageReference(ref csprojPackageReference) bool {
// Skip packages that are private assets only (build-time dependencies)
if ref.PrivateAssets == "all" || ref.PrivateAssets == "All" {
return true
}
// Skip conditional references that are likely build/development only
condition := strings.ToLower(ref.Condition)
if strings.Contains(condition, "debug") && !strings.Contains(condition, "release") {
return true
}
// Skip packages that are commonly build-time only
lowerName := strings.ToLower(ref.Include)
buildTimePackages := []string{
"microsoft.net.test.sdk",
"stylecop.analyzers",
"microsoft.codeanalysis",
"coverlet.collector",
"xunit.runner.visualstudio",
}
for _, buildPkg := range buildTimePackages {
if strings.Contains(lowerName, buildPkg) {
return true
}
}
return false
}
// buildPackageFromReference creates a Package from a PackageReference element
func buildPackageFromReference(ref csprojPackageReference, location file.Location) *pkg.Package {
name := strings.TrimSpace(ref.Include)
if name == "" {
return nil
}
version := strings.TrimSpace(ref.Version)
// If version is empty, this might be a framework reference or implicit version
// For now, we'll skip packages without explicit versions since we can't determine them
// from the .csproj alone (would need props/targets files or lock files)
if version == "" {
return nil
}
// Generate PURL following the established pattern for .NET packages
purl := packageurl.NewPackageURL(
packageurl.TypeNuget,
"",
name,
version,
nil,
"",
)
p := &pkg.Package{
Name: name,
Version: version,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: purl.ToString(),
Locations: file.NewLocationSet(location),
Metadata: pkg.DotnetDepsEntry{
Name: name,
Version: version,
Path: filepath.Dir(location.RealPath),
Sha512: "",
HashPath: "",
},
}
return p
}

View File

@ -0,0 +1,384 @@
package dotnet
import (
"context"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
func TestParseDotnetCsproj(t *testing.T) {
tests := []struct {
name string
input string
expected []pkg.Package
expectedError bool
}{
{
name: "basic PackageReference parsing",
input: `<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="2.10.0" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
{
Name: "Serilog",
Version: "2.10.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog@2.10.0",
},
},
},
{
name: "skip private assets",
input: `<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" PrivateAssets="All" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
},
{
name: "skip conditional debug-only references",
input: `<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" Condition="'$(Configuration)' == 'Debug'" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
},
{
name: "skip build-time packages",
input: `<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
},
{
name: "skip packages without version",
input: `<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
},
{
name: "multiple ItemGroup elements",
input: `<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
{
Name: "Serilog",
Version: "2.10.0",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Serilog@2.10.0",
},
},
},
{
name: "empty project",
input: `<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>`,
expected: []pkg.Package{},
},
{
name: "ProjectReference ignored",
input: `<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<ProjectReference Include="../SharedLibrary/SharedLibrary.csproj" />
</ItemGroup>
</Project>`,
expected: []pkg.Package{
{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
},
{
name: "malformed XML",
input: `<Project><ItemGroup><PackageReference Include="Test"`,
expected: nil,
expectedError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
reader := file.NewLocationReadCloser(
file.NewLocation("/test/project.csproj"),
io.NopCloser(strings.NewReader(test.input)),
)
packages, relationships, err := parseDotnetCsproj(context.Background(), nil, &generic.Environment{}, reader)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Empty(t, relationships)
require.Len(t, packages, len(test.expected))
for i, expectedPkg := range test.expected {
actualPkg := packages[i]
assert.Equal(t, expectedPkg.Name, actualPkg.Name)
assert.Equal(t, expectedPkg.Version, actualPkg.Version)
assert.Equal(t, expectedPkg.Language, actualPkg.Language)
assert.Equal(t, expectedPkg.Type, actualPkg.Type)
assert.Equal(t, expectedPkg.PURL, actualPkg.PURL)
// Verify metadata structure
metadata, ok := actualPkg.Metadata.(pkg.DotnetDepsEntry)
require.True(t, ok, "expected DotnetDepsEntry metadata")
assert.Equal(t, expectedPkg.Name, metadata.Name)
assert.Equal(t, expectedPkg.Version, metadata.Version)
assert.NotEmpty(t, metadata.Path)
// Verify locations are set
assert.NotEmpty(t, actualPkg.Locations)
}
})
}
}
func TestShouldSkipPackageReference(t *testing.T) {
tests := []struct {
name string
ref csprojPackageReference
expected bool
}{
{
name: "regular package",
ref: csprojPackageReference{
Include: "Newtonsoft.Json",
Version: "13.0.3",
},
expected: false,
},
{
name: "private assets all",
ref: csprojPackageReference{
Include: "StyleCop.Analyzers",
Version: "1.2.0",
PrivateAssets: "all",
},
expected: true,
},
{
name: "private assets All (capitalized)",
ref: csprojPackageReference{
Include: "StyleCop.Analyzers",
Version: "1.2.0",
PrivateAssets: "All",
},
expected: true,
},
{
name: "debug condition",
ref: csprojPackageReference{
Include: "Microsoft.NET.Test.Sdk",
Version: "17.8.0",
Condition: "'$(Configuration)' == 'Debug'",
},
expected: true,
},
{
name: "test SDK package",
ref: csprojPackageReference{
Include: "Microsoft.NET.Test.Sdk",
Version: "17.8.0",
},
expected: true,
},
{
name: "stylecop analyzer",
ref: csprojPackageReference{
Include: "StyleCop.Analyzers",
Version: "1.2.0",
},
expected: true,
},
{
name: "code analysis package",
ref: csprojPackageReference{
Include: "Microsoft.CodeAnalysis.Analyzers",
Version: "3.3.4",
},
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := shouldSkipPackageReference(test.ref)
assert.Equal(t, test.expected, result)
})
}
}
func TestBuildPackageFromReference(t *testing.T) {
tests := []struct {
name string
ref csprojPackageReference
location file.Location
expected *pkg.Package
}{
{
name: "valid package reference",
ref: csprojPackageReference{
Include: "Newtonsoft.Json",
Version: "13.0.3",
},
location: file.NewLocation("/test/project.csproj"),
expected: &pkg.Package{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
{
name: "empty name",
ref: csprojPackageReference{
Include: "",
Version: "13.0.3",
},
location: file.NewLocation("/test/project.csproj"),
expected: nil,
},
{
name: "empty version",
ref: csprojPackageReference{
Include: "Newtonsoft.Json",
Version: "",
},
location: file.NewLocation("/test/project.csproj"),
expected: nil,
},
{
name: "whitespace in name and version",
ref: csprojPackageReference{
Include: " Newtonsoft.Json ",
Version: " 13.0.3 ",
},
location: file.NewLocation("/test/project.csproj"),
expected: &pkg.Package{
Name: "Newtonsoft.Json",
Version: "13.0.3",
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
PURL: "pkg:nuget/Newtonsoft.Json@13.0.3",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := buildPackageFromReference(test.ref, test.location)
if test.expected == nil {
assert.Nil(t, result)
return
}
require.NotNil(t, result)
assert.Equal(t, test.expected.Name, result.Name)
assert.Equal(t, test.expected.Version, result.Version)
assert.Equal(t, test.expected.Language, result.Language)
assert.Equal(t, test.expected.Type, result.Type)
assert.Equal(t, test.expected.PURL, result.PURL)
// Verify metadata
metadata, ok := result.Metadata.(pkg.DotnetDepsEntry)
require.True(t, ok)
assert.Equal(t, test.expected.Name, metadata.Name)
assert.Equal(t, test.expected.Version, metadata.Version)
})
}
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Steeltoe.Discovery.Eureka" Version="3.2.0" />
<PackageReference Include="Steeltoe.Extensions.Configuration.CloudFoundry" Version="3.2.0" />
<PackageReference Include="Steeltoe.Management.Endpoint" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
</ItemGroup>
<!-- Development-only packages -->
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" PrivateAssets="All" />
</ItemGroup>
<!-- Build-time analyzers -->
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
</ItemGroup>
</Project>