Skip to content

Commit bbb2e67

Browse files
committed
Update project configuration and add tests for ResourceGroupsController
- Changed target framework from net9.0 to net8.0 in AzureIntegration.csproj - Added CodeQL configuration files for custom queries - Enhanced settings.json with CodeQL query pack location - Implemented ResourceGroupsControllerTest to validate management group retrieval - Updated _Layout.cshtml to include Application Insights tracking script - Added AzureResourceGroups.md documentation for subscriptions and resource groups - Created copilot-instructions.md for project guidance
1 parent b7c94d3 commit bbb2e67

10 files changed

Lines changed: 310 additions & 6 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# erdserteer ertt

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"appService.preDeployTask": "publish-release",
3-
"appService.deploySubpath": "bin/Release/net9.0/publish"
3+
"appService.deploySubpath": "bin/Release/net9.0/publish",
4+
"codeQL.createQuery.qlPackLocation": "c:\\Users\\jomyburg\\OneDrive - Microsoft\\Documents\\Content\\repos\\AzureIntegration"
45
}

AzureIntegration.csproj

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
7-
</PropertyGroup>
8-
9-
<ItemGroup>
7+
</PropertyGroup> <ItemGroup>
108
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.1" />
119
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
1210
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.1" />
1311
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />
1412
</ItemGroup>
1513

14+
<ItemGroup>
15+
<Compile Remove="Controllers\ResourceGroupsControllerTest.cs" />
16+
</ItemGroup>
17+
1618
</Project>

AzureResourceGroups.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Azure Subscriptions and Resource Groups
2+
3+
## PROD Subscription
4+
5+
**ID:** fe040da2-5247-41cd-a8d7-3755d235fda7
6+
7+
| Resource Group | Location |
8+
| ------------------------- | ------------- |
9+
| AI | swedencentral |
10+
| DefaultResourceGroup-SUK | uksouth |
11+
| Default-ActivityLogAlerts | eastus |
12+
| alwayson | eastus |
13+
14+
---
15+
16+
## DEV Subscription
17+
18+
**ID:** cd2a8d75-0982-4292-be56-6142df44c75f
19+
20+
| Resource Group | Location |
21+
| ------------------------- | -------- |
22+
| AlwaysOn | uksouth |
23+
| IoT | uksouth |
24+
| Default-ActivityLogAlerts | eastus |
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using AzureIntegration.Controllers;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Configuration;
4+
using Moq;
5+
using Moq.Protected;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Net;
9+
using System.Net.Http;
10+
using System.Text.Json;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
using Xunit;
14+
15+
namespace AzureIntegration.Tests.Controllers
16+
{
17+
public class ResourceGroupsControllerTest
18+
{
19+
private readonly Mock<IHttpClientFactory> _mockHttpClientFactory;
20+
private readonly Mock<IConfiguration> _mockConfiguration;
21+
22+
public ResourceGroupsControllerTest()
23+
{
24+
_mockHttpClientFactory = new Mock<IHttpClientFactory>();
25+
_mockConfiguration = new Mock<IConfiguration>();
26+
}
27+
28+
[Fact]
29+
public async Task GetAzureManagementGroups_ReturnsManagementGroups_WhenApiCallSucceeds()
30+
{
31+
// Arrange
32+
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
33+
var response = new HttpResponseMessage
34+
{
35+
StatusCode = HttpStatusCode.OK,
36+
Content = new StringContent(@"
37+
{
38+
""value"": [
39+
{
40+
""id"": ""/providers/Microsoft.Management/managementGroups/mg1"",
41+
""type"": ""Microsoft.Management/managementGroups"",
42+
""properties"": {
43+
""displayName"": ""Management Group 1""
44+
}
45+
},
46+
{
47+
""id"": ""/providers/Microsoft.Management/managementGroups/mg2"",
48+
""type"": ""Microsoft.Management/managementGroups"",
49+
""properties"": {
50+
""displayName"": ""Management Group 2""
51+
}
52+
}
53+
]
54+
}")
55+
};
56+
57+
mockHttpMessageHandler
58+
.Protected()
59+
.Setup<Task<HttpResponseMessage>>(
60+
"SendAsync",
61+
ItExpr.IsAny<HttpRequestMessage>(),
62+
ItExpr.IsAny<CancellationToken>())
63+
.ReturnsAsync(response);
64+
65+
var httpClient = new HttpClient(mockHttpMessageHandler.Object);
66+
_mockHttpClientFactory
67+
.Setup(factory => factory.CreateClient("AzureServices"))
68+
.Returns(httpClient);
69+
70+
var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object);
71+
72+
// Act
73+
var result = await controller.ManagementGroups();
74+
75+
// Assert
76+
var viewResult = Assert.IsType<ViewResult>(result);
77+
var model = Assert.IsAssignableFrom<List<AzureManagementGroup>>(viewResult.Model);
78+
Assert.Equal(2, model.Count);
79+
Assert.Equal("Management Group 1", model[0].Name);
80+
Assert.Equal("/providers/Microsoft.Management/managementGroups/mg1", model[0].Id);
81+
Assert.Equal("Microsoft.Management/managementGroups", model[0].Type);
82+
Assert.Equal("Management Group 2", model[1].Name);
83+
}
84+
85+
[Fact]
86+
public async Task GetAzureManagementGroups_ReturnsEmptyList_WhenNoValuePropertyInResponse()
87+
{
88+
// Arrange
89+
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
90+
var response = new HttpResponseMessage
91+
{
92+
StatusCode = HttpStatusCode.OK,
93+
Content = new StringContent(@"{ ""someOtherProperty"": [] }")
94+
};
95+
96+
mockHttpMessageHandler
97+
.Protected()
98+
.Setup<Task<HttpResponseMessage>>(
99+
"SendAsync",
100+
ItExpr.IsAny<HttpRequestMessage>(),
101+
ItExpr.IsAny<CancellationToken>())
102+
.ReturnsAsync(response);
103+
104+
var httpClient = new HttpClient(mockHttpMessageHandler.Object);
105+
_mockHttpClientFactory
106+
.Setup(factory => factory.CreateClient("AzureServices"))
107+
.Returns(httpClient);
108+
109+
var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object);
110+
111+
// Act
112+
var result = await controller.ManagementGroups();
113+
114+
// Assert
115+
var viewResult = Assert.IsType<ViewResult>(result);
116+
var model = Assert.IsAssignableFrom<List<AzureManagementGroup>>(viewResult.Model);
117+
Assert.Empty(model);
118+
}
119+
120+
[Fact]
121+
public async Task GetAzureManagementGroups_HandlesNullValues_InResponseProperties()
122+
{
123+
// Arrange
124+
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
125+
var response = new HttpResponseMessage
126+
{
127+
StatusCode = HttpStatusCode.OK,
128+
Content = new StringContent(@"
129+
{
130+
""value"": [
131+
{
132+
""id"": null,
133+
""type"": null,
134+
""properties"": {
135+
""displayName"": null
136+
}
137+
}
138+
]
139+
}")
140+
};
141+
142+
mockHttpMessageHandler
143+
.Protected()
144+
.Setup<Task<HttpResponseMessage>>(
145+
"SendAsync",
146+
ItExpr.IsAny<HttpRequestMessage>(),
147+
ItExpr.IsAny<CancellationToken>())
148+
.ReturnsAsync(response);
149+
150+
var httpClient = new HttpClient(mockHttpMessageHandler.Object);
151+
_mockHttpClientFactory
152+
.Setup(factory => factory.CreateClient("AzureServices"))
153+
.Returns(httpClient);
154+
155+
var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object);
156+
157+
// Act
158+
var result = await controller.ManagementGroups();
159+
160+
// Assert
161+
var viewResult = Assert.IsType<ViewResult>(result);
162+
var model = Assert.IsAssignableFrom<List<AzureManagementGroup>>(viewResult.Model);
163+
Assert.Single(model);
164+
Assert.Equal(string.Empty, model[0].Name);
165+
Assert.Equal(string.Empty, model[0].Id);
166+
Assert.Equal(string.Empty, model[0].Type);
167+
}
168+
169+
[Fact]
170+
public async Task GetAzureManagementGroups_CallsCorrectEndpoint()
171+
{
172+
// Arrange
173+
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
174+
HttpRequestMessage capturedRequest = null;
175+
176+
mockHttpMessageHandler
177+
.Protected()
178+
.Setup<Task<HttpResponseMessage>>(
179+
"SendAsync",
180+
ItExpr.IsAny<HttpRequestMessage>(),
181+
ItExpr.IsAny<CancellationToken>())
182+
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
183+
.ReturnsAsync(new HttpResponseMessage
184+
{
185+
StatusCode = HttpStatusCode.OK,
186+
Content = new StringContent(@"{ ""value"": [] }")
187+
});
188+
189+
var httpClient = new HttpClient(mockHttpMessageHandler.Object);
190+
_mockHttpClientFactory
191+
.Setup(factory => factory.CreateClient("AzureServices"))
192+
.Returns(httpClient);
193+
194+
var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object);
195+
196+
// Act
197+
await controller.ManagementGroups();
198+
199+
// Assert
200+
Assert.NotNull(capturedRequest);
201+
Assert.Equal(HttpMethod.Get, capturedRequest.Method);
202+
Assert.Equal("providers/Microsoft.Management/managementGroups?api-version=2020-05-01",
203+
capturedRequest.RequestUri.ToString());
204+
}
205+
}
206+
}

Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
// Register the HTTP client
4141
builder.Services.AddHttpClient();
4242

43-
builder.Services.AddSingleton(new Subscription(subscriptionId));
43+
builder.Services.AddSingleton(new Subscription(subscriptionId ?? throw new InvalidOperationException("SubscriptionId is required")));
4444
// Register the configuration
4545
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
4646
builder.Services.AddDistributedMemoryCache();

Views/Shared/_Layout.cshtml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,37 @@
6262
&copy; 2025 - AzureIntegration - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
6363
</div>
6464
</footer>
65+
6566
<script src="~/lib/jquery/dist/jquery.min.js"></script>
6667
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
6768
<script src="~/js/site.js" asp-append-version="true"></script>
69+
70+
<script type="text/javascript" src="https://js.monitor.azure.com/scripts/b/ext/ai.clck.2.min.js"></script>
71+
<script type="text/javascript">
72+
var clickPluginInstance = new Microsoft.ApplicationInsights.ClickAnalyticsPlugin();
73+
// Click Analytics configuration
74+
var clickPluginConfig = {
75+
autoCapture: true,
76+
dataTags: {
77+
useDefaultContentNameOrId: true
78+
}
79+
}
80+
// Application Insights configuration
81+
var configObj = {
82+
//connectionString: "YOUR_CONNECTION_STRING",
83+
// Alternatively, you can pass in the instrumentation key,
84+
// but support for instrumentation key ingestion will end on March 31, 2025.
85+
instrumentationKey: "14532cba-f01d-4cab-8c27-2f353bcd2707",
86+
extensions: [
87+
clickPluginInstance
88+
],
89+
extensionConfig: { [clickPluginInstance.identifier]: clickPluginConfig
90+
},
91+
};
92+
// Application Insights tracking removed for demo
93+
</script>
6894
@await RenderSectionAsync("Scripts", required: false)
95+
6996
</body>
7097

7198
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
lockVersion: 1.0.0
3+
dependencies:
4+
codeql/controlflow:
5+
version: 2.0.23
6+
codeql/csharp-all:
7+
version: 5.4.4
8+
codeql/dataflow:
9+
version: 2.0.23
10+
codeql/mad:
11+
version: 1.0.39
12+
codeql/ssa:
13+
version: 2.0.15
14+
codeql/threat-models:
15+
version: 1.0.39
16+
codeql/tutorial:
17+
version: 1.0.39
18+
codeql/typetracking:
19+
version: 2.0.23
20+
codeql/util:
21+
version: 2.0.26
22+
codeql/xml:
23+
version: 1.0.39
24+
compiled: false
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
library: false
3+
warnOnImplicitThis: false
4+
name: getting-started/codeql-extra-queries-csharp
5+
version: 1.0.0
6+
dependencies:
7+
codeql/csharp-all: ^5.4.4
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* This is an automatically generated file
3+
* @name Hello world
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id csharp/example/hello-world
7+
*/
8+
9+
import csharp
10+
11+
from File f
12+
select f, "Hello, world!"

0 commit comments

Comments
 (0)