11using System ;
2+ using System . Collections . Generic ;
23using System . Diagnostics ;
34using System . IO ;
45using System . Linq ;
78using Azure . Identity ;
89using Azure . Security . KeyVault . Secrets ;
910using DataPipelineTools . Tests . Common ;
10- using Microsoft . VisualBasic . FileIO ;
11+ using Flurl . Util ;
12+ using Microsoft . VisualStudio . TestPlatform . ObjectModel ;
1113using NUnit . Framework ;
1214using SearchOption = System . IO . SearchOption ;
1315
@@ -43,6 +45,8 @@ protected bool UseFunctionsEmulator
4345 protected string ServicePrincipalName => TestContext . Parameters [ "ServicePrincipalName" ] ;
4446 protected string ApplicationInsightsName => TestContext . Parameters [ "ApplicationInsightsName" ] ;
4547
48+ protected abstract string FunctionUri { get ; }
49+
4650
4751 // The properties that we get from Azure Key Vault are cached for reuse
4852 private string _functionsAppKey ;
@@ -151,6 +155,39 @@ protected bool IsRunningOnCIServer
151155 }
152156
153157 protected string GetKeyVaultSecretValue ( string secretName )
158+ {
159+ var client = GetKeyVaultClient ( ) ;
160+
161+ try
162+ {
163+ var result = client . GetSecretAsync ( secretName ) . Result ;
164+
165+ return result ? . Value ? . Value ;
166+ }
167+ catch ( Exception ex )
168+ {
169+ throw new SettingsException (
170+ $ "The key vault { KeyVaultName } is inaccessible or has been deleted. Check your run settings file.\n \n Inner Exception Message:\n { ex . Message . Split ( '\n ' ) . First ( ) } ") ;
171+ }
172+ }
173+
174+
175+ protected IEnumerable < string > GetKeyVaultSecretNames ( )
176+ {
177+ var client = GetKeyVaultClient ( ) ;
178+ try
179+ {
180+ var results = client . GetPropertiesOfSecrets ( ) ;
181+ return results . Select ( x => x . Name ) ;
182+ }
183+ catch ( Exception ex )
184+ {
185+ throw new SettingsException (
186+ $ "The key vault { KeyVaultName } is inaccessible or has been deleted. Check your run settings file.\n \n Inner Exception Message:\n { ex . Message . Split ( '\n ' ) . First ( ) } ") ;
187+ }
188+ }
189+
190+ private SecretClient GetKeyVaultClient ( )
154191 {
155192 /* For some reason the DefaultAzureCredential (SharedTokenCacheCredential / VisualStudioCredential) returns a 403 trying to access the key vault, even when access policies are configured correctly
156193 * We either use one of the following to authenticate:
@@ -159,25 +196,21 @@ protected string GetKeyVaultSecretValue(string secretName)
159196 *
160197 * See here for more info: https://docs.microsoft.com/en-us/answers/questions/74848/access-denied-to-first-party-service.html
161198 */
162- var credential = new DefaultAzureCredential ( new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true , ExcludeVisualStudioCredential = true } ) ;
199+ var credential = new DefaultAzureCredential ( new DefaultAzureCredentialOptions
200+ { ExcludeSharedTokenCacheCredential = true , ExcludeVisualStudioCredential = true } ) ;
163201
164202 if ( string . IsNullOrWhiteSpace ( KeyVaultName ) )
165203 throw new ArgumentException ( "The run setting file does not have a value for 'KeyVaultName'" ) ;
166204
167205 var keyVaultUri = $ "https://{ KeyVaultName } .vault.azure.net";
168- var client = new SecretClient ( new Uri ( keyVaultUri ) , credential ) ;
169- var result = client . GetSecretAsync ( secretName ) . Result ;
206+ return new SecretClient ( new Uri ( keyVaultUri ) , credential ) ;
170207
171- return result ? . Value ? . Value ;
172208 }
173209
174210
175211
176212
177213
178-
179-
180-
181214 #region Azure Functions Local Host
182215 // We use one time setup and teardown to generate a single instance of the emulator across all classes that implement this base class
183216
@@ -186,6 +219,13 @@ protected string GetKeyVaultSecretValue(string secretName)
186219 [ OneTimeSetUp ]
187220 public void StartFunctionsEmulator ( )
188221 {
222+ // If the run settings is referencing secrets via key vault, make sure we can connect
223+ if ( TestContext . Parameters . Names . Any ( x => x . StartsWith ( "KeyVaultSecret" ) && ! string . IsNullOrWhiteSpace ( TestContext . Parameters [ x ] . ToString ( ) ) ) )
224+ GetKeyVaultSecretNames ( ) ;
225+
226+
227+ // Start the local functions emulator if required. Use a lock so that multiple test classes inheriting from this base class share a
228+ // functions emulator instance
189229 lock ( _functionsProcessLock )
190230 {
191231 if ( UseFunctionsEmulator )
@@ -201,6 +241,7 @@ public void StartFunctionsEmulator()
201241 [ OneTimeTearDown ]
202242 public void StopFunctionsEmulator ( )
203243 {
244+ // Once the last instance finishes, stop the local emulator instance if we're using it.
204245 lock ( _functionsProcessLock )
205246 {
206247 if ( UseFunctionsEmulator )
0 commit comments