1
1
using System ;
2
- using System . Runtime . InteropServices . ComTypes ;
2
+ using System . Diagnostics ;
3
+ using System . IO ;
4
+ using System . Linq ;
5
+ using System . Reflection ;
6
+ using System . Threading ;
3
7
using Azure . Identity ;
4
8
using Azure . Security . KeyVault . Secrets ;
5
9
using DataPipelineTools . Tests . Common ;
6
- using Microsoft . Extensions . Logging ;
10
+ using Microsoft . VisualBasic . FileIO ;
7
11
using NUnit . Framework ;
12
+ using SearchOption = System . IO . SearchOption ;
8
13
9
14
namespace DataPipelineTools . Functions . Tests
10
15
{
11
16
/// <summary>
12
- /// Base class for functions tests. Exposes the
17
+ /// Base class for functions integration tests. Exposes the run settings as properties, along with secrets from either the .runsettings file directly, or
18
+ /// Azure Key Vault as specified by the .runsettings file.
13
19
/// </summary>
14
20
public abstract class IntegrationTestBase : TestBase
15
21
{
16
- public IntegrationTestBase ( )
22
+ protected IntegrationTestBase ( )
17
23
{
18
24
if ( TestContext . Parameters . Count == 0 )
19
25
throw new ArgumentException ( "No setting file is configured for the integration tests." ) ;
@@ -29,22 +35,80 @@ protected bool UseFunctionsEmulator
29
35
}
30
36
}
31
37
32
- protected string FunctionsAppName => TestContext . Parameters [ "FunctionsAppName" ] ;
33
- protected string FunctionsAppUrl => TestContext . Parameters [ "FunctionsAppUrl" ] ;
38
+ protected string FunctionsAppName => UseFunctionsEmulator ? "localhost" : TestContext . Parameters [ "FunctionsAppName" ] ;
39
+ protected string FunctionsAppUrl => UseFunctionsEmulator ? "http://localhost:7071" : $ "https:// { TestContext . Parameters [ "FunctionsAppUrl" ] } " ;
34
40
protected string StorageAccountName => TestContext . Parameters [ "StorageAccountName" ] ;
35
41
protected string StorageContainerName => TestContext . Parameters [ "StorageContainerName" ] ;
36
42
protected string KeyVaultName => TestContext . Parameters [ "KeyVaultName" ] ;
37
43
protected string ServicePrincipalName => TestContext . Parameters [ "ServicePrincipalName" ] ;
38
44
protected string ApplicationInsightsName => TestContext . Parameters [ "ApplicationInsightsName" ] ;
39
-
40
-
41
-
42
-
43
- protected string FunctionsAppKey => TestContext . Parameters [ "FunctionsAppKey" ] ?? GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretFunctionsAppKey" ] ) ;
44
- protected string ServicePrincipalSecretKey => TestContext . Parameters [ "ServicePrincipalSecretKey" ] ?? GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretServicePrincipalSecretKey" ] ) ;
45
- protected string StorageContainerSasToken => TestContext . Parameters [ "StorageContainerSasToken" ] ?? GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretStorageContainerSasToken" ] ) ;
46
- protected string StorageAccountAccessKey => TestContext . Parameters [ "StorageAccountAccessKey" ] ?? GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretStorageAccountAccessKey" ] ) ;
47
- protected string ApplicationInsightsKey => TestContext . Parameters [ "ApplicationInsightsKey" ] ?? GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretApplicationInsightsKey" ] ) ;
45
+
46
+
47
+ // The properties that we get from Azure Key Vault are cached for reuse
48
+ private string _functionsAppKey ;
49
+ protected string FunctionsAppKey
50
+ {
51
+ get
52
+ {
53
+ if ( _functionsAppKey == null )
54
+ _functionsAppKey = TestContext . Parameters [ "FunctionsAppKey" ] ??
55
+ GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretFunctionsAppKey" ] ) ;
56
+
57
+ return _functionsAppKey ;
58
+ }
59
+ }
60
+
61
+ private string _servicePrincipalSecretKey ;
62
+ protected string ServicePrincipalSecretKey
63
+ {
64
+ get
65
+ {
66
+ if ( _servicePrincipalSecretKey == null )
67
+ _servicePrincipalSecretKey = TestContext . Parameters [ "ServicePrincipalSecretKey" ] ??
68
+ GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretServicePrincipalSecretKey" ] ) ;
69
+
70
+ return _servicePrincipalSecretKey ;
71
+ }
72
+ }
73
+
74
+ private string _storageContainerSasToken ;
75
+ protected string StorageContainerSasToken
76
+ {
77
+ get
78
+ {
79
+ if ( _storageContainerSasToken == null )
80
+ _storageContainerSasToken = TestContext . Parameters [ "StorageContainerSasToken" ] ??
81
+ GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretStorageContainerSasToken" ] ) ;
82
+
83
+ return _storageContainerSasToken ;
84
+ }
85
+ }
86
+
87
+ private string _storageAccountAccessKey ;
88
+ protected string StorageAccountAccessKey
89
+ {
90
+ get
91
+ {
92
+ if ( _storageAccountAccessKey == null )
93
+ _storageAccountAccessKey = TestContext . Parameters [ "StorageAccountAccessKey" ] ??
94
+ GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretStorageAccountAccessKey" ] ) ;
95
+
96
+ return _storageAccountAccessKey ;
97
+ }
98
+ }
99
+
100
+ private string _applicationInsightsKey ;
101
+ protected string ApplicationInsightsKey
102
+ {
103
+ get
104
+ {
105
+ if ( _applicationInsightsKey == null )
106
+ _applicationInsightsKey = TestContext . Parameters [ "ApplicationInsightsKey" ] ??
107
+ GetKeyVaultSecretValue ( TestContext . Parameters [ "KeyVaultSecretApplicationInsightsKey" ] ) ;
108
+
109
+ return _applicationInsightsKey ;
110
+ }
111
+ }
48
112
49
113
50
114
[ Test ]
@@ -59,12 +123,13 @@ public void Test_RunSettingsLoadedOk()
59
123
Assert . IsNotNull ( ServicePrincipalName ) ;
60
124
Assert . IsNotNull ( ApplicationInsightsName ) ;
61
125
Assert . IsNotNull ( FunctionsAppKey ) ;
126
+
62
127
Assert . IsNotNull ( ServicePrincipalSecretKey ) ;
63
128
Assert . IsNotNull ( StorageContainerSasToken ) ;
64
129
Assert . IsNotNull ( StorageAccountAccessKey ) ;
65
130
Assert . IsNotNull ( ApplicationInsightsKey ) ;
66
131
67
- // Check that the StorageContainerSasToken got unquoted correctly
132
+ // Check that the StorageContainerSasToken got unquoted correctly from the .runsettings XML file.
68
133
Assert . IsFalse ( StorageContainerSasToken . Contains ( "&" ) ) ;
69
134
}
70
135
@@ -84,12 +149,7 @@ protected bool IsRunningOnCIServer
84
149
return false ;
85
150
}
86
151
}
87
-
88
- protected void StartLocalFunctionsInstance ( )
89
- {
90
- throw new NotImplementedException ( ) ;
91
- }
92
-
152
+
93
153
protected string GetKeyVaultSecretValue ( string secretName )
94
154
{
95
155
/* For some reason the DefaultAzureCredential (SharedTokenCacheCredential / VisualStudioCredential) returns a 403 trying to access the key vault, even when access policies are configured correctly
@@ -111,7 +171,101 @@ protected string GetKeyVaultSecretValue(string secretName)
111
171
return result ? . Value ? . Value ;
112
172
}
113
173
114
-
115
174
175
+
176
+
177
+
178
+
179
+
180
+
181
+ #region Azure Functions Local Host
182
+ // We use one time setup and teardown to generate a single instance of the emulator across all classes that implement this base class
183
+
184
+ private static object _functionsProcessLock = new object ( ) ;
185
+
186
+ [ OneTimeSetUp ]
187
+ public void StartFunctionsEmulator ( )
188
+ {
189
+ lock ( _functionsProcessLock )
190
+ {
191
+ if ( UseFunctionsEmulator )
192
+ {
193
+ if ( InstanceCount == 0 )
194
+ StartFunctionsEmulatorInternal ( ) ;
195
+
196
+ InstanceCount ++ ;
197
+ }
198
+ }
199
+ }
200
+
201
+ [ OneTimeTearDown ]
202
+ public void StopFunctionsEmulator ( )
203
+ {
204
+ lock ( _functionsProcessLock )
205
+ {
206
+ if ( UseFunctionsEmulator )
207
+ {
208
+ InstanceCount -- ;
209
+
210
+ if ( InstanceCount == 0 )
211
+ StopFunctionsEmulatorInternal ( ) ;
212
+ }
213
+ }
214
+ }
215
+
216
+
217
+ protected static bool IsEmulatorRunning => LocalFunctionsHostProcess != null ;
218
+ private static Process LocalFunctionsHostProcess { get ; set ; }
219
+ private static int InstanceCount { get ; set ; }
220
+
221
+
222
+
223
+ private void StartFunctionsEmulatorInternal ( )
224
+ {
225
+ if ( IsEmulatorRunning )
226
+ return ;
227
+
228
+ string appData = Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ;
229
+ string toolsPath = Path . Join ( appData , "AzureFunctionsTools" , "Releases" ) ;
230
+
231
+ var toolsVersions = Directory . GetFiles ( toolsPath , "func.exe" , SearchOption . AllDirectories ) ;
232
+ var latestToolsVersion = toolsVersions . OrderBy ( x => x ) . FirstOrDefault ( ) ;
233
+
234
+ if ( latestToolsVersion == null )
235
+ throw new FileNotFoundException ( "The Azure Functions Core tools are not installed. Run the functions app locally to install the tools." ) ;
236
+
237
+ const string args = "host start" ;
238
+ string binDir = Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ;
239
+
240
+ binDir = binDir . Replace ( "DataPipelineTools.Functions.Tests" , "DataPipelineTools.Functions" ) ;
241
+
242
+ binDir = @"C:\Users\Niall\src\sqlcollaborative\AzureDataPipelineTools\DataPipelineTools.Functions\bin\Debug" ;
243
+
244
+ ProcessStartInfo hostProcess = new ProcessStartInfo
245
+ {
246
+ FileName = latestToolsVersion ,
247
+ Arguments = args ,
248
+ WorkingDirectory = binDir ,
249
+ CreateNoWindow = false ,
250
+ WindowStyle = ProcessWindowStyle . Normal
251
+ } ;
252
+
253
+ LocalFunctionsHostProcess = Process . Start ( hostProcess ) ;
254
+
255
+ // Sleep for 5 seconds to allow the emulated functions app to start
256
+ Thread . Sleep ( 5000 ) ;
257
+ }
258
+
259
+ private void StopFunctionsEmulatorInternal ( )
260
+ {
261
+ if ( ! IsEmulatorRunning )
262
+ return ;
263
+
264
+ LocalFunctionsHostProcess . Kill ( ) ;
265
+ LocalFunctionsHostProcess . WaitForExit ( ) ;
266
+ LocalFunctionsHostProcess . Dispose ( ) ;
267
+ }
268
+
269
+ #endregion Azure Functions Local Host
116
270
}
117
271
}
0 commit comments