Skip to content

Commit 7db13cd

Browse files
committed
Adds validation to required string fields
1 parent 31578c3 commit 7db13cd

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

.github/copilot-instructions.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,16 @@ Increase `-20` if needed to find the cutoff commit. `git log` shows commits in r
240240
### Step 4: Organize and finalize
241241
1. **Remove empty categories** and **validate formatting**
242242
2. **Spell check**: Run `cspell lint "cli/azd/CHANGELOG.md" --relative --config cli/azd/.vscode/cspell.yaml --no-progress` and update `.vscode/cspell-github-user-aliases.txt` if needed
243+
244+
## Extensions Development
245+
246+
### Building Extensions
247+
Extensions are located in `extensions/` directory and use the extension framework:
248+
```bash
249+
# Build and install extension (example using demo extension)
250+
cd extensions/microsoft.azd.demo
251+
azd x build
252+
253+
# Test extension (using extension's namespace from extension.yaml)
254+
azd demo <command>
255+
```

cli/azd/internal/grpcserver/project_service.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/azure/azure-dev/cli/azd/pkg/project"
1616
"github.com/azure/azure-dev/cli/azd/pkg/templates"
1717
"github.com/azure/azure-dev/cli/azd/pkg/tools/github"
18+
"google.golang.org/grpc/codes"
19+
"google.golang.org/grpc/status"
1820
"google.golang.org/protobuf/types/known/structpb"
1921
)
2022

@@ -180,6 +182,10 @@ func (s *projectService) Get(ctx context.Context, req *azdext.EmptyRequest) (*az
180182
// The service name from req.Service.Name is used as the key in the services map.
181183
// If the services map doesn't exist, it will be initialized.
182184
func (s *projectService) AddService(ctx context.Context, req *azdext.AddServiceRequest) (*azdext.EmptyResponse, error) {
185+
if req.Service == nil || req.Service.Name == "" {
186+
return nil, status.Error(codes.InvalidArgument, "service name cannot be empty")
187+
}
188+
183189
azdContext, err := s.lazyAzdContext.GetValue()
184190
if err != nil {
185191
return nil, err
@@ -277,6 +283,10 @@ func (s *projectService) GetConfigValue(
277283
ctx context.Context,
278284
req *azdext.GetProjectConfigValueRequest,
279285
) (*azdext.GetProjectConfigValueResponse, error) {
286+
if req.Path == "" {
287+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
288+
}
289+
280290
azdContext, err := s.lazyAzdContext.GetValue()
281291
if err != nil {
282292
return nil, err
@@ -322,6 +332,10 @@ func (s *projectService) SetConfigSection(
322332
ctx context.Context,
323333
req *azdext.SetProjectConfigSectionRequest,
324334
) (*azdext.EmptyResponse, error) {
335+
if req.Path == "" {
336+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
337+
}
338+
325339
azdContext, err := s.lazyAzdContext.GetValue()
326340
if err != nil {
327341
return nil, err
@@ -366,6 +380,10 @@ func (s *projectService) SetConfigValue(
366380
ctx context.Context,
367381
req *azdext.SetProjectConfigValueRequest,
368382
) (*azdext.EmptyResponse, error) {
383+
if req.Path == "" {
384+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
385+
}
386+
369387
azdContext, err := s.lazyAzdContext.GetValue()
370388
if err != nil {
371389
return nil, err
@@ -410,6 +428,10 @@ func (s *projectService) UnsetConfig(
410428
ctx context.Context,
411429
req *azdext.UnsetProjectConfigRequest,
412430
) (*azdext.EmptyResponse, error) {
431+
if req.Path == "" {
432+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
433+
}
434+
413435
azdContext, err := s.lazyAzdContext.GetValue()
414436
if err != nil {
415437
return nil, err
@@ -455,6 +477,10 @@ func (s *projectService) GetServiceConfigSection(
455477
ctx context.Context,
456478
req *azdext.GetServiceConfigSectionRequest,
457479
) (*azdext.GetServiceConfigSectionResponse, error) {
480+
if req.ServiceName == "" {
481+
return nil, status.Error(codes.InvalidArgument, "service name cannot be empty")
482+
}
483+
458484
azdContext, err := s.lazyAzdContext.GetValue()
459485
if err != nil {
460486
return nil, err
@@ -515,6 +541,13 @@ func (s *projectService) GetServiceConfigValue(
515541
ctx context.Context,
516542
req *azdext.GetServiceConfigValueRequest,
517543
) (*azdext.GetServiceConfigValueResponse, error) {
544+
if req.ServiceName == "" {
545+
return nil, status.Error(codes.InvalidArgument, "service name cannot be empty")
546+
}
547+
if req.Path == "" {
548+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
549+
}
550+
518551
azdContext, err := s.lazyAzdContext.GetValue()
519552
if err != nil {
520553
return nil, err
@@ -569,6 +602,10 @@ func (s *projectService) SetServiceConfigSection(
569602
ctx context.Context,
570603
req *azdext.SetServiceConfigSectionRequest,
571604
) (*azdext.EmptyResponse, error) {
605+
if req.ServiceName == "" {
606+
return nil, status.Error(codes.InvalidArgument, "service name cannot be empty")
607+
}
608+
572609
azdContext, err := s.lazyAzdContext.GetValue()
573610
if err != nil {
574611
return nil, err
@@ -626,6 +663,13 @@ func (s *projectService) SetServiceConfigValue(
626663
ctx context.Context,
627664
req *azdext.SetServiceConfigValueRequest,
628665
) (*azdext.EmptyResponse, error) {
666+
if req.ServiceName == "" {
667+
return nil, status.Error(codes.InvalidArgument, "service name cannot be empty")
668+
}
669+
if req.Path == "" {
670+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
671+
}
672+
629673
azdContext, err := s.lazyAzdContext.GetValue()
630674
if err != nil {
631675
return nil, err
@@ -680,6 +724,13 @@ func (s *projectService) UnsetServiceConfig(
680724
ctx context.Context,
681725
req *azdext.UnsetServiceConfigRequest,
682726
) (*azdext.EmptyResponse, error) {
727+
if req.ServiceName == "" {
728+
return nil, status.Error(codes.InvalidArgument, "service name cannot be empty")
729+
}
730+
if req.Path == "" {
731+
return nil, status.Error(codes.InvalidArgument, "path cannot be empty")
732+
}
733+
683734
azdContext, err := s.lazyAzdContext.GetValue()
684735
if err != nil {
685736
return nil, err

cli/azd/internal/grpcserver/project_service_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,3 +1677,208 @@ func Test_ProjectService_EventDispatcherPreservation_MultipleUpdates(t *testing.
16771677

16781678
require.Equal(t, int32(1), eventCount.Load(), "event handler should be invoked after multiple config updates")
16791679
}
1680+
1681+
func Test_ProjectService_ServiceConfigValue_EmptyPath(t *testing.T) {
1682+
mockContext := mocks.NewMockContext(context.Background())
1683+
temp := t.TempDir()
1684+
1685+
// Initialize AzdContext with the temporary directory
1686+
azdContext := azdcontext.NewAzdContextWithDirectory(temp)
1687+
1688+
// Define and save project configuration with a service
1689+
projectConfig := project.ProjectConfig{
1690+
Name: "test",
1691+
Services: map[string]*project.ServiceConfig{
1692+
"api": {
1693+
Name: "api",
1694+
Host: "containerapp",
1695+
Language: "go",
1696+
},
1697+
},
1698+
}
1699+
err := project.Save(*mockContext.Context, &projectConfig, azdContext.ProjectPath())
1700+
require.NoError(t, err)
1701+
1702+
// Configure and initialize environment manager
1703+
fileConfigManager := config.NewFileConfigManager(config.NewManager())
1704+
localDataStore := environment.NewLocalFileDataStore(azdContext, fileConfigManager)
1705+
envManager, err := environment.NewManager(mockContext.Container, azdContext, mockContext.Console, localDataStore, nil)
1706+
require.NoError(t, err)
1707+
1708+
// Create lazy-loaded instances
1709+
lazyAzdContext := lazy.From(azdContext)
1710+
lazyEnvManager := lazy.From(envManager)
1711+
lazyProjectConfig := lazy.From(&projectConfig)
1712+
1713+
// Create the service
1714+
importManager := project.NewImportManager(&project.DotNetImporter{})
1715+
projectService := NewProjectService(lazyAzdContext, lazyEnvManager, lazyProjectConfig, importManager, nil)
1716+
1717+
t.Run("GetServiceConfigValue_EmptyPath", func(t *testing.T) {
1718+
_, err := projectService.GetServiceConfigValue(*mockContext.Context, &azdext.GetServiceConfigValueRequest{
1719+
ServiceName: "api",
1720+
Path: "",
1721+
})
1722+
require.Error(t, err)
1723+
require.Contains(t, err.Error(), "path cannot be empty")
1724+
})
1725+
1726+
t.Run("SetServiceConfigValue_EmptyPath", func(t *testing.T) {
1727+
_, err := projectService.SetServiceConfigValue(*mockContext.Context, &azdext.SetServiceConfigValueRequest{
1728+
ServiceName: "api",
1729+
Path: "",
1730+
Value: structpb.NewStringValue("test"),
1731+
})
1732+
require.Error(t, err)
1733+
require.Contains(t, err.Error(), "path cannot be empty")
1734+
})
1735+
1736+
t.Run("UnsetServiceConfig_EmptyPath", func(t *testing.T) {
1737+
_, err := projectService.UnsetServiceConfig(*mockContext.Context, &azdext.UnsetServiceConfigRequest{
1738+
ServiceName: "api",
1739+
Path: "",
1740+
})
1741+
require.Error(t, err)
1742+
require.Contains(t, err.Error(), "path cannot be empty")
1743+
})
1744+
}
1745+
1746+
func Test_ProjectService_EmptyStringValidation(t *testing.T) {
1747+
mockContext := mocks.NewMockContext(context.Background())
1748+
temp := t.TempDir()
1749+
1750+
// Initialize AzdContext with the temporary directory
1751+
azdContext := azdcontext.NewAzdContextWithDirectory(temp)
1752+
1753+
// Define and save project configuration with a service
1754+
projectConfig := project.ProjectConfig{
1755+
Name: "test",
1756+
Services: map[string]*project.ServiceConfig{
1757+
"api": {
1758+
Name: "api",
1759+
Host: "containerapp",
1760+
Language: "go",
1761+
},
1762+
},
1763+
}
1764+
err := project.Save(*mockContext.Context, &projectConfig, azdContext.ProjectPath())
1765+
require.NoError(t, err)
1766+
1767+
// Configure and initialize environment manager
1768+
fileConfigManager := config.NewFileConfigManager(config.NewManager())
1769+
localDataStore := environment.NewLocalFileDataStore(azdContext, fileConfigManager)
1770+
envManager, err := environment.NewManager(mockContext.Container, azdContext, mockContext.Console, localDataStore, nil)
1771+
require.NoError(t, err)
1772+
1773+
// Create lazy-loaded instances
1774+
lazyAzdContext := lazy.From(azdContext)
1775+
lazyEnvManager := lazy.From(envManager)
1776+
lazyProjectConfig := lazy.From(&projectConfig)
1777+
1778+
// Create the service
1779+
importManager := project.NewImportManager(&project.DotNetImporter{})
1780+
projectService := NewProjectService(lazyAzdContext, lazyEnvManager, lazyProjectConfig, importManager, nil)
1781+
1782+
// Project-level config method validations
1783+
t.Run("GetConfigValue_EmptyPath", func(t *testing.T) {
1784+
_, err := projectService.GetConfigValue(*mockContext.Context, &azdext.GetProjectConfigValueRequest{
1785+
Path: "",
1786+
})
1787+
require.Error(t, err)
1788+
require.Contains(t, err.Error(), "path cannot be empty")
1789+
})
1790+
1791+
t.Run("SetConfigSection_EmptyPath", func(t *testing.T) {
1792+
_, err := projectService.SetConfigSection(*mockContext.Context, &azdext.SetProjectConfigSectionRequest{
1793+
Path: "",
1794+
Section: &structpb.Struct{},
1795+
})
1796+
require.Error(t, err)
1797+
require.Contains(t, err.Error(), "path cannot be empty")
1798+
})
1799+
1800+
t.Run("SetConfigValue_EmptyPath", func(t *testing.T) {
1801+
_, err := projectService.SetConfigValue(*mockContext.Context, &azdext.SetProjectConfigValueRequest{
1802+
Path: "",
1803+
Value: structpb.NewStringValue("test"),
1804+
})
1805+
require.Error(t, err)
1806+
require.Contains(t, err.Error(), "path cannot be empty")
1807+
})
1808+
1809+
t.Run("UnsetConfig_EmptyPath", func(t *testing.T) {
1810+
_, err := projectService.UnsetConfig(*mockContext.Context, &azdext.UnsetProjectConfigRequest{
1811+
Path: "",
1812+
})
1813+
require.Error(t, err)
1814+
require.Contains(t, err.Error(), "path cannot be empty")
1815+
})
1816+
1817+
// Service-level config method validations - empty serviceName
1818+
t.Run("GetServiceConfigSection_EmptyServiceName", func(t *testing.T) {
1819+
_, err := projectService.GetServiceConfigSection(*mockContext.Context, &azdext.GetServiceConfigSectionRequest{
1820+
ServiceName: "",
1821+
Path: "host",
1822+
})
1823+
require.Error(t, err)
1824+
require.Contains(t, err.Error(), "service name cannot be empty")
1825+
})
1826+
1827+
t.Run("GetServiceConfigValue_EmptyServiceName", func(t *testing.T) {
1828+
_, err := projectService.GetServiceConfigValue(*mockContext.Context, &azdext.GetServiceConfigValueRequest{
1829+
ServiceName: "",
1830+
Path: "host",
1831+
})
1832+
require.Error(t, err)
1833+
require.Contains(t, err.Error(), "service name cannot be empty")
1834+
})
1835+
1836+
t.Run("SetServiceConfigSection_EmptyServiceName", func(t *testing.T) {
1837+
_, err := projectService.SetServiceConfigSection(*mockContext.Context, &azdext.SetServiceConfigSectionRequest{
1838+
ServiceName: "",
1839+
Path: "custom",
1840+
Section: &structpb.Struct{},
1841+
})
1842+
require.Error(t, err)
1843+
require.Contains(t, err.Error(), "service name cannot be empty")
1844+
})
1845+
1846+
t.Run("SetServiceConfigValue_EmptyServiceName", func(t *testing.T) {
1847+
_, err := projectService.SetServiceConfigValue(*mockContext.Context, &azdext.SetServiceConfigValueRequest{
1848+
ServiceName: "",
1849+
Path: "host",
1850+
Value: structpb.NewStringValue("test"),
1851+
})
1852+
require.Error(t, err)
1853+
require.Contains(t, err.Error(), "service name cannot be empty")
1854+
})
1855+
1856+
t.Run("UnsetServiceConfig_EmptyServiceName", func(t *testing.T) {
1857+
_, err := projectService.UnsetServiceConfig(*mockContext.Context, &azdext.UnsetServiceConfigRequest{
1858+
ServiceName: "",
1859+
Path: "host",
1860+
})
1861+
require.Error(t, err)
1862+
require.Contains(t, err.Error(), "service name cannot be empty")
1863+
})
1864+
1865+
// AddService validations
1866+
t.Run("AddService_NilService", func(t *testing.T) {
1867+
_, err := projectService.AddService(*mockContext.Context, &azdext.AddServiceRequest{
1868+
Service: nil,
1869+
})
1870+
require.Error(t, err)
1871+
require.Contains(t, err.Error(), "service name cannot be empty")
1872+
})
1873+
1874+
t.Run("AddService_EmptyServiceName", func(t *testing.T) {
1875+
_, err := projectService.AddService(*mockContext.Context, &azdext.AddServiceRequest{
1876+
Service: &azdext.ServiceConfig{
1877+
Name: "",
1878+
Host: "containerapp",
1879+
},
1880+
})
1881+
require.Error(t, err)
1882+
require.Contains(t, err.Error(), "service name cannot be empty")
1883+
})
1884+
}

0 commit comments

Comments
 (0)