|
| 1 | +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. |
| 2 | + |
| 3 | +using System; |
| 4 | +using System.Net; |
| 5 | +using System.Net.Http; |
| 6 | +using System.Threading.Tasks; |
| 7 | +using Microsoft.AspNet.Builder; |
| 8 | +using Microsoft.AspNet.TestHost; |
| 9 | +using Shouldly; |
| 10 | +using Xunit; |
| 11 | + |
| 12 | +namespace Microsoft.AspNet.StaticFiles |
| 13 | +{ |
| 14 | + public class CacheHeaderTests |
| 15 | + { |
| 16 | + [Fact] |
| 17 | + public async Task ServerShouldReturnETag() |
| 18 | + { |
| 19 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 20 | + |
| 21 | + HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 22 | + response.Headers.ETag.ShouldNotBe(null); |
| 23 | + response.Headers.ETag.Tag.ShouldNotBe(null); |
| 24 | + } |
| 25 | + |
| 26 | + [Fact] |
| 27 | + public async Task SameETagShouldBeReturnedAgain() |
| 28 | + { |
| 29 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 30 | + |
| 31 | + HttpResponseMessage response1 = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 32 | + HttpResponseMessage response2 = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 33 | + response1.Headers.ETag.ShouldBe(response2.Headers.ETag); |
| 34 | + } |
| 35 | + |
| 36 | + // 14.24 If-Match |
| 37 | + // If none of the entity tags match, or if "*" is given and no current |
| 38 | + // entity exists, the server MUST NOT perform the requested method, and |
| 39 | + // MUST return a 412 (Precondition Failed) response. This behavior is |
| 40 | + // most useful when the client wants to prevent an updating method, such |
| 41 | + // as PUT, from modifying a resource that has changed since the client |
| 42 | + // last retrieved it. |
| 43 | + |
| 44 | + [Fact] |
| 45 | + public async Task IfMatchShouldReturn412WhenNotListed() |
| 46 | + { |
| 47 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 48 | + var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/Extra.xml"); |
| 49 | + req.Headers.Add("If-Match", "\"fake\""); |
| 50 | + HttpResponseMessage resp = await server.CreateClient().SendAsync(req); |
| 51 | + resp.StatusCode.ShouldBe(HttpStatusCode.PreconditionFailed); |
| 52 | + } |
| 53 | + |
| 54 | + [Fact] |
| 55 | + public async Task IfMatchShouldBeServedWhenListed() |
| 56 | + { |
| 57 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 58 | + HttpResponseMessage original = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 59 | + |
| 60 | + var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/Extra.xml"); |
| 61 | + req.Headers.Add("If-Match", original.Headers.ETag.ToString()); |
| 62 | + HttpResponseMessage resp = await server.CreateClient().SendAsync(req); |
| 63 | + resp.StatusCode.ShouldBe(HttpStatusCode.OK); |
| 64 | + } |
| 65 | + |
| 66 | + [Fact] |
| 67 | + public async Task IfMatchShouldBeServedForAstrisk() |
| 68 | + { |
| 69 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 70 | + var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/Extra.xml"); |
| 71 | + req.Headers.Add("If-Match", "*"); |
| 72 | + HttpResponseMessage resp = await server.CreateClient().SendAsync(req); |
| 73 | + resp.StatusCode.ShouldBe(HttpStatusCode.OK); |
| 74 | + } |
| 75 | + |
| 76 | + // 14.26 If-None-Match |
| 77 | + // If any of the entity tags match the entity tag of the entity that |
| 78 | + // would have been returned in the response to a similar GET request |
| 79 | + // (without the If-None-Match header) on that resource, or if "*" is |
| 80 | + // given and any current entity exists for that resource, then the |
| 81 | + // server MUST NOT perform the requested method, unless required to do |
| 82 | + // so because the resource's modification date fails to match that |
| 83 | + // supplied in an If-Modified-Since header field in the request. |
| 84 | + // Instead, if the request method was GET or HEAD, the server SHOULD |
| 85 | + // respond with a 304 (Not Modified) response, including the cache- |
| 86 | + // related header fields (particularly ETag) of one of the entities that |
| 87 | + // matched. For all other request methods, the server MUST respond with |
| 88 | + // a status of 412 (Precondition Failed). |
| 89 | + |
| 90 | + [Fact] |
| 91 | + public async Task IfNoneMatchShouldReturn304ForMatchingOnGetAndHeadMethod() |
| 92 | + { |
| 93 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 94 | + HttpResponseMessage resp1 = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 95 | + |
| 96 | + var req2 = new HttpRequestMessage(HttpMethod.Get, "http://localhost/SubFolder/Extra.xml"); |
| 97 | + req2.Headers.Add("If-None-Match", resp1.Headers.ETag.ToString()); |
| 98 | + HttpResponseMessage resp2 = await server.CreateClient().SendAsync(req2); |
| 99 | + resp2.StatusCode.ShouldBe(HttpStatusCode.NotModified); |
| 100 | + |
| 101 | + var req3 = new HttpRequestMessage(HttpMethod.Head, "http://localhost/SubFolder/Extra.xml"); |
| 102 | + req3.Headers.Add("If-None-Match", resp1.Headers.ETag.ToString()); |
| 103 | + HttpResponseMessage resp3 = await server.CreateClient().SendAsync(req3); |
| 104 | + resp3.StatusCode.ShouldBe(HttpStatusCode.NotModified); |
| 105 | + } |
| 106 | + |
| 107 | + [Fact] |
| 108 | + public async Task IfNoneMatchShouldBeIgnoredForNonTwoHundredAnd304Responses() |
| 109 | + { |
| 110 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 111 | + HttpResponseMessage resp1 = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 112 | + |
| 113 | + var req2 = new HttpRequestMessage(HttpMethod.Post, "http://localhost/SubFolder/Extra.xml"); |
| 114 | + req2.Headers.Add("If-None-Match", resp1.Headers.ETag.ToString()); |
| 115 | + HttpResponseMessage resp2 = await server.CreateClient().SendAsync(req2); |
| 116 | + resp2.StatusCode.ShouldBe(HttpStatusCode.NotFound); |
| 117 | + |
| 118 | + var req3 = new HttpRequestMessage(HttpMethod.Put, "http://localhost/SubFolder/Extra.xml"); |
| 119 | + req3.Headers.Add("If-None-Match", resp1.Headers.ETag.ToString()); |
| 120 | + HttpResponseMessage resp3 = await server.CreateClient().SendAsync(req3); |
| 121 | + resp3.StatusCode.ShouldBe(HttpStatusCode.NotFound); |
| 122 | + } |
| 123 | + |
| 124 | + // 14.26 If-None-Match |
| 125 | + // If none of the entity tags match, then the server MAY perform the |
| 126 | + // requested method as if the If-None-Match header field did not exist, |
| 127 | + // but MUST also ignore any If-Modified-Since header field(s) in the |
| 128 | + // request. That is, if no entity tags match, then the server MUST NOT |
| 129 | + // return a 304 (Not Modified) response. |
| 130 | + |
| 131 | + // A server MUST use the strong comparison function (see section 13.3.3) |
| 132 | + // to compare the entity tags in If-Match. |
| 133 | + |
| 134 | + [Fact] |
| 135 | + public async Task ServerShouldReturnLastModified() |
| 136 | + { |
| 137 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 138 | + |
| 139 | + HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/SubFolder/Extra.xml"); |
| 140 | + response.Content.Headers.LastModified.ShouldNotBe(null); |
| 141 | + } |
| 142 | + |
| 143 | + // 13.3.4 |
| 144 | + // An HTTP/1.1 origin server, upon receiving a conditional request that |
| 145 | + // includes both a Last-Modified date (e.g., in an If-Modified-Since or |
| 146 | + // If-Unmodified-Since header field) and one or more entity tags (e.g., |
| 147 | + // in an If-Match, If-None-Match, or If-Range header field) as cache |
| 148 | + // validators, MUST NOT return a response status of 304 (Not Modified) |
| 149 | + // unless doing so is consistent with all of the conditional header |
| 150 | + // fields in the request. |
| 151 | + |
| 152 | + [Fact] |
| 153 | + public async Task MatchingBothConditionsReturnsNotModified() |
| 154 | + { |
| 155 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 156 | + HttpResponseMessage resp1 = await server |
| 157 | + .CreateRequest("/SubFolder/Extra.xml") |
| 158 | + .GetAsync(); |
| 159 | + |
| 160 | + HttpResponseMessage resp2 = await server |
| 161 | + .CreateRequest("/SubFolder/Extra.xml") |
| 162 | + .AddHeader("If-None-Match", resp1.Headers.ETag.ToString()) |
| 163 | + .And(req => req.Headers.IfModifiedSince = resp1.Content.Headers.LastModified) |
| 164 | + .GetAsync(); |
| 165 | + |
| 166 | + resp2.StatusCode.ShouldBe(HttpStatusCode.NotModified); |
| 167 | + } |
| 168 | + |
| 169 | + [Fact] |
| 170 | + public async Task MissingEitherOrBothConditionsReturnsNormally() |
| 171 | + { |
| 172 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 173 | + HttpResponseMessage resp1 = await server |
| 174 | + .CreateRequest("/SubFolder/Extra.xml") |
| 175 | + .GetAsync(); |
| 176 | + |
| 177 | + DateTimeOffset lastModified = resp1.Content.Headers.LastModified.Value; |
| 178 | + DateTimeOffset pastDate = lastModified.AddHours(-1); |
| 179 | + DateTimeOffset furtureDate = lastModified.AddHours(1); |
| 180 | + |
| 181 | + HttpResponseMessage resp2 = await server |
| 182 | + .CreateRequest("/SubFolder/Extra.xml") |
| 183 | + .AddHeader("If-None-Match", "\"fake\"") |
| 184 | + .And(req => req.Headers.IfModifiedSince = lastModified) |
| 185 | + .GetAsync(); |
| 186 | + |
| 187 | + HttpResponseMessage resp3 = await server |
| 188 | + .CreateRequest("/SubFolder/Extra.xml") |
| 189 | + .AddHeader("If-None-Match", resp1.Headers.ETag.ToString()) |
| 190 | + .And(req => req.Headers.IfModifiedSince = pastDate) |
| 191 | + .GetAsync(); |
| 192 | + |
| 193 | + HttpResponseMessage resp4 = await server |
| 194 | + .CreateRequest("/SubFolder/Extra.xml") |
| 195 | + .AddHeader("If-None-Match", "\"fake\"") |
| 196 | + .And(req => req.Headers.IfModifiedSince = furtureDate) |
| 197 | + .GetAsync(); |
| 198 | + |
| 199 | + resp2.StatusCode.ShouldBe(HttpStatusCode.OK); |
| 200 | + resp3.StatusCode.ShouldBe(HttpStatusCode.OK); |
| 201 | + resp4.StatusCode.ShouldBe(HttpStatusCode.OK); |
| 202 | + } |
| 203 | + |
| 204 | + // 14.25 If-Modified-Since |
| 205 | + // The If-Modified-Since request-header field is used with a method to |
| 206 | + // make it conditional: if the requested variant has not been modified |
| 207 | + // since the time specified in this field, an entity will not be |
| 208 | + // returned from the server; instead, a 304 (not modified) response will |
| 209 | + // be returned without any message-body. |
| 210 | + |
| 211 | + // a) If the request would normally result in anything other than a |
| 212 | + // 200 (OK) status, or if the passed If-Modified-Since date is |
| 213 | + // invalid, the response is exactly the same as for a normal GET. |
| 214 | + // A date which is later than the server's current time is |
| 215 | + // invalid. |
| 216 | + [Fact] |
| 217 | + public async Task InvalidIfModifiedSinceDateFormatGivesNormalGet() |
| 218 | + { |
| 219 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 220 | + |
| 221 | + HttpResponseMessage res = await server |
| 222 | + .CreateRequest("/SubFolder/Extra.xml") |
| 223 | + .AddHeader("If-Modified-Since", "bad-date") |
| 224 | + .GetAsync(); |
| 225 | + |
| 226 | + res.StatusCode.ShouldBe(HttpStatusCode.OK); |
| 227 | + } |
| 228 | + |
| 229 | + // b) If the variant has been modified since the If-Modified-Since |
| 230 | + // date, the response is exactly the same as for a normal GET. |
| 231 | + |
| 232 | + // c) If the variant has not been modified since a valid If- |
| 233 | + // Modified-Since date, the server SHOULD return a 304 (Not |
| 234 | + // Modified) response. |
| 235 | + |
| 236 | + [Fact] |
| 237 | + public async Task IfModifiedSinceDateEqualsLastModifiedShouldReturn304() |
| 238 | + { |
| 239 | + TestServer server = TestServer.Create(app => app.UseFileServer()); |
| 240 | + |
| 241 | + HttpResponseMessage res1 = await server |
| 242 | + .CreateRequest("/SubFolder/Extra.xml") |
| 243 | + .GetAsync(); |
| 244 | + |
| 245 | + HttpResponseMessage res2 = await server |
| 246 | + .CreateRequest("/SubFolder/Extra.xml") |
| 247 | + .And(req => req.Headers.IfModifiedSince = res1.Content.Headers.LastModified) |
| 248 | + .GetAsync(); |
| 249 | + |
| 250 | + res2.StatusCode.ShouldBe(HttpStatusCode.NotModified); |
| 251 | + } |
| 252 | + } |
| 253 | +} |
0 commit comments