From 06e07f55cd5305f76be7af3bb5cc2bd0d3115f9c Mon Sep 17 00:00:00 2001 From: Daniel Martins Date: Mon, 3 Aug 2026 16:44:41 +0300 Subject: [PATCH 1/3] refactor: get the file filter from the invocation context --- go.mod | 2 +- go.sum | 4 +- .../commands/code_workflow/native_workflow.go | 33 +++-- .../code_workflow/native_workflow_test.go | 123 +++++++++++++----- pkg/code/code_test.go | 64 ++++----- 5 files changed, 150 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index 379439d..5d43f63 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/puzpuzpuz/xsync v1.5.2 github.com/rs/zerolog v1.34.0 github.com/snyk/error-catalog-golang-public v0.0.0-20260205094614-116c03822905 - github.com/snyk/go-application-framework v0.9.0 + github.com/snyk/go-application-framework v0.10.0 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.55.0 diff --git a/go.sum b/go.sum index 76835bb..3cb8d88 100644 --- a/go.sum +++ b/go.sum @@ -246,8 +246,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/snyk/error-catalog-golang-public v0.0.0-20260205094614-116c03822905 h1:pUe6iOWHEOFY0t4u4ssXeTqpMmZBu1xq06VBFI9zUik= github.com/snyk/error-catalog-golang-public v0.0.0-20260205094614-116c03822905/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= -github.com/snyk/go-application-framework v0.9.0 h1:el8tH/5gHLXFVzi63ldQeUxzbnACDvrpP2yLvoGfLQ0= -github.com/snyk/go-application-framework v0.9.0/go.mod h1:0YC7xCETnFTdz6rq8OQPL0aWekMLOkBQu1FE4/cReMA= +github.com/snyk/go-application-framework v0.10.0 h1:fXYMHpB14T3MYSzR94dyzpMtTiALzCTYvlZdVnsHrzs= +github.com/snyk/go-application-framework v0.10.0/go.mod h1:9GV/CTAhM8PT9MbxwYt/Za7tKDtw/Wuq6SyCu1XFzvk= github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530 h1:s9PHNkL6ueYRiAKNfd8OVxlUOqU3qY0VDbgCD1f6WQY= github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= diff --git a/internal/commands/code_workflow/native_workflow.go b/internal/commands/code_workflow/native_workflow.go index 4893df8..ddffc10 100644 --- a/internal/commands/code_workflow/native_workflow.go +++ b/internal/commands/code_workflow/native_workflow.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "net/http" "os" "path" "path/filepath" @@ -19,7 +18,6 @@ import ( "github.com/snyk/code-client-go/sarif" "github.com/snyk/code-client-go/scan" "github.com/snyk/error-catalog-golang-public/code" - "github.com/snyk/go-application-framework/pkg/analytics" "github.com/snyk/go-application-framework/pkg/configuration" "github.com/snyk/go-application-framework/pkg/instrumentation" "github.com/snyk/go-application-framework/pkg/local_workflows/content_type" @@ -59,7 +57,7 @@ const ( noReport reportType = "no_report" ) -type OptionalAnalysisFunctions func(context.Context, string, func() *http.Client, *zerolog.Logger, configuration.Configuration, ui.UserInterface, analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) +type OptionalAnalysisFunctions func(workflow.InvocationContext, string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) type ProgressTrackerFactory struct { userInterface ui.UserInterface @@ -130,7 +128,7 @@ func EntryPointNative(invocationCtx workflow.InvocationContext, opts ...Optional analyzeFnc = opts[0] } - result, bundleHash, resultMetaData, err := analyzeFnc(invocationCtx.Context(), path, invocationCtx.GetNetworkAccess().GetHttpClient, logger, config, invocationCtx.GetUserInterface(), invocationCtx.GetAnalytics()) + result, bundleHash, resultMetaData, err := analyzeFnc(invocationCtx, path) isNoFilesErr := bundle.IsNoFilesError(err) if err != nil && !isNoFilesErr { return nil, err @@ -199,7 +197,14 @@ func EntryPointNative(invocationCtx workflow.InvocationContext, opts ...Optional } // default function that uses the code-client-go library -func defaultAnalyzeFunction(ctx context.Context, path string, httpClientFunc func() *http.Client, logger *zerolog.Logger, config configuration.Configuration, userInterface ui.UserInterface, analyticsClient analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { +func defaultAnalyzeFunction(invocationCtx workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { + ctx := invocationCtx.Context() + config := invocationCtx.GetConfiguration() + logger := invocationCtx.GetEnhancedLogger() + httpClientFunc := invocationCtx.GetNetworkAccess().GetHttpClient + userInterface := invocationCtx.GetUserInterface() + analyticsClient := invocationCtx.GetAnalytics() + var result *sarif.SarifResponse var resultMetaData *scan.ResultMetaData requestId, err := uuid.GenerateUUID() @@ -285,7 +290,7 @@ func defaultAnalyzeFunction(ctx context.Context, path string, httpClientFunc fun ) } - target, files, err := determineAnalyzeInput(path, config, logger) + target, files, err := determineAnalyzeInput(invocationCtx, path) if err != nil { return nil, "", nil, err } @@ -347,7 +352,10 @@ func analyzeWithLegacyEngine( return result, bundleHash, nil, err } -func determineAnalyzeInput(path string, config configuration.Configuration, logger *zerolog.Logger) (scan.Target, <-chan string, error) { +func determineAnalyzeInput(invocationCtx workflow.InvocationContext, path string) (scan.Target, <-chan string, error) { + config := invocationCtx.GetConfiguration() + logger := invocationCtx.GetEnhancedLogger() + var files <-chan string pathIsDirectory := false @@ -377,7 +385,7 @@ func determineAnalyzeInput(path string, config configuration.Configuration, logg logger.Warn().Err(err).Msg("could not determine repository URL; consistent-ignores and SCM association may not be applied. Pass --remote-repo-url to set it explicitly") } - files, err = getFilesForPath(path, logger, config) + files, err = getFilesForPath(invocationCtx, path) if err != nil { return nil, nil, err } @@ -385,9 +393,12 @@ func determineAnalyzeInput(path string, config configuration.Configuration, logg return target, files, nil } -// Return a channel that notifies each file in the path that doesn't match the filter rules -func getFilesForPath(path string, logger *zerolog.Logger, config configuration.Configuration) (<-chan string, error) { - filter := utils.NewFileFilterFromConfig(path, logger, config, utils.WithThreadNumber(config.GetInt(configuration.MAX_THREADS))) +// Return a channel that notifies each file in the path that doesn't match the filter rules. +// The file filter comes from the invocation context, so it is already wired to the invocation's +// configuration and logger. +func getFilesForPath(invocationCtx workflow.InvocationContext, path string) (<-chan string, error) { + maxThreads := invocationCtx.GetConfiguration().GetInt(configuration.MAX_THREADS) + filter := invocationCtx.GetFileFilter(path, utils.WithThreadNumber(maxThreads)) rules, err := filter.GetRules([]string{".gitignore", ".dcignore", ".snyk"}) if err != nil { return nil, err diff --git a/internal/commands/code_workflow/native_workflow_test.go b/internal/commands/code_workflow/native_workflow_test.go index c21f4aa..64f4cb6 100644 --- a/internal/commands/code_workflow/native_workflow_test.go +++ b/internal/commands/code_workflow/native_workflow_test.go @@ -12,6 +12,7 @@ import ( "sync" "testing" + "github.com/golang/mock/gomock" "github.com/google/uuid" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" @@ -24,10 +25,50 @@ import ( "github.com/snyk/code-client-go/scan" "github.com/snyk/go-application-framework/pkg/analytics" "github.com/snyk/go-application-framework/pkg/configuration" + gafmocks "github.com/snyk/go-application-framework/pkg/mocks" "github.com/snyk/go-application-framework/pkg/networking" "github.com/snyk/go-application-framework/pkg/ui" + "github.com/snyk/go-application-framework/pkg/utils" + "github.com/snyk/go-application-framework/pkg/workflow" ) +// testInvocationContext builds the InvocationContext the analyze function pulls its dependencies +// out of. GetFileFilter mirrors the framework implementation, so file filtering is exercised for +// real rather than stubbed. +func testInvocationContext( + t *testing.T, + config configuration.Configuration, + logger *zerolog.Logger, + httpClientFunc func() *http.Client, + userInterface ui.UserInterface, + analyticsClient analytics.Analytics, +) workflow.InvocationContext { + t.Helper() + ctrl := gomock.NewController(t) + + networkAccess := gafmocks.NewMockNetworkAccess(ctrl) + networkAccess.EXPECT().GetHttpClient().DoAndReturn(func() *http.Client { + if httpClientFunc == nil { + return nil + } + return httpClientFunc() + }).AnyTimes() + + ictx := gafmocks.NewMockInvocationContext(ctrl) + ictx.EXPECT().Context().Return(context.Background()).AnyTimes() + ictx.EXPECT().GetConfiguration().Return(config).AnyTimes() + ictx.EXPECT().GetEnhancedLogger().Return(logger).AnyTimes() + ictx.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() + ictx.EXPECT().GetUserInterface().Return(userInterface).AnyTimes() + ictx.EXPECT().GetAnalytics().Return(analyticsClient).AnyTimes() + ictx.EXPECT().GetFileFilter(gomock.Any(), gomock.Any()).DoAndReturn( + func(path string, options ...utils.FileFilterOption) *utils.FileFilter { + return utils.NewFileFilter(path, logger, append([]utils.FileFilterOption{utils.WithConfig(config)}, options...)...) + }).AnyTimes() + + return ictx +} + func Test_defaultAnalyzeFunction_reportNotSupportedWithSCLE(t *testing.T) { logger := zerolog.Nop() @@ -37,7 +78,9 @@ func Test_defaultAnalyzeFunction_reportNotSupportedWithSCLE(t *testing.T) { config.Set(ConfigurationProjectName, "my-project") // makes report mode localCode config.Set(ConfigurationSlceEnabled, true) - _, _, _, err := defaultAnalyzeFunction(context.Background(), t.TempDir(), nil, &logger, config, nil, analytics.New()) + ictx := testInvocationContext(t, config, &logger, nil, nil, analytics.New()) + + _, _, _, err := defaultAnalyzeFunction(ictx, t.TempDir()) assert.Error(t, err) assert.Contains(t, err.Error(), "Snyk Code Local Engine") @@ -119,15 +162,9 @@ func Test_defaultAnalyzeFunction_usesLocalEngineLegacyEndpoints(t *testing.T) { analyticsClient := analytics.New() - result, actualBundleHash, resultMetaData, err := defaultAnalyzeFunction( - context.Background(), - path, - func() *http.Client { return server.Client() }, - &logger, - config, - ui.DefaultUi(), - analyticsClient, - ) + ictx := testInvocationContext(t, config, &logger, func() *http.Client { return server.Client() }, ui.DefaultUi(), analyticsClient) + + result, actualBundleHash, resultMetaData, err := defaultAnalyzeFunction(ictx, path) require.NoError(t, err) require.NotNil(t, result) @@ -204,15 +241,9 @@ func Test_defaultAnalyzeFunction_usesFileUploadApi(t *testing.T) { analyticsClient := analytics.New() - result, _, _, err := defaultAnalyzeFunction( - context.Background(), - path, - func() *http.Client { return server.Client() }, - &logger, - config, - ui.DefaultUi(), - analyticsClient, - ) + ictx := testInvocationContext(t, config, &logger, func() *http.Client { return server.Client() }, ui.DefaultUi(), analyticsClient) + + result, _, _, err := defaultAnalyzeFunction(ictx, path) require.NoError(t, err) assert.Nil(t, result) @@ -286,15 +317,9 @@ func Test_defaultAnalyzeFunction_recordsFailedFileUploadApiUpload(t *testing.T) analyticsClient := analytics.New() - result, _, _, err := defaultAnalyzeFunction( - context.Background(), - path, - func() *http.Client { return server.Client() }, - &logger, - config, - ui.DefaultUi(), - analyticsClient, - ) + ictx := testInvocationContext(t, config, &logger, func() *http.Client { return server.Client() }, ui.DefaultUi(), analyticsClient) + + result, _, _, err := defaultAnalyzeFunction(ictx, path) require.Error(t, err) assert.Contains(t, err.Error(), "error uploading files") @@ -405,7 +430,9 @@ func Test_determineAnalyzeInput(t *testing.T) { t.Run("given a folder", func(t *testing.T) { count := 0 - target, files, err := determineAnalyzeInput(path, config, &logger) + ictx := testInvocationContext(t, config, &logger, nil, nil, analytics.New()) + + target, files, err := determineAnalyzeInput(ictx, path) assert.NoError(t, err) assert.NotNil(t, target) assert.NotNil(t, files) @@ -422,7 +449,9 @@ func Test_determineAnalyzeInput(t *testing.T) { t.Run("given a file", func(t *testing.T) { count := 0 - target, files, err := determineAnalyzeInput(filenames[1], config, &logger) + ictx := testInvocationContext(t, config, &logger, nil, nil, analytics.New()) + + target, files, err := determineAnalyzeInput(ictx, filenames[1]) assert.NoError(t, err) assert.NotNil(t, target) assert.NotNil(t, files) @@ -459,3 +488,37 @@ func Test_TrackUsage(t *testing.T) { assert.True(t, trackUsageCalled) } + +// Test_determineAnalyzeInput_usesInvocationContextFileFilter pins that filtering is obtained from +// the invocation context. Constructing a FileFilter directly would still compile and still filter, +// but would silently drop every configuration-gated behavior (the ignore-rule metacharacter fix, +// tracked-file handling), so nothing else in the suite would fail. +func Test_determineAnalyzeInput_usesInvocationContextFileFilter(t *testing.T) { + logger := zerolog.Nop() + config := configuration.NewWithOpts() + config.Set(configuration.MAX_THREADS, 1) + + dir := t.TempDir() + writeFile(t, filepath.Join(dir, "app.js")) + + ctrl := gomock.NewController(t) + ictx := gafmocks.NewMockInvocationContext(ctrl) + ictx.EXPECT().Context().Return(context.Background()).AnyTimes() + ictx.EXPECT().GetConfiguration().Return(config).AnyTimes() + ictx.EXPECT().GetEnhancedLogger().Return(&logger).AnyTimes() + + // exactly once, rooted at the scanned directory + ictx.EXPECT().GetFileFilter(dir, gomock.Any()).Times(1).DoAndReturn( + func(path string, options ...utils.FileFilterOption) *utils.FileFilter { + return utils.NewFileFilter(path, &logger, options...) + }) + + _, files, err := determineAnalyzeInput(ictx, dir) + require.NoError(t, err) + + var found []string + for f := range files { + found = append(found, filepath.Base(f)) + } + assert.Equal(t, []string{"app.js"}, found) +} diff --git a/pkg/code/code_test.go b/pkg/code/code_test.go index b321580..6d97ba0 100644 --- a/pkg/code/code_test.go +++ b/pkg/code/code_test.go @@ -181,15 +181,15 @@ func Test_Code_nativeImplementation_happyPath(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() + invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()) + invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() - analysisFunc := func(_ context.Context, path string, _ func() *http.Client, _ *zerolog.Logger, _ configuration.Configuration, _ ui.UserInterface, _ analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { + analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { assert.Equal(t, expectedPath, path) suppressions := []sarif.Suppression{ { @@ -288,8 +288,8 @@ func Test_Code_entrypoint_recordsSCLEInAnalytics(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) - invocationContext.EXPECT().GetAnalytics().Return(analyticsClient) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() + invocationContext.EXPECT().GetAnalytics().Return(analyticsClient).AnyTimes() // The legacy path dispatches to the legacycli workflow, which is not // registered here, so the invocation fails after analytics are recorded. invocationContext.EXPECT().GetEngine().Return(workflow.NewWorkFlowEngine(config)) @@ -312,15 +312,15 @@ func Test_Code_nativeImplementation_analysisFails(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() + invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()) + invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() - analysisFunc := func(context.Context, string, func() *http.Client, *zerolog.Logger, configuration.Configuration, ui.UserInterface, analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { + analysisFunc := func(workflow.InvocationContext, string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { return nil, "", nil, fmt.Errorf("something went wrong") } @@ -335,15 +335,15 @@ func Test_Code_nativeImplementation_analysisNil(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() + invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()) + invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() - analysisFunc := func(_ context.Context, path string, _ func() *http.Client, _ *zerolog.Logger, _ configuration.Configuration, _ ui.UserInterface, _ analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { + analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { return nil, "", nil, nil } @@ -375,15 +375,15 @@ func Test_Code_nativeImplementation_analysisEmpty(t *testing.T) { t.Run("returns UnsupportedProjectError when no supported files", func(t *testing.T) { invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() + invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()) + invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() - analysisFunc := func(_ context.Context, path string, _ func() *http.Client, _ *zerolog.Logger, _ configuration.Configuration, _ ui.UserInterface, _ analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { + analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { response := &sarif.SarifResponse{ Sarif: sarif.SarifDocument{ Runs: []sarif.Run{ @@ -420,15 +420,15 @@ func Test_Code_nativeImplementation_analysisEmpty(t *testing.T) { t.Run("returns no error when supported files fail to parse", func(t *testing.T) { invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() + invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()) + invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() - analysisFunc := func(_ context.Context, path string, _ func() *http.Client, _ *zerolog.Logger, _ configuration.Configuration, _ ui.UserInterface, _ analytics.Analytics) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { + analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { response := &sarif.SarifResponse{ Sarif: sarif.SarifDocument{ Runs: []sarif.Run{ From 769a8aadd0efd23f802418f5152b0fa48d3eb04b Mon Sep 17 00:00:00 2001 From: Daniel Martins Date: Tue, 4 Aug 2026 15:10:36 +0300 Subject: [PATCH 2/3] test: drop unused mock stubs, restore exact expectations --- .../code_workflow/native_workflow_test.go | 28 +++++++++ pkg/code/code_test.go | 61 +++++++------------ 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/internal/commands/code_workflow/native_workflow_test.go b/internal/commands/code_workflow/native_workflow_test.go index 64f4cb6..bc9354a 100644 --- a/internal/commands/code_workflow/native_workflow_test.go +++ b/internal/commands/code_workflow/native_workflow_test.go @@ -522,3 +522,31 @@ func Test_determineAnalyzeInput_usesInvocationContextFileFilter(t *testing.T) { } assert.Equal(t, []string{"app.js"}, found) } + +// Test_defaultAnalyzeFunction_readsDependenciesFromInvocationContext pins that the analysis pulls +// each dependency from the invocation context. EntryPointNative used to extract these and pass them +// in, so this assertion lives here now that the analysis does it itself. +func Test_defaultAnalyzeFunction_readsDependenciesFromInvocationContext(t *testing.T) { + logger := zerolog.Nop() + config := configuration.NewWithOpts() + config.Set(ConfigurationReportFlag, true) + config.Set(ConfigurationProjectName, "my-project") + config.Set(ConfigurationSlceEnabled, true) // errors out after the dependencies are read + + ctrl := gomock.NewController(t) + networkAccess := gafmocks.NewMockNetworkAccess(ctrl) + networkAccess.EXPECT().GetHttpClient().Return(nil).AnyTimes() + + ictx := gafmocks.NewMockInvocationContext(ctrl) + ictx.EXPECT().Context().Return(context.Background()).Times(1) + ictx.EXPECT().GetNetworkAccess().Return(networkAccess).Times(1) + ictx.EXPECT().GetUserInterface().Return(ui.DefaultUi()).Times(1) + ictx.EXPECT().GetAnalytics().Return(analytics.New()).Times(1) + ictx.EXPECT().GetConfiguration().Return(config).AnyTimes() + ictx.EXPECT().GetEnhancedLogger().Return(&logger).AnyTimes() + + result, bundleHash, _, err := defaultAnalyzeFunction(ictx, t.TempDir()) + require.Error(t, err) + assert.Nil(t, result) + assert.Empty(t, bundleHash) +} diff --git a/pkg/code/code_test.go b/pkg/code/code_test.go index 6d97ba0..c32dc65 100644 --- a/pkg/code/code_test.go +++ b/pkg/code/code_test.go @@ -2,7 +2,6 @@ package code import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -31,7 +30,6 @@ import ( testutils "github.com/snyk/go-application-framework/pkg/local_workflows/test_utils" "github.com/snyk/go-application-framework/pkg/mocks" "github.com/snyk/go-application-framework/pkg/networking" - "github.com/snyk/go-application-framework/pkg/ui" "github.com/snyk/go-application-framework/pkg/workflow" ) @@ -181,13 +179,10 @@ func Test_Code_nativeImplementation_happyPath(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() - invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() + invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { assert.Equal(t, expectedPath, path) @@ -288,8 +283,8 @@ func Test_Code_entrypoint_recordsSCLEInAnalytics(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analyticsClient).AnyTimes() + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) + invocationContext.EXPECT().GetAnalytics().Return(analyticsClient) // The legacy path dispatches to the legacycli workflow, which is not // registered here, so the invocation fails after analytics are recorded. invocationContext.EXPECT().GetEngine().Return(workflow.NewWorkFlowEngine(config)) @@ -312,13 +307,10 @@ func Test_Code_nativeImplementation_analysisFails(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() - invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() + invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) analysisFunc := func(workflow.InvocationContext, string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { return nil, "", nil, fmt.Errorf("something went wrong") @@ -335,13 +327,10 @@ func Test_Code_nativeImplementation_analysisNil(t *testing.T) { mockController := gomock.NewController(t) invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() - invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() + invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { return nil, "", nil, nil @@ -375,13 +364,10 @@ func Test_Code_nativeImplementation_analysisEmpty(t *testing.T) { t.Run("returns UnsupportedProjectError when no supported files", func(t *testing.T) { invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() - invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() + invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { response := &sarif.SarifResponse{ @@ -420,13 +406,10 @@ func Test_Code_nativeImplementation_analysisEmpty(t *testing.T) { t.Run("returns no error when supported files fail to parse", func(t *testing.T) { invocationContext := mocks.NewMockInvocationContext(mockController) - invocationContext.EXPECT().GetConfiguration().Return(config).AnyTimes() - invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess).AnyTimes() - invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}).AnyTimes() - invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")).AnyTimes() - invocationContext.EXPECT().GetUserInterface().Return(ui.DefaultUi()).AnyTimes() - invocationContext.EXPECT().Context().Return(context.Background()).AnyTimes() - invocationContext.EXPECT().GetAnalytics().Return(analytics.New()).AnyTimes() + invocationContext.EXPECT().GetConfiguration().Return(config) + invocationContext.EXPECT().GetNetworkAccess().Return(networkAccess) + invocationContext.EXPECT().GetEnhancedLogger().Return(&zerolog.Logger{}) + invocationContext.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("code")) analysisFunc := func(_ workflow.InvocationContext, path string) (*sarif.SarifResponse, string, *scan.ResultMetaData, error) { response := &sarif.SarifResponse{ From 8687dc9dfa6a493ba18f8c4316113bfaf736199c Mon Sep 17 00:00:00 2001 From: Daniel Martins Date: Tue, 4 Aug 2026 15:31:11 +0300 Subject: [PATCH 3/3] chore: bump GAF to 0.11.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5d43f63..0def3b2 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/puzpuzpuz/xsync v1.5.2 github.com/rs/zerolog v1.34.0 github.com/snyk/error-catalog-golang-public v0.0.0-20260205094614-116c03822905 - github.com/snyk/go-application-framework v0.10.0 + github.com/snyk/go-application-framework v0.11.0 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.55.0 diff --git a/go.sum b/go.sum index 3cb8d88..9655aad 100644 --- a/go.sum +++ b/go.sum @@ -246,8 +246,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/snyk/error-catalog-golang-public v0.0.0-20260205094614-116c03822905 h1:pUe6iOWHEOFY0t4u4ssXeTqpMmZBu1xq06VBFI9zUik= github.com/snyk/error-catalog-golang-public v0.0.0-20260205094614-116c03822905/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= -github.com/snyk/go-application-framework v0.10.0 h1:fXYMHpB14T3MYSzR94dyzpMtTiALzCTYvlZdVnsHrzs= -github.com/snyk/go-application-framework v0.10.0/go.mod h1:9GV/CTAhM8PT9MbxwYt/Za7tKDtw/Wuq6SyCu1XFzvk= +github.com/snyk/go-application-framework v0.11.0 h1:lOZhYO8JmzMoZKQbCb/jHMnrB/N3TvX8Z6oE/L9OQ7o= +github.com/snyk/go-application-framework v0.11.0/go.mod h1:9GV/CTAhM8PT9MbxwYt/Za7tKDtw/Wuq6SyCu1XFzvk= github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530 h1:s9PHNkL6ueYRiAKNfd8OVxlUOqU3qY0VDbgCD1f6WQY= github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=