Skip to content
This repository was archived by the owner on Dec 12, 2023. It is now read-only.

Commit 1ed583d

Browse files
authored
Add request value lookup binding (#3)
1 parent 1a4de2c commit 1ed583d

27 files changed

+1073
-155
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Bindings:
66

77
- [Query Parameter](./docs/query-parameter.md)
88
- [Multipart Request](./docs/multipart-request.md)
9+
- [Request Value Lookup](./docs/request-value-lookup.md)

azure-pipelines.yml

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,25 @@ stages:
6969
command: "build"
7070
arguments: "--no-restore --configuration $(BUILD_CONFIGURATION)"
7171
projects: "**/*.csproj"
72+
- task: DotNetCoreCLI@2
73+
displayName: "Run Unit Tests"
74+
inputs:
75+
command: "test"
76+
projects: "**/*Tests/*UnitTests.csproj"
77+
arguments: '--configuration $(BUILD_CONFIGURATION) --collect "Code coverage"'
78+
testRunTitle: "Unit Tests"
79+
- task: DotNetCoreCLI@2
80+
displayName: "Run Integration Tests"
81+
env:
82+
AFTU_RUN_AZURITE: true
83+
AFTU_FUNC_APP_PATH: '../../../../Integration.FunctionApp/bin/Release/netcoreapp3.1'
84+
AFTU_WRITE_LOG: ${{ parameters.WRITE_LOG }}
85+
AFTU_AZURITE_SILENT: ${{ parameters.AZURITE_SILENT }}
86+
inputs:
87+
command: "test"
88+
projects: "**/*Tests/*IntegrationTests.csproj"
89+
arguments: '--configuration $(BUILD_CONFIGURATION) --collect "Code coverage"'
90+
testRunTitle: "Integration Tests"
7291
- task: PowerShell@2
7392
displayName: 'Set package version'
7493
name: SetVersion
@@ -88,18 +107,6 @@ stages:
88107
nobuild: true
89108
versioningScheme: 'byEnvVar'
90109
versionEnvVar: 'SETVERSION_UPDATED_PACKAGE_VERSION'
91-
- task: DotNetCoreCLI@2
92-
displayName: "Run Tests"
93-
env:
94-
AFTU_RUN_AZURITE: true
95-
AFTU_FUNC_APP_PATH: '../../../../Integration.FunctionApp/bin/Release/netcoreapp3.1'
96-
AFTU_WRITE_LOG: ${{ parameters.WRITE_LOG }}
97-
AFTU_AZURITE_SILENT: ${{ parameters.AZURITE_SILENT }}
98-
inputs:
99-
command: "test"
100-
projects: "**/*Tests/*IntegrationTests.csproj"
101-
arguments: '--configuration $(BUILD_CONFIGURATION) --collect "Code coverage"'
102-
testRunTitle: "Integration Tests"
103110
- task: Bash@3
104111
condition: and(always(), eq(${{ parameters.WRITE_LOG }}, true))
105112
inputs:

docs/request-value-lookup.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Request Value Lookup
2+
3+
Get a value from one of multiple places in a request. Currently supports:
4+
5+
- Query
6+
- Header
7+
8+
## Attribute
9+
10+
| Name | Type | Description |
11+
| -------- | ---------------------- | ----------------------------- |
12+
| Location | `RequestValueLocation` | Location to check for value |
13+
| Name | `string` | Priamary key name to look for |
14+
| Aliases | `string[]` | Alternate key names |
15+
16+
## Example
17+
18+
```csharp
19+
public static class VersionedHttpTrigger
20+
{
21+
[FunctionName(nameof(VersionedHttpTrigger))]
22+
public static async Task<IActionResult> RunAsync(
23+
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "version")]
24+
HttpRequest req, ILogger log, [
25+
RequestValue(
26+
Location = RequestValueLocation.Header | RequestValueLocation.Query,
27+
Name = "apiVersion",
28+
Aliases = new[] { "x-api-version" }
29+
)]
30+
string version
31+
)
32+
{
33+
if (string.IsNullOrEmpty(version))
34+
{
35+
return new BadRequestResult();
36+
}
37+
38+
log.LogInformation("Triggered for version {Version}", version);
39+
return new OkObjectResult(version);
40+
}
41+
}
42+
```

src/Integration.FunctionApp.IntegrationTests/Functions/MultipartHttpTriggerTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Net;
12
using System.Net.Http;
23
using System.Net.Mime;
34
using System.Text;
@@ -48,5 +49,26 @@ public async Task MultipartHttpTrigger_ContentWithFiles_ReturnsValue()
4849
Assert.AreEqual(jsonData.Username, model.Username);
4950
Assert.That.BlobExists("multipart-files", "greeting.txt");
5051
}
52+
53+
[TestMethod]
54+
[StartFunctions(nameof(MultipartHttpTrigger))]
55+
public async Task MultipartHttpTrigger_ContentWithoutFilesAndInvalidBody_ReturnsBadRequest()
56+
{
57+
var multipart = new MultipartFormDataContent();
58+
var jsonData = new MultipartRequestBodyData
59+
{
60+
Email = "john@doe.local"
61+
};
62+
multipart.Add(new StringContent(JsonConvert.SerializeObject(jsonData), Encoding.UTF8,
63+
MediaTypeNames.Application.Json));
64+
65+
var request = new HttpRequestMessage(HttpMethod.Post, "api/test/multipart")
66+
{
67+
Content = multipart
68+
};
69+
70+
var response = await Fixture.Client.SendAsync(request);
71+
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
72+
}
5173
}
5274
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using Integration.FunctionApp.Functions;
5+
using JoachimDalen.AzureFunctions.TestUtils.Attributes;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
8+
namespace Integration.FunctionApp.IntegrationTests.Functions
9+
{
10+
[TestClass]
11+
public class VersionedHttpTriggerTests : BaseFunctionTest
12+
{
13+
[TestMethod]
14+
[StartFunctions(nameof(VersionedHttpTrigger))]
15+
public async Task VersionedHttpTrigger_ValueInQuery_ReturnsValue()
16+
{
17+
var response = await Fixture.Client.GetAsync("/api/version?apiVersion=v1");
18+
var responseBody = await response.Content.ReadAsStringAsync();
19+
20+
Assert.IsTrue(response.IsSuccessStatusCode);
21+
Assert.AreEqual("v1", responseBody);
22+
}
23+
24+
[TestMethod]
25+
[StartFunctions(nameof(VersionedHttpTrigger))]
26+
public async Task VersionedHttpTrigger_ValueInHeader_ReturnsValue()
27+
{
28+
var request = new HttpRequestMessage(HttpMethod.Get, "/api/version");
29+
request.Headers.Add("x-api-version", "v2");
30+
var response = await Fixture.Client.SendAsync(request);
31+
var responseBody = await response.Content.ReadAsStringAsync();
32+
33+
Assert.IsTrue(response.IsSuccessStatusCode);
34+
Assert.AreEqual("v2", responseBody);
35+
}
36+
37+
[TestMethod]
38+
[StartFunctions(nameof(VersionedHttpTrigger))]
39+
public async Task VersionedHttpTrigger_ValueInQuery_ReturnsError()
40+
{
41+
var response = await Fixture.Client.GetAsync("/api/version");
42+
43+
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
44+
}
45+
}
46+
}

src/Integration.FunctionApp/Functions/MultipartHttpTrigger.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public static async Task<IActionResult> RunAsync(
2424
[Blob("multipart-files", FileAccess.Write)]
2525
CloudBlobContainer blobContainer)
2626
{
27+
if (!multipartRequest.IsValid)
28+
{
29+
return new BadRequestObjectResult(multipartRequest.ValidationResults);
30+
}
31+
32+
2733
foreach (var requestFile in multipartRequest.Files)
2834
{
2935
var blob = blobContainer.GetBlockBlobReference(requestFile.FileName);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Threading.Tasks;
2+
using System.Web.Http;
3+
using JoachimDalen.AzureFunctions.Extensions.Attributes;
4+
using JoachimDalen.AzureFunctions.Extensions.Models;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Azure.WebJobs;
7+
using Microsoft.Azure.WebJobs.Extensions.Http;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Integration.FunctionApp.Functions
12+
{
13+
public static class VersionedHttpTrigger
14+
{
15+
[FunctionName("VersionedHttpTrigger")]
16+
public static async Task<IActionResult> RunAsync(
17+
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "version")]
18+
HttpRequest req, ILogger log, [
19+
RequestValue(Location = RequestValueLocation.Header | RequestValueLocation.Query, Name = "apiVersion",
20+
Aliases = new[]
21+
{
22+
"x-api-version"
23+
})
24+
]
25+
string version
26+
)
27+
{
28+
if (string.IsNullOrEmpty(version))
29+
{
30+
return new BadRequestResult();
31+
}
32+
33+
log.LogInformation("Triggered for version {Version}", version);
34+
return new OkObjectResult(version);
35+
}
36+
}
37+
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
namespace Integration.FunctionApp.Models
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace Integration.FunctionApp.Models
24
{
35
public class MultipartRequestBodyData
46
{
7+
[Required]
58
public string Username { get; set; }
9+
610
public string Email { get; set; }
711
}
812
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Linq;
2+
using System.Net.Http;
3+
using System.Net.Http.Headers;
4+
using System.Net.Mime;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
9+
namespace JoachimDalen.AzureFunctions.Extensions.UnitTests
10+
{
11+
[TestClass]
12+
public class HttpRequestExtensionsTests
13+
{
14+
[DataTestMethod]
15+
[DataRow("form-data;name=\"file\"; filename=\"fileOne.png\"")]
16+
[DataRow("form-data;name=\"file\"; filename=fileOne.png")]
17+
public void GetEscapedContentDispositionFileName_Data_ReturnsCorrect(string contentDisposition)
18+
{
19+
var contentDispositionHeaderValue = ContentDispositionHeaderValue.Parse(contentDisposition);
20+
Assert.AreEqual("fileOne.png", contentDispositionHeaderValue.GetEscapedContentDispositionFileName());
21+
}
22+
23+
[TestMethod]
24+
public async Task HasFiles_Files_ReturnsTrue()
25+
{
26+
var bytes = Encoding.UTF8.GetBytes("Hello");
27+
var multipart = new MultipartFormDataContent
28+
{
29+
{new ByteArrayContent(bytes, 0, bytes.Length), "file", "greeting.txt"}
30+
};
31+
32+
var mp = (await multipart.ReadAsMultipartAsync()).Contents.First();
33+
Assert.IsTrue(mp.HasFiles("file"));
34+
}
35+
36+
[TestMethod]
37+
public async Task HasFiles_StringContent_ReturnsFalse()
38+
{
39+
var multipart = new MultipartFormDataContent
40+
{
41+
new StringContent("Hello", Encoding.UTF8, MediaTypeNames.Text.Plain)
42+
};
43+
44+
var mp = (await multipart.ReadAsMultipartAsync()).Contents.First();
45+
Assert.IsFalse(mp.HasFiles("file"));
46+
}
47+
48+
[TestMethod]
49+
public async Task HasData_JsonContent_ReturnsTrue()
50+
{
51+
var multipart = new MultipartFormDataContent
52+
{
53+
new StringContent("Hello", Encoding.UTF8, MediaTypeNames.Application.Json)
54+
};
55+
56+
var mp = (await multipart.ReadAsMultipartAsync()).Contents.First();
57+
Assert.IsTrue(mp.HasData());
58+
}
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
11+
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
12+
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
13+
<PackageReference Include="coverlet.collector" Version="1.3.0" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\JoachimDalen.AzureFunctions.Extensions\JoachimDalen.AzureFunctions.Extensions.csproj" />
18+
</ItemGroup>
19+
20+
</Project>

0 commit comments

Comments
 (0)