Skip to content

Commit cc0a01c

Browse files
Niall LangleyNJLangley
authored andcommitted
Added route to categorise functions, eg https://<functions app name>/api/DataLake/GetItems
1 parent c6872ca commit cc0a01c

File tree

5 files changed

+112
-29
lines changed

5 files changed

+112
-29
lines changed

DataPipelineTools.Functions.Tests/DataLake/DataLakeFunctions/Integration/DataLakeGetItemsIntegrationTests.cs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace DataPipelineTools.Functions.Tests.DataLake.DataLakeFunctions.Integrati
1515
[Parallelizable(ParallelScope.Children)]
1616
public class DataLakeGetItemsIntegrationTests : IntegrationTestBase
1717
{
18-
protected override string FunctionUri => $"{FunctionsAppUrl}/api/DataLakeGetItems";
18+
protected override string FunctionUri => $"{FunctionsAppUrl}/api/DataLake/GetItems";
1919

2020
[SetUp]
2121
public void Setup()
@@ -30,6 +30,39 @@ public void Setup()
3030

3131

3232

33+
[Test]
34+
public async Task Test_FunctionReturnsError_With_MissingAccountParam()
35+
{
36+
var parameters = new Dictionary<string, string>
37+
{
38+
{DataLakeConfigFactory.ContainerParam, StorageContainerName}
39+
};
40+
var response = await RunQueryFromParameters(parameters);
41+
LogContent(response);
42+
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
43+
44+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
45+
dynamic results = GetResultsObject(response);
46+
Assert.IsNotNull(results.error);
47+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.AccountParamIsMandatory,results.error.ToString());
48+
}
49+
50+
[Test]
51+
public async Task Test_FunctionReturnsError_With_MissingContainerParam()
52+
{
53+
var parameters = new Dictionary<string, string>
54+
{
55+
{DataLakeConfigFactory.AccountParam, StorageAccountName}
56+
};
57+
var response = await RunQueryFromParameters(parameters);
58+
LogContent(response);
59+
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
60+
61+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
62+
dynamic results = GetResultsObject(response);
63+
Assert.IsNotNull(results.error);
64+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.ContainerParamIsMandatory, results.error.ToString());
65+
}
3366

3467
[Test]
3568
public async Task Test_FunctionIsRunnable_With_FunctionsServicePrincipalAuth()
@@ -48,11 +81,6 @@ public async Task Test_FunctionIsRunnable_With_FunctionsServicePrincipalAuth()
4881
Assert.AreEqual(AuthType.FunctionsServicePrincipal, (AuthType) results.authType);
4982
}
5083

51-
52-
53-
54-
55-
5684
[Test]
5785
public async Task Test_FunctionIsRunnable_With_UserServicePrincipalAuthAndPlainTextKey()
5886
{
@@ -108,6 +136,11 @@ public async Task Test_FunctionReturnsError_With_UserServicePrincipalAuthAndEmpt
108136
};
109137
var response = await RunQueryFromParameters(parameters);
110138
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
139+
140+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
141+
dynamic results = GetResultsObject(response);
142+
Assert.IsNotNull(results.error);
143+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.UserDefinedServicePrincipalParamsMissing, results.error.ToString());
111144
}
112145

113146
[Test]
@@ -126,6 +159,11 @@ public async Task Test_FunctionReturnsError_With_UserServicePrincipalAuthAndEmpt
126159
};
127160
var response = await RunQueryFromParameters(parameters);
128161
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
162+
163+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
164+
dynamic results = GetResultsObject(response);
165+
Assert.IsNotNull(results.error);
166+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.UserDefinedServicePrincipalParamsMissing, results.error.ToString());
129167
}
130168

131169

@@ -229,6 +267,11 @@ public async Task Test_FunctionReturnsError_With_SasTokenAuthAndEmptySasToken(st
229267
};
230268
var response = await RunQueryFromParameters(parameters);
231269
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
270+
271+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
272+
dynamic results = GetResultsObject(response);
273+
Assert.IsNotNull(results.error);
274+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.SasTokenParamMustHaveValue, results.error.ToString());
232275
}
233276

234277
[Test]
@@ -303,6 +346,11 @@ public async Task Test_FunctionReturnsError_With_AccountKeyAuthAndEmptyAccountKe
303346
};
304347
var response = await RunQueryFromParameters(parameters);
305348
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
349+
350+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
351+
dynamic results = GetResultsObject(response);
352+
Assert.IsNotNull(results.error);
353+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.AccountKeyParamMustHaveValue, results.error.ToString());
306354
}
307355

308356

@@ -367,6 +415,9 @@ public async Task Test_FunctionReturnsError_When_MultipleAuthTypesUsed(bool useU
367415
var response = await RunQueryFromParameters(parameters);
368416
Assert.AreEqual(expectedResponse, response.StatusCode);
369417

418+
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
419+
dynamic results = GetResultsObject(response);
420+
370421
// If the response was ok, check the correct auth type got used
371422
if (response.StatusCode == HttpStatusCode.OK)
372423
{
@@ -378,10 +429,14 @@ public async Task Test_FunctionReturnsError_When_MultipleAuthTypesUsed(bool useU
378429
else if (useAccountKey)
379430
expectedAuthType = AuthType.AccountKey;
380431

381-
// Check response details. Its important to cast the actual or we test against JToken from the dynamic results
382-
dynamic results = GetResultsObject(response);
432+
383433
Assert.AreEqual(expectedAuthType, (AuthType) results.authType);
384434
}
435+
// If it was a bad request, check the error message is set correctly
436+
else if (response.StatusCode == HttpStatusCode.BadRequest)
437+
{
438+
Assert.AreEqual(DataLakeConfigFactory.ErrorMessage.MultipleAuthTypesUsed, results.error.ToString());
439+
}
385440
}
386441

387442

DataPipelineTools.Functions.Tests/IntegrationTestBase.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,16 @@ protected void LogContent(HttpResponseMessage response)
225225
if (string.IsNullOrWhiteSpace(content))
226226
return;
227227

228-
var json = JObject.Parse(content).ToString();
229-
Logger.LogInformation($"Content:\n {json}");
228+
try
229+
{
230+
231+
var json = JObject.Parse(content).ToString();
232+
Logger.LogInformation($"Content:\n{json}");
233+
}
234+
catch
235+
{
236+
Logger.LogInformation($"Content:\n '{content}'");
237+
}
230238
#endif
231239
}
232240

@@ -320,7 +328,8 @@ private void StartFunctionsEmulatorInternal()
320328
Arguments = args,
321329
WorkingDirectory = binDir,
322330
CreateNoWindow = false,
323-
WindowStyle = ProcessWindowStyle.Normal
331+
WindowStyle = ProcessWindowStyle.Normal,
332+
UseShellExecute = true
324333
};
325334

326335
LocalFunctionsHostProcess = Process.Start(hostProcess);

DataPipelineTools.Functions/DataLake/DataLakeConfigFactory.cs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Runtime.CompilerServices;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.Extensions.Logging;
78
using Microsoft.Extensions.Primitives;
@@ -67,36 +68,50 @@ private static Dictionary<string, QueryParameter> GetParameters(HttpRequest req)
6768
//return req.Query.Keys.ToDictionary(k => k, req.GetQueryParameter);
6869
}
6970

71+
internal struct ErrorMessage
72+
{
73+
private const string OneMandatoryParamMissing = "Mandatory parameter '{0}' was not provided.";
74+
private const string ParamMustHaveValue = "The parameter '{0}' must have a value if it is provided.";
75+
76+
internal static string AccountParamIsMandatory = string.Format(OneMandatoryParamMissing, AccountParam);
77+
internal static string ContainerParamIsMandatory = string.Format(OneMandatoryParamMissing, ContainerParam);
78+
internal static string UserDefinedServicePrincipalParamsMissing =
79+
$"To use a user defined service principal you must supply valid values for the the parameters '{ServicePrincipalClientIdParam}' and '{ServicePrincipalClientSecretParam}'";
80+
81+
internal static string SasTokenParamMustHaveValue = string.Format(ErrorMessage.ParamMustHaveValue, SasTokenParam);
82+
internal static string AccountKeyParamMustHaveValue = string.Format(ErrorMessage.ParamMustHaveValue, AccountKeyParam);
83+
84+
internal static string MultipleAuthTypesUsed =
85+
"Authentication parameters are invalid. Authentication params must be one of the following sets\n"
86+
+ " - None (for authentication using the Azure Functions Service Principal)\n"
87+
+ $" - {ServicePrincipalClientIdParam}, {ServicePrincipalClientSecretParam}\n"
88+
+ $" - {SasTokenParam}\n"
89+
+ $" - {AccountKeyParam}\n";
90+
}
7091

7192
private void ValidateParameters(IReadOnlyDictionary<string, QueryParameter> parameters)
7293
{
7394
if (!parameters[AccountParam].Exists || parameters[AccountParam].IsNullOrWhiteSpace)
74-
throw new ArgumentException($"Mandatory parameter '{AccountParam}' was not provided.");
95+
throw new ArgumentException(ErrorMessage.AccountParamIsMandatory);
7596

7697
if (!parameters[ContainerParam].Exists || parameters[ContainerParam].IsNullOrWhiteSpace)
77-
throw new ArgumentException($"Mandatory parameter '{ContainerParam}' was not provided.");
98+
throw new ArgumentException(ErrorMessage.ContainerParamIsMandatory);
7899

79100
// We either need both params for a user defined service principal, or none
80101
var userServicePrincipalParams = new[] {parameters[ServicePrincipalClientIdParam], parameters[ServicePrincipalClientSecretParam]};
81102
if (userServicePrincipalParams.Count(x => x.Exists) == 1 || userServicePrincipalParams.Count(x => x.Exists && x.IsNullOrWhiteSpace) > 0)
82-
throw new ArgumentException($"To use a user defined service principal you must supply valid values for the the parameters '{ServicePrincipalClientIdParam}' and '{ServicePrincipalClientSecretParam}'");
103+
throw new ArgumentException(ErrorMessage.UserDefinedServicePrincipalParamsMissing);
83104

84105
// We need zero or one auth types (if nothing is specified we use the functions app service principal)
85106
var secrets = new[] {parameters[ServicePrincipalClientSecretParam], parameters[SasTokenParam], parameters[AccountKeyParam]};
86107
if (secrets.Count(x => x.Exists) > 1)
87-
throw new ArgumentException(
88-
"Authentication parameters are invalid. Authentication params must be one of the following sets\n"
89-
+ " - None (for authentication using the Azure Functions Service Principal)\n"
90-
+ $" - {ServicePrincipalClientIdParam}, {ServicePrincipalClientSecretParam}\n"
91-
+ $" - {SasTokenParam}\n"
92-
+ $" - {AccountKeyParam}\n"
93-
);
108+
throw new ArgumentException(ErrorMessage.MultipleAuthTypesUsed);
94109

95110
// Check any sas token or account key that was passed is not null, empty or whitespace
96111
if (parameters[SasTokenParam].Exists && parameters[SasTokenParam].IsNullOrWhiteSpace)
97-
throw new ArgumentException($"Mandatory parameter '{SasTokenParam}' does not have a value.");
112+
throw new ArgumentException( ErrorMessage.SasTokenParamMustHaveValue);
98113
if (parameters[AccountKeyParam].Exists && parameters[AccountKeyParam].IsNullOrWhiteSpace)
99-
throw new ArgumentException($"Mandatory parameter '{AccountKeyParam}' does not have a value.");
114+
throw new ArgumentException( ErrorMessage.AccountKeyParamMustHaveValue);
100115

101116

102117
// If secrets are specified without a ref to a Key Vault, log a warning

DataPipelineTools.Functions/DataLake/DataLakeFunctions.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ public DataLakeFunctions(ILogger<DataLakeFunctions> logger, DataLakeConfigFactor
2424
_serviceFactory = serviceFactory;
2525
}
2626

27-
28-
[FunctionName("DataLakeGetItems")]
27+
[FunctionName("DataLake-GetItems")]
2928
public async Task<IActionResult> DataLakeGetItems(
30-
[HttpTrigger(AuthorizationLevel.Function, "get" /*, "post"*/, Route = null)] HttpRequest req)
29+
[HttpTrigger(AuthorizationLevel.Function, "get" /*, "post"*/, Route = "DataLake/GetItems")] HttpRequest req)
3130
{
3231
req.GetQueryParameterDictionary();
3332

@@ -51,7 +50,7 @@ public async Task<IActionResult> DataLakeGetItems(
5150
catch (ArgumentException ex)
5251
{
5352
_logger.LogError(ex, ex.Message);
54-
return new BadRequestObjectResult(ex.Message);
53+
return new BadRequestObjectResult($"{{ \"error\": \"{ex.Message}\" }}");
5554
}
5655
catch (Exception ex)
5756
{
@@ -62,9 +61,9 @@ public async Task<IActionResult> DataLakeGetItems(
6261

6362

6463

65-
[FunctionName("DataLakeCheckPathCase")]
64+
[FunctionName("DataLake-CheckPathCase")]
6665
public async Task<IActionResult> DataLakeCheckPathCase(
67-
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req)
66+
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "DataLake/CheckPathCase")] HttpRequest req)
6867
{
6968
var userAgentKey = req.Headers.Keys.FirstOrDefault(k => k.ToLower() == "user-agent" || k.ToLower() == "useragent");
7069
_logger.LogInformation($"C# HTTP trigger function processed a request [User Agent: { (userAgentKey == null ? "Unknown" : req.Headers[userAgentKey].ToString()) }].");

DataPipelineTools.Functions/DataPipelineTools.Functions.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,9 @@
4444
<ItemGroup>
4545
<ProjectReference Include="..\DataPipelineTools\DataPipelineTools.csproj" />
4646
</ItemGroup>
47+
<ItemGroup>
48+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
49+
<_Parameter1>DataPipelineTools.Functions.Tests</_Parameter1>
50+
</AssemblyAttribute>
51+
</ItemGroup>
4752
</Project>

0 commit comments

Comments
 (0)