/
githubmirror
/
trivy
Обзор
Документация
Войти
/
githubmirror
/
trivy
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
pkg/notification/notice_test.go
479 строк
15 KB
Owen Rumney
fix(cli): add some values to the telemetry call (#9056)
27 июн 2025, 10:14
Не верифицирован
27 июн 2025, 10:14
fd2bc91
Код
Авторство
О чём код?
package notification import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/flag" ) func TestPrintNotices(t *testing.T) { tests := []struct { name string skipVersionCheck bool quiet bool disableTelemetry bool currentVersion string latestVersion string announcements []announcement responseExpected bool expectedOutput string }{ { name: "New version with no announcements", currentVersion: "0.58.0", latestVersion: "0.60.0", responseExpected: true, expectedOutput: "\n📣 \x1b[34mNotices:\x1b[0m\n - Version 0.60.0 of Trivy is now available, current version is 0.58.0\n\nTo suppress version checks, run Trivy scans with the --skip-version-check flag\n\n", }, { name: "New version available but includes a prefixed version number", currentVersion: "0.58.0", latestVersion: "v0.60.0", responseExpected: true, expectedOutput: "\n📣 \x1b[34mNotices:\x1b[0m\n - Version 0.60.0 of Trivy is now available, current version is 0.58.0\n\nTo suppress version checks, run Trivy scans with the --skip-version-check flag\n\n", }, { name: "new version available but --quiet mode enabled", quiet: true, currentVersion: "0.58.0", latestVersion: "0.60.0", responseExpected: false, expectedOutput: "", }, { name: "new version available but --skip-version-check mode enabled", skipVersionCheck: true, currentVersion: "0.58.0", latestVersion: "0.60.0", responseExpected: false, expectedOutput: "", }, { name: "New version with announcements", currentVersion: "0.58.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), Announcement: "There are some amazing things happening right now!", }, }, responseExpected: true, expectedOutput: "\n📣 \x1b[34mNotices:\x1b[0m\n - There are some amazing things happening right now!\n - Version 0.60.0 of Trivy is now available, current version is 0.58.0\n\nTo suppress version checks, run Trivy scans with the --skip-version-check flag\n\n", }, { name: "No new version with announcements", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), Announcement: "There are some amazing things happening right now!", }, }, responseExpected: true, expectedOutput: "\n📣 \x1b[34mNotices:\x1b[0m\n - There are some amazing things happening right now!\n\nTo suppress version checks, run Trivy scans with the --skip-version-check flag\n\n", }, { name: "No new version with announcements and zero time", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Time{}, ToDate: time.Time{}, Announcement: "There are some amazing things happening right now!", }, }, responseExpected: true, expectedOutput: "\n📣 \x1b[34mNotices:\x1b[0m\n - There are some amazing things happening right now!\n\nTo suppress version checks, run Trivy scans with the --skip-version-check flag\n\n", }, { name: "No new version with announcement that fails announcement version constraints", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), FromVersion: "0.61.0", Announcement: "There are some amazing things happening right now!", }, }, responseExpected: true, expectedOutput: "", }, { name: "No new version with announcement where current version is greater than to_version", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), ToVersion: "0.59.0", Announcement: "There are some amazing things happening right now!", }, }, responseExpected: true, expectedOutput: "", }, { name: "No new version with announcement that satisfies version constraint but outside date range", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Date(2024, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), FromVersion: "0.60.0", Announcement: "There are some amazing things happening right now!", }, }, responseExpected: true, expectedOutput: "", }, { name: "No new version with multiple announcements, one of which is valid", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{ { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), Announcement: "There are some amazing things happening right now!", }, { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), FromVersion: "0.61.0", Announcement: "This announcement should not be displayed", }, }, responseExpected: true, expectedOutput: "\n📣 \x1b[34mNotices:\x1b[0m\n - There are some amazing things happening right now!\n\nTo suppress version checks, run Trivy scans with the --skip-version-check flag\n\n", }, { name: "No new version with no announcements and quiet mode", quiet: true, currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{}, responseExpected: false, expectedOutput: "", }, { name: "No new version with no announcements", currentVersion: "0.60.0", latestVersion: "0.60.0", announcements: []announcement{}, responseExpected: true, expectedOutput: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { updates := newUpdatesServer(t, tt.latestVersion, tt.announcements) server := httptest.NewServer(http.HandlerFunc(updates.handler)) cliOpts := &flag.Options{ GlobalOptions: flag.GlobalOptions{ Quiet: tt.quiet, }, ScanOptions: flag.ScanOptions{ SkipVersionCheck: tt.skipVersionCheck, DisableTelemetry: tt.disableTelemetry, }, } v := NewVersionChecker("testCommand", cliOpts) v.updatesApi = server.URL v.currentVersion = tt.currentVersion v.RunUpdateCheck(t.Context()) require.Eventually(t, func() bool { return v.done }, time.Second*5, 500) require.Eventually(t, func() bool { return v.responseReceived == tt.responseExpected }, time.Second*5, 500) sb := bytes.NewBufferString("") v.PrintNotices(t.Context(), sb) assert.Equal(t, tt.expectedOutput, sb.String()) // check metrics are sent require.NotNil(t, updates.lastRequest) require.NotEmpty(t, updates.lastRequest.Header.Get("Trivy-Identifier")) }) } } func TestCheckForNotices(t *testing.T) { tests := []struct { name string skipVersionCheck bool disableTelemetry bool quiet bool currentVersion string expectedVersion string expectedAnnouncements []announcement expectNoMetrics bool }{ { name: "new version with no announcements", currentVersion: "0.58.0", expectedVersion: "0.60.0", }, { name: "new version with disabled metrics", disableTelemetry: true, currentVersion: "0.58.0", expectedVersion: "0.60.0", expectNoMetrics: true, }, { name: "new version and a new announcement", currentVersion: "0.58.0", expectedVersion: "0.60.0", expectedAnnouncements: []announcement{ { FromDate: time.Date(2025, 2, 2, 12, 0, 0, 0, time.UTC), ToDate: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC), Announcement: "There are some amazing things happening right now!", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { updates := newUpdatesServer(t, tt.expectedVersion, tt.expectedAnnouncements) server := httptest.NewServer(http.HandlerFunc(updates.handler)) defer server.Close() cliOpts := &flag.Options{ GlobalOptions: flag.GlobalOptions{ Quiet: tt.quiet, }, ScanOptions: flag.ScanOptions{ SkipVersionCheck: tt.skipVersionCheck, DisableTelemetry: tt.disableTelemetry, }, } v := NewVersionChecker("testCommand", cliOpts) v.updatesApi = server.URL v.RunUpdateCheck(t.Context()) require.Eventually(t, func() bool { return v.done }, time.Second*5, 500) require.Eventually(t, func() bool { return v.responseReceived }, time.Second*5, 500) latestVersion, err := v.LatestVersion() require.NoError(t, err) assert.Equal(t, tt.expectedVersion, latestVersion.String()) assert.ElementsMatch(t, tt.expectedAnnouncements, v.Announcements()) if tt.expectNoMetrics { require.NotNil(t, updates.lastRequest) assert.Empty(t, updates.lastRequest.Header.Get("Trivy-Identifier")) } else { require.NotNil(t, updates.lastRequest) assert.NotEmpty(t, updates.lastRequest.Header.Get("Trivy-Identifier")) } }) } } type updatesServer struct { t *testing.T lastRequest *http.Request expectedVersion string expectedAnnouncements []announcement } func newUpdatesServer(t *testing.T, expectedVersion string, expectedAnnouncements []announcement) *updatesServer { return &updatesServer{ t: t, expectedVersion: expectedVersion, expectedAnnouncements: expectedAnnouncements, } } func (u *updatesServer) handler(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.Header.Get("User-Agent"), "trivy") { w.WriteHeader(http.StatusForbidden) } u.lastRequest = r response := updateResponse{ Trivy: versionInfo{ LatestVersion: u.expectedVersion, LatestDate: flexibleTime{Time: time.Now()}, }, Announcements: u.expectedAnnouncements, } out, err := json.Marshal(response) if err != nil { u.t.Fail() } w.Write(out) } func TestFlexibleDate(t *testing.T) { tests := []struct { name string dateStr string expected time.Time }{ { name: "RFC3339 date format", dateStr: `"2023-10-01T12:00:00Z"`, expected: time.Date(2023, 10, 1, 12, 0, 0, 0, time.UTC), }, { name: "RFC1123 date format", dateStr: `"Sun, 01 Oct 2023 12:00:00 GMT"`, expected: time.Date(2023, 10, 1, 12, 0, 0, 0, time.UTC), }, { name: "RFC3339 date only format", dateStr: `"2023-10-01"`, expected: time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var ft flexibleTime err := json.Unmarshal([]byte(tt.dateStr), &ft) require.NoError(t, err) assert.Equal(t, tt.expected.Unix(), ft.Unix()) }) } } func TestCheckCommandHeaders(t *testing.T) { tests := []struct { name string command string commandArgs []string env map[string]string ignoreParseError bool expectedCommandHeader string expectedCommandArgsHeader string }{ { name: "image command with no flags", command: "image", commandArgs: []string{"nginx"}, expectedCommandHeader: "image", }, { name: "image command with flags", command: "image", commandArgs: []string{"--severity", "CRITICAL", "--scanners", "vuln,misconfig", "--pkg-types", "library", "nginx", "--include-dev-deps"}, expectedCommandHeader: "image", expectedCommandArgsHeader: "--include-dev-deps=true --pkg-types=library --severity=CRITICAL --scanners=vuln,misconfig", }, { name: "image command with multiple flags", command: "image", commandArgs: []string{"--severity", "MEDIUM", "-s", "CRITICAL", "--scanners", "misconfig", "nginx"}, expectedCommandHeader: "image", expectedCommandArgsHeader: "--severity=MEDIUM,CRITICAL --scanners=misconfig", }, { name: "filesystem command with flags", command: "fs", commandArgs: []string{"--severity=HIGH", "--vex", "repo", "--vuln-severity-source", "nvd,debian", "../trivy-ci-test"}, expectedCommandHeader: "fs", expectedCommandArgsHeader: "--severity=HIGH --vex=*** --vuln-severity-source=nvd,debian", }, { name: "filesystem command with flags including an invalid flag", command: "fs", commandArgs: []string{"--severity=HIGH", "--vex", "repo", "--vuln-severity-source", "nvd,debian", "--invalid-flag", "../trivy-ci-test"}, ignoreParseError: true, expectedCommandHeader: "fs", expectedCommandArgsHeader: "--severity=HIGH --vex=*** --vuln-severity-source=nvd,debian", }, { name: "filesystem with environment variables", command: "fs", commandArgs: []string{"--severity", "HIGH", "--vex", "repo", "/home/user/code"}, env: map[string]string{ "TRIVY_SCANNERS": "secret,misconfig", }, expectedCommandHeader: "fs", expectedCommandArgsHeader: "--severity=HIGH --scanners=secret,misconfig --vex=***", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { updates := newUpdatesServer(t, "0.60.0", nil) server := httptest.NewServer(http.HandlerFunc(updates.handler)) defer server.Close() for key, value := range tt.env { t.Setenv(key, value) } // clean up the env defer func() { server.Close() }() opts := getOptionsForArgs(t, tt.commandArgs, tt.ignoreParseError) v := NewVersionChecker(tt.command, opts) v.updatesApi = server.URL v.RunUpdateCheck(t.Context()) require.Eventually(t, func() bool { return v.done }, time.Second*5, 500) require.NotNil(t, updates.lastRequest) assert.Equal(t, tt.expectedCommandHeader, updates.lastRequest.Header.Get("Trivy-Command")) assert.Equal(t, tt.expectedCommandArgsHeader, updates.lastRequest.Header.Get("Trivy-Flags")) }) } } // getOptionsForArgs uses a basic command to parse the flags so we can generate // an options object from it func getOptionsForArgs(t *testing.T, commandArgs []string, ignoreParseError bool) *flag.Options { flags := flag.Flags{ flag.NewGlobalFlagGroup(), flag.NewImageFlagGroup(), flag.NewMisconfFlagGroup(), flag.NewPackageFlagGroup(), flag.NewReportFlagGroup(), flag.NewScanFlagGroup(), flag.NewVulnerabilityFlagGroup(), } // simple command to facilitate flag parsing cmd := &cobra.Command{} flags.AddFlags(cmd) err := cmd.ParseFlags(commandArgs) if !ignoreParseError { require.NoError(t, err) } require.NoError(t, flags.Bind(cmd)) opts, err := flags.ToOptions(commandArgs) require.NoError(t, err) return &opts }