Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions AzureMcp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.Core.UnitTests", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.Tests", "core\tests\AzureMcp.Tests\AzureMcp.Tests.csproj", "{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.AppConfig.UnitTests", "areas\appconfig\tests\AzureMcp.AppConfig.UnitTests\AzureMcp.AppConfig.UnitTests.csproj", "{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1097,6 +1099,18 @@ Global
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}.Release|x64.Build.0 = Release|Any CPU
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}.Release|x86.ActiveCfg = Release|Any CPU
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}.Release|x86.Build.0 = Release|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|x64.ActiveCfg = Debug|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|x64.Build.0 = Debug|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|x86.Build.0 = Debug|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Release|Any CPU.Build.0 = Release|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Release|x64.ActiveCfg = Release|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Release|x64.Build.0 = Release|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Release|x86.ActiveCfg = Release|Any CPU
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1240,5 +1254,6 @@ Global
{2A3CD1B4-38A3-46A1-AEDC-2C2AC47CB8F1} = {8783C0BC-EE27-8E0C-7452-5882FB8E96CA}
{1AE3FC50-8E8C-4637-AAB1-A871D5FB4535} = {8783C0BC-EE27-8E0C-7452-5882FB8E96CA}
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7} = {8783C0BC-EE27-8E0C-7452-5882FB8E96CA}
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD} = {7ECA6DB2-F8EF-407B-F2FD-DEF81B86CC73}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine.Parsing;
using System.Text.Json;
using AzureMcp.AppConfig.Commands.Account;
using AzureMcp.AppConfig.Models;
using AzureMcp.AppConfig.Services;
using AzureMcp.Core.Models.Command;
using AzureMcp.Core.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
using static AzureMcp.AppConfig.Commands.Account.AccountListCommand;

namespace AzureMcp.AppConfig.UnitTests.Account;

[Trait("Area", "AppConfig")]
public class AccountListCommandTests
{
private readonly IServiceProvider _serviceProvider;
private readonly IAppConfigService _appConfigService;
private readonly ILogger<AccountListCommand> _logger;
private readonly AccountListCommand _command;
private readonly CommandContext _context;
private readonly Parser _parser;

public AccountListCommandTests()
{
_appConfigService = Substitute.For<IAppConfigService>();
_logger = Substitute.For<ILogger<AccountListCommand>>();

_command = new(_logger);
_parser = new(_command.GetCommand());
_serviceProvider = new ServiceCollection()
.AddSingleton(_appConfigService)
.BuildServiceProvider();
_context = new(_serviceProvider);
}

[Fact]
public async Task ExecuteAsync_ReturnsAccounts_WhenAccountsExist()
{
// Arrange
var expectedAccounts = new List<AppConfigurationAccount>
{
new() { Name = "account1", Location = "East US", Endpoint = "https://account1.azconfig.io" },
new() { Name = "account2", Location = "West US", Endpoint = "https://account2.azconfig.io" }
};
_appConfigService.GetAppConfigAccounts(
"sub123",
Arg.Any<string?>(),
Arg.Any<RetryPolicyOptions?>())
.Returns(expectedAccounts);

var args = _parser.Parse(["--subscription", "sub123"]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(200, response.Status);
Assert.NotNull(response.Results);

var json = JsonSerializer.Serialize(response.Results);
var result = JsonSerializer.Deserialize<AccountListCommandResult>(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

Assert.NotNull(result);
Assert.Equal(2, result.Accounts.Count);
Assert.Equal("account1", result.Accounts[0].Name);
Assert.Equal("account2", result.Accounts[1].Name);
}

[Fact]
public async Task ExecuteAsync_ReturnsNull_WhenNoAccountsExist()
{
// Arrange
var expectedAccounts = new List<AppConfigurationAccount>();

_appConfigService.GetAppConfigAccounts(
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<RetryPolicyOptions>())
.Returns(expectedAccounts);

var args = _parser.Parse(["--subscription", "sub123"]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(200, response.Status);
Assert.Null(response.Results);
}

[Fact]
public async Task ExecuteAsync_Returns500_WhenServiceThrowsException()
{
// Arrange
_appConfigService.GetAppConfigAccounts(
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<RetryPolicyOptions>())
.Returns(Task.FromException<List<AppConfigurationAccount>>(new Exception("Service error")));
Comment thread
chidozieononiwu marked this conversation as resolved.

var args = _parser.Parse(["--subscription", "sub123"]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(500, response.Status);
Assert.Contains("Service error", response.Message);
}

[Fact]
public async Task ExecuteAsync_Returns400_WhenSubscriptionIsMissing()
{
// Arrange && Act
var response = await _command.ExecuteAsync(_context, _parser.Parse([]));

// Assert
Assert.Equal(400, response.Status);
Assert.Contains("required", response.Message.ToLower());
}

[Fact]
public async Task ExecuteAsync_Returns503_WhenServiceIsUnavailable()
{
// Arrange
_appConfigService.GetAppConfigAccounts(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<RetryPolicyOptions>())
.ThrowsAsync(new HttpRequestException("Service Unavailable", null, System.Net.HttpStatusCode.ServiceUnavailable));

var args = _parser.Parse(["--subscription", "sub123"]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(503, response.Status);
Assert.Contains("Service Unavailable", response.Message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NSubstitute" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\core\tests\AzureMcp.Tests\AzureMcp.Tests.csproj" />
<ProjectReference Include="..\..\src\AzureMcp.AppConfig\AzureMcp.AppConfig.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine.Parsing;
using System.Text.Json;
using AzureMcp.AppConfig.Commands.KeyValue;
using AzureMcp.AppConfig.Services;
using AzureMcp.Core.Models.Command;
using AzureMcp.Core.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
using static AzureMcp.AppConfig.Commands.KeyValue.KeyValueDeleteCommand;

namespace AzureMcp.AppConfig.UnitTests.KeyValue;

[Trait("Area", "AppConfig")]
public class KeyValueDeleteCommandTests
{
private readonly IServiceProvider _serviceProvider;
private readonly IAppConfigService _appConfigService;
private readonly ILogger<KeyValueDeleteCommand> _logger;
private readonly KeyValueDeleteCommand _command;
private readonly CommandContext _context;
private readonly Parser _parser;

public KeyValueDeleteCommandTests()
{
_appConfigService = Substitute.For<IAppConfigService>();
_logger = Substitute.For<ILogger<KeyValueDeleteCommand>>();

_command = new(_logger);
_parser = new(_command.GetCommand());
_serviceProvider = new ServiceCollection()
.AddSingleton(_appConfigService)
.BuildServiceProvider();
_context = new(_serviceProvider);
}

[Fact]
public async Task ExecuteAsync_DeletesKeyValue_WhenValidParametersProvided()
{
// Arrange
var args = _parser.Parse([
"--subscription", "sub123",
"--account-name", "account1",
"--key", "my-key"
]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(200, response.Status);
await _appConfigService.Received(1).DeleteKeyValue(
"account1",
"my-key",
"sub123",
null,
Arg.Any<RetryPolicyOptions>(),
null);

var json = JsonSerializer.Serialize(response.Results);
var result = JsonSerializer.Deserialize<KeyValueDeleteCommandResult>(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

Assert.NotNull(result);
Assert.Equal("my-key", result.Key);
}

[Fact]
public async Task ExecuteAsync_DeletesKeyValueWithLabel_WhenLabelProvided()
{
// Arrange
var args = _parser.Parse([
"--subscription", "sub123",
"--account-name", "account1",
"--key", "my-key",
"--label", "prod"
]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(200, response.Status);
await _appConfigService.Received(1).DeleteKeyValue(
"account1",
"my-key",
"sub123", null,
Comment thread
chidozieononiwu marked this conversation as resolved.
Arg.Any<RetryPolicyOptions>(),
"prod");

var json = JsonSerializer.Serialize(response.Results);
var result = JsonSerializer.Deserialize<KeyValueDeleteCommandResult>(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

Assert.NotNull(result);
Assert.Equal("my-key", result.Key);
Assert.Equal("prod", result.Label);
}

[Fact]
public async Task ExecuteAsync_Returns500_WhenServiceThrowsException()
{
// Arrange
_appConfigService.DeleteKeyValue(
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<RetryPolicyOptions>(),
Arg.Any<string>())
.ThrowsAsync(new Exception("Failed to delete key-value"));

var args = _parser.Parse([
"--subscription", "sub123",
"--account-name", "account1",
"--key", "my-key"
]);

// Act
var response = await _command.ExecuteAsync(_context, args);

// Assert
Assert.Equal(500, response.Status);
Assert.Contains("Failed to delete key-value", response.Message);
}

[Theory]
[InlineData("--account-name", "account1", "--key", "my-key")] // Missing subscription
[InlineData("--subscription", "sub123", "--key", "my-key")] // Missing account-name
[InlineData("--subscription", "sub123", "--account-name", "account1")] // Missing key
public async Task ExecuteAsync_Returns400_WhenRequiredParametersAreMissing(params string[] args)
{
// Arrange
var parseResult = _parser.Parse(args);

// Act
var response = await _command.ExecuteAsync(_context, parseResult);

// Assert
Assert.Equal(400, response.Status);
Assert.Contains("required", response.Message.ToLower());
}
}
Loading
Loading