Skip to content

Commit 0ad5052

Browse files
authored
feat: uv mock integration (#105)
1 parent 34de797 commit 0ad5052

File tree

8 files changed

+232
-9
lines changed

8 files changed

+232
-9
lines changed

internal/commands/ostest/routing.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/snyk/error-catalog-golang-public/opensource/ecosystems"
99

1010
"github.com/snyk/cli-extension-os-flows/internal/commands/cmdctx"
11+
"github.com/snyk/cli-extension-os-flows/internal/constants"
1112
"github.com/snyk/cli-extension-os-flows/internal/errors"
1213
"github.com/snyk/cli-extension-os-flows/internal/flags"
1314
"github.com/snyk/cli-extension-os-flows/internal/settings"
@@ -117,7 +118,8 @@ func RouteToFlow(ctx context.Context, orgUUID uuid.UUID, sc settings.Client) (Fl
117118
sbomReachabilityTest := reachability && sbom != ""
118119
reachabilityFilter := cfg.GetString(flags.FlagReachabilityFilter)
119120

120-
forceLegacyTest := cfg.GetBool(ForceLegacyCLIEnvVar)
121+
experimentalUvSupport := cfg.GetBool(constants.EnableExperimentalUvSupportEnvVar)
122+
forceLegacyTest := cfg.GetBool(constants.ForceLegacyCLIEnvVar)
121123
requiresLegacy := cfg.GetBool(flags.FlagPrintGraph) ||
122124
cfg.GetBool(flags.FlagPrintDeps) ||
123125
cfg.GetBool(flags.FlagPrintDepPaths) ||
@@ -147,12 +149,13 @@ func RouteToFlow(ctx context.Context, orgUUID uuid.UUID, sc settings.Client) (Fl
147149
}
148150

149151
switch {
150-
case forceLegacyTest || requiresLegacy || (!riskScoreTest && !reachability && sbom == ""):
152+
case forceLegacyTest || requiresLegacy || (!riskScoreTest && !reachability && sbom == "" && !experimentalUvSupport):
151153
logger.Debug().Msgf(
152-
"Using legacy flow. Legacy CLI Env var: %t. SBOM Reachability Test: %t. Risk Score Test: %t.",
154+
"Using legacy flow. Legacy CLI Env var: %t. SBOM Reachability Test: %t. Risk Score Test: %t. Experimental uv Support: %t.",
153155
forceLegacyTest,
154156
sbomReachabilityTest,
155157
riskScoreTest,
158+
experimentalUvSupport,
156159
)
157160
return LegacyFlow, nil
158161
case sbomReachabilityTest:

internal/commands/ostest/routing_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/snyk/cli-extension-os-flows/internal/commands/cmdctx"
1414
"github.com/snyk/cli-extension-os-flows/internal/commands/ostest"
15+
"github.com/snyk/cli-extension-os-flows/internal/constants"
1516
"github.com/snyk/cli-extension-os-flows/internal/flags"
1617
"github.com/snyk/cli-extension-os-flows/internal/settings"
1718
)
@@ -80,7 +81,7 @@ func Test_RouteToFlow_LegacyCLIFlow(t *testing.T) {
8081
t.Run("when env var is set", func(t *testing.T) {
8182
t.Parallel()
8283
cfg := defaultConfig.Clone()
83-
cfg.Set(ostest.ForceLegacyCLIEnvVar, true)
84+
cfg.Set(constants.ForceLegacyCLIEnvVar, true)
8485

8586
ctx := t.Context()
8687
ctx = cmdctx.WithConfig(ctx, cfg)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package ostest_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"testing"
8+
9+
"github.com/golang/mock/gomock"
10+
"github.com/rs/zerolog"
11+
gafclientmocks "github.com/snyk/go-application-framework/pkg/apiclients/mocks"
12+
"github.com/snyk/go-application-framework/pkg/apiclients/testapi"
13+
"github.com/snyk/go-application-framework/pkg/configuration"
14+
gafmocks "github.com/snyk/go-application-framework/pkg/mocks"
15+
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
16+
"github.com/snyk/go-application-framework/pkg/workflow"
17+
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
19+
20+
"github.com/snyk/cli-extension-os-flows/internal/commands/cmdctx"
21+
"github.com/snyk/cli-extension-os-flows/internal/commands/ostest"
22+
common "github.com/snyk/cli-extension-os-flows/internal/common"
23+
"github.com/snyk/cli-extension-os-flows/internal/constants"
24+
"github.com/snyk/cli-extension-os-flows/internal/errors"
25+
"github.com/snyk/cli-extension-os-flows/internal/flags"
26+
)
27+
28+
// TestSBOMResolutionIntegration_DepGraphsPassedToUnifiedTestAPI verifies that when the UV test flow
29+
// is enabled, dep-graphs from SBOM resolution are properly passed to the Unified Test API.
30+
func TestSBOMResolutionIntegration_DepGraphsPassedToUnifiedTestAPI(t *testing.T) {
31+
ctrl := gomock.NewController(t)
32+
defer ctrl.Finish()
33+
34+
ctx := context.Background()
35+
mockIctx, mockTestClient, mockDepGraph, orgID, cfg, testLogger := setupSBOMResolutionIntegrationTest(t, ctrl)
36+
37+
// Set up context with dependencies
38+
testErrFactory := errors.NewErrorFactory(testLogger)
39+
nopProgressBar := &NopProgressBar{}
40+
ctx = cmdctx.WithIctx(ctx, mockIctx)
41+
ctx = cmdctx.WithConfig(ctx, cfg)
42+
ctx = cmdctx.WithLogger(ctx, testLogger)
43+
ctx = cmdctx.WithErrorFactory(ctx, testErrFactory)
44+
ctx = cmdctx.WithProgressBar(ctx, nopProgressBar)
45+
46+
_, _, err := ostest.RunUnifiedTestFlow(
47+
ctx,
48+
".",
49+
mockTestClient,
50+
orgID,
51+
nil,
52+
nil,
53+
)
54+
55+
require.NoError(t, err)
56+
require.NotNil(t, mockDepGraph)
57+
}
58+
59+
//nolint:gocritic // Test helper needs to return multiple values for test setup
60+
func setupSBOMResolutionIntegrationTest(
61+
t *testing.T,
62+
ctrl *gomock.Controller,
63+
) (
64+
workflow.InvocationContext,
65+
testapi.TestClient,
66+
*testapi.IoSnykApiV1testdepgraphRequestDepGraph,
67+
string,
68+
configuration.Configuration,
69+
*zerolog.Logger,
70+
) {
71+
t.Helper()
72+
73+
mockEngine := gafmocks.NewMockEngine(ctrl)
74+
mockIctx := gafmocks.NewMockInvocationContext(ctrl)
75+
mockTestClient := gafclientmocks.NewMockTestClient(ctrl)
76+
77+
cfg := configuration.New()
78+
cfg.Set(constants.EnableExperimentalUvSupportEnvVar, true)
79+
cfg.Set(configuration.ORGANIZATION, "test-org-id")
80+
cfg.Set(flags.FlagFile, "uv.lock")
81+
82+
nopLogger := zerolog.Nop()
83+
84+
mockIctx.EXPECT().GetConfiguration().Return(cfg).AnyTimes()
85+
mockIctx.EXPECT().GetEnhancedLogger().Return(&nopLogger).AnyTimes()
86+
mockIctx.EXPECT().GetRuntimeInfo().Return(runtimeinfo.New()).AnyTimes()
87+
mockIctx.EXPECT().GetEngine().Return(mockEngine).AnyTimes()
88+
mockIctx.EXPECT().GetWorkflowIdentifier().Return(workflow.NewWorkflowIdentifier("test")).AnyTimes()
89+
90+
depGraphBytes, err := os.ReadFile("testdata/uv_depgraph.json")
91+
require.NoError(t, err)
92+
93+
var mockDepGraph testapi.IoSnykApiV1testdepgraphRequestDepGraph
94+
require.NoError(t, json.Unmarshal(depGraphBytes, &mockDepGraph))
95+
96+
depGraphData := workflow.NewData(
97+
workflow.NewTypeIdentifier(common.DepGraphWorkflowID, "depgraph"),
98+
"application/json",
99+
depGraphBytes,
100+
)
101+
depGraphData.SetMetaData("Content-Location", "uv.lock")
102+
103+
mockEngine.EXPECT().
104+
InvokeWithConfig(common.DepGraphWorkflowID, gomock.Any()).
105+
DoAndReturn(func(_ workflow.Identifier, cfg configuration.Configuration) ([]workflow.Data, error) {
106+
// Verify that use-sbom-resolution flag is set
107+
assert.True(t, cfg.GetBool("use-sbom-resolution"), "use-sbom-resolution flag should be set when UV support is enabled")
108+
return []workflow.Data{depGraphData}, nil
109+
}).
110+
Times(1)
111+
112+
mockTestClient.EXPECT().
113+
StartTest(gomock.Any(), gomock.Any()).
114+
DoAndReturn(func(_ context.Context, params testapi.StartTestParams) (testapi.TestHandle, error) {
115+
require.NotNil(t, params.Subject)
116+
117+
depGraphSubject, subjectErr := params.Subject.AsDepGraphSubjectCreate()
118+
require.NoError(t, subjectErr)
119+
120+
assert.Equal(t, mockDepGraph, depGraphSubject.DepGraph)
121+
122+
handle := gafclientmocks.NewMockTestHandle(ctrl)
123+
handle.EXPECT().Wait(gomock.Any()).Return(nil).Times(1)
124+
125+
result := gafclientmocks.NewMockTestResult(ctrl)
126+
result.EXPECT().GetExecutionState().Return(testapi.TestExecutionStatesFinished).AnyTimes()
127+
result.EXPECT().Findings(gomock.Any()).Return([]testapi.FindingData{}, true, nil).AnyTimes()
128+
handle.EXPECT().Result().Return(result).Times(1)
129+
130+
return handle, nil
131+
}).
132+
Times(1)
133+
134+
return mockIctx, mockTestClient, &mockDepGraph, "test-org-id", cfg, &nopLogger
135+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"schemaVersion": "1.2.0",
3+
"pkgManager": {
4+
"name": "pip"
5+
},
6+
"pkgs": [
7+
{
8+
"id": "test-uv-project@1.0.0",
9+
"info": {
10+
"name": "test-uv-project",
11+
"version": "1.0.0"
12+
}
13+
},
14+
{
15+
"id": "flask@2.3.0",
16+
"info": {
17+
"name": "flask",
18+
"version": "2.3.0"
19+
}
20+
}
21+
],
22+
"graph": {
23+
"rootNodeId": "root-node",
24+
"nodes": [
25+
{
26+
"nodeId": "root-node",
27+
"pkgId": "test-uv-project@1.0.0",
28+
"deps": [
29+
{
30+
"nodeId": "flask@2.3.0"
31+
}
32+
]
33+
},
34+
{
35+
"nodeId": "flask@2.3.0",
36+
"pkgId": "flask@2.3.0",
37+
"deps": []
38+
}
39+
]
40+
}
41+
}

internal/commands/ostest/workflow.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ const FeatureFlagRiskScore = "feature_flag_experimental_risk_score"
5454
// FeatureFlagRiskScoreInCLI is used to gate the risk score feature in the CLI.
5555
const FeatureFlagRiskScoreInCLI = "feature_flag_experimental_risk_score_in_cli"
5656

57-
// ForceLegacyCLIEnvVar is an internal environment variable to force the legacy CLI flow.
58-
const ForceLegacyCLIEnvVar = "SNYK_FORCE_LEGACY_CLI"
59-
6057
// PollInterval is the polling interval for the test API. It is exported to be configurable in tests.
6158
var PollInterval = 2 * time.Second
6259

internal/commands/ostest/workflow_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/snyk/cli-extension-os-flows/internal/commands/cmdctx"
2929
"github.com/snyk/cli-extension-os-flows/internal/commands/ostest"
3030
common "github.com/snyk/cli-extension-os-flows/internal/common"
31+
"github.com/snyk/cli-extension-os-flows/internal/constants"
3132
"github.com/snyk/cli-extension-os-flows/internal/errors"
3233
"github.com/snyk/cli-extension-os-flows/internal/flags"
3334
"github.com/snyk/cli-extension-os-flows/internal/legacy/definitions"
@@ -564,6 +565,36 @@ func TestOSWorkflow_FlagCombinations(t *testing.T) {
564565
},
565566
expectedError: "The feature you are trying to use is not available for your organization",
566567
},
568+
{
569+
name: "UV test flow enabled, expects depgraph workflow with use-sbom-resolutionflag",
570+
setup: func(config configuration.Configuration, mockEngine *mocks.MockEngine) {
571+
config.Set(constants.EnableExperimentalUvSupportEnvVar, true)
572+
mockEngine.EXPECT().
573+
InvokeWithConfig(common.DepGraphWorkflowID, gomock.Any()).
574+
DoAndReturn(func(_ workflow.Identifier, cfg configuration.Configuration) ([]workflow.Data, error) {
575+
// Verify that use-sbom-resolution flag is set
576+
if !cfg.GetBool("use-sbom-resolution") {
577+
return nil, fmt.Errorf("Expected use-sbom-resolution flag to be set")
578+
}
579+
return nil, assert.AnError
580+
}).
581+
Times(1)
582+
},
583+
expectedError: "failed to get dependency graph",
584+
},
585+
{
586+
name: "UV test flow disabled, depgraph calls legacy internally",
587+
setup: func(config configuration.Configuration, mockEngine *mocks.MockEngine) {
588+
config.Set(constants.EnableExperimentalUvSupportEnvVar, false)
589+
// When UV is disabled, depgraph workflow is called without use-sbom-resolution flag
590+
// Then depgraph internally calls legacycli
591+
mockEngine.EXPECT().
592+
InvokeWithConfig(gomock.Any(), gomock.Any()).
593+
Return([]workflow.Data{}, nil).
594+
Times(1)
595+
},
596+
expectedError: "", // No error, should succeed via legacy path
597+
},
567598
}
568599

569600
for _, test := range tests {

internal/common/depgraph.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/snyk/go-application-framework/pkg/configuration"
88
"github.com/snyk/go-application-framework/pkg/workflow"
99

10+
"github.com/snyk/cli-extension-os-flows/internal/constants"
1011
"github.com/snyk/cli-extension-os-flows/internal/errors"
1112
)
1213

@@ -29,9 +30,16 @@ func GetDepGraph(ictx workflow.InvocationContext, inputDir string) (*DepGraphRes
2930
logger := ictx.GetEnhancedLogger()
3031
errFactory := errors.NewErrorFactory(logger)
3132

32-
logger.Println("Invoking depgraph workflow")
33-
3433
depGraphConfig := config.Clone()
34+
experimentalUvSupportEnabled := config.GetBool(constants.EnableExperimentalUvSupportEnvVar)
35+
36+
if experimentalUvSupportEnabled {
37+
logger.Info().Msg("Experimental uv support enabled, using SBOM resolution in depgraph workflow")
38+
depGraphConfig.Set("use-sbom-resolution", true)
39+
} else {
40+
logger.Println("Invoking depgraph workflow")
41+
}
42+
3543
// Overriding the INPUT_DIRECTORY flag which the depgraph workflow will use to extract the depgraphs.
3644
depGraphConfig.Set(configuration.INPUT_DIRECTORY, inputDir)
3745
depGraphs, err := engine.InvokeWithConfig(DepGraphWorkflowID, depGraphConfig)

internal/constants/envvars.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package constants
2+
3+
// EnableExperimentalUvSupportEnvVar is an internal environment variable to enable support for testing UV projects.
4+
const EnableExperimentalUvSupportEnvVar = "SNYK_ENABLE_EXPERIMENTAL_UV_SUPPORT"
5+
6+
// ForceLegacyCLIEnvVar is an internal environment variable to force the legacy CLI flow.
7+
const ForceLegacyCLIEnvVar = "SNYK_FORCE_LEGACY_CLI"

0 commit comments

Comments
 (0)