Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backend API] Add Table Name property to appsettings.json #320

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace AzureOpenAIProxy.ApiApp.Configurations;

/// <summary>
/// This represents the settings entity for storage account.
/// </summary>
public class StorageAccountSettings
{
/// <summary>
/// Gets the name of the configuration settings.
/// </summary>
public const string Name = "StorageAccount";

/// <summary>
/// Gets or sets the <see cref="TableStorageSettings"/> instance.
/// </summary>
public TableStorageSettings TableStorage { get; set; } = new();
}
17 changes: 17 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Configurations/TableStorageSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace AzureOpenAIProxy.ApiApp.Configurations;

/// <summary>
/// This represents the settings entity for Azure Table Stroage.
/// </summary>
public class TableStorageSettings
{
/// <summary>
/// Gets the name of the configuration settings.
/// </summary>
public const string Name = "TableStorage";

/// <summary>
/// Gets or sets the table name.
/// </summary>
public string? TableName { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,31 @@ public static IServiceCollection AddTableStorageService(this IServiceCollection
return client;
});

return services;
}

/// <summary>
/// Gets the storage account configuration settings by reading appsettings.json.
/// </summary>
/// <param name="services"><see cref="IServiceCollection"/> instance.</param>
/// <returns>Returns <see cref="StorageAccountSettings"/> instance.</returns>
public static IServiceCollection AddStorageAccountSettings(this IServiceCollection services)
{
services.AddSingleton<StorageAccountSettings>(sp => {
var configuration = sp.GetService<IConfiguration>()
?? throw new InvalidOperationException($"{nameof(IConfiguration)} service is not registered.");

var settings = configuration.GetSection(AzureSettings.Name).GetSection(StorageAccountSettings.Name).Get<StorageAccountSettings>()
?? throw new InvalidOperationException($"{nameof(StorageAccountSettings)} could not be retrieved from the configuration.");

if (string.IsNullOrWhiteSpace(settings.TableStorage.TableName) == true)
{
throw new InvalidOperationException($"{StorageAccountSettings.Name}.{TableStorageSettings.Name} is not defined.");
}

return settings;
});

return services;
}
}
3 changes: 3 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
// Add OpenAPI service
builder.Services.AddOpenApiService();

// Add Storage Account settings
builder.Services.AddStorageAccountSettings();

// Add TableStorageClient
builder.Services.AddTableStorageService();

Expand Down
11 changes: 9 additions & 2 deletions src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using AzureOpenAIProxy.ApiApp.Models;
using Azure.Data.Tables;

using AzureOpenAIProxy.ApiApp.Configurations;
using AzureOpenAIProxy.ApiApp.Extensions;
using AzureOpenAIProxy.ApiApp.Models;

namespace AzureOpenAIProxy.ApiApp.Repositories;

Expand Down Expand Up @@ -39,28 +43,31 @@
/// <summary>
/// This represents the repository entity for the admin event.
/// </summary>
public class AdminEventRepository : IAdminEventRepository
public class AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings) : IAdminEventRepository
{
private readonly TableServiceClient _tableServiceClient = tableServiceClient ?? throw new ArgumentNullException(nameof(tableServiceClient));
private readonly StorageAccountSettings _storageAccountSettings = storageAccountSettings ?? throw new ArgumentNullException(nameof(storageAccountSettings));

/// <inheritdoc />
public async Task<AdminEventDetails> CreateEvent(AdminEventDetails eventDetails)

Check warning on line 52 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 52 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
throw new NotImplementedException();
}

/// <inheritdoc />
public async Task<List<AdminEventDetails>> GetEvents()

Check warning on line 58 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 58 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
throw new NotImplementedException();
}

/// <inheritdoc />
public async Task<AdminEventDetails> GetEvent(Guid eventId)

Check warning on line 64 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
throw new NotImplementedException();
}

/// <inheritdoc />
public async Task<AdminEventDetails> UpdateEvent(Guid eventId, AdminEventDetails eventDetails)

Check warning on line 70 in src/AzureOpenAIProxy.ApiApp/Repositories/AdminEventRepository.cs

View workflow job for this annotation

GitHub Actions / build-test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
throw new NotImplementedException();
}
Expand Down
5 changes: 5 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"OpenAI": "azure-openai-instances",
"Storage": "storage-connection-string"
}
},
"StorageAccount": {
"TableStorage": {
"TableName": "events"
}
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Azure.Data.Tables;
using Azure.Security.KeyVault.Secrets;

using AzureOpenAIProxy.ApiApp.Configurations;
using AzureOpenAIProxy.ApiApp.Extensions;

using FluentAssertions;
Expand Down Expand Up @@ -313,7 +314,7 @@
var services = new ServiceCollection();
var dict = new Dictionary<string, string>()
{
{ "Azure:KeyVault:SecretNames:Storage", secretName },

Check warning on line 317 in test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference argument for parameter 'value' in 'void Dictionary<string, string>.Add(string key, string value)'.
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
Expand Down Expand Up @@ -363,4 +364,127 @@
result.Should().NotBeNull()
.And.BeOfType<TableServiceClient>();
}

[Fact]
public void Given_Empty_AzureSettings_When_Added_ToServiceCollection_Then_It_Should_Throw_Exception()
{
// Arrange
var dict = new Dictionary<string, string>()
{
{ "Azure", string.Empty }
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.

var sc = new ServiceCollection();
sc.AddSingleton<IConfiguration>(config);

sc.AddStorageAccountSettings();

// Act
Action action = () => sc.BuildServiceProvider().GetService<StorageAccountSettings>();

// Assert
action.Should().Throw<InvalidOperationException>();
}

[Fact]
public void Given_Empty_StorageAccountSettings_When_Added_ToServiceCollection_Then_It_Should_Throw_Exception()
{
// Arrange
var dict = new Dictionary<string, string>()
{
{ "Azure:StorageAccount", string.Empty }
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.

var sc = new ServiceCollection();
sc.AddSingleton<IConfiguration>(config);

sc.AddStorageAccountSettings();

// Act
Action action = () => sc.BuildServiceProvider().GetService<StorageAccountSettings>();

// Assert
action.Should().Throw<InvalidOperationException>();
}

[Fact]
public void Given_Empty_TableStorageSettings_When_Added_ToServiceCollection_Then_It_Should_Throw_Exception()
{
// Arrange
var dict = new Dictionary<string, string>()
{
{ "Azure:StorageAccount:TableStorage", string.Empty }
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.

var sc = new ServiceCollection();
sc.AddSingleton<IConfiguration>(config);

sc.AddStorageAccountSettings();

// Act
Action action = () => sc.BuildServiceProvider().GetService<StorageAccountSettings>();

// Assert
action.Should().Throw<InvalidOperationException>();
}

[Theory]
[InlineData(default(string))]
[InlineData("")]
public void Given_NullOrEmpty_TableName_When_Added_ToServiceColleciton_Then_It_Should_Throw_Exception(string? tableName)
{
// Arrange
var dict = new Dictionary<string, string>()
{
{ "Azure:StorageAccount:TableStorage:TableName", tableName }

Check warning on line 448 in test/AzureOpenAIProxy.ApiApp.Tests/Extensions/ServiceCollectionExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference argument for parameter 'value' in 'void Dictionary<string, string>.Add(string key, string value)'.
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.

var sc = new ServiceCollection();
sc.AddSingleton<IConfiguration>(config);

sc.AddStorageAccountSettings();

// Act
Action action = () => sc.BuildServiceProvider().GetService<StorageAccountSettings>();

// Assert
action.Should().Throw<InvalidOperationException>();
}

[Theory]
[InlineData("table-name")]
public void Given_Appsettings_When_Added_ToServiceCollection_Then_It_Should_Return_StorageAccountSettings(string tableName)
{
// Arrange
var dict = new Dictionary<string, string>()
{
{ "Azure:StorageAccount:TableStorage:TableName", tableName }
};
#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build();
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.

var sc = new ServiceCollection();
sc.AddSingleton<IConfiguration>(config);

sc.AddStorageAccountSettings();

// Act
var settings = sc.BuildServiceProvider().GetService<StorageAccountSettings>();

// Assert
settings?.TableStorage.TableName.Should().BeEquivalentTo(tableName);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
using AzureOpenAIProxy.ApiApp.Models;
using Azure.Data.Tables;

using AzureOpenAIProxy.ApiApp.Configurations;
using AzureOpenAIProxy.ApiApp.Models;
using AzureOpenAIProxy.ApiApp.Repositories;

using Castle.Core.Configuration;

using FluentAssertions;

using Microsoft.Extensions.DependencyInjection;

using NSubstitute;

namespace AzureOpenAIProxy.ApiApp.Tests.Repositories;

public class AdminEventRepositoryTests
Expand All @@ -22,12 +29,42 @@
services.SingleOrDefault(p => p.ServiceType == typeof(IAdminEventRepository)).Should().NotBeNull();
}

[Fact]
public void Given_Null_TableServiceClient_When_Creating_AdminEventRepository_Then_It_Should_Throw_Exception()
{
// Arrange
var settings = Substitute.For<StorageAccountSettings>();
var tableServiceClient = default(TableServiceClient);

// Act
Action action = () => new AdminEventRepository(tableServiceClient, settings);

Check warning on line 40 in test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference argument for parameter 'tableServiceClient' in 'AdminEventRepository.AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings)'.

// Assert
action.Should().Throw<ArgumentNullException>();
}

[Fact]
public void Given_Null_StorageAccountSettings_When_Creating_AdminEventRepository_Then_It_Should_Throw_Exception()
{
// Arrange
var settings = default(StorageAccountSettings);
var tableServiceClient = Substitute.For<TableServiceClient>();

// Act
Action action = () => new AdminEventRepository(tableServiceClient, settings);

Check warning on line 54 in test/AzureOpenAIProxy.ApiApp.Tests/Repositories/AdminEventRepositoryTests.cs

View workflow job for this annotation

GitHub Actions / build-test

Possible null reference argument for parameter 'storageAccountSettings' in 'AdminEventRepository.AdminEventRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings)'.

// Assert
action.Should().Throw<ArgumentNullException>();
}

[Fact]
public void Given_Instance_When_CreateEvent_Invoked_Then_It_Should_Throw_Exception()
justinyoo marked this conversation as resolved.
Show resolved Hide resolved
{
// Arrange
var settings = Substitute.For<StorageAccountSettings>();
var tableServiceClient = Substitute.For<TableServiceClient>();
var eventDetails = new AdminEventDetails();
var repository = new AdminEventRepository();
var repository = new AdminEventRepository(tableServiceClient, settings);

// Act
Func<Task> func = async () => await repository.CreateEvent(eventDetails);
Expand All @@ -40,7 +77,9 @@
public void Given_Instance_When_GetEvents_Invoked_Then_It_Should_Throw_Exception()
{
// Arrange
var repository = new AdminEventRepository();
var settings = Substitute.For<StorageAccountSettings>();
var tableServiceClient = Substitute.For<TableServiceClient>();
var repository = new AdminEventRepository(tableServiceClient, settings);

// Act
Func<Task> func = async () => await repository.GetEvents();
Expand All @@ -53,8 +92,10 @@
public void Given_Instance_When_GetEvent_Invoked_Then_It_Should_Throw_Exception()
{
// Arrange
var settings = Substitute.For<StorageAccountSettings>();
var tableServiceClient = Substitute.For<TableServiceClient>();
var eventId = Guid.NewGuid();
var repository = new AdminEventRepository();
var repository = new AdminEventRepository(tableServiceClient, settings);

// Act
Func<Task> func = async () => await repository.GetEvent(eventId);
Expand All @@ -67,9 +108,11 @@
public void Given_Instance_When_UpdateEvent_Invoked_Then_It_Should_Throw_Exception()
{
// Arrange
var settings = Substitute.For<StorageAccountSettings>();
var tableServiceClient = Substitute.For<TableServiceClient>();
var eventId = Guid.NewGuid();
var eventDetails = new AdminEventDetails();
var repository = new AdminEventRepository();
var repository = new AdminEventRepository(tableServiceClient, settings);

// Act
Func<Task> func = async () => await repository.UpdateEvent(eventId, eventDetails);
Expand Down