Skip to content

Commit 63224bf

Browse files
committed
Task 6
1 parent 603612d commit 63224bf

File tree

4 files changed

+228
-1825
lines changed

4 files changed

+228
-1825
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.IO;
18+
using System.Linq;
19+
using System.Net;
20+
using System.Net.Http;
21+
using System.Threading;
22+
using System.Threading.Tasks;
23+
using Xunit;
24+
25+
namespace Amazon.Lambda.RuntimeSupport.UnitTests
26+
{
27+
/// <summary>
28+
/// Tests for RuntimeApiClient streaming and buffered behavior.
29+
/// Validates Properties 7, 8, 10, 13, 18.
30+
/// </summary>
31+
public class RuntimeApiClientTests
32+
{
33+
private const long MaxResponseSize = 20 * 1024 * 1024;
34+
35+
/// <summary>
36+
/// Mock HttpMessageHandler that captures the request for header inspection.
37+
/// It completes the ResponseStream and returns immediately without reading
38+
/// the content body, avoiding the SerializeToStreamAsync blocking issue.
39+
/// </summary>
40+
private class MockHttpMessageHandler : HttpMessageHandler
41+
{
42+
public HttpRequestMessage CapturedRequest { get; private set; }
43+
private readonly ResponseStream _responseStream;
44+
45+
public MockHttpMessageHandler(ResponseStream responseStream)
46+
{
47+
_responseStream = responseStream;
48+
}
49+
50+
protected override Task<HttpResponseMessage> SendAsync(
51+
HttpRequestMessage request, CancellationToken cancellationToken)
52+
{
53+
CapturedRequest = request;
54+
55+
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
56+
}
57+
}
58+
59+
private static RuntimeApiClient CreateClientWithMockHandler(
60+
ResponseStream stream, out MockHttpMessageHandler handler)
61+
{
62+
handler = new MockHttpMessageHandler(stream);
63+
var httpClient = new HttpClient(handler);
64+
var envVars = new TestEnvironmentVariables();
65+
envVars.SetEnvironmentVariable("AWS_LAMBDA_RUNTIME_API", "localhost:9001");
66+
return new RuntimeApiClient(envVars, httpClient);
67+
}
68+
69+
// --- Property 7: Streaming Response Mode Header ---
70+
71+
/// <summary>
72+
/// Property 7: Streaming Response Mode Header
73+
/// For any streaming response, the HTTP request should include
74+
/// "Lambda-Runtime-Function-Response-Mode: streaming".
75+
/// **Validates: Requirements 4.1**
76+
/// </summary>
77+
[Fact]
78+
public async Task StartStreamingResponseAsync_IncludesStreamingResponseModeHeader()
79+
{
80+
var stream = new ResponseStream(MaxResponseSize);
81+
var client = CreateClientWithMockHandler(stream, out var handler);
82+
83+
await client.StartStreamingResponseAsync("req-1", stream, CancellationToken.None);
84+
85+
Assert.NotNull(handler.CapturedRequest);
86+
Assert.True(handler.CapturedRequest.Headers.Contains(StreamingConstants.ResponseModeHeader));
87+
var values = handler.CapturedRequest.Headers.GetValues(StreamingConstants.ResponseModeHeader).ToList();
88+
Assert.Single(values);
89+
Assert.Equal(StreamingConstants.StreamingResponseMode, values[0]);
90+
}
91+
92+
// --- Property 8: Chunked Transfer Encoding Header ---
93+
94+
/// <summary>
95+
/// Property 8: Chunked Transfer Encoding Header
96+
/// For any streaming response, the HTTP request should include
97+
/// "Transfer-Encoding: chunked".
98+
/// **Validates: Requirements 4.2**
99+
/// </summary>
100+
[Fact]
101+
public async Task StartStreamingResponseAsync_IncludesChunkedTransferEncodingHeader()
102+
{
103+
var stream = new ResponseStream(MaxResponseSize);
104+
var client = CreateClientWithMockHandler(stream, out var handler);
105+
106+
await client.StartStreamingResponseAsync("req-2", stream, CancellationToken.None);
107+
108+
Assert.NotNull(handler.CapturedRequest);
109+
Assert.True(handler.CapturedRequest.Headers.TransferEncodingChunked);
110+
}
111+
112+
// --- Property 13: Trailer Declaration Header ---
113+
114+
/// <summary>
115+
/// Property 13: Trailer Declaration Header
116+
/// For any streaming response, the HTTP request should include a "Trailer" header
117+
/// declaring the error trailer headers upfront (since we cannot know at request
118+
/// start whether an error will occur).
119+
/// **Validates: Requirements 5.4**
120+
/// </summary>
121+
[Fact]
122+
public async Task StartStreamingResponseAsync_DeclaresTrailerHeaderUpfront()
123+
{
124+
var stream = new ResponseStream(MaxResponseSize);
125+
var client = CreateClientWithMockHandler(stream, out var handler);
126+
127+
await client.StartStreamingResponseAsync("req-3", stream, CancellationToken.None);
128+
129+
Assert.NotNull(handler.CapturedRequest);
130+
Assert.True(handler.CapturedRequest.Headers.Contains("Trailer"));
131+
var trailerValue = string.Join(", ", handler.CapturedRequest.Headers.GetValues("Trailer"));
132+
Assert.Contains(StreamingConstants.ErrorTypeTrailer, trailerValue);
133+
Assert.Contains(StreamingConstants.ErrorBodyTrailer, trailerValue);
134+
}
135+
136+
// --- Property 18: Stream Finalization ---
137+
138+
/// <summary>
139+
/// Property 18: Stream Finalization
140+
/// For any streaming response that completes successfully, the ResponseStream
141+
/// should be marked as completed (IsCompleted = true) after the HTTP response succeeds.
142+
/// **Validates: Requirements 8.3**
143+
/// </summary>
144+
[Fact]
145+
public async Task StartStreamingResponseAsync_MarksStreamCompletedAfterSuccess()
146+
{
147+
var stream = new ResponseStream(MaxResponseSize);
148+
var client = CreateClientWithMockHandler(stream, out _);
149+
150+
await client.StartStreamingResponseAsync("req-4", stream, CancellationToken.None);
151+
152+
Assert.True(stream.IsCompleted);
153+
}
154+
155+
// --- Property 10: Buffered Responses Exclude Streaming Headers ---
156+
157+
/// <summary>
158+
/// Mock HttpMessageHandler that captures the request for buffered response header inspection.
159+
/// Returns an Accepted (202) response since that's what the InternalRuntimeApiClient expects.
160+
/// </summary>
161+
private class BufferedMockHttpMessageHandler : HttpMessageHandler
162+
{
163+
public HttpRequestMessage CapturedRequest { get; private set; }
164+
165+
protected override Task<HttpResponseMessage> SendAsync(
166+
HttpRequestMessage request, CancellationToken cancellationToken)
167+
{
168+
CapturedRequest = request;
169+
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Accepted));
170+
}
171+
}
172+
173+
/// <summary>
174+
/// Property 10: Buffered Responses Exclude Streaming Headers
175+
/// For any buffered response (where CreateStream was not called), the HTTP request
176+
/// should not include "Lambda-Runtime-Function-Response-Mode" or
177+
/// "Transfer-Encoding: chunked" or "Trailer" headers.
178+
/// **Validates: Requirements 4.6**
179+
/// </summary>
180+
[Fact]
181+
public async Task SendResponseAsync_BufferedResponse_ExcludesStreamingHeaders()
182+
{
183+
var bufferedHandler = new BufferedMockHttpMessageHandler();
184+
var httpClient = new HttpClient(bufferedHandler);
185+
var envVars = new TestEnvironmentVariables();
186+
envVars.SetEnvironmentVariable("AWS_LAMBDA_RUNTIME_API", "localhost:9001");
187+
var client = new RuntimeApiClient(envVars, httpClient);
188+
189+
var outputStream = new MemoryStream(new byte[] { 1, 2, 3 });
190+
await client.SendResponseAsync("req-buffered", outputStream, CancellationToken.None);
191+
192+
Assert.NotNull(bufferedHandler.CapturedRequest);
193+
// Buffered responses must not include streaming-specific headers
194+
Assert.False(bufferedHandler.CapturedRequest.Headers.Contains(StreamingConstants.ResponseModeHeader),
195+
"Buffered response should not include Lambda-Runtime-Function-Response-Mode header");
196+
Assert.NotEqual(true, bufferedHandler.CapturedRequest.Headers.TransferEncodingChunked);
197+
Assert.False(bufferedHandler.CapturedRequest.Headers.Contains("Trailer"),
198+
"Buffered response should not include Trailer header");
199+
}
200+
201+
// --- Argument validation ---
202+
203+
[Fact]
204+
public async Task StartStreamingResponseAsync_NullRequestId_ThrowsArgumentNullException()
205+
{
206+
var stream = new ResponseStream(MaxResponseSize);
207+
var client = CreateClientWithMockHandler(stream, out _);
208+
209+
await Assert.ThrowsAsync<ArgumentNullException>(
210+
() => client.StartStreamingResponseAsync(null, stream, CancellationToken.None));
211+
}
212+
213+
[Fact]
214+
public async Task StartStreamingResponseAsync_NullResponseStream_ThrowsArgumentNullException()
215+
{
216+
var stream = new ResponseStream(MaxResponseSize);
217+
var client = CreateClientWithMockHandler(stream, out _);
218+
219+
await Assert.ThrowsAsync<ArgumentNullException>(
220+
() => client.StartStreamingResponseAsync("req-5", null, CancellationToken.None));
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)