diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..5f27bc7 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +# erdserteer ertt diff --git a/.vscode/settings.json b/.vscode/settings.json index ff3f4ef..39cb0c8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "appService.preDeployTask": "publish-release", - "appService.deploySubpath": "bin/Release/net9.0/publish" + "appService.deploySubpath": "bin/Release/net9.0/publish", + "codeQL.createQuery.qlPackLocation": "c:\\Users\\jomyburg\\OneDrive - Microsoft\\Documents\\Content\\repos\\AzureIntegration" } \ No newline at end of file diff --git a/AzureIntegration.csproj b/AzureIntegration.csproj index 1a81f33..0376369 100644 --- a/AzureIntegration.csproj +++ b/AzureIntegration.csproj @@ -1,16 +1,18 @@ - net9.0 + net8.0 enable enable - - - + + + + + diff --git a/AzureResourceGroups.md b/AzureResourceGroups.md new file mode 100644 index 0000000..93f49f6 --- /dev/null +++ b/AzureResourceGroups.md @@ -0,0 +1,24 @@ +# Azure Subscriptions and Resource Groups + +## PROD Subscription + +**ID:** fe040da2-5247-41cd-a8d7-3755d235fda7 + +| Resource Group | Location | +| ------------------------- | ------------- | +| AI | swedencentral | +| DefaultResourceGroup-SUK | uksouth | +| Default-ActivityLogAlerts | eastus | +| alwayson | eastus | + +--- + +## DEV Subscription + +**ID:** cd2a8d75-0982-4292-be56-6142df44c75f + +| Resource Group | Location | +| ------------------------- | -------- | +| AlwaysOn | uksouth | +| IoT | uksouth | +| Default-ActivityLogAlerts | eastus | diff --git a/Controllers/ResourceGroupsControllerTest.cs b/Controllers/ResourceGroupsControllerTest.cs new file mode 100644 index 0000000..6423e8b --- /dev/null +++ b/Controllers/ResourceGroupsControllerTest.cs @@ -0,0 +1,206 @@ +using AzureIntegration.Controllers; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Moq; +using Moq.Protected; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AzureIntegration.Tests.Controllers +{ + public class ResourceGroupsControllerTest + { + private readonly Mock _mockHttpClientFactory; + private readonly Mock _mockConfiguration; + + public ResourceGroupsControllerTest() + { + _mockHttpClientFactory = new Mock(); + _mockConfiguration = new Mock(); + } + + [Fact] + public async Task GetAzureManagementGroups_ReturnsManagementGroups_WhenApiCallSucceeds() + { + // Arrange + var mockHttpMessageHandler = new Mock(); + var response = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@" + { + ""value"": [ + { + ""id"": ""/providers/Microsoft.Management/managementGroups/mg1"", + ""type"": ""Microsoft.Management/managementGroups"", + ""properties"": { + ""displayName"": ""Management Group 1"" + } + }, + { + ""id"": ""/providers/Microsoft.Management/managementGroups/mg2"", + ""type"": ""Microsoft.Management/managementGroups"", + ""properties"": { + ""displayName"": ""Management Group 2"" + } + } + ] + }") + }; + + mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(response); + + var httpClient = new HttpClient(mockHttpMessageHandler.Object); + _mockHttpClientFactory + .Setup(factory => factory.CreateClient("AzureServices")) + .Returns(httpClient); + + var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object); + + // Act + var result = await controller.ManagementGroups(); + + // Assert + var viewResult = Assert.IsType(result); + var model = Assert.IsAssignableFrom>(viewResult.Model); + Assert.Equal(2, model.Count); + Assert.Equal("Management Group 1", model[0].Name); + Assert.Equal("/providers/Microsoft.Management/managementGroups/mg1", model[0].Id); + Assert.Equal("Microsoft.Management/managementGroups", model[0].Type); + Assert.Equal("Management Group 2", model[1].Name); + } + + [Fact] + public async Task GetAzureManagementGroups_ReturnsEmptyList_WhenNoValuePropertyInResponse() + { + // Arrange + var mockHttpMessageHandler = new Mock(); + var response = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@"{ ""someOtherProperty"": [] }") + }; + + mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(response); + + var httpClient = new HttpClient(mockHttpMessageHandler.Object); + _mockHttpClientFactory + .Setup(factory => factory.CreateClient("AzureServices")) + .Returns(httpClient); + + var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object); + + // Act + var result = await controller.ManagementGroups(); + + // Assert + var viewResult = Assert.IsType(result); + var model = Assert.IsAssignableFrom>(viewResult.Model); + Assert.Empty(model); + } + + [Fact] + public async Task GetAzureManagementGroups_HandlesNullValues_InResponseProperties() + { + // Arrange + var mockHttpMessageHandler = new Mock(); + var response = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@" + { + ""value"": [ + { + ""id"": null, + ""type"": null, + ""properties"": { + ""displayName"": null + } + } + ] + }") + }; + + mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(response); + + var httpClient = new HttpClient(mockHttpMessageHandler.Object); + _mockHttpClientFactory + .Setup(factory => factory.CreateClient("AzureServices")) + .Returns(httpClient); + + var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object); + + // Act + var result = await controller.ManagementGroups(); + + // Assert + var viewResult = Assert.IsType(result); + var model = Assert.IsAssignableFrom>(viewResult.Model); + Assert.Single(model); + Assert.Equal(string.Empty, model[0].Name); + Assert.Equal(string.Empty, model[0].Id); + Assert.Equal(string.Empty, model[0].Type); + } + + [Fact] + public async Task GetAzureManagementGroups_CallsCorrectEndpoint() + { + // Arrange + var mockHttpMessageHandler = new Mock(); + HttpRequestMessage capturedRequest = null; + + mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Callback((request, _) => capturedRequest = request) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@"{ ""value"": [] }") + }); + + var httpClient = new HttpClient(mockHttpMessageHandler.Object); + _mockHttpClientFactory + .Setup(factory => factory.CreateClient("AzureServices")) + .Returns(httpClient); + + var controller = new ResourceGroupsController(_mockHttpClientFactory.Object, _mockConfiguration.Object); + + // Act + await controller.ManagementGroups(); + + // Assert + Assert.NotNull(capturedRequest); + Assert.Equal(HttpMethod.Get, capturedRequest.Method); + Assert.Equal("providers/Microsoft.Management/managementGroups?api-version=2020-05-01", + capturedRequest.RequestUri.ToString()); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index d0a0eba..8a0645a 100644 --- a/Program.cs +++ b/Program.cs @@ -40,7 +40,7 @@ // Register the HTTP client builder.Services.AddHttpClient(); -builder.Services.AddSingleton(new Subscription(subscriptionId)); +builder.Services.AddSingleton(new Subscription(subscriptionId ?? throw new InvalidOperationException("SubscriptionId is required"))); // Register the configuration builder.Services.AddSingleton(builder.Configuration); builder.Services.AddDistributedMemoryCache(); diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 2c1de97..9011c24 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -62,10 +62,37 @@ © 2025 - AzureIntegration - Privacy + + + + @await RenderSectionAsync("Scripts", required: false) + diff --git a/codeql-custom-queries-csharp/codeql-pack.lock.yml b/codeql-custom-queries-csharp/codeql-pack.lock.yml new file mode 100644 index 0000000..0580cdf --- /dev/null +++ b/codeql-custom-queries-csharp/codeql-pack.lock.yml @@ -0,0 +1,24 @@ +--- +lockVersion: 1.0.0 +dependencies: + codeql/controlflow: + version: 2.0.23 + codeql/csharp-all: + version: 5.4.4 + codeql/dataflow: + version: 2.0.23 + codeql/mad: + version: 1.0.39 + codeql/ssa: + version: 2.0.15 + codeql/threat-models: + version: 1.0.39 + codeql/tutorial: + version: 1.0.39 + codeql/typetracking: + version: 2.0.23 + codeql/util: + version: 2.0.26 + codeql/xml: + version: 1.0.39 +compiled: false diff --git a/codeql-custom-queries-csharp/codeql-pack.yml b/codeql-custom-queries-csharp/codeql-pack.yml new file mode 100644 index 0000000..f7df349 --- /dev/null +++ b/codeql-custom-queries-csharp/codeql-pack.yml @@ -0,0 +1,7 @@ +--- +library: false +warnOnImplicitThis: false +name: getting-started/codeql-extra-queries-csharp +version: 1.0.0 +dependencies: + codeql/csharp-all: ^5.4.4 diff --git a/codeql-custom-queries-csharp/example.ql b/codeql-custom-queries-csharp/example.ql new file mode 100644 index 0000000..11b8d3b --- /dev/null +++ b/codeql-custom-queries-csharp/example.ql @@ -0,0 +1,12 @@ +/** + * This is an automatically generated file + * @name Hello world + * @kind problem + * @problem.severity warning + * @id csharp/example/hello-world + */ + +import csharp + +from File f +select f, "Hello, world!" \ No newline at end of file