Skip to content
This repository was archived by the owner on Nov 22, 2018. It is now read-only.

Commit 3a0d8c4

Browse files
committed
Add more unit tests
1 parent e3ed603 commit 3a0d8c4

File tree

7 files changed

+344
-97
lines changed

7 files changed

+344
-97
lines changed

src/Microsoft.AspNetCore.StaticFiles/Infrastructure/RangeHelpers.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ internal static class RangeHelpers
1616
internal static IList<RangeItemHeaderValue> NormalizeRanges(ICollection<RangeItemHeaderValue> ranges, long length)
1717
{
1818
IList<RangeItemHeaderValue> normalizedRanges = new List<RangeItemHeaderValue>(ranges.Count);
19+
if (length == 0)
20+
{
21+
return normalizedRanges;
22+
}
1923
foreach (var range in ranges)
2024
{
2125
long? start = range.From;

src/Microsoft.AspNetCore.StaticFiles/StaticFileContext.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,19 @@ private void ComputeIfMatch()
197197

198198
private void ComputeIfModifiedSince()
199199
{
200+
var now = DateTimeOffset.UtcNow;
201+
200202
// 14.25 If-Modified-Since
201203
var ifModifiedSince = _requestHeaders.IfModifiedSince;
202-
if (ifModifiedSince.HasValue)
204+
if (ifModifiedSince.HasValue && ifModifiedSince <= now)
203205
{
204206
bool modified = ifModifiedSince < _lastModified;
205207
_ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified;
206208
}
207209

208210
// 14.28 If-Unmodified-Since
209211
var ifUnmodifiedSince = _requestHeaders.IfUnmodifiedSince;
210-
if (ifUnmodifiedSince.HasValue)
212+
if (ifUnmodifiedSince.HasValue && ifModifiedSince <= now)
211213
{
212214
bool unmodified = ifUnmodifiedSince >= _lastModified;
213215
_ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed;

test/Microsoft.AspNetCore.StaticFiles.Tests/CacheHeaderTests.cs

Lines changed: 159 additions & 48 deletions
Large diffs are not rendered by default.

test/Microsoft.AspNetCore.StaticFiles.Tests/RangeHeaderTests.cs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ public async Task IfRangeWithCurrentDateShouldServePartialContent()
7272
Assert.Equal("0123456789a", await resp.Content.ReadAsStringAsync());
7373
}
7474

75+
[Fact]
76+
public async Task IfModifiedSinceWithPastDateShouldServePartialContent()
77+
{
78+
TestServer server = StaticFilesTestServer.Create(app => app.UseFileServer());
79+
HttpResponseMessage original = await server.CreateClient().GetAsync("http://localhost/SubFolder/ranges.txt");
80+
81+
var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/ranges.txt");
82+
req.Headers.Add("If-Modified-Since", original.Content.Headers.LastModified.Value.AddHours(-1).ToString("r"));
83+
req.Headers.Add("Range", "bytes=0-10");
84+
HttpResponseMessage resp = await server.CreateClient().SendAsync(req);
85+
Assert.Equal(HttpStatusCode.PartialContent, resp.StatusCode);
86+
Assert.Equal("bytes 0-10/62", resp.Content.Headers.ContentRange.ToString());
87+
Assert.Equal(11, resp.Content.Headers.ContentLength);
88+
Assert.Equal("0123456789a", await resp.Content.ReadAsStringAsync());
89+
}
90+
91+
[Fact]
92+
public async Task IfModifiedSinceWithCurrentDateShouldReturn304()
93+
{
94+
TestServer server = StaticFilesTestServer.Create(app => app.UseFileServer());
95+
HttpResponseMessage original = await server.CreateClient().GetAsync("http://localhost/SubFolder/ranges.txt");
96+
97+
var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/ranges.txt");
98+
req.Headers.Add("If-Modified-Since", original.Content.Headers.LastModified.Value.ToString("r"));
99+
req.Headers.Add("Range", "bytes=0-10");
100+
HttpResponseMessage resp = await server.CreateClient().SendAsync(req);
101+
Assert.Equal(HttpStatusCode.NotModified, resp.StatusCode);
102+
}
103+
75104
// 14.27 If-Range
76105
// If the client has no entity tag for an entity, but does have a Last- Modified date, it MAY use that date in an If-Range header.
77106
// HEAD requests should ignore the Range header
@@ -216,7 +245,9 @@ public async Task HEADIfRangeWithoutRangeShouldServeFullContent()
216245
// 14.35 Range
217246
[Theory]
218247
[InlineData("0-0", "0-0", 1, "0")]
219-
[InlineData("0-9", "0-9", 10, "0123456789")]
248+
[InlineData("0- 9", "0-9", 10, "0123456789")]
249+
[InlineData("0 -9", "0-9", 10, "0123456789")]
250+
[InlineData("0 - 9", "0-9", 10, "0123456789")]
220251
[InlineData("10-35", "10-35", 26, "abcdefghijklmnopqrstuvwxyz")]
221252
[InlineData("36-61", "36-61", 26, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")]
222253
[InlineData("36-", "36-61", 26, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")] // Last 26
@@ -236,6 +267,42 @@ public async Task SingleValidRangeShouldServePartialContent(string range, string
236267
Assert.Equal(expectedData, await resp.Content.ReadAsStringAsync());
237268
}
238269

270+
[Theory]
271+
[InlineData("0-0", "0-0", 1, "A")]
272+
[InlineData("0-", "0-0", 1, "A")]
273+
[InlineData("-1", "0-0", 1, "A")]
274+
[InlineData("-2", "0-0", 1, "A")]
275+
[InlineData("0-1", "0-0", 1, "A")]
276+
[InlineData("0-2", "0-0", 1, "A")]
277+
public async Task SingleValidRangeShouldServePartialContentSingleByteFile(string range, string expectedRange, int length, string expectedData)
278+
{
279+
TestServer server = StaticFilesTestServer.Create(app => app.UseFileServer());
280+
var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/SingleByte.txt");
281+
req.Headers.Add("Range", "bytes=" + range);
282+
HttpResponseMessage resp = await server.CreateClient().SendAsync(req);
283+
Assert.Equal(HttpStatusCode.PartialContent, resp.StatusCode);
284+
Assert.NotNull(resp.Content.Headers.ContentRange);
285+
Assert.Equal("bytes " + expectedRange + "/1", resp.Content.Headers.ContentRange.ToString());
286+
Assert.Equal(length, resp.Content.Headers.ContentLength);
287+
Assert.Equal(expectedData, await resp.Content.ReadAsStringAsync());
288+
}
289+
290+
[Theory]
291+
[InlineData("0-0")]
292+
[InlineData("0-")]
293+
[InlineData("-1")]
294+
[InlineData("-2")]
295+
[InlineData("0-1")]
296+
[InlineData("0-2")]
297+
public async Task SingleValidRangeShouldServeRequestedRangeNotSatisfiableEmptyFile(string range)
298+
{
299+
TestServer server = StaticFilesTestServer.Create(app => app.UseFileServer());
300+
var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/Empty.txt");
301+
req.Headers.Add("Range", "bytes=" + range);
302+
HttpResponseMessage resp = await server.CreateClient().SendAsync(req);
303+
Assert.Equal(HttpStatusCode.RequestedRangeNotSatisfiable, resp.StatusCode);
304+
}
305+
239306
// 14.35 Range
240307
// HEAD ignores range headers
241308
[Theory]
@@ -287,6 +354,9 @@ public async Task HEADSingleNotSatisfiableRangeReturnsOk(string range)
287354
[InlineData("0")]
288355
[InlineData("1-0")]
289356
[InlineData("-")]
357+
[InlineData("a-")]
358+
[InlineData("-b")]
359+
[InlineData("a-b")]
290360
public async Task SingleInvalidRangeIgnored(string range)
291361
{
292362
TestServer server = StaticFilesTestServer.Create(app => app.UseFileServer());
@@ -305,6 +375,9 @@ public async Task SingleInvalidRangeIgnored(string range)
305375
[InlineData("0")]
306376
[InlineData("1-0")]
307377
[InlineData("-")]
378+
[InlineData("a-")]
379+
[InlineData("-b")]
380+
[InlineData("a-b")]
308381
public async Task HEADSingleInvalidRangeIgnored(string range)
309382
{
310383
TestServer server = StaticFilesTestServer.Create(app => app.UseFileServer());

test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFileMiddlewareTests.cs

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
7+
using System.Linq;
68
using System.Net;
79
using System.Threading.Tasks;
810
using Microsoft.AspNetCore.Builder;
@@ -29,6 +31,24 @@ public async Task ReturnsNotFoundWithoutWwwroot()
2931
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
3032
}
3133

34+
public async Task FoundFile_LastModifiedTrimsSeconds()
35+
{
36+
using (var fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory()))
37+
{
38+
var server = StaticFilesTestServer.Create(app => app.UseStaticFiles(new StaticFileOptions
39+
{
40+
FileProvider = fileProvider
41+
}));
42+
var fileInfo = fileProvider.GetFileInfo("TestDocument.txt");
43+
var response = await server.CreateRequest("TestDocument.txt").GetAsync();
44+
45+
var last = fileInfo.LastModified;
46+
var trimed = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset).ToUniversalTime();
47+
48+
Assert.Equal(response.Content.Headers.LastModified.Value, trimed);
49+
}
50+
}
51+
3252
[Fact]
3353
public async Task NullArguments()
3454
{
@@ -45,30 +65,7 @@ public async Task NullArguments()
4565
}
4666

4767
[Theory]
48-
[InlineData("", @".", "/missing.file")]
49-
[InlineData("/subdir", @".", "/subdir/missing.file")]
50-
[InlineData("/missing.file", @"./", "/missing.file")]
51-
[InlineData("", @"./", "/xunit.xml")]
52-
public async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl)
53-
{
54-
using (var fileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), baseDir)))
55-
{
56-
var server = StaticFilesTestServer.Create(app => app.UseStaticFiles(new StaticFileOptions
57-
{
58-
RequestPath = new PathString(baseUrl),
59-
FileProvider = fileProvider
60-
}));
61-
var response = await server.CreateRequest(requestUrl).GetAsync();
62-
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
63-
}
64-
}
65-
66-
[Theory]
67-
[InlineData("", @".", "/TestDocument.txt")]
68-
[InlineData("/somedir", @".", "/somedir/TestDocument.txt")]
69-
[InlineData("/SomeDir", @".", "/soMediR/TestDocument.txt")]
70-
[InlineData("", @"SubFolder", "/ranges.txt")]
71-
[InlineData("/somedir", @"SubFolder", "/somedir/ranges.txt")]
68+
[MemberData(nameof(ExistingFiles))]
7269
public async Task FoundFile_Served_All(string baseUrl, string baseDir, string requestUrl)
7370
{
7471
await FoundFile_Served(baseUrl, baseDir, requestUrl);
@@ -95,22 +92,27 @@ public async Task FoundFile_Served(string baseUrl, string baseDir, string reques
9592
RequestPath = new PathString(baseUrl),
9693
FileProvider = fileProvider
9794
}));
95+
var fileInfo = fileProvider.GetFileInfo(Path.GetFileName(requestUrl));
9896
var response = await server.CreateRequest(requestUrl).GetAsync();
97+
var responseContent = await response.Content.ReadAsByteArrayAsync();
9998

10099
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
101100
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
102-
Assert.True(response.Content.Headers.ContentLength > 0);
103-
Assert.Equal(response.Content.Headers.ContentLength, (await response.Content.ReadAsByteArrayAsync()).Length);
101+
Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
102+
Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length);
103+
104+
using (var stream = fileInfo.CreateReadStream())
105+
{
106+
var fileContents = new byte[stream.Length];
107+
stream.Read(fileContents, 0, (int)stream.Length);
108+
Assert.True(responseContent.SequenceEqual(fileContents));
109+
}
104110
}
105111
}
106112

107113
[Theory]
108-
[InlineData("", @".", "/TestDocument.txt")]
109-
[InlineData("/somedir", @".", "/somedir/TestDocument.txt")]
110-
[InlineData("/SomeDir", @".", "/soMediR/TestDocument.txt")]
111-
[InlineData("", @"SubFolder", "/ranges.txt")]
112-
[InlineData("/somedir", @"SubFolder", "/somedir/ranges.txt")]
113-
public async Task PostFile_PassesThrough(string baseUrl, string baseDir, string requestUrl)
114+
[MemberData(nameof(ExistingFiles))]
115+
public async Task HeadFile_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl)
114116
{
115117
using (var fileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), baseDir)))
116118
{
@@ -119,18 +121,57 @@ public async Task PostFile_PassesThrough(string baseUrl, string baseDir, string
119121
RequestPath = new PathString(baseUrl),
120122
FileProvider = fileProvider
121123
}));
122-
var response = await server.CreateRequest(requestUrl).PostAsync();
123-
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
124+
var fileInfo = fileProvider.GetFileInfo(Path.GetFileName(requestUrl));
125+
var response = await server.CreateRequest(requestUrl).SendAsync("HEAD");
126+
127+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
128+
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
129+
Assert.True(response.Content.Headers.ContentLength == fileInfo.Length);
130+
Assert.Equal(0, (await response.Content.ReadAsByteArrayAsync()).Length);
124131
}
125132
}
126133

127134
[Theory]
128-
[InlineData("", @".", "/TestDocument.txt")]
129-
[InlineData("/somedir", @".", "/somedir/TestDocument.txt")]
130-
[InlineData("/SomeDir", @".", "/soMediR/TestDocument.txt")]
131-
[InlineData("", @"SubFolder", "/ranges.txt")]
132-
[InlineData("/somedir", @"SubFolder", "/somedir/ranges.txt")]
133-
public async Task HeadFile_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl)
135+
[MemberData(nameof(MissingFiles))]
136+
public async Task Get_NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
137+
await PassesThrough("GET", baseUrl, baseDir, requestUrl);
138+
139+
[Theory]
140+
[MemberData(nameof(MissingFiles))]
141+
public async Task Head_NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
142+
await PassesThrough("HEAD", baseUrl, baseDir, requestUrl);
143+
144+
[Theory]
145+
[MemberData(nameof(MissingFiles))]
146+
public async Task Unknown_NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
147+
await PassesThrough("VERB", baseUrl, baseDir, requestUrl);
148+
149+
[Theory]
150+
[MemberData(nameof(ExistingFiles))]
151+
public async Task Options_Match_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
152+
await PassesThrough("OPTIONS", baseUrl, baseDir, requestUrl);
153+
154+
[Theory]
155+
[MemberData(nameof(ExistingFiles))]
156+
public async Task Trace_Match_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
157+
await PassesThrough("TRACE", baseUrl, baseDir, requestUrl);
158+
159+
[Theory]
160+
[MemberData(nameof(ExistingFiles))]
161+
public async Task Post_Match_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
162+
await PassesThrough("POST", baseUrl, baseDir, requestUrl);
163+
164+
[Theory]
165+
[MemberData(nameof(ExistingFiles))]
166+
public async Task Put_Match_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
167+
await PassesThrough("PUT", baseUrl, baseDir, requestUrl);
168+
169+
[Theory]
170+
[MemberData(nameof(ExistingFiles))]
171+
public async Task Unknown_Match_PassesThrough(string baseUrl, string baseDir, string requestUrl) =>
172+
await PassesThrough("VERB", baseUrl, baseDir, requestUrl);
173+
174+
public async Task PassesThrough(string method, string baseUrl, string baseDir, string requestUrl)
134175
{
135176
using (var fileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), baseDir)))
136177
{
@@ -139,13 +180,28 @@ public async Task HeadFile_HeadersButNotBodyServed(string baseUrl, string baseDi
139180
RequestPath = new PathString(baseUrl),
140181
FileProvider = fileProvider
141182
}));
142-
var response = await server.CreateRequest(requestUrl).SendAsync("HEAD");
143-
144-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
145-
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
146-
Assert.True(response.Content.Headers.ContentLength > 0);
147-
Assert.Equal(0, (await response.Content.ReadAsByteArrayAsync()).Length);
183+
var response = await server.CreateRequest(requestUrl).SendAsync(method);
184+
Assert.Null(response.Content.Headers.LastModified);
185+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
148186
}
149187
}
188+
189+
public static IEnumerable<string[]> MissingFiles => new[]
190+
{
191+
new[] {"", @".", "/missing.file"},
192+
new[] {"/subdir", @".", "/subdir/missing.file"},
193+
new[] {"/missing.file", @"./", "/missing.file"},
194+
new[] {"", @"./", "/xunit.xml"}
195+
};
196+
197+
public static IEnumerable<string[]> ExistingFiles => new[]
198+
{
199+
new[] {"", @".", "/TestDocument.txt"},
200+
new[] {"/somedir", @".", "/somedir/TestDocument.txt"},
201+
new[] {"/SomeDir", @".", "/soMediR/TestDocument.txt"},
202+
new[] {"", @"SubFolder", "/ranges.txt"},
203+
new[] {"/somedir", @"SubFolder", "/somedir/ranges.txt"},
204+
new[] {"", @"SubFolder", "/Empty.txt"}
205+
};
150206
}
151207
}

test/Microsoft.AspNetCore.StaticFiles.Tests/SubFolder/Empty.txt

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A

0 commit comments

Comments
 (0)