Unit testing with Cosmos Db fails within Azure Devops pipeline - c#

I have written unit test cases where I have my test cases written against Cosmos Db emulator. (Those who don't know what emulator is , it is a local development cosmos Db provided by Microsoft which are generally used to test your queries)
In my unit test case I am instantiating the Emulator db and then running the test cases. problem occurs when I push this changes to my Azure devops pipeline. there the test cases fails with error as
Target machine actively refused the connection.
It does mean it is not able to instansiate db. How can i fix this. Any idea??
here is the initial code for testing
public class CosmosDataFixture : IDisposable
{
public static readonly string CosmosEndpoint = "https://localhost:8081";
public static readonly string EmulatorKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
public static readonly string DatabaseId = "testdb";
public static readonly string RecordingCollection = "testcolec";
public static string Root = Directory.GetParent( Directory.GetCurrentDirectory() ).Parent.Parent.FullName;
public static DocumentClient client { get; set; }
public async Task ReadConfigAsync()
{
// StartEmulatorDatabaseFromPowerShell();
client = new DocumentClient( new Uri( CosmosEndpoint ), EmulatorKey,
new ConnectionPolicy
{
ConnectionMode = ConnectionMode.Direct,
ConnectionProtocol = Protocol.Tcp
} );
await client.CreateDatabaseIfNotExistsAsync( new Database { Id = DatabaseId } );
await client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri( DatabaseId ),
new DocumentCollection { Id = RecordingCollection } );
await ReadAllData( client );
}
public CosmosDataFixture()
{
ReadConfigAsync();
}
public void Dispose()
{
DeleteDatabaseFromPowerShell();// this is also defined in above class
}
}
public class CosmosDataTests : IClassFixture<CosmosDataFixture>
{ // mu unit test case goes here

You need to add this statement to your yaml Pipeline:
- task: PowerShell#2
inputs:
targetType: 'inline'
script: |
Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
Start-CosmosDbEmulator
And the Connection String for CosmosDB should be: AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
You can instantiate the CosmosDB client like that:
var connectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
var client = new CosmosClient(connectionString);
var database = client.CreateDatabaseIfNotExistsAsync("testdb");
var container = await database.Database.CreateContainerIfNotExistsAsync("testcolec", "/partitionKey");

Related

Proper way to use Azure Keyvault with Dependency Injection

Information
I am building an Azure hosted protected API. I am targeting .Net 5 and using the MVC pattern. Inside this API, I have two DAL (data access layer) classes, which are added to the service container via the Startup.cs file. Both of these classes are Transient and expect a connection string in their constructors.
Problem
I want to move the connection strings from the appsetting.json file of the API to the Azure key vault, but I only want to have to pull down the connection strings once from the Azure key vault and use them as static values to initialize any instances of the DAL classes. I've tried calling the respective method of the SecretClient class called GetSecretAsync and I use the constructor of SecretClient(Uri, ManagedIdentityCredential). This all works when I call this after Startup.cs has ran, but when I try to do it inside of the Startup.cs it seems to hang for some unknown reason. I know that the calls do work later in the pipeline as I've tested them in the API as well as in the Azure Portal. This is what I've been trying to do via code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//For Production, set the properties of the KeyVault
Secrets.SetProps(new KeyVaultHelper(Configuration)).GetAwaiter().GetResult();
//Get the AzureAd section
try
{
//Instead of storing the AzureAdBinding info in appsettings, I created a
//json string which deserializes into a Dictionary which is used to
//create the ConfigurationSection object
Dictionary<string, string> azureadkvps = JsonConvert.DeserializeObject<Dictionary<string, string>>(Secrets.AzureAdBinding);
//Turn it into an IConfigurationSection
IConfigurationSection azureconfigsection = new ConfigurationBuilder().AddInMemoryCollection(azureadkvps).Build().GetSection("AzureAd");
//Bind authentication to Configuration AzureAd section
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(azureconfigsection);
}
catch { }
Secrets.cs
/// <summary>
/// Static class to hold values of keys pulled from Azure key vault
/// </summary>
public static class Secrets
{
private const string MOBILE_APP_ID = "xxxxxxxxxx";
private const string B_DB_CONN = "xxxxxxxxxx";
private const string MOBILE_ADMIN_ROLE = "xxxxxxxxxx";
private const string MOBILE_API_SCOPES = "xxxxxxxxxx";
private const string MOBILE_COMMON_ROLE = "xxxxxxxxxx";
private const string BL_DB_CONN = "xxxxxxxxxx";
private const string AZUREAD_BINDING = "xxxxxxxxxx";
public static string BLConn { get; private set; } = null;
public static string BConn { get; private set; } = null;
public static string MobileAdminRole { get; private set; } = null;
public static string MobileUserRole { get; private set; } = null;
public static string MobileApiScopes { get; private set; } = null;
public static string MobileClientID { get; private set; } = null;
public static string AzureAdBinding { get; private set; } = null;
public async static Task SetProps(IKeyVaultHelp keyvault)
{
Task<string>[] secretretrievaltasks = new Task<string>[7];
secretretrievaltasks[0] = keyvault.GetSecretAsync(MOBILE_APP_ID);
secretretrievaltasks[1] = keyvault.GetSecretAsync(B_DB_CONN);
secretretrievaltasks[2] = keyvault.GetSecretAsync(MOBILE_ADMIN_ROLE);
secretretrievaltasks[3] = keyvault.GetSecretAsync(MOBILE_API_SCOPES);
secretretrievaltasks[4] = keyvault.GetSecretAsync(MOBILE_COMMON_ROLE);
secretretrievaltasks[5] = keyvault.GetSecretAsync(BL_DB_CONN);
secretretrievaltasks[6] = keyvault.GetSecretAsync(AZUREAD_BINDING);
await Task.WhenAll(secretretrievaltasks).ConfigureAwait(false);
BLConn = secretretrievaltasks[5].Result;
BConn = secretretrievaltasks[1].Result;
MobileAdminRole = secretretrievaltasks[2].Result;
MobileUserRole = secretretrievaltasks[4].Result;
MobileApiScopes = secretretrievaltasks[3].Result;
MobileClientID = secretretrievaltasks[0].Result;
AzureAdBinding = secretretrievaltasks[6].Result;
}
}
KeyVaultHelper.cs
public class KeyVaultHelper : IKeyVaultHelp
{
private readonly IConfiguration _config;
public KeyVaultHelper(IConfiguration config)
{
_config = config;
}
public string GetSecret(string secretkey)
{
string kvuri = $"https://{_config.GetValue<string>("KVN")}.vault.azure.net";
var secretclient = new SecretClient(new Uri(kvuri), new ManagedIdentityCredential("xxxxxxxxxxxxxxxxxxxx"));
return secretclient.GetSecret(secretkey).Value.Value;
}
public async Task<string> GetSecretAsync(string secretkey)
{
string kvuri = $"https://{_config.GetValue<string>("KVN")}.vault.azure.net";
var secretclient = new SecretClient(new Uri(kvuri), new ManagedIdentityCredential());
return (await secretclient.GetSecretAsync(secretkey).ConfigureAwait(false)).Value.Value;
}
}
Perhaps I am going about this all wrong, but I'd like to be able to pull these values before adding the DAL classes to the service container so that the static values of the Secrets.cs hold the correct connection strings which will be used to construct the DAL objects.
I understand there is a way to add a Configuration file in Azure which can be built in Program.cs via the following, but I was hoping to just pull the values directly from KeyVault and use them throughout the lifetime of the application. Any direction or advice is appreciated.
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
})
.UseStartup<Startup>());
}
Thanks Camilo Terevinto, your comments are converting to an answer. So that it may helps to other community members to find this correct solution.
Instead in the Startup, use await with async Task Main in Program class where you already have the IConfiguration configured.
use AddAzureAppConfiguration if you're using app services, or
put the secrets in environment variables if you're running on VMs/containers
To populate the static values before the call to CreateHostBuilder(args).Build().Run(), you should call the async or await method.

Writing Xunit test cases in C#

I am kind of learning to write unit test cases and I am using Xunit framework. I have a scenario where I would like to write a test case to test different scenario in my cosmos db emulator. To do that I am trying to create a database, container and insert few test data in my cosmos db emulator and then write my facts and also delete it once test cases are completed...below is the code which I figured out from internet, would like to know if I am doing it correctly... and where can I start writing my test cases.
namespace Project.Tests
{
public class DatabaseFixture : IDisposable
{
private static readonly string CosmosEndpoint = "https://localhost:8081";
private static readonly string EmulatorKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
private static readonly string DatabaseId = "Recordings";
private static readonly string RecordingCollection = "testdata";
public DatabaseFixture()
{
var client = new DocumentClient( new Uri( CosmosEndpoint ), EmulatorKey,
new ConnectionPolicy
{
ConnectionMode = ConnectionMode.Direct,
ConnectionProtocol = Protocol.Tcp
} );
var databaseCreationResult = client.CreateDatabaseAsync( new Database { Id = DatabaseId } ).Result;
var collectionCreationResult = client.CreateDocumentCollectionAsync( UriFactory.CreateDatabaseUri( DatabaseId ),
new DocumentCollection { Id = RecordingCollection } ).Result;
var recData = new Recordings { Id = "Test" };
var itemResult = client
.CreateDocumentAsync(
UriFactory.CreateDocumentCollectionUri( DatabaseId, RecordingCollection ), recData )
.Result;
var document = client
.ReadDocumentAsync(
UriFactory.CreateDocumentUri( DatabaseId, RecordingCollection, itemResult.Resource.Id ) )
.Result;
Recordings site = (dynamic)document.Resource;
}
public void Dispose()
{
// ... clean up test data from the database ...
throw new NotImplementedException();
}
}
public class Recordings
{
public string Id { get; set; }
}
public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
DatabaseFixture fixture;
public MyDatabaseTests( DatabaseFixture fixture )
{
this.fixture = fixture;
}
// ... write tests, using fixture.Db to get access to the database server ...
}
}
Be careful that using a web API is not really part of the Unit Test philosophy. A Unit Test is usually expected to be independent from external interaction.
You can still use xUnit to peform your testing, but you are not in a UNIT test context.
If you have access to the code behind the service, you could Unit Test it without the Web layer. (as an exemple, you can Unit test directly the REST controller class.)
If you ignore this point, I think the response is already in your question.
You can directly write your tests in the test class.
public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
DatabaseFixture fixture;
public MyDatabaseTests( DatabaseFixture fixture )
{
this.fixture = fixture;
}
// Write test method here
[Fact]
private void MyTestMethod()
{
// Prepare Test
/// Prepare your test data here.
// Execute Test
/// Execute your test operation here.
// Validate Test
/// Use Assert methods here.
/// Assert.True(....);
}
}

How to do Integration Tests for Azure Webjobs V3?

Azure Webjob is now on V3, so this answer is not up to date anymore (How to integration test Azure Web Jobs?)
I imagine we need to do something like this:
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
using (host)
{
var jobHost = host.Services.GetService(typeof(IJobHost)) as JobHost;
var arguments = new Dictionary<string, object>
{
// parameters of MyQueueTriggerMethodAsync
};
await host.StartAsync();
await jobHost.CallAsync("MyQueueTriggerMethodAsync", arguments);
await host.StopAsync();
}
QueueTrigger Function
public MyService(
ILogger<MyService> logger
)
{
_logger = logger;
}
public async Task MyQueueTriggerMethodAsync(
[QueueTrigger("MyQueue")] MyObj obj
)
{
_logger.Log("ReadFromQueueAsync success");
}
But after that, how can I see what's happened?
What do you suggest to be able to do Integration Tests for Azure Webjobs V3?
I'm guessing this is a cross post with Github. The product team recommends looking at their own end-to-end testing for ideas on how to handle integration testing.
To summarize:
You can configure an IHost as a TestHost and add your integrated services to it.
public TestFixture()
{
IHost host = new HostBuilder()
.ConfigureDefaultTestHost<TestFixture>(b =>
{
b.AddAzureStorage();
})
.Build();
var provider = host.Services.GetService<StorageAccountProvider>();
StorageAccount = provider.GetHost().SdkObject;
}
Tests would look something like this:
/// <summary>
/// Covers:
/// - queue binding to custom object
/// - queue trigger
/// - table writing
/// </summary>
public static void QueueToICollectorAndQueue(
[QueueTrigger(TestQueueNameEtag)] CustomObject e2equeue,
[Table(TableName)] ICollector<ITableEntity> table,
[Queue(TestQueueName)] out CustomObject output)
{
const string tableKeys = "testETag";
DynamicTableEntity result = new DynamicTableEntity
{
PartitionKey = tableKeys,
RowKey = tableKeys,
Properties = new Dictionary<string, EntityProperty>()
{
{ "Text", new EntityProperty("before") },
{ "Number", new EntityProperty("1") }
}
};
table.Add(result);
result.Properties["Text"] = new EntityProperty("after");
result.ETag = "*";
table.Add(result);
output = e2equeue;
}
The difficulty in setting up a specific test depends on which triggers and outputs you are using and whether or not an emulator.

Getting database connection string in .NET Core integration tests with xunit

I'm doing integration tests for my .net core project.I need to get access to a connection string for my integration tests database.
I am using my base project as reference and using its Startup file. But I am getting this error
System.ArgumentNullException : Value cannot be null.
Parameter name: connectionString at Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Check.NotEmpty(String
value, String parameterName) in
/home/roji/projects/EFCore.PG/src/EFCore.PG/Utilities/Check.cs:line 99
at Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsExtensions.UseNpgsql(DbContextOptionsBuilder optionsBuilder, String connectionString, Action`1 npgsqlOptionsAction) in /home/roji/projects/EFCore.PG/src/EFCore.PG/Extensions/NpgsqlDbContextOptionsExtensions.cs:line 49
at SurveyAPI.Startup.<ConfigureServices>b__4_1(DbContextOptionsBuilder options)
My TestClientProvider class
public class TestClientProvider
{
public HttpClient Client { get; private set; }
public TestClientProvider()
{
var server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
Client = server.CreateClient();
}
}
the test class
public class UnitTest1
{
[Fact]
public async Task Test_Get()
{
var client = new TestClientProvider().Client;
var response = await client.GetAsync("/api");
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
How to make the tests use my current database ?
I have fixed this issue by getting the appsettings.json file and configuring the database connection in TestProvider class.
public class TestClientProvider
{
public HttpClient Client { get; private set; }
public TestClientProvider()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
WebHostBuilder webHostBuilder = new WebHostBuilder();
webHostBuilder.ConfigureServices(s => s.AddDbContext<DatabaseContext>(options => options.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))));
webHostBuilder.UseStartup<Startup>();
var server = new TestServer(webHostBuilder);
Client = server.CreateClient();
}
}

Cassandra - correct approach asp.net core application

I creating new asp.net core project and I decided to use Cassandra as a Database but I never do it before. I don't know my approach is correct? Can you gave me some advice to use this DB in .net core. (I using autofac as a dependency injection container, cassandra provider: CassandraCSharpDriver)
My current solve:
Create Cluster:
cluster = Cluster.Builder()
.AddContactPoint(cassandraData.ContactPoint)
.WithPort(int.Parse(cassandraData.Port))
.WithCredentials(cassandraData.User, cassandraData.Password)
.Build();
After that i inject this by dependency injection to my Session Cache:
public CassandraSessionCache(Cluster cluster)
{
_cluster = cluster;
_sessions = new ConcurrentDictionary<string, Lazy<ISession>>();
}
public ISession GetSession(string keyspaceName)
{
if (!_sessions.ContainsKey(keyspaceName))
_sessions.GetOrAdd(keyspaceName, key => new Lazy<ISession>(() =>
_cluster.Connect(key)));
var result = _sessions[keyspaceName];
return result.Value;
}
I Create also Map to Cassandra and use it my startup class.
After this configuration my repository look like this:
public class TestRepository : ITestRepository
{
private readonly CassandraSessionCache _cassandra;
private Mapper _mapper;
public TestRepository(CassandraSessionCache cassandra)
{
_cassandra = cassandra;
}
public async Task DeleteAsync(Guid id, string keySpace)
{
SetSessionAndMapper(keySpace);
await _mapper.DeleteAsync<Test>("WHERE id = ?", id);
}
public async Task<Install> GetAsync(string id, string keySpace)
{
SetSessionAndMapper(keySpace);
return await _mapper.FirstOrDefaultAsync<Test>("SELECT * FROM \"Test\" WHERE id = ?", id);
}
public async Task PostAsync(Test data, string keySpace)
{
SetSessionAndMapper(keySpace);
await _mapper.InsertAsync(data);
}
private void SetSessionAndMapper(string keySpace)
{
var session = _cassandra.GetSession(keySpace);
_mapper = new Mapper(session);
}
}
All is correct or I do something wrong?

Categories

Resources