Files
trivy/pkg/dependency/parser/java/jar/parse_test.go
Teppei Fukuda 3eecfc6b6e refactor: unify Library and Package structs (#6633)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
2024-05-07 12:25:52 +00:00

329 lines
8.2 KiB
Go

package jar_test
import (
"encoding/json"
"github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar/sonatype"
"net/http"
"net/http/httptest"
"os"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)
var (
// cd testdata/testimage/maven && docker build -t test .
// docker run --rm --name test -it test bash
// mvn dependency:list
// mvn dependency:tree -Dscope=compile -Dscope=runtime | awk '/:tree/,/BUILD SUCCESS/' | awk 'NR > 1 { print }' | head -n -2 | awk '{print $NF}' | awk -F":" '{printf("{\""$1":"$2"\", \""$4 "\", \"\"},\n")}'
// paths filled in manually
wantMaven = []ftypes.Package{
{
Name: "com.example:web-app",
Version: "1.0-SNAPSHOT",
FilePath: "testdata/maven.war",
},
{
Name: "com.fasterxml.jackson.core:jackson-databind",
Version: "2.9.10.6",
FilePath: "testdata/maven.war/WEB-INF/lib/jackson-databind-2.9.10.6.jar",
},
{
Name: "com.fasterxml.jackson.core:jackson-annotations",
Version: "2.9.10",
FilePath: "testdata/maven.war/WEB-INF/lib/jackson-annotations-2.9.10.jar",
},
{
Name: "com.fasterxml.jackson.core:jackson-core",
Version: "2.9.10",
FilePath: "testdata/maven.war/WEB-INF/lib/jackson-core-2.9.10.jar",
},
{
Name: "com.cronutils:cron-utils",
Version: "9.1.2",
FilePath: "testdata/maven.war/WEB-INF/lib/cron-utils-9.1.2.jar",
},
{
Name: "org.slf4j:slf4j-api",
Version: "1.7.30",
FilePath: "testdata/maven.war/WEB-INF/lib/slf4j-api-1.7.30.jar",
},
{
Name: "org.glassfish:javax.el",
Version: "3.0.0",
FilePath: "testdata/maven.war/WEB-INF/lib/javax.el-3.0.0.jar",
},
{
Name: "org.apache.commons:commons-lang3",
Version: "3.11",
FilePath: "testdata/maven.war/WEB-INF/lib/commons-lang3-3.11.jar",
},
}
// cd testdata/testimage/gradle && docker build -t test .
// docker run --rm --name test -it test bash
// gradle app:dependencies --configuration implementation | grep "[+\]---" | cut -d" " -f2 | awk -F":" '{printf("{\""$1":"$2"\", \""$3"\", \"\"},\n")}'
// paths filled in manually
wantGradle = []ftypes.Package{
{
Name: "commons-dbcp:commons-dbcp",
Version: "1.4",
FilePath: "testdata/gradle.war/WEB-INF/lib/commons-dbcp-1.4.jar",
},
{
Name: "commons-pool:commons-pool",
Version: "1.6",
FilePath: "testdata/gradle.war/WEB-INF/lib/commons-pool-1.6.jar",
},
{
Name: "log4j:log4j",
Version: "1.2.17",
FilePath: "testdata/gradle.war/WEB-INF/lib/log4j-1.2.17.jar",
},
{
Name: "org.apache.commons:commons-compress",
Version: "1.19",
FilePath: "testdata/gradle.war/WEB-INF/lib/commons-compress-1.19.jar",
},
}
// manually created
wantSHA1 = []ftypes.Package{
{
Name: "org.springframework:spring-core",
Version: "5.3.3",
FilePath: "testdata/test.jar",
},
}
// offline
wantOffline = []ftypes.Package{
{
Name: "org.springframework:Spring Framework",
Version: "2.5.6.SEC03",
FilePath: "testdata/test.jar",
},
}
// manually created
wantHeuristic = []ftypes.Package{
{
Name: "com.example:heuristic",
Version: "1.0.0-SNAPSHOT",
FilePath: "testdata/heuristic-1.0.0-SNAPSHOT.jar",
},
}
// manually created
wantFatjar = []ftypes.Package{
{
Name: "com.google.guava:failureaccess",
Version: "1.0.1",
FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar",
},
{
Name: "com.google.guava:guava",
Version: "29.0-jre",
FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar",
},
{
Name: "com.google.guava:listenablefuture",
Version: "9999.0-empty-to-avoid-conflict-with-guava",
FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar",
},
{
Name: "com.google.j2objc:j2objc-annotations",
Version: "1.3",
FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar",
},
{
Name: "org.apache.hadoop.thirdparty:hadoop-shaded-guava",
Version: "1.1.0-SNAPSHOT",
FilePath: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar",
},
}
// manually created
wantNestedJar = []ftypes.Package{
{
Name: "test:nested",
Version: "0.0.1",
FilePath: "testdata/nested.jar",
},
{
Name: "test:nested2",
Version: "0.0.2",
FilePath: "testdata/nested.jar/META-INF/jars/nested2.jar",
},
{
Name: "test:nested3",
Version: "0.0.3",
FilePath: "testdata/nested.jar/META-INF/jars/nested2.jar/META-INF/jars/nested3.jar",
},
}
// manually created
wantDuplicatesJar = []ftypes.Package{
{
Name: "io.quarkus.gizmo:gizmo",
Version: "1.1.1.Final",
FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar",
},
{
Name: "log4j:log4j",
Version: "1.2.16",
FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar/jars/log4j-1.2.16.jar",
},
{
Name: "log4j:log4j",
Version: "1.2.17",
FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar/jars/log4j-1.2.17.jar",
},
}
)
type apiResponse struct {
Response response `json:"response"`
}
type response struct {
NumFound int `json:"numFound"`
Docs []doc `json:"docs"`
}
type doc struct {
ID string `json:"id"`
GroupID string `json:"g"`
ArtifactID string `json:"a"`
Version string `json:"v"`
P string `json:"p"`
VersionCount int `json:versionCount`
}
func TestParse(t *testing.T) {
vectors := []struct {
name string
file string // Test input file
offline bool
want []ftypes.Package
}{
{
name: "maven",
file: "testdata/maven.war",
want: wantMaven,
},
{
name: "gradle",
file: "testdata/gradle.war",
want: wantGradle,
},
{
name: "nested jars",
file: "testdata/nested.jar",
want: wantNestedJar,
},
{
name: "sha1 search",
file: "testdata/test.jar",
want: wantSHA1,
},
{
name: "offline",
file: "testdata/test.jar",
offline: true,
want: wantOffline,
},
{
name: "artifactId search",
file: "testdata/heuristic-1.0.0-SNAPSHOT.jar",
want: wantHeuristic,
},
{
name: "fat jar",
file: "testdata/hadoop-shaded-guava-1.1.0-SNAPSHOT.jar",
want: wantFatjar,
},
{
name: "duplicate libraries",
file: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar",
want: wantDuplicatesJar,
},
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res := apiResponse{
Response: response{
NumFound: 1,
},
}
switch {
case strings.Contains(r.URL.Query().Get("q"), "springframework"):
res.Response.NumFound = 0
case strings.Contains(r.URL.Query().Get("q"), "c666f5bc47eb64ed3bbd13505a26f58be71f33f0"):
res.Response.Docs = []doc{
{
ID: "org.springframework.spring-core",
GroupID: "org.springframework",
ArtifactID: "spring-core",
Version: "5.3.3",
},
}
case strings.Contains(r.URL.Query().Get("q"), "Gizmo"):
res.Response.NumFound = 0
case strings.Contains(r.URL.Query().Get("q"), "85d30c06026afd9f5be26da3194d4698c447a904"):
res.Response.Docs = []doc{
{
ID: "io.quarkus.gizmo.gizmo",
GroupID: "io.quarkus.gizmo",
ArtifactID: "gizmo",
Version: "1.1.1.Final",
},
}
case strings.Contains(r.URL.Query().Get("q"), "heuristic"):
res.Response.Docs = []doc{
{
ID: "org.springframework.heuristic",
GroupID: "org.springframework",
ArtifactID: "heuristic",
VersionCount: 10,
},
{
ID: "com.example.heuristic",
GroupID: "com.example",
ArtifactID: "heuristic",
VersionCount: 100,
},
}
}
_ = json.NewEncoder(w).Encode(res)
}))
for _, v := range vectors {
t.Run(v.name, func(t *testing.T) {
f, err := os.Open(v.file)
require.NoError(t, err)
stat, err := f.Stat()
require.NoError(t, err)
c := sonatype.New(sonatype.WithURL(ts.URL), sonatype.WithHTTPClient(ts.Client()))
p := jar.NewParser(c, jar.WithFilePath(v.file), jar.WithOffline(v.offline), jar.WithSize(stat.Size()))
got, _, err := p.Parse(f)
require.NoError(t, err)
sort.Sort(ftypes.Packages(got))
sort.Sort(ftypes.Packages(v.want))
assert.Equal(t, v.want, got)
})
}
}