Skip to content

Commit 1bc1dca

Browse files
Copilottherealjohn
andauthored
test: add unit tests for env refresh with extension failure handling
Add tests that verify: 1. TestEnvRefreshAction_SucceedsWhenProjectInitFails: env refresh succeeds even when projectManager.Initialize() fails (the key fix for #7195) 2. TestEnvRefreshAction_RaisesServiceEventsOnSuccess: when project initialization succeeds, service events are still raised Agent-Logs-Url: https://github.com/Azure/azure-dev/sessions/ec998279-c8d0-4ad1-98d3-830624a06afd Co-authored-by: therealjohn <1501196+therealjohn@users.noreply.github.com>
1 parent 71de3e6 commit 1bc1dca

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

cli/azd/cmd/env_refresh_test.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package cmd
5+
6+
import (
7+
"bytes"
8+
"context"
9+
"fmt"
10+
"path/filepath"
11+
"testing"
12+
13+
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
14+
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
15+
"github.com/azure/azure-dev/cli/azd/pkg/config"
16+
"github.com/azure/azure-dev/cli/azd/pkg/environment"
17+
"github.com/azure/azure-dev/cli/azd/pkg/ext"
18+
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
19+
"github.com/azure/azure-dev/cli/azd/pkg/ioc"
20+
"github.com/azure/azure-dev/cli/azd/pkg/output"
21+
"github.com/azure/azure-dev/cli/azd/pkg/project"
22+
"github.com/azure/azure-dev/cli/azd/test/mocks/mockenv"
23+
"github.com/azure/azure-dev/cli/azd/test/mocks/mockinput"
24+
"github.com/stretchr/testify/mock"
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
// mockEnvRefreshProvider implements provisioning.Provider for env refresh testing.
29+
type mockEnvRefreshProvider struct{}
30+
31+
func (p *mockEnvRefreshProvider) Name() string { return "test" }
32+
33+
func (p *mockEnvRefreshProvider) Initialize(
34+
_ context.Context, _ string, _ provisioning.Options,
35+
) error {
36+
return nil
37+
}
38+
39+
func (p *mockEnvRefreshProvider) State(
40+
_ context.Context, _ *provisioning.StateOptions,
41+
) (*provisioning.StateResult, error) {
42+
return &provisioning.StateResult{
43+
State: &provisioning.State{
44+
Outputs: map[string]provisioning.OutputParameter{
45+
"WEBSITE_URL": {Type: "string", Value: "https://example.com"},
46+
},
47+
Resources: []provisioning.Resource{},
48+
},
49+
}, nil
50+
}
51+
52+
func (p *mockEnvRefreshProvider) Deploy(
53+
_ context.Context,
54+
) (*provisioning.DeployResult, error) {
55+
return nil, nil
56+
}
57+
58+
func (p *mockEnvRefreshProvider) Preview(
59+
_ context.Context,
60+
) (*provisioning.DeployPreviewResult, error) {
61+
return nil, nil
62+
}
63+
64+
func (p *mockEnvRefreshProvider) Destroy(
65+
_ context.Context, _ provisioning.DestroyOptions,
66+
) (*provisioning.DestroyResult, error) {
67+
return nil, nil
68+
}
69+
70+
func (p *mockEnvRefreshProvider) EnsureEnv(_ context.Context) error {
71+
return nil
72+
}
73+
74+
func (p *mockEnvRefreshProvider) Parameters(
75+
_ context.Context,
76+
) ([]provisioning.Parameter, error) {
77+
return nil, nil
78+
}
79+
80+
func (p *mockEnvRefreshProvider) PlannedOutputs(
81+
_ context.Context,
82+
) ([]provisioning.PlannedOutput, error) {
83+
return nil, nil
84+
}
85+
86+
// TestEnvRefreshAction_SucceedsWhenProjectInitFails verifies that env refresh
87+
// completes successfully even when projectManager.Initialize() returns an
88+
// error. This is the key fix for issue #7195 where projects using the
89+
// azure.ai.agent extension would fail because the extension's service target
90+
// initialization could not complete during env refresh.
91+
func TestEnvRefreshAction_SucceedsWhenProjectInitFails(t *testing.T) {
92+
t.Parallel()
93+
94+
projectDir := t.TempDir()
95+
96+
// Register mock provider in IoC
97+
container := ioc.NewNestedContainer(nil)
98+
ioc.RegisterNamedInstance[provisioning.Provider](
99+
container, string(provisioning.Test), &mockEnvRefreshProvider{},
100+
)
101+
102+
env := environment.NewWithValues("test-env", map[string]string{
103+
"AZURE_SUBSCRIPTION_ID": "00000000-0000-0000-0000-000000000000",
104+
"AZURE_LOCATION": "eastus2",
105+
})
106+
107+
envManager := &mockenv.MockEnvManager{}
108+
envManager.On("Save", mock.Anything, mock.Anything, mock.Anything).
109+
Return(nil)
110+
envManager.On("EnvPath", mock.Anything).
111+
Return(filepath.Join(projectDir, ".azure", "test-env", ".env"))
112+
113+
console := mockinput.NewMockConsole()
114+
115+
provisionMgr := provisioning.NewManager(
116+
container,
117+
func() (provisioning.ProviderKind, error) {
118+
return provisioning.Test, nil
119+
},
120+
envManager,
121+
env,
122+
console,
123+
alpha.NewFeaturesManagerWithConfig(config.NewEmptyConfig()),
124+
nil,
125+
cloud.AzurePublic(),
126+
)
127+
128+
// Project manager that fails on Initialize (simulating extension failure)
129+
pm := &mockProjectManager{}
130+
pm.On("Initialize", mock.Anything, mock.Anything).
131+
Return(fmt.Errorf("extension service target initialization failed"))
132+
133+
projectConfig := &project.ProjectConfig{
134+
Name: "test-project",
135+
Path: projectDir,
136+
Infra: provisioning.Options{
137+
Provider: provisioning.Test,
138+
Path: "infra",
139+
Module: "main",
140+
},
141+
}
142+
143+
action := &envRefreshAction{
144+
provisionManager: provisionMgr,
145+
projectConfig: projectConfig,
146+
projectManager: pm,
147+
env: env,
148+
envManager: envManager,
149+
flags: &envRefreshFlags{},
150+
console: console,
151+
formatter: &output.NoneFormatter{},
152+
writer: &bytes.Buffer{},
153+
importManager: project.NewImportManager(nil),
154+
alphaFeatureManager: alpha.NewFeaturesManagerWithConfig(config.NewEmptyConfig()),
155+
}
156+
157+
result, err := action.Run(t.Context())
158+
159+
// env refresh should succeed even though project init failed
160+
require.NoError(t, err)
161+
require.NotNil(t, result)
162+
require.Equal(t, "Environment refresh completed", result.Message.Header)
163+
164+
// Verify that the environment was updated with deployment outputs
165+
require.Equal(t, "https://example.com", env.Getenv("WEBSITE_URL"))
166+
}
167+
168+
// TestEnvRefreshAction_RaisesServiceEventsOnSuccess verifies that when
169+
// project initialization succeeds, ServiceEventEnvUpdated events are raised
170+
// for each service.
171+
func TestEnvRefreshAction_RaisesServiceEventsOnSuccess(t *testing.T) {
172+
t.Parallel()
173+
174+
projectDir := t.TempDir()
175+
176+
// Register mock provider in IoC
177+
container := ioc.NewNestedContainer(nil)
178+
ioc.RegisterNamedInstance[provisioning.Provider](
179+
container, string(provisioning.Test), &mockEnvRefreshProvider{},
180+
)
181+
182+
env := environment.NewWithValues("test-env", map[string]string{
183+
"AZURE_SUBSCRIPTION_ID": "00000000-0000-0000-0000-000000000000",
184+
"AZURE_LOCATION": "eastus2",
185+
})
186+
187+
envManager := &mockenv.MockEnvManager{}
188+
envManager.On("Save", mock.Anything, mock.Anything, mock.Anything).
189+
Return(nil)
190+
envManager.On("EnvPath", mock.Anything).
191+
Return(filepath.Join(projectDir, ".azure", "test-env", ".env"))
192+
193+
console := mockinput.NewMockConsole()
194+
195+
provisionMgr := provisioning.NewManager(
196+
container,
197+
func() (provisioning.ProviderKind, error) {
198+
return provisioning.Test, nil
199+
},
200+
envManager,
201+
env,
202+
console,
203+
alpha.NewFeaturesManagerWithConfig(config.NewEmptyConfig()),
204+
nil,
205+
cloud.AzurePublic(),
206+
)
207+
208+
// Project manager that succeeds
209+
pm := &mockProjectManager{}
210+
pm.On("Initialize", mock.Anything, mock.Anything).Return(nil)
211+
212+
projectConfig := &project.ProjectConfig{
213+
Name: "test-project",
214+
Path: projectDir,
215+
Infra: provisioning.Options{
216+
Provider: provisioning.Test,
217+
Path: "infra",
218+
Module: "main",
219+
},
220+
Services: map[string]*project.ServiceConfig{
221+
"web": {
222+
Name: "web",
223+
EventDispatcher: ext.NewEventDispatcher[project.ServiceLifecycleEventArgs](
224+
project.ServiceEvents...,
225+
),
226+
},
227+
},
228+
}
229+
230+
action := &envRefreshAction{
231+
provisionManager: provisionMgr,
232+
projectConfig: projectConfig,
233+
projectManager: pm,
234+
env: env,
235+
envManager: envManager,
236+
flags: &envRefreshFlags{},
237+
console: console,
238+
formatter: &output.NoneFormatter{},
239+
writer: &bytes.Buffer{},
240+
importManager: project.NewImportManager(nil),
241+
alphaFeatureManager: alpha.NewFeaturesManagerWithConfig(config.NewEmptyConfig()),
242+
}
243+
244+
result, err := action.Run(t.Context())
245+
246+
require.NoError(t, err)
247+
require.NotNil(t, result)
248+
require.Equal(t, "Environment refresh completed", result.Message.Header)
249+
250+
// Verify that project initialization was called
251+
pm.AssertCalled(t, "Initialize", mock.Anything, mock.Anything)
252+
}

0 commit comments

Comments
 (0)