Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions pkg/cli/cmd/recipe/show/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,19 @@ func (r *Runner) Run(ctx context.Context) error {
return err
}

// Get the recipe metadata (template schema)
recipeDetails, err := client.GetRecipeMetadata(ctx, r.Workspace.Environment, v20231001preview.RecipeGetMetadata{Name: &r.RecipeName, ResourceType: &r.ResourceType})
if err != nil {
return err
}

// Try to get the environment to retrieve configured parameter values (optional)
// If this fails, we'll just continue with the original behavior
var environment *v20231001preview.EnvironmentResource
if env, err := client.GetEnvironment(ctx, r.Workspace.Environment); err == nil {
environment = &env
}

recipe := types.EnvironmentRecipe{
Name: r.RecipeName,
ResourceType: r.ResourceType,
Expand All @@ -180,6 +188,18 @@ func (r *Runner) Run(ctx context.Context) error {

var recipeParams []types.RecipeParameter

// Get environment-specific parameter values for this recipe (if environment is available)
var environmentParameters map[string]any
if environment != nil && environment.Properties.Recipes != nil {
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing nil check for environment.Properties before accessing environment.Properties.Recipes. According to the EnvironmentResource struct definition, Properties is a pointer field that could be nil, which would cause a panic. Add a nil check for Properties before checking Recipes.

Suggested change
if environment != nil && environment.Properties.Recipes != nil {
if environment != nil && environment.Properties != nil && environment.Properties.Recipes != nil {

Copilot uses AI. Check for mistakes.
if resourceTypeRecipes, exists := environment.Properties.Recipes[r.ResourceType]; exists {
if recipeProps, exists := resourceTypeRecipes[r.RecipeName]; exists {
if recipePropsActual := recipeProps.GetRecipeProperties(); recipePropsActual != nil {
environmentParameters = recipePropsActual.Parameters
}
}
}
}

for parameter := range recipeDetails.Parameters {
values := recipeDetails.Parameters[parameter].(map[string]any)

Expand All @@ -190,6 +210,7 @@ func (r *Runner) Run(ctx context.Context) error {
MinValue: "-",
}

// Process template schema information
for paramDetailName, paramDetailValue := range values {
switch paramDetailName {
case "type":
Expand All @@ -203,6 +224,13 @@ func (r *Runner) Run(ctx context.Context) error {
}
}

// Override with environment-specific value if it exists
if environmentParameters != nil {
if envValue, exists := environmentParameters[parameter]; exists {
paramItem.DefaultValue = envValue
}
}

recipeParams = append(recipeParams, paramItem)
}

Expand Down
200 changes: 196 additions & 4 deletions pkg/cli/cmd/recipe/show/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package show

import (
"context"
"fmt"
"testing"

"go.uber.org/mock/gomock"
Expand Down Expand Up @@ -93,6 +94,19 @@ func Test_Validate(t *testing.T) {
radcli.SharedValidateValidation(t, NewCommand, testcases)
}

// setupTestClient creates a mock client with default behavior for common calls
func setupTestClient(ctrl *gomock.Controller) *clients.MockApplicationsManagementClient {
appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)

// Default behavior: GetEnvironment returns "not found" error (most common case)
appManagementClient.EXPECT().
GetEnvironment(gomock.Any(), gomock.Any()).
Return(v20231001preview.EnvironmentResource{}, fmt.Errorf("not found")).
AnyTimes()

return appManagementClient
}

func Test_Run(t *testing.T) {
t.Run("Show bicep recipe details - Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -132,7 +146,7 @@ func Test_Run(t *testing.T) {
},
}

appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
appManagementClient := setupTestClient(ctrl)
appManagementClient.EXPECT().
GetRecipeMetadata(gomock.Any(), gomock.Any(), gomock.Any()).
Return(envRecipe, nil).Times(1)
Expand Down Expand Up @@ -209,7 +223,7 @@ func Test_Run(t *testing.T) {
},
}

appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
appManagementClient := setupTestClient(ctrl)
appManagementClient.EXPECT().
GetRecipeMetadata(gomock.Any(), gomock.Any(), gomock.Any()).
Return(envRecipe, nil).Times(1)
Expand Down Expand Up @@ -246,7 +260,7 @@ func Test_Run(t *testing.T) {
require.Equal(t, expected, outputSink.Writes)
})

t.Run("Show terraformn recipe details - Success", func(t *testing.T) {
t.Run("Show terraform recipe details - Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
envRecipe := v20231001preview.RecipeGetMetadataResponse{
TemplateKind: to.Ptr(recipes.TemplateKindTerraform),
Expand Down Expand Up @@ -286,7 +300,7 @@ func Test_Run(t *testing.T) {
},
}

appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
appManagementClient := setupTestClient(ctrl)
appManagementClient.EXPECT().
GetRecipeMetadata(gomock.Any(), gomock.Any(), gomock.Any()).
Return(envRecipe, nil).Times(1)
Expand Down Expand Up @@ -322,4 +336,182 @@ func Test_Run(t *testing.T) {
}
require.Equal(t, expected, outputSink.Writes)
})

t.Run("Show recipe details with environment parameter values - Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
envRecipe := v20231001preview.RecipeGetMetadataResponse{
TemplateKind: to.Ptr(recipes.TemplateKindBicep),
TemplatePath: to.Ptr("ghcr.io/testpublicrecipe/bicep/modules/openai:v1"),
Parameters: map[string]any{
"location": map[string]any{
"type": "string",
"defaultValue": "null",
},
"resource_group_name": map[string]any{
"type": "string",
"defaultValue": "null",
},
},
}
recipe := types.EnvironmentRecipe{
Name: "default",
ResourceType: "Radius.Resources/openAI",
TemplateKind: recipes.TemplateKindBicep,
TemplatePath: "ghcr.io/testpublicrecipe/bicep/modules/openai:v1",
}

// Environment has configured parameter values
envResource := v20231001preview.EnvironmentResource{
Properties: &v20231001preview.EnvironmentProperties{
Recipes: map[string]map[string]v20231001preview.RecipePropertiesClassification{
"Radius.Resources/openAI": {
"default": &v20231001preview.BicepRecipeProperties{
TemplateKind: to.Ptr(recipes.TemplateKindBicep),
TemplatePath: to.Ptr("ghcr.io/testpublicrecipe/bicep/modules/openai:v1"),
Parameters: map[string]any{
"location": "eastus",
"resource_group_name": "my-rg",
},
},
},
},
},
}

// Expected parameters should show environment values, not template defaults
recipeParams := []types.RecipeParameter{
{
Name: "resource_group_name",
Type: "string",
MaxValue: "-",
MinValue: "-",
DefaultValue: "my-rg", // Environment value overrides template default
},
{
Name: "location",
Type: "string",
MaxValue: "-",
MinValue: "-",
DefaultValue: "eastus", // Environment value overrides template default
},
}

appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
appManagementClient.EXPECT().
GetRecipeMetadata(gomock.Any(), gomock.Any(), gomock.Any()).
Return(envRecipe, nil).Times(1)

appManagementClient.EXPECT().
GetEnvironment(gomock.Any(), gomock.Any()).
Return(envResource, nil).Times(1)

outputSink := &output.MockOutput{}

runner := &Runner{
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient},
Output: outputSink,
Workspace: &workspaces.Workspace{},
Format: "table",
RecipeName: "default",
ResourceType: "Radius.Resources/openAI",
}

err := runner.Run(context.Background())
require.NoError(t, err)

expected := []any{
output.FormattedOutput{
Format: "table",
Obj: recipe,
Options: common.RecipeFormat(),
},
output.LogOutput{
Format: "",
},
output.FormattedOutput{
Format: "table",
Obj: recipeParams,
Options: common.RecipeParametersFormat(),
},
}
require.Equal(t, expected, outputSink.Writes)
})

t.Run("Show recipe details when GetEnvironment fails - Success", func(t *testing.T) {
ctrl := gomock.NewController(t)
envRecipe := v20231001preview.RecipeGetMetadataResponse{
TemplateKind: to.Ptr(recipes.TemplateKindBicep),
TemplatePath: to.Ptr("ghcr.io/testpublicrecipe/bicep/modules/openai:v1"),
Parameters: map[string]any{
"location": map[string]any{
"type": "string",
"defaultValue": "westus",
},
"resource_group_name": map[string]any{
"type": "string",
"defaultValue": "null",
},
},
}
recipe := types.EnvironmentRecipe{
Name: "default",
ResourceType: "Radius.Resources/openAI",
TemplateKind: recipes.TemplateKindBicep,
TemplatePath: "ghcr.io/testpublicrecipe/bicep/modules/openai:v1",
}

// Expected parameters should show template defaults since environment call fails
recipeParams := []types.RecipeParameter{
{
Name: "resource_group_name",
Type: "string",
MaxValue: "-",
MinValue: "-",
DefaultValue: "null", // Template default since environment fails
},
{
Name: "location",
Type: "string",
MaxValue: "-",
MinValue: "-",
DefaultValue: "westus", // Template default since environment fails
},
}

appManagementClient := setupTestClient(ctrl)
appManagementClient.EXPECT().
GetRecipeMetadata(gomock.Any(), gomock.Any(), gomock.Any()).
Return(envRecipe, nil).Times(1)

outputSink := &output.MockOutput{}

runner := &Runner{
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient},
Output: outputSink,
Workspace: &workspaces.Workspace{},
Format: "table",
RecipeName: "default",
ResourceType: "Radius.Resources/openAI",
}

err := runner.Run(context.Background())
require.NoError(t, err)

expected := []any{
output.FormattedOutput{
Format: "table",
Obj: recipe,
Options: common.RecipeFormat(),
},
output.LogOutput{
Format: "",
},
output.FormattedOutput{
Format: "table",
Obj: recipeParams,
Options: common.RecipeParametersFormat(),
},
}
require.Equal(t, expected, outputSink.Writes)
})
Comment on lines +440 to +516
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for the case where GetEnvironment returns an EnvironmentResource with nil Properties. This is an important edge case to test since the code needs to handle it gracefully without panicking.

Copilot uses AI. Check for mistakes.
}