Skip to content
Draft
15 changes: 15 additions & 0 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/state"
"github.com/azure/azure-dev/cli/azd/pkg/templates"
"github.com/azure/azure-dev/cli/azd/pkg/tools/az"
"github.com/azure/azure-dev/cli/azd/pkg/tools/bash"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/git"
"github.com/azure/azure-dev/cli/azd/pkg/tools/github"
"github.com/azure/azure-dev/cli/azd/pkg/tools/javac"
"github.com/azure/azure-dev/cli/azd/pkg/tools/kubectl"
"github.com/azure/azure-dev/cli/azd/pkg/tools/language"
"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
"github.com/azure/azure-dev/cli/azd/pkg/tools/node"
"github.com/azure/azure-dev/cli/azd/pkg/tools/powershell"
"github.com/azure/azure-dev/cli/azd/pkg/tools/python"
"github.com/azure/azure-dev/cli/azd/pkg/tools/swa"
"github.com/azure/azure-dev/cli/azd/pkg/workflow"
Expand Down Expand Up @@ -811,6 +814,18 @@ func registerCommonDependencies(container *ioc.NestedContainer) {

container.MustRegisterNamedScoped(string(project.ServiceLanguageDocker), project.NewDockerProjectAsFrameworkService)

// Hook executors registered by language name (transient — fresh per hook invocation).
// The HooksRunner resolves these via serviceLocator.ResolveNamed().
hookExecutorMap := map[language.ScriptLanguage]any{
language.ScriptLanguageBash: bash.NewExecutor,
language.ScriptLanguagePowerShell: powershell.NewExecutor,
language.ScriptLanguagePython: language.NewPythonExecutor,
}

for lang, constructor := range hookExecutorMap {
container.MustRegisterNamedTransient(string(lang), constructor)
}

// Pipelines
container.MustRegisterScoped(pipeline.NewPipelineManager)
container.MustRegisterSingleton(func(flags *pipelineConfigFlags) *pipeline.PipelineManagerArgs {
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/cmd/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ func (hra *hooksRunAction) execHook(
hooksManager, hra.commandRunner, hra.envManager, hra.console, cwd, hooksMap, hra.env, hra.serviceLocator)

// Always run in interactive mode for 'azd hooks run', to help with testing/debugging
runOptions := &tools.ExecOptions{
runOptions := &tools.ExecutionContext{
Interactive: new(true),
}

Expand Down
28 changes: 27 additions & 1 deletion cli/azd/cmd/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,37 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/ext"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/tools/bash"
"github.com/azure/azure-dev/cli/azd/pkg/tools/language"
"github.com/azure/azure-dev/cli/azd/pkg/tools/powershell"
"github.com/azure/azure-dev/cli/azd/pkg/tools/python"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockenv"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

// registerHookExecutors registers all hook executors as named
// transients in the mock container so that IoC resolution works
// in tests.
func registerHookExecutors(mockCtx *mocks.MockContext) {
mockCtx.Container.MustRegisterNamedTransient(
string(language.ScriptLanguageBash), bash.NewExecutor,
)
mockCtx.Container.MustRegisterNamedTransient(
string(language.ScriptLanguagePowerShell),
powershell.NewExecutor,
)
mockCtx.Container.MustRegisterSingleton(python.NewCli)
mockCtx.Container.MustRegisterNamedTransient(
string(language.ScriptLanguagePython),
language.NewPythonExecutor,
)
}

func Test_HooksRunAction_RunsLayerHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
env := environment.NewWithValues("test", nil)
envManager := &mockenv.MockEnvManager{}
envManager.On("Reload", mock.Anything, mock.Anything).Return(nil)
Expand Down Expand Up @@ -92,6 +115,7 @@ func Test_HooksRunAction_RunsLayerHooks(t *testing.T) {

func Test_HooksRunAction_FiltersLayerHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
env := environment.NewWithValues("test", nil)
envManager := &mockenv.MockEnvManager{}
envManager.On("Reload", mock.Anything, mock.Anything).Return(nil)
Expand Down Expand Up @@ -158,6 +182,7 @@ func Test_HooksRunAction_FiltersLayerHooks(t *testing.T) {

func Test_HooksRunAction_SetsTelemetryTypeForLayer(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
env := environment.NewWithValues("test", nil)
envManager := &mockenv.MockEnvManager{}
envManager.On("Reload", mock.Anything, mock.Anything).Return(nil)
Expand Down Expand Up @@ -272,5 +297,6 @@ func Test_HooksRunAction_ValidatesLayerHooksRelativeToLayerPath(t *testing.T) {
err := action.validateAndWarnHooks(*mockContext.Context)
require.NoError(t, err)
require.False(t, layerHook.IsUsingDefaultShell())
require.Equal(t, ext.ScriptTypeUnknown, layerHook.Shell)
// validate() infers shell type from the .sh file extension
require.Equal(t, ext.ShellTypeBash, layerHook.Shell)
}
36 changes: 36 additions & 0 deletions cli/azd/cmd/middleware/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/ext"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/tools/bash"
"github.com/azure/azure-dev/cli/azd/pkg/tools/language"
"github.com/azure/azure-dev/cli/azd/pkg/tools/powershell"
"github.com/azure/azure-dev/cli/azd/pkg/tools/python"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockenv"
"github.com/azure/azure-dev/cli/azd/test/ostest"
Expand All @@ -28,6 +32,7 @@ import (

func Test_CommandHooks_Middleware_WithValidProjectAndMatchingCommand(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -62,6 +67,7 @@ func Test_CommandHooks_Middleware_WithValidProjectAndMatchingCommand(t *testing.

func Test_CommandHooks_Middleware_ValidProjectWithDifferentCommand(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -96,6 +102,7 @@ func Test_CommandHooks_Middleware_ValidProjectWithDifferentCommand(t *testing.T)

func Test_CommandHooks_Middleware_ValidProjectWithNoHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand All @@ -122,6 +129,7 @@ func Test_CommandHooks_Middleware_ValidProjectWithNoHooks(t *testing.T) {

func Test_CommandHooks_Middleware_PreHookWithError(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -159,6 +167,7 @@ func Test_CommandHooks_Middleware_PreHookWithError(t *testing.T) {

func Test_CommandHooks_Middleware_PreHookWithErrorAndContinue(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -197,6 +206,7 @@ func Test_CommandHooks_Middleware_PreHookWithErrorAndContinue(t *testing.T) {

func Test_CommandHooks_Middleware_WithCmdAlias(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -231,6 +241,7 @@ func Test_CommandHooks_Middleware_WithCmdAlias(t *testing.T) {

func Test_ServiceHooks_Registered(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -293,6 +304,7 @@ func Test_ServiceHooks_Registered(t *testing.T) {

func Test_ServiceHooks_ValidationUsesServicePath(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -492,6 +504,7 @@ func ensureAzdProject(ctx context.Context, azdContext *azdcontext.AzdContext, pr

func Test_PowerShellWarning_WithPowerShellHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -540,6 +553,7 @@ func Test_PowerShellWarning_WithPowerShellHooks(t *testing.T) {

func Test_PowerShellWarning_WithPs1FileHook(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -586,6 +600,7 @@ func Test_PowerShellWarning_WithPs1FileHook(t *testing.T) {

func Test_PowerShellWarning_WithoutPowerShellHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -664,6 +679,7 @@ func Test_CommandHooks_ChildAction_HooksStillFire(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -706,6 +722,7 @@ func Test_CommandHooks_ChildAction_HooksStillFire(t *testing.T) {
// guard in HooksMiddleware.Run() only affects validation, not hook execution itself.
func Test_CommandHooks_ChildAction_SkipsValidationOnly(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -762,6 +779,7 @@ func Test_CommandHooks_ChildAction_SkipsValidationOnly(t *testing.T) {
// command execution).
func Test_CommandHooks_ChildAction_PreHookError_StopsAction(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -799,6 +817,7 @@ func Test_CommandHooks_ChildAction_PreHookError_StopsAction(t *testing.T) {

func Test_PowerShellWarning_WithPwshAvailable(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -845,6 +864,7 @@ func Test_PowerShellWarning_WithPwshAvailable(t *testing.T) {

func Test_PowerShellWarning_WithNoPowerShellInstalled(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
azdContext := createAzdContext(t)

envName := "test"
Expand Down Expand Up @@ -890,3 +910,19 @@ func Test_PowerShellWarning_WithNoPowerShellInstalled(t *testing.T) {
}
require.True(t, foundWarning, "Expected 'No PowerShell installation detected' warning to be displayed")
}

// registerHookExecutors registers all hook executors as named
// transients in the mock container so that IoC resolution works
// in tests.
func registerHookExecutors(mockCtx *mocks.MockContext) {
mockCtx.Container.MustRegisterNamedTransient(
string(language.ScriptLanguageBash), bash.NewExecutor,
)
mockCtx.Container.MustRegisterNamedTransient(
string(language.ScriptLanguagePowerShell), powershell.NewExecutor,
)
mockCtx.Container.MustRegisterSingleton(python.NewCli)
mockCtx.Container.MustRegisterNamedTransient(
string(language.ScriptLanguagePython), language.NewPythonExecutor,
)
}
32 changes: 32 additions & 0 deletions cli/azd/cover
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Remove

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
mode: set
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:76.56,79.13 2 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:80.13,81.30 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:82.13,83.34 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:84.13,85.34 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:86.13,87.30 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:88.13,89.28 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:90.14,91.34 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:92.10,93.31 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:110.27,111.18 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:112.28,113.58 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:116.24,119.4 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:120.52,123.4 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:124.10,127.4 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:140.19,145.2 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:148.52,150.2 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:157.9,158.56 1 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:158.56,160.3 1 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:162.2,165.44 3 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:165.44,169.24 2 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:169.24,174.4 1 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:177.2,177.12 1 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:186.27,193.32 3 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:193.32,197.3 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:199.2,199.27 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:199.27,201.3 1 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:203.2,203.42 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:212.10,213.31 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:213.31,214.44 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:214.44,216.4 1 1
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:218.3,218.18 1 0
github.com/azure/azure-dev/cli/azd/pkg/tools/language/executor.go:221.2,221.18 1 0
Loading
Loading