From 755ceab264f9c6a1a3f7b522f4d05ae6aa334e1d Mon Sep 17 00:00:00 2001 From: jaydeep869 Date: Wed, 1 Apr 2026 16:26:08 +0530 Subject: [PATCH 1/4] test: add unit tests for rust resolver Signed-off-by: jaydeep869 --- pkg/resolver/rust_test.go | 162 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 pkg/resolver/rust_test.go diff --git a/pkg/resolver/rust_test.go b/pkg/resolver/rust_test.go new file mode 100644 index 0000000..a7219a1 --- /dev/null +++ b/pkg/resolver/rust_test.go @@ -0,0 +1,162 @@ +package resolver + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +func TestRustResolver_Resolve(t *testing.T) { + resolver := NewRustResolver() + + tests := []struct { + name string + files []FileInfo + expectedPackages []PackageInfo + expectedRemaining int + }{ + { + name: "Valid Cargo registry src path", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Valid Cargo registry cache path (.crate)", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/log-0.4.14.crate"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "log", + Version: "0.4.14", + Ecosystem: "cargo", + PURL: "pkg:cargo/log@0.4.14", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Crates.io index path", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/index.crates.io-1ecc6299db9ec823/se/rd/serde-1.0.130"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Invalid path handling - regular source file", + files: []FileInfo{ + {Path: "/home/user/project/src/main.rs"}, + }, + expectedPackages: nil, + expectedRemaining: 1, // Remains as it isn't resolved by rust resolver + }, + { + name: "Target directory - ignored path", + files: []FileInfo{ + {Path: "/home/user/project/target/debug/build/serde-1.0.130/out.rs"}, + }, + expectedPackages: nil, + expectedRemaining: 0, // Gets filtered out + }, + { + name: "Complex version extraction", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1-alpha.3/src/lib.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "memchr", + Version: "2.4.1-alpha.3", + Ecosystem: "cargo", + PURL: "pkg:cargo/memchr@2.4.1-alpha.3", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + packages, remainingFiles := resolver.Resolve(tt.files) + + // Map expected for an easy comparison by PURL to avoid order flakiness + expectedByPURL := make(map[string]PackageInfo) + for _, pkg := range tt.expectedPackages { + expectedByPURL[pkg.PURL] = pkg + } + + // verify packages + assert.Len(t, packages, len(tt.expectedPackages)) + for _, pkg := range packages { + expected, exists := expectedByPURL[pkg.PURL] + assert.True(t, exists, "unexpected package found: %s", pkg.PURL) + assert.Equal(t, expected.Name, pkg.Name) + assert.Equal(t, expected.Version, pkg.Version) + assert.Equal(t, expected.Ecosystem, pkg.Ecosystem) + assert.Equal(t, expected.PURL, pkg.PURL) + assert.Equal(t, expected.FoundBy, pkg.FoundBy) + } + + // verify remaining + assert.Len(t, remainingFiles, tt.expectedRemaining) + }) + } +} + +func TestRustResolver_CreateFileFilters(t *testing.T) { + resolver := NewRustResolver() + + packages := []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + }, + { + Name: "not-cargo", + Version: "1.0.0", + Ecosystem: "npm", + }, + } + + filters := resolver.CreateFileFilters(packages) + + // Since only rust packages are converted to filters + assert.Len(t, filters, 1) + + // Test matches + rustFilter := filters[0] + + // Match src path + assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs")) + + // Match cache path (.crate) + assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/serde-1.0.130.crate")) + + // Non-matching version + assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.131/src/lib.rs")) + + // Non-matching package + assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs")) +} \ No newline at end of file From db3fdd09714c24efaef5abfefec4c512208a2428 Mon Sep 17 00:00:00 2001 From: jaydeep869 Date: Wed, 1 Apr 2026 17:26:00 +0530 Subject: [PATCH 2/4] test: expand rust resolver test coverage to handle edge cases Signed-off-by: jaydeep869 --- pkg/resolver/rust_test.go | 394 ++++++++++++++++++++++++-------------- 1 file changed, 250 insertions(+), 144 deletions(-) diff --git a/pkg/resolver/rust_test.go b/pkg/resolver/rust_test.go index a7219a1..bdb029f 100644 --- a/pkg/resolver/rust_test.go +++ b/pkg/resolver/rust_test.go @@ -1,162 +1,268 @@ package resolver import ( - "testing" - "github.com/stretchr/testify/assert" +"testing" + +"github.com/stretchr/testify/assert" ) func TestRustResolver_Resolve(t *testing.T) { - resolver := NewRustResolver() - - tests := []struct { - name string - files []FileInfo - expectedPackages []PackageInfo - expectedRemaining int - }{ - { - name: "Valid Cargo registry src path", - files: []FileInfo{ - {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, - }, - expectedPackages: []PackageInfo{ - { - Name: "serde", - Version: "1.0.130", - Ecosystem: "cargo", - PURL: "pkg:cargo/serde@1.0.130", - FoundBy: "attestation:rust", - }, - }, - expectedRemaining: 1, - }, - { - name: "Valid Cargo registry cache path (.crate)", - files: []FileInfo{ - {Path: "/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/log-0.4.14.crate"}, - }, - expectedPackages: []PackageInfo{ - { - Name: "log", - Version: "0.4.14", - Ecosystem: "cargo", - PURL: "pkg:cargo/log@0.4.14", - FoundBy: "attestation:rust", - }, - }, - expectedRemaining: 1, - }, - { - name: "Crates.io index path", - files: []FileInfo{ - {Path: "/home/user/.cargo/registry/index.crates.io-1ecc6299db9ec823/se/rd/serde-1.0.130"}, - }, - expectedPackages: []PackageInfo{ - { - Name: "serde", - Version: "1.0.130", - Ecosystem: "cargo", - PURL: "pkg:cargo/serde@1.0.130", - FoundBy: "attestation:rust", - }, - }, - expectedRemaining: 1, - }, - { - name: "Invalid path handling - regular source file", - files: []FileInfo{ - {Path: "/home/user/project/src/main.rs"}, - }, - expectedPackages: nil, - expectedRemaining: 1, // Remains as it isn't resolved by rust resolver - }, - { - name: "Target directory - ignored path", - files: []FileInfo{ - {Path: "/home/user/project/target/debug/build/serde-1.0.130/out.rs"}, - }, - expectedPackages: nil, - expectedRemaining: 0, // Gets filtered out - }, - { - name: "Complex version extraction", - files: []FileInfo{ - {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1-alpha.3/src/lib.rs"}, - }, - expectedPackages: []PackageInfo{ - { - Name: "memchr", - Version: "2.4.1-alpha.3", - Ecosystem: "cargo", - PURL: "pkg:cargo/memchr@2.4.1-alpha.3", - FoundBy: "attestation:rust", - }, - }, - expectedRemaining: 1, - }, - } +resolver := NewRustResolver() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - packages, remainingFiles := resolver.Resolve(tt.files) - - // Map expected for an easy comparison by PURL to avoid order flakiness - expectedByPURL := make(map[string]PackageInfo) - for _, pkg := range tt.expectedPackages { - expectedByPURL[pkg.PURL] = pkg - } +tests := []struct { +name string +files []FileInfo +expectedPackages []PackageInfo +expectedRemaining int +}{ +{ +name: "Valid Cargo registry src path", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "serde", +Version: "1.0.130", +Ecosystem: "cargo", +PURL: "pkg:cargo/serde@1.0.130", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, +}, +{ +name: "Valid Cargo registry cache path (.crate)", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/log-0.4.14.crate"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "log", +Version: "0.4.14", +Ecosystem: "cargo", +PURL: "pkg:cargo/log@0.4.14", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, +}, +{ +name: "Crates.io index path", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/index.crates.io-1ecc6299db9ec823/se/rd/serde-1.0.130"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "serde", +Version: "1.0.130", +Ecosystem: "cargo", +PURL: "pkg:cargo/serde@1.0.130", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, +}, +{ +name: "Invalid path handling - regular source file", +files: []FileInfo{ +{Path: "/home/user/project/src/main.rs"}, +}, +expectedPackages: nil, +expectedRemaining: 1, // Remains as it isn't resolved by rust resolver +}, +{ +name: "Target directory - ignored path", +files: []FileInfo{ +{Path: "/home/user/project/target/debug/build/serde-1.0.130/out.rs"}, +}, +expectedPackages: nil, +expectedRemaining: 0, // Gets filtered out +}, +{ +name: "Complex version extraction", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1-alpha.3/src/lib.rs"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "memchr", +Version: "2.4.1-alpha.3", +Ecosystem: "cargo", +PURL: "pkg:cargo/memchr@2.4.1-alpha.3", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, +}, +{ +name: "Compound hyphenated crate names", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-build-config-0.27.1/src/lib.rs"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "pyo3-build-config", +Version: "0.27.1", +Ecosystem: "cargo", +PURL: "pkg:cargo/pyo3-build-config@0.27.1", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, +}, +{ +name: "Crate names with underscores", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.130/src/lib.rs"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "serde_derive", +Version: "1.0.130", +Ecosystem: "cargo", +PURL: "pkg:cargo/serde_derive@1.0.130", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, +}, +{ +name: "Deduplication of multiple files from the same crate", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/de.rs"}, +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/ser.rs"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "serde", +Version: "1.0.130", +Ecosystem: "cargo", +PURL: "pkg:cargo/serde@1.0.130", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 3, +}, +{ +name: "Multiple distinct crates resolved in one pass", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs"}, +{Path: "/home/user/project/src/main.rs"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "serde", +Version: "1.0.130", +Ecosystem: "cargo", +PURL: "pkg:cargo/serde@1.0.130", +FoundBy: "attestation:rust", +}, +{ +Name: "log", +Version: "0.4.14", +Ecosystem: "cargo", +PURL: "pkg:cargo/log@0.4.14", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 3, +}, +{ +name: "Non-rust paths passed through to remaining files", +files: []FileInfo{ +{Path: "/usr/local/lib/node_modules/npm/package.json"}, +}, +expectedPackages: nil, +expectedRemaining: 1, +}, +{ +name: "Fingerprint directory - ignored path", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/.fingerprint/log-0.4.14"}, +}, +expectedPackages: nil, +expectedRemaining: 0, +}, +{ +name: "Compiled artifacts are filtered if owned by known crate", +files: []FileInfo{ +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, +{Path: "/home/user/project/target/debug/deps/libserde-123.rlib"}, +}, +expectedPackages: []PackageInfo{ +{ +Name: "serde", +Version: "1.0.130", +Ecosystem: "cargo", +PURL: "pkg:cargo/serde@1.0.130", +FoundBy: "attestation:rust", +}, +}, +expectedRemaining: 1, // src/lib.rs is remaining, but the .rlib is filtered out +}, +} - // verify packages - assert.Len(t, packages, len(tt.expectedPackages)) - for _, pkg := range packages { - expected, exists := expectedByPURL[pkg.PURL] - assert.True(t, exists, "unexpected package found: %s", pkg.PURL) - assert.Equal(t, expected.Name, pkg.Name) - assert.Equal(t, expected.Version, pkg.Version) - assert.Equal(t, expected.Ecosystem, pkg.Ecosystem) - assert.Equal(t, expected.PURL, pkg.PURL) - assert.Equal(t, expected.FoundBy, pkg.FoundBy) - } +for _, tt := range tests { +t.Run(tt.name, func(t *testing.T) { +packages, remainingFiles := resolver.Resolve(tt.files) - // verify remaining - assert.Len(t, remainingFiles, tt.expectedRemaining) - }) - } +// Map expected for an easy comparison by PURL to avoid order flakiness +expectedByPURL := make(map[string]PackageInfo) +for _, pkg := range tt.expectedPackages { +expectedByPURL[pkg.PURL] = pkg } -func TestRustResolver_CreateFileFilters(t *testing.T) { - resolver := NewRustResolver() +// verify packages +assert.Len(t, packages, len(tt.expectedPackages)) +for _, pkg := range packages { +expected, exists := expectedByPURL[pkg.PURL] +assert.True(t, exists, "unexpected package found: %s", pkg.PURL) +assert.Equal(t, expected.Name, pkg.Name) +assert.Equal(t, expected.Version, pkg.Version) +assert.Equal(t, expected.Ecosystem, pkg.Ecosystem) +assert.Equal(t, expected.PURL, pkg.PURL) +assert.Equal(t, expected.FoundBy, pkg.FoundBy) +} - packages := []PackageInfo{ - { - Name: "serde", - Version: "1.0.130", - Ecosystem: "cargo", - }, - { - Name: "not-cargo", - Version: "1.0.0", - Ecosystem: "npm", - }, - } +// verify remaining +assert.Len(t, remainingFiles, tt.expectedRemaining) +}) +} +} - filters := resolver.CreateFileFilters(packages) +func TestRustResolver_CreateFileFilters(t *testing.T) { +resolver := NewRustResolver() - // Since only rust packages are converted to filters - assert.Len(t, filters, 1) +packages := []PackageInfo{ +{ +Name: "serde", +Version: "1.0.130", +Ecosystem: "cargo", +}, +{ +Name: "not-cargo", +Version: "1.0.0", +Ecosystem: "npm", +}, +} - // Test matches - rustFilter := filters[0] +filters := resolver.CreateFileFilters(packages) - // Match src path - assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs")) - - // Match cache path (.crate) - assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/serde-1.0.130.crate")) +// Since only rust packages are converted to filters +assert.Len(t, filters, 1) - // Non-matching version - assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.131/src/lib.rs")) +// Test matches +rustFilter := filters[0] - // Non-matching package - assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs")) -} \ No newline at end of file +// Match src path +assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs")) +// Match cache path (.crate) +assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/serde-1.0.130.crate")) +// Non-matching version +assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.131/src/lib.rs")) +// Non-matching package +assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs")) +} From 0b5bec3d4a87309c852b44c8d112d7602d24d21a Mon Sep 17 00:00:00 2001 From: jaydeep869 Date: Sat, 4 Apr 2026 20:50:31 +0530 Subject: [PATCH 3/4] test: refine compiled artifacts test and add unit tests for NormalizeRustCrateName Signed-off-by: jaydeep869 --- pkg/resolver/rust_test.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/pkg/resolver/rust_test.go b/pkg/resolver/rust_test.go index bdb029f..629d82c 100644 --- a/pkg/resolver/rust_test.go +++ b/pkg/resolver/rust_test.go @@ -190,7 +190,7 @@ expectedRemaining: 0, name: "Compiled artifacts are filtered if owned by known crate", files: []FileInfo{ {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, -{Path: "/home/user/project/target/debug/deps/libserde-123.rlib"}, +{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/libserde.rlib"}, }, expectedPackages: []PackageInfo{ { @@ -266,3 +266,38 @@ assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1e // Non-matching package assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs")) } + +func TestNormalizeRustCrateName(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "Uppercase letters", + input: "SerDe", + expected: "serde", + }, + { + name: "With spaces", + input: " Log ", + expected: "log", + }, + { + name: "Mixed case and spaces", + input: " TokIO ", + expected: "tokio", + }, + { + name: "Already normalized", + input: "regex", + expected: "regex", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, NormalizeRustCrateName(tt.input)) + }) + } +} From 81bcda8c956acedf097cd73a5f64ec42fc3584dd Mon Sep 17 00:00:00 2001 From: jaydeep869 Date: Wed, 8 Apr 2026 21:33:45 +0530 Subject: [PATCH 4/4] style: run gofmt Signed-off-by: jaydeep869 --- pkg/resolver/resolver.go | 2 +- pkg/resolver/rust_test.go | 494 +++++++++++++++++++------------------- 2 files changed, 248 insertions(+), 248 deletions(-) diff --git a/pkg/resolver/resolver.go b/pkg/resolver/resolver.go index c59cf57..34662ee 100644 --- a/pkg/resolver/resolver.go +++ b/pkg/resolver/resolver.go @@ -7,7 +7,7 @@ type PackageInfo struct { PURL string `json:"purl"` Licenses []string `json:"licenses,omitempty"` Hashes map[string]string `json:"hashes,omitempty"` - FoundBy string `json:"found_by"` // which resolver found this + FoundBy string `json:"found_by"` // which resolver found this DownloadURL string `json:"download_url,omitempty"` // set by network resolvers DownloadIP string `json:"download_ip,omitempty"` // set by network resolvers } diff --git a/pkg/resolver/rust_test.go b/pkg/resolver/rust_test.go index 629d82c..c431135 100644 --- a/pkg/resolver/rust_test.go +++ b/pkg/resolver/rust_test.go @@ -1,270 +1,270 @@ package resolver import ( -"testing" + "testing" -"github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestRustResolver_Resolve(t *testing.T) { -resolver := NewRustResolver() + resolver := NewRustResolver() -tests := []struct { -name string -files []FileInfo -expectedPackages []PackageInfo -expectedRemaining int -}{ -{ -name: "Valid Cargo registry src path", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "serde", -Version: "1.0.130", -Ecosystem: "cargo", -PURL: "pkg:cargo/serde@1.0.130", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, -}, -{ -name: "Valid Cargo registry cache path (.crate)", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/log-0.4.14.crate"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "log", -Version: "0.4.14", -Ecosystem: "cargo", -PURL: "pkg:cargo/log@0.4.14", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, -}, -{ -name: "Crates.io index path", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/index.crates.io-1ecc6299db9ec823/se/rd/serde-1.0.130"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "serde", -Version: "1.0.130", -Ecosystem: "cargo", -PURL: "pkg:cargo/serde@1.0.130", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, -}, -{ -name: "Invalid path handling - regular source file", -files: []FileInfo{ -{Path: "/home/user/project/src/main.rs"}, -}, -expectedPackages: nil, -expectedRemaining: 1, // Remains as it isn't resolved by rust resolver -}, -{ -name: "Target directory - ignored path", -files: []FileInfo{ -{Path: "/home/user/project/target/debug/build/serde-1.0.130/out.rs"}, -}, -expectedPackages: nil, -expectedRemaining: 0, // Gets filtered out -}, -{ -name: "Complex version extraction", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1-alpha.3/src/lib.rs"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "memchr", -Version: "2.4.1-alpha.3", -Ecosystem: "cargo", -PURL: "pkg:cargo/memchr@2.4.1-alpha.3", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, -}, -{ -name: "Compound hyphenated crate names", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-build-config-0.27.1/src/lib.rs"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "pyo3-build-config", -Version: "0.27.1", -Ecosystem: "cargo", -PURL: "pkg:cargo/pyo3-build-config@0.27.1", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, -}, -{ -name: "Crate names with underscores", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.130/src/lib.rs"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "serde_derive", -Version: "1.0.130", -Ecosystem: "cargo", -PURL: "pkg:cargo/serde_derive@1.0.130", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, -}, -{ -name: "Deduplication of multiple files from the same crate", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/de.rs"}, -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/ser.rs"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "serde", -Version: "1.0.130", -Ecosystem: "cargo", -PURL: "pkg:cargo/serde@1.0.130", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 3, -}, -{ -name: "Multiple distinct crates resolved in one pass", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs"}, -{Path: "/home/user/project/src/main.rs"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "serde", -Version: "1.0.130", -Ecosystem: "cargo", -PURL: "pkg:cargo/serde@1.0.130", -FoundBy: "attestation:rust", -}, -{ -Name: "log", -Version: "0.4.14", -Ecosystem: "cargo", -PURL: "pkg:cargo/log@0.4.14", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 3, -}, -{ -name: "Non-rust paths passed through to remaining files", -files: []FileInfo{ -{Path: "/usr/local/lib/node_modules/npm/package.json"}, -}, -expectedPackages: nil, -expectedRemaining: 1, -}, -{ -name: "Fingerprint directory - ignored path", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/.fingerprint/log-0.4.14"}, -}, -expectedPackages: nil, -expectedRemaining: 0, -}, -{ -name: "Compiled artifacts are filtered if owned by known crate", -files: []FileInfo{ -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, -{Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/libserde.rlib"}, -}, -expectedPackages: []PackageInfo{ -{ -Name: "serde", -Version: "1.0.130", -Ecosystem: "cargo", -PURL: "pkg:cargo/serde@1.0.130", -FoundBy: "attestation:rust", -}, -}, -expectedRemaining: 1, // src/lib.rs is remaining, but the .rlib is filtered out -}, -} + tests := []struct { + name string + files []FileInfo + expectedPackages []PackageInfo + expectedRemaining int + }{ + { + name: "Valid Cargo registry src path", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Valid Cargo registry cache path (.crate)", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/log-0.4.14.crate"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "log", + Version: "0.4.14", + Ecosystem: "cargo", + PURL: "pkg:cargo/log@0.4.14", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Crates.io index path", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/index.crates.io-1ecc6299db9ec823/se/rd/serde-1.0.130"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Invalid path handling - regular source file", + files: []FileInfo{ + {Path: "/home/user/project/src/main.rs"}, + }, + expectedPackages: nil, + expectedRemaining: 1, // Remains as it isn't resolved by rust resolver + }, + { + name: "Target directory - ignored path", + files: []FileInfo{ + {Path: "/home/user/project/target/debug/build/serde-1.0.130/out.rs"}, + }, + expectedPackages: nil, + expectedRemaining: 0, // Gets filtered out + }, + { + name: "Complex version extraction", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1-alpha.3/src/lib.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "memchr", + Version: "2.4.1-alpha.3", + Ecosystem: "cargo", + PURL: "pkg:cargo/memchr@2.4.1-alpha.3", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Compound hyphenated crate names", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-build-config-0.27.1/src/lib.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "pyo3-build-config", + Version: "0.27.1", + Ecosystem: "cargo", + PURL: "pkg:cargo/pyo3-build-config@0.27.1", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Crate names with underscores", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.130/src/lib.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde_derive", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde_derive@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, + }, + { + name: "Deduplication of multiple files from the same crate", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/de.rs"}, + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/ser.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 3, + }, + { + name: "Multiple distinct crates resolved in one pass", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs"}, + {Path: "/home/user/project/src/main.rs"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + { + Name: "log", + Version: "0.4.14", + Ecosystem: "cargo", + PURL: "pkg:cargo/log@0.4.14", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 3, + }, + { + name: "Non-rust paths passed through to remaining files", + files: []FileInfo{ + {Path: "/usr/local/lib/node_modules/npm/package.json"}, + }, + expectedPackages: nil, + expectedRemaining: 1, + }, + { + name: "Fingerprint directory - ignored path", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/.fingerprint/log-0.4.14"}, + }, + expectedPackages: nil, + expectedRemaining: 0, + }, + { + name: "Compiled artifacts are filtered if owned by known crate", + files: []FileInfo{ + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs"}, + {Path: "/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/libserde.rlib"}, + }, + expectedPackages: []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + PURL: "pkg:cargo/serde@1.0.130", + FoundBy: "attestation:rust", + }, + }, + expectedRemaining: 1, // src/lib.rs is remaining, but the .rlib is filtered out + }, + } -for _, tt := range tests { -t.Run(tt.name, func(t *testing.T) { -packages, remainingFiles := resolver.Resolve(tt.files) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + packages, remainingFiles := resolver.Resolve(tt.files) -// Map expected for an easy comparison by PURL to avoid order flakiness -expectedByPURL := make(map[string]PackageInfo) -for _, pkg := range tt.expectedPackages { -expectedByPURL[pkg.PURL] = pkg -} + // Map expected for an easy comparison by PURL to avoid order flakiness + expectedByPURL := make(map[string]PackageInfo) + for _, pkg := range tt.expectedPackages { + expectedByPURL[pkg.PURL] = pkg + } -// verify packages -assert.Len(t, packages, len(tt.expectedPackages)) -for _, pkg := range packages { -expected, exists := expectedByPURL[pkg.PURL] -assert.True(t, exists, "unexpected package found: %s", pkg.PURL) -assert.Equal(t, expected.Name, pkg.Name) -assert.Equal(t, expected.Version, pkg.Version) -assert.Equal(t, expected.Ecosystem, pkg.Ecosystem) -assert.Equal(t, expected.PURL, pkg.PURL) -assert.Equal(t, expected.FoundBy, pkg.FoundBy) -} + // verify packages + assert.Len(t, packages, len(tt.expectedPackages)) + for _, pkg := range packages { + expected, exists := expectedByPURL[pkg.PURL] + assert.True(t, exists, "unexpected package found: %s", pkg.PURL) + assert.Equal(t, expected.Name, pkg.Name) + assert.Equal(t, expected.Version, pkg.Version) + assert.Equal(t, expected.Ecosystem, pkg.Ecosystem) + assert.Equal(t, expected.PURL, pkg.PURL) + assert.Equal(t, expected.FoundBy, pkg.FoundBy) + } -// verify remaining -assert.Len(t, remainingFiles, tt.expectedRemaining) -}) -} + // verify remaining + assert.Len(t, remainingFiles, tt.expectedRemaining) + }) + } } func TestRustResolver_CreateFileFilters(t *testing.T) { -resolver := NewRustResolver() + resolver := NewRustResolver() -packages := []PackageInfo{ -{ -Name: "serde", -Version: "1.0.130", -Ecosystem: "cargo", -}, -{ -Name: "not-cargo", -Version: "1.0.0", -Ecosystem: "npm", -}, -} + packages := []PackageInfo{ + { + Name: "serde", + Version: "1.0.130", + Ecosystem: "cargo", + }, + { + Name: "not-cargo", + Version: "1.0.0", + Ecosystem: "npm", + }, + } -filters := resolver.CreateFileFilters(packages) + filters := resolver.CreateFileFilters(packages) -// Since only rust packages are converted to filters -assert.Len(t, filters, 1) + // Since only rust packages are converted to filters + assert.Len(t, filters, 1) -// Test matches -rustFilter := filters[0] + // Test matches + rustFilter := filters[0] -// Match src path -assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs")) -// Match cache path (.crate) -assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/serde-1.0.130.crate")) -// Non-matching version -assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.131/src/lib.rs")) -// Non-matching package -assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs")) + // Match src path + assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.130/src/lib.rs")) + // Match cache path (.crate) + assert.True(t, rustFilter.Matches("/home/user/.cargo/registry/cache/github.com-1ecc6299db9ec823/serde-1.0.130.crate")) + // Non-matching version + assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.131/src/lib.rs")) + // Non-matching package + assert.False(t, rustFilter.Matches("/home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.14/src/lib.rs")) } func TestNormalizeRustCrateName(t *testing.T) {